Calling dll from excel

title manager ms.date ms.audience ms.topic keywords ms.assetid ms.localizationpriority

Access DLLs in Excel

soliver

03/09/2015

Developer

overview

accessing dlls [excel 2007],DLLs [Excel 2007], accessing in Excel

e2bfd6ea-efa3-45c1-a5b8-2ccb8650c6ab

high

Access DLLs in Excel

Applies to: Excel 2013 | Office 2013 | Visual Studio

You can access a DLL function or command in Microsoft Excel in several ways:

  • Through a Microsoft Visual Basic for Applications (VBA) code module in which the function or command has been made available using a Declare statement.

  • Through an XLM macro sheet by using the CALL or REGISTER functions.

  • Directly from the worksheet or from a customized item in the user interface (UI).

This documentation does not cover XLM functions. It is recommended that you use either of the other two approaches.

To be accessed directly from the worksheet or from a customized item in the UI, the function or command must first be registered with Excel. For information about registering commands and functions, see Accessing XLL Code in Excel.

Calling DLL functions and commands from VBA

You can access DLL functions and commands in VBA by using the Declare statement. This statement has one syntax for commands and one for functions.

  • Syntax 1 — commands

    [Public | Private] Declare Sub name Lib "libname" [Alias "aliasname"] [([arglist])]
  • Syntax 2 — functions

    [Public | Private] Declare Function name Lib "libname" [Alias "aliasname"] [([arglist])] [As type]

The optional Public and Private keywords specify the scope of the imported function: the entire Visual Basic project or just the Visual Basic module, respectively. The name is the name that you want to use in the VBA code. If this differs from the name in the DLL, you must use the Alias «aliasname» specifier, and you should give the name of the function as exported by the DLL. If you want to access a DLL function by reference to a DLL ordinal number, you must provide an alias name, which is the ordinal prefixed by #.

Commands should return void. Functions should return types that VBA can recognize ByVal. This means that some types are more easily returned by modifying arguments in place: strings, arrays, user-defined types, and objects.

[!NOTE]
VBA cannot check that the argument list and return stated in the Visual Basic module are the same as coded in the DLL. You should check this yourself very carefully, because a mistake could cause Excel to crash.

When the function or command’s arguments are not passed by reference or pointer, they must be preceded by the ByVal keyword in the arglist declaration. When a C/C++ function takes pointer arguments, or a C++ function takes reference arguments, they should be passed ByRef. The keyword ByRef can be omitted from argument lists because it is the default in VBA.

Argument types in C/C++ and VBA

You should note the following when you compare the declarations of argument types in C/C++ and VBA.

  • A VBA String is passed as a pointer to a byte-string BSTR structure when passed ByVal, and as a pointer to a pointer when passed ByRef.

  • A VBA Variant that contains a string is passed as a pointer to a Unicode wide-character string BSTR structure when passed ByVal, and as a pointer to a pointer when passed ByRef.

  • The VBA Integer is a 16-bit type equivalent to a signed short in C/C++.

  • The VBA Long is a 32-bit type equivalent to a signed int in C/C++.

  • Both VBA and C/C++ allow the definition of user-defined data types, using the Type and struct statements respectively.

  • Both VBA and C/C++ support the Variant data type, defined for C/C++ in the Windows OLE/COM header files as VARIANT.

  • VBA arrays are OLE SafeArrays, defined for C/C++ in the Windows OLE/COM header files as SAFEARRAY.

  • The VBA Currency data type is passed as a structure of type CY, defined in the Windows header file wtypes.h, when passed ByVal, and as a pointer to this when passed ByRef.

In VBA, data elements in user-defined data types are packed to 4-byte boundaries, whereas in Visual Studio, by default, they are packed to 8-byte boundaries. Therefore you must enclose the C/C++ structure definition in a #pragma pack(4) … #pragma pack() block to avoid elements being misaligned.

The following is an example of equivalent user type definitions.

Type VB_User_Type
    i As Integer
    d As Double
    s As String
End Type
#pragma pack(4)
struct C_user_type
{
    short iVal;
    double dVal;
    BSTR bstr; // VBA String type is a byte string
}
#pragma pack() // restore default

VBA supports a greater range of values in some cases than Excel supports. The VBA double is IEEE compliant, supporting subnormal numbers that are currently rounded down to zero on the worksheet. The VBA Date type can represent dates as early as 1-Jan-0100 using negative serialized dates. Excel only allows serialized dates greater than or equal to zero. The VBA Currency type—a scaled 64-bit integer—can achieve accuracy not supported in 8-byte doubles, and so is not matched in the worksheet.

Excel only passes Variants of the following types to a VBA user-defined function.

VBA data type C/C++ Variant type bit flags Description
Double VT_R8
Boolean VT_BOOL
Date VT_DATE
String VT_BSTR OLE Bstr byte string
Range VT_DISPATCH Range and cell references
Variant containing an array VT_ARRAY VT_VARIANT Literal arrays
Ccy VT_CY 64-bit integer scaled to permit 4 decimal places of accuracy.
Variant containing an error VT_ERROR
VT_EMPTY Empty cells or omitted arguments

You can check the type of a passed-in Variant in VBA using the VarType, except that the function returns the type of the range’s values when called with references. To determine if a Variant is a Range reference object, you can use the IsObject function.

You can create Variants that contain arrays of variants in VBA from a Range by assigning its Value property to a Variant. Any cells in the source range that are formatted using the standard currency format for the regional settings in force at the time are converted to array elements of type Currency. Any cells formatted as dates are converted to array elements of type Date. Cells containing strings are converted to wide-character BSTR Variants. Cells containing errors are converted to Variants of type VT_ERROR. Cells containing Boolean True or False are converted to Variants of type VT_BOOL.

[!NOTE]
The Variant stores True as -1 and False as 0. Numbers not formatted as dates or currency amounts are converted to Variants of type VT_R8.

Variant and string arguments

Excel works internally with wide-character Unicode strings. When a VBA user-defined function is declared as taking a String argument, Excel converts the supplied string to a byte-string in a locale-specific way. If you want your function to be passed a Unicode string, your VBA user-defined function should accept a Variant instead of a String argument. Your DLL function can then accept that Variant BSTR wide-character string from VBA.

To return Unicode strings to VBA from a DLL, you should modify a Variant string argument in place. For this to work, you must declare the DLL function as taking a pointer to the Variant and in your C/C++ code, and declare the argument in the VBA code as ByRef varg As Variant. The old string memory should be released, and the new string value created by using the OLE Bstr string functions only in the DLL.

To return a byte string to VBA from a DLL, you should modify a byte-string BSTR argument in place. For this to work, you must declare the DLL function as taking a pointer to a pointer to the BSTR and in your C/C++ code, and declare the argument in the VBA code as ‘ ByRef varg As String‘.

You should only handle strings that are passed in these ways from VBA using the OLE BSTR string functions to avoid memory-related problems. For example, you must call SysFreeString to free the memory before overwriting the passed in string, and SysAllocStringByteLen or SysAllocStringLen to allocate space for a new string.

You can create Excel worksheet errors as Variants in VBA by using the CVerr function with arguments as shown in the following table. Worksheet errors can also be returned to VBA from a DLL using Variants of type VT_ERROR, and with the following values in the ulVal field.

Error Variant ulVal value CVerr argument
#NULL! 2148141008 2000
#DIV/0! 2148141015 2007
#VALUE! 2148141023 2015
#REF! 2148141031 2023
#NAME? 2148141037 2029
#NUM! 2148141044 2036
#N/A 2148141050 2042

Note that the Variant ulVal value given is equivalent to the CVerr argument value plus x800A0000 hexadecimal.

Calling DLL functions directly from the worksheet

You cannot access Win32 DLL functions from the worksheet without, for example, using VBA or XLM as interfaces, or without letting Excel know about the function, its arguments, and its return type in advance. The process of doing this is called registration.

The ways in which the functions of a DLL can be accessed in the worksheet are as follows:

  • Declare the function in VBA as described previously and access it via a VBA user-defined function.

  • Call the DLL function using CALL on an XLM macro sheet, and access it via an XLM user-defined function.

  • Use an XLM or VBA command to call the XLM REGISTER function, which provides the information that Excel needs to recognize the function when it is entered into a worksheet cell.

  • Turn the DLL into an XLL and register the function using the C API xlfRegister function when the XLL is activated.

The fourth approach is self-contained: the code that registers the functions and the function code are both contained in the same code project. Making changes to the add-in does not involve making changes to an XLM sheet or to a VBA code module. To do this in a well-managed way while still staying within the capabilities of the C API, you must turn your DLL into an XLL and load the resulting add-in by using the Add-in Manager. This enables Excel to call a function that your DLL exposes when the add-in is loaded or activated, from which you can register all of the functions your XLL contains, and carry out any other DLL initialization.

Calling DLL commands directly from Excel

Win32 DLL commands are not accessible directly from Excel dialog boxes and menus without there being an interface, such as VBA, or without the commands being registered in advance.

The ways in which you can access the commands of a DLL are as follows:

  • Declare the command in VBA as described previously and access it via a VBA macro.

  • Call the DLL command using CALL on an XLM macro sheet, and access it via an XLM macro.

  • Use an XLM or VBA command to call the XLM REGISTER function, which provides the information Excel needs to recognize the command when it is entered into a dialog box that expects the name of a macro command.

  • Turn the DLL into an XLL and register the command using the C API xlfRegister function.

As discussed earlier in the context of DLL functions, the fourth approach is the most self-contained, keeping the registration code close to the command code. To do this, you must turn your DLL into an XLL and load the resulting add-in using the Add-in Manager. Registering commands in this way also lets you attach the command to an element of the user interface, such as a custom menu, or to set up an event trap that calls the command on a given keystroke or other event.

All XLL commands that are registered with Excel are assumed by Excel to be of the following form.

int WINAPI my_xll_cmd(void)
{
// Function code...
    return 1;
}

[!NOTE]
Excel ignores the return value unless it is called from an XLM macro sheet, in which case the return value is converted to TRUE or FALSE. You should therefore return 1 if your command executed successfully, and 0 if it failed or was canceled by the user.

DLL memory and multiple DLL instances

When an application loads a DLL, the DLL’s executable code is loaded into the global heap so that it can be run, and space is allocated on the global heap for its data structures. Windows uses memory mapping to make these areas of memory appear as if they are in the application’s process so that the application can access them.

If a second application then loads the DLL, Windows does not make another copy of the DLL executable code, as that memory is read-only. Windows maps the DLL executable code memory to the processes of both applications. It does, however, allocate a second space for a private copy of the DLL’s data structures and maps this copy to the second process only. This ensures that neither application can interfere with the DLL data of the other.

This means that DLL developers do not have to be concerned about static and global variables and data structures being accessed by more than one application, or more than one instance of the same application. Every instance of every application gets its own copy of the DLL’s data.

DLL developers do need to be concerned about the same instance of an application calling their DLL many times from different threads, because this can result in contention for that instance’s data. For more information, see Memory Management in Excel.

See also

  • Developing DLLs
  • Calling into Excel from the DLL or XLL

Содержание

  1. Доступ к библиотекам DLL в Excel
  2. Вызов функций и команд DLL из VBA
  3. Типы аргументов в C/C++ и VBA
  4. Аргументы строк и переменных
  5. Вызов функций DLL непосредственно с листа
  6. Вызов команд DLL непосредственно из Excel
  7. Память DLL и многочисленные экземпляры DLL

Доступ к библиотекам DLL в Excel

Относится к: Excel 2013 | Office 2013 | Visual Studio

Вы можете получить доступ к функции или команде DLL в Microsoft Excel несколькими способами:

с помощью модуля кода Microsoft Visual Basic для приложений (VBA), в котором функция или команда была сделана доступной с помощью выражения Declare;

через лист макросов XLM, используя функции CALL или REGISTER;

непосредственно с листа или из настроенного элемента в пользовательском интерфейсе.

В этой документации не рассматриваются функции XLM. Рекомендуется использовать любой из двух остальных подходов.

Для доступа к функции или команде непосредственно с листа или из настроенного элемента в пользовательском интерфейсе необходимо сначала зарегистрировать ее в Excel. Сведения о регистрации команд и функций см. в статье Доступ к коду XLL в Excel.

Вызов функций и команд DLL из VBA

Вы можете получать доступ к функциям и командам DLL в VBA с помощью оператора Declare. В случае этого оператора предусмотрен один синтаксис для команд, а другой — для функций.

Синтаксис 1: команды

Синтаксис 2: функции

Необязательные ключевые слова Public и Private определяют область импортированной функции (весь проект Visual Basic и модуль Visual Basic соответственно). Имя — это имя, которое вы хотите использовать в коде VBA. Если оно отличается от имени в библиотеке DLL, необходимо использовать указатель Alias «aliasname» и предоставить имя функции, экспортируемое библиотекой DLL. Для доступа к функции DLL с помощью ссылки на порядковый номер DLL необходимо предоставить псевдоним, который является порядковым номером с префиксом #.

Команды должны возвратить void. Функции должны возвращать типы, которые VBA может распознавать с помощью ByVal. Это означает, что некоторые типы легче возвращаются за счет изменения имеющихся аргументов (строк, массивов, определяемых пользователем типов и объектов).

VBA не может проверить совпадение списка аргументов и возвращаемого значения в модуле Visual Basic со значениями, закодированными в DLL. Это необходимо внимательно проверить самостоятельно, поскольку ошибка может привести к сбою Excel.

Если аргументы команды или функции не передаются с помощью ссылки или указателя, они должны стоять после ключевого слова ByVal в объявлении arglist. Когда функция C/C++ принимает аргументы указателя или функция C++ принимает аргументы ссылки, они должны передаваться ByRef. Ключевое слово ByRef можно пропустить в списке аргументов, так как оно используется по умолчанию в VBA.

Типы аргументов в C/C++ и VBA

Следует помнить об указанных ниже фактах при сравнении объявлений типов аргументов в C/C++ и VBA.

Тип String в VBA передается как указатель на структуру BSTR байтовых строк в режиме ByVal и как указатель на указатель в режиме ByRef.

Тип Variant в VBA, содержащий строку, передается как указатель на структуру BSTR строк Юникода из двухбайтовых знаков при передаче в режиме ByVal и как указатель на указатель в режиме ByRef.

Integer VBA — это 16-битный тип, эквивалентный signed short в C/C++.

Long VBA — это 32-битный тип, эквивалентный signed int в C/C++.

VBA и C/C++ допускают определяемые пользователем типы данных (применяются операторы Type и struct соответственно).

VBA и C/C++ поддерживают тип данных Variant, заданный для C/C++ в файлах заголовка Windows OLE/COM как VARIANT.

Массивы VBA — это объекты OLE SafeArrays, определенные для C/C++ в файлах заголовка Windows OLE/COM как SAFEARRAY.

Тип данных Currency в VBA передается как структура типа CY, определенная в файле заголовка Windows wtypes.h, в режиме ByVal и как указатель на него в режиме ByRef.

В VBA элементы данных в определяемых пользователем типах данных упаковываются с учетом 4-байтовых границ, тогда как в Visual Studio по умолчанию они упаковываются с учетом 8-байтовых границ. Поэтому необходимо заключить определение структуры C/C++ в блок #pragma pack(4) … #pragma pack() , чтобы избежать несовпадения элементов.

Ниже приведен пример определений эквивалентных пользовательских типов.

В некоторых случаях VBA поддерживает более широкий диапазон значений, чем Excel. Тип данных Double в VBA соответствует требованиям IEEE и поддерживает субнормальные числа, которые в настоящее время на листе округляются до нуля. Тип Date в VBA представляет даты от 1 января 0100 года, которые можно задать с помощью отрицательных сериализованных значений дат. Excel поддерживает сериализованные даты не меньше нуля. Тип Currency в VBA (64-битное масштабированное целое число) позволяет добиться точности, не поддерживаемой 8-байтовыми значениями Double, и поэтому не имеет совпадений на листе.

Excel передает только значения Variant указанных ниже типов в определяемую пользователем функцию VBA.

Тип данных VBA Битовые флаги типа Variant C/C++ Описание
Double VT_R8
Boolean VT_BOOL
Date VT_DATE
String VT_BSTR Строка байтов OLE Bstr
Диапазон VT_DISPATCH Ссылки на ячейку и диапазон
Переменная, содержащая массив VT_VARIANT VT_ARRAY Литеральные массивы
Ccy VT_CY 64-битное целое число, масштабированное для достижения точности 4 знака после запятой.
Переменная, содержащая ошибку VT_ERROR
VT_EMPTY Пустые ячейки или пропущенные аргументы

Вы можете проверить тип переданного значения Variant в VBA с помощью VarType, если функция не возвращает тип значений диапазона при вызове с использованием ссылок. Чтобы определить, является ли Variant объектом ссылки Range, используйте функцию IsObject.

В VBA можно создавать Variant, содержащие массивы переменных, назначая свойство ValueRange для Variant. Ячейки в исходном диапазоне, отформатированные с использованием стандартного денежного формата для действующих региональных параметров, преобразуются в элементы массива типа Currency. Ячейки, отформатированные как даты, преобразуются в элементы массива типа Date. Ячейки, содержащие строки, преобразуются в Variant BSTR с расширенными символами. Ячейки, содержащие ошибки, преобразуются в Variant типа VT_ERROR. Ячейки, содержащие значения True или False типа Boolean, преобразуются в объекты Variants типа VT_BOOL.

В Variant значение True сохраняется как -1, а False — как 0. Числа, не отформатированные как даты или денежные суммы, преобразуются в Variant типа VT_R8.

Аргументы строк и переменных

Excel поддерживает внутреннюю работу со строками Юникода с расширенными символами. Когда определяемая пользователем функция в VBA объявлена как принимающая аргумент String, Excel преобразует передаваемую строку в байтовую строку в соответствии с языковым стандартом. Чтобы функции передавалась строка Юникода, определяемая пользователем функция в VBA должна принимать Variant вместо аргумента String. При выполнении этого условия функция DLL сможет принимать строку Variant BSTR с расширенными символами VBA.

Чтобы из DLL возвращались строки Юникода в VBA, следует изменить имеющийся аргумент строки Variant. Для этого следует объявить функцию DLL как принимающую указатель на Variant в коде C/C++, а также объявить аргумент в коде VBA как ByRef varg As Variant . Память предыдущей строки следует освободить, а значение новой строки, созданное с использованием строки OLE Bstr, работает только в DLL.

Чтобы байтовая строка возвращалась в VBA из DLL, следует изменить имеющийся аргумент BSTR байтовой строки. Для этого следует объявить функцию DLL как принимающую указатель на BSTR в коде C/C++, а также объявить аргумент в коде VBA как ByRef varg As String.

Чтобы избежать проблем, связанных с памятью, необходимо обеспечить обработку только строк, которые передаются этими способами из VBA, используя функции строки OLE BSTR. Например, необходимо обеспечить вызов SysFreeString для освобождения памяти перед перезаписью переданной строки, а также SysAllocStringByteLen или SysAllocStringLen, чтобы назначить место для новой строки.

Вы можете обеспечить создание сообщений об ошибках на листе Excel как Variant в VBA при помощи функции CVerr с аргументами, как показано в приведенной ниже таблице. Ошибки на листе также могут быть возвращены в VBA из DLL при помощи Variant типа VT_ERROR, а также с указанными ниже значениями в поле ulVal.

Ошибка Значение Variant ulVal Аргумент CVerr
#NULL! 2148141008 2000
#ДЕЛ/0! 2148141015 2007
#ЗНАЧ! 2148141023 2015
#ССЫЛКА! 2148141031 2023
#ИМЯ? 2148141037 2029
#ЧИСЛО! 2148141044 2036
#Н/Д 2148141050 2042

Обратите внимание на то, что значение Variant ulVal эквивалентно значению аргумента CVerr с шестнадцатеричным значением x800A0000.

Вызов функций DLL непосредственно с листа

Вы не сможете получить доступ к функциям DLL Win32 с листа, если не используете, к примеру, интерфейсы VBA или XLM либо не сообщите Excel заранее о функции, ее аргументах и типе возвращаемого значения. Этот процесс называется регистрацией.

Ниже приведены способы, которыми можно получить доступ к функциям DLL на листе.

Объявите функцию в VBA, как показано выше, и получите доступ к ней через пользовательскую функцию VBA.

Сначала обеспечьте вызов функции DLL с помощью CALL на листе макросов XLM, а затем — доступ к ней с помощью определяемой пользователем функции XLM.

Используйте команду XLM или VBA, чтобы вызвать функцию XLM REGISTER, которая предоставляет сведения, необходимые Excel для опознания функции при ее вводе в ячейке листа.

Преобразуйте DLL в XLL и зарегистрируйте функцию с помощью функции xlfRegister C API после активации XLL.

Четвертый подход изолированный: код, регистрирующий функции, и код функций хранятся в одном объекте кода. Изменение надстройки не включает изменение листа XLM или модуля кода VBA. Чтобы сделать это с широкими возможностями управления, оставаясь в рамках возможностей API C, необходимо преобразовать DLL в XLL и загрузить получившуюся надстройку с помощью диспетчера настроек. Это позволяет Excel вызывать функцию, предоставленную библиотекой DLL, при загрузке или активации надстройки, из которой затем можно зарегистрировать все функции, которые содержит XLL, и выполнять другие задачи инициализации DLL.

Вызов команд DLL непосредственно из Excel

Команды DLL Win32 недоступны напрямую из диалоговых окон и меню Excel без интерфейса, например VBA, или без предварительной регистрации команд.

Получать доступ к командам DLL можно следующими способами:

Объявите команду в VBA так, как описано выше, и получите к ней доступ с помощью макроса VBA.

Сначала обеспечьте вызов команды DLL с помощью CALL на листе макросов XLM, а затем — доступ к ней с помощью макроса XLM.

Используйте команду XLM или VBA, чтобы вызвать функцию XLM REGISTER, которая предоставляет сведения, необходимые Excel для опознания команды при ее вводе в диалоговом окне, которое запрашивает имя команды макроса.

Преобразуйте DLL в XLL и зарегистрируйте команду с помощью функции xlfRegister C API.

Как упоминалось ранее в контексте функций DLL, четвертый подход является самым изолированным, так как код регистрации хранится наряду с кодом команд. Для этого необходимо преобразовать DLL в XLL и загрузить получившуюся надстройку с помощью диспетчера надстроек. Регистрация команд таким способом также позволяет присоединить команду к элементу пользовательского интерфейса, например пользовательскому меню, или настроить перехват события с вызовом команды по нажатию определенной клавиши или другому событию.

Приложение Excel обрабатывает все команды XLL, зарегистрированные в нем, как имеющие такой вид:

Excel игнорирует возвращаемое значение, если оно не было вызвано с листа макросов XLM. Если оно было вызвано так, возвращаемое значение преобразуется в TRUE или FALSE. Поэтому следует обеспечить возвращение 1 при успешном выполнении команды и 0 при ошибке или отмене команды.

Память DLL и многочисленные экземпляры DLL

Когда приложение загружает DLL, исполняемый код DLL загружается в глобальную кучу для выполнения, а для его структур данных назначается место в глобальной куче. Windows использует сопоставление памяти, чтобы эти области памяти отображались, как в процессе приложения. Таким образом приложение может получать к ним доступ.

Если второе приложение загружает эту библиотеку DLL, Windows не создает дополнительную копию исполняемого кода DLL, так как эта память доступна только для чтения. Windows сопоставляет память исполняемого кода DLL с процессами обоих приложений. Но при этом Windows выделяет другое место для частной копии структур данных DLL и сопоставляет эту копию только со вторым процессом. Благодаря этому одно приложение не может конфликтовать с данными DLL другого.

Это означает, что разработчикам DLL не следует беспокоиться, что несколько приложений (или несколько экземпляров одного приложения) будут получать доступ к статическим и глобальным переменным и структурам данных. Каждый экземпляр всех приложений получает собственную копию данных DLL.

Разработчикам DLL следует позаботиться о том, чтобы один и тот же экземпляр приложения не вызывал DLL много раз из разных потоков, так как это может привести к состязанию за данные этого экземпляра. Дополнительные сведения см. в статье Управление памятью в Excel.

Источник

Modular Programming

As you recall from our computer programming lessons, one of the key concepts in good software design is modularity: breaking our code into functional subroutines or functions, each responsible for a specific task with a clear interface for calling it.

Once encapsulated in a self-contained function, this functionality can be re-used in many locations in your program. This improves the size, quality, maintainability and readability of your program. Also, without modular design, your program can only grow so much before you lose your arms and legs.

I’d go as much to say that a good modular design is one of the first signs I look for in judging the quality of a programmer.

Using Functions Outside of Excel

Expanding on the modularity concept, why limit ourselves to functions we coded in our VBA application? There are many functions out there already developed by others and proven robust and performant – why not break the boundaries of Excel VBA and consume them, or CALL them from our Excel VBA program?

Some of the things we would like to do can’t even be achieved without using an external function that is not part of Excel VBA or that we can write ourselves without consuming such a function.

Consider, for example, the need to control the Window properties (maybe size or position) of another application running on our Windows operating system, from Excel VBA. This Window is governed by the operating system and we have no access to it without calling a Windows function that has access to that Window and can manipulate it.

What is a DLL?

A DLL (Dynamic Link Library) is a package of functions/subroutines callable by other programs running in the Windows operating system.

Typically, a DLL file has the .dll extension, although other extensions are possible for certain types of files (e.g. .ocx for ActiveX controls).

Most of the Windows operating system functionality itself is implemented by DLLs, each calling and called by other programs. One such example would be the Comdlg32.DLL file, offering a function to open the File Open dialog box for selecting file(s) – a functionality needed by many programs running on Windows.

Almost every program that is installed on a Windows machine registers its own DLLs in the Windows registry.

Once a DLL is properly registered with Windows, its published functions can be consumed and called by any other program running on that machine, and that includes Excel VBA, of course!

Almost every program developed for running on Windows makes use of existing DLL files and contributes its own DLL files to the party.

For an elaborate discussion on DLLs, read this Microsoft article.

Calling a DLL from Excel VBA

In order to gain access to the functions/subroutines included in a DLL file, you need to first declare your intentions to do that, using the Declare statement.

If you intent to call the PlayMusic sub exposed by the (imaginary) FunActivities.DLL file, your declaration statement may look like this:

Declare Sub PlayMusic Lib “FunActivities” ()

If the PlayMusic sub expects arguments, the declaration must also include those arguments (passed ByRef by default):

Declare Sub PlayMusic Lib “FunActivities” (ByVal Duration as Long)

As with any declaration in VBA, you can precede the declaration with the Public or Private qualifiers to contol for the scope of the sub in your VBA project:

Public Declare Sub PlayMusic Lib “FunActivities” ()

Declaring a function is very similar, with the notable return type expected as with any function:

Private Declare Function CountPixels Lib “PixelsInfo” () As Long

Sometimes, a function name as exposed by the DLL may be in conflict with VBA or other variables used in your program. To circumvent this, you can specify a local name to reference that function by in your VBA program, instead of the original name as determined by the DLL developer. In this case, you will add the Alias qualifier to reference the original function name, while the declared name will be your own local flavor:

Declare Sub MyPlayMusic Lib “FunActivities” Alias “PlayMusic”()

Another way of referencing a function in the DLL, instead of by its published (or exported) name, would be by its index, or ordinal number, as defined by the developer. In this case, we must use the Alias qualifier and the “#” character to indicate an ordinal number:

Declare Sub PlayMusic Lib “FunActivities” Alias “#241”()

Referencing by ordinal number guaranties consistency even if the function name will be changed in future versions, but developers are very aware not to mess with function names in DLLs, and this way is rarely used nowadays.

For a complete and detailed explanation of the Declare statement, read this Microsoft article.

Calling a DLL Sub from Excel VBA Example

The following example is implemented in The Ultimate Excel Date Picker. If you got this neat perk already, you may be familiar with the following code, as the Date Picker comes with its VBA code open.

In developing The Ultimate Excel Date Picker, I needed to reference several DLL services, one of which is to simulate a keyboard TAB pressed.

I could have used the SendKeys() VBA function, but let me tell you – it has more promise than it delivers. I would not rely on this statement at all in any of my programs.

However, the Windows user32.DLL file offers a keyboard event sub, directly from Windows, not VBA. Here’s the declaration part I have:

#If VBA7 Then   

    Private Declare PtrSafe Sub keybd_event Lib "user32.dll" _

    (ByVal bVk As Byte, ByVal bScan As Byte, _

     ByVal dwFlags As LongPtr, ByVal dwExtraInfo As LongPtr)

#Else

    ' Code is NOT running in 32-bit or 64-bit VBA7

    Private Declare Sub keybd_event Lib "user32.dll" _

    (ByVal bVk As Byte, ByVal bScan As Byte, _

     ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

#End If

And here’s the function implementing a TAB keypress. Two calls are invoked here, one for pressing the TAB key, another for releasing it:

Public Sub PressTab()

    keybd_event VB_TAB, 0, 0, 0

    keybd_event VB_TAB, 0, KEYEVENTF_KEYUP, 0

End Sub

The two Constants used are declared as usual at the top of the module:

Const VB_TAB = 9

Const KEYEVENTF_KEYUP = &H2

I know I know, you must be thinking: what’s that PtrSafe qualifier I did not talk about, and what’s the story with the “IF VBA7 clause and LongPtr… All about that in next week’s Blog post!

And to expand on registering the DLL on different computers.

Once you compile and build the above code on your development machine, if you have

The project properties Build tab, select Register for COM interop.

your Visual Studio output folder (usually binDebug) where the compiled *.dll is found will also have a *.tlb file.

This *.tlb file is a ‘Type Library’. And is needed by the client machine to understand the different ‘Types’ in your *.dll and to basically tell the client machine how to use it.

By setting the above ‘Register for COM interop’ — aswell as a *.tlb file being produced, the assembly(dll) is registered on your machine, and is therefore accessible.

In VBA you can now add this file as a reference by

VBA Editor -> Tools -> References -> Browse -> Select

this will allow you to then declare the classes found in your library.

Dim TestClass As Test
Set TestClass = New Test
MsgBox TestClass.HelloWorld

HOWEVER — if you want to then use your dll on a different client machine, you will have to use regasm.exe — to register the assembly(dll) on that machine.

This can be done by the command line,

regasm.exe

in this case

regasm.exe TestDll.dll

once you have registered the assembly on the new client machine, you will be able to access it by again adding a reference to its *.tlb

Hope this helps!

I am trying to call function from dll which I have made in Visual Studio. I receive error message 

«Run-time error ‘453’:

Cant find DLL entry point SGR in

C:UsersAcasourcereposPERMHP_1PERMHP_1binDebugPERMHP_1.dll»

I have made dll under visual basic project: Class Library (.NET Framework).

I have added my dll to VBA project references

Can anyone help how to call function?

DLL CODE:

Option Strict On
Option Explicit On
Public Class Residual_SGR

    Public Function SGR(VISW As Double, VISO As Double, FI As Double, PB As Double, T As Double, RSPB As Double, CW As Double, GAMAO As Double, PMIN As Double, SWEX As Double, EQSW As Double, AK As Double, PCM As Double) As Double
        Dim A, B, X, C As Double

        PB = PB * 14.22334
        T = T * 1.8 + 32.0
        RSPB = RSPB * 5.6145821
        GAMAO = 141.5 / GAMAO - 131.5
        PMIN = PMIN * 14.22334
        A = -758.0 + 0.86 * FI + 5.29 * CW - 0.0444 * CW ^ 2 - 77.2 * Math.Log(CW) - 0.0386 * RSPB
        B = A + 0.0000533 * RSPB ^ 2 + 4.06 * GAMAO - 0.057 * GAMAO ^ 2
        X = 0.000156 * (PB / PMIN) ^ 2
        C = B + X + 2.02 * Math.Log(PB / PMIN) + 0.0123 * PB - 0.00000369 * PB ^ 2
        SGR = C - 3.71 * T + 0.00643 * T ^ 2 + 253.0 * Math.Log(T)
        Return SGR
    End Function
End Class

VBA CODE:

Option Explicit

Private Declare PtrSafe Function SGR Lib "C:UsersAcasourcereposPERMHP_1PERMHP_1binDebugPERMHP_1.dll" _
(ByRef VISW As Double, ByRef VISO As Double, ByRef FI As Double, ByRef PB As Double, ByRef T As Double, ByRef RSPB As Double, ByRef CW As Double, _
ByRef GAMAO As Double, ByRef PMIN As Double, ByRef SWEX As Double, ByRef EQSW As Double, ByRef AK As Double, ByRef PCM As Double) As Double


' using the function from VBA
Sub Button1_Click()
   MsgBox SGR(0.4406, 0.42512, 22.2, 141, 82.5, 95.8, 35.8, 0.82, 50, 2, 0.2, 0, 0)
End Sub

  • Moved by

    Friday, June 8, 2018 7:37 AM

Introduction

It’s fairly straightforward to call a .NET Framework library directly from Excel on Windows, particularly if you are using Visual Studio.  You don’t need Visual Studio Tools for Office.  However there doesn’t seem to be an easy guide on the internet anywhere. The Microsoft documentation is quite good on the subject, but can be a little confusing. This article is an attempt to redress the situation.

This article was updated in May 2020 to cover the latest versions of Visual Studio and Excel.  The article was originally written in 2007 and will work with all versions of Visual Studio and Excel since that date.

.NET Core

The approach below will NOT work with any version of .NET Core or .NET 5.  The last version of .NET this walk through will work with is the .NET Framework 4.8.

A Basic Walk Through

We’ll start by walking through a very basic example. We’ll get Excel to call a .NET method that takes a string as input (for example “ World”) and returns “Hello” concatenated with that input string (so, for example, “Hello World”).

1. Create a C# Windows Class Library (.NET Framework) project in Visual Studio called ‘DotNetLibrary’. It doesn’t matter which folder this is in for the purposes of this example.

2. To call a method in a class in our library from Excel we need the class to have a default public constructor. Obviously the class also needs to contain any methods we want to call. For this walk through just copy and paste the following code into our default class file:

using System;
using System.Collections.Generic;
using System.Text;
 
namespace DotNetLibrary
{
    public class DotNetClass
    {
        public string DotNetMethod(string input)
        {
            return "Hello " + input;
        }
    }
}

That’s it: if you look at existing articles on the web, or read the MSDN help, you might think you need to use interfaces, or to decorate your class with attributes and GUIDs. However, for a basic interop scenario you don’t need to do this.

3. Excel is going to communicate with our library using COM. For Excel to use a COM library there need to be appropriate entries in the registry. Visual Studio can generate those entries for us.

To do this bring up the project properties (double-click ‘Properties’ in Solution Explorer). Then:

i) On the ‘Application’ tab click the ‘Assembly Information…’ button. In the resulting dialog check the ‘Make assembly COM-visible’ checkbox. Click ‘OK’.

ii) On the ‘Build’ tab check the ‘Register for COM interop’ checkbox (towards the bottom: you may need to scroll down).

If you are working with 64-bit Excel then you may need to set the Platform Target in the Build properties to x64: thanks to danh for a comment to this effect.

4. Build the library.  You need Visual Studio to be running as an administrator for this to work because it’s putting COM entries in the registry.  If it’s not just right-click your Visual Studio icon and ‘Run As Administrator’.

5. Now start Excel and open a new blank workbook. Open the VBA code editor.  This can be tricky to find: you have to get the Developer tab visible on the Ribbon if it’s not already set up. To do this click the File tab, then click Options, Customize Ribbon, and under ‘Customize the Ribbon’ make sure ‘Main Tabs’ is selected in the dropdown, and then check the Developer checkbox. Now click the Developer tab, then click Visual Basic.

6. We now need to include a reference to our new library. Select ‘References’ on the Visual Basic Editor’s ‘Tools’ menu. If you scroll down in the resulting dialog you should find that ‘DotNetLibrary’ is in the list. Check the checkbox alongside it and click ‘OK’.

7. Now open the code window for Sheet1 (double click Sheet1 in the Project window). Paste the VBA code below into the code window for Sheet1:

Private Sub TestDotNetCall()

Dim testClass As New DotNetClass

MsgBox testClass.DotNetMethod(“World”)

End Sub

8. Click anywhere in the code you’ve just pasted in and hit ‘F5’ to run the code. You should get a ‘Hello World’ message box.

Getting Intellisense Working in Excel

Whilst the VBA code above compiles and executes, you will discover that intellisense is not working in the code editor. This is because by default our library is built with a late binding (run-time binding) interface only. The code editor therefore doesn’t know about the types in the library at design time.

There are good reasons for only using a late-bound interface by default: with COM versioning libraries can become difficult with early-bound interfaces. In particular, if you change the early-bound interface by adding, for example, a method in between two existing methods you are likely to break existing clients as they are binding based on the order of the methods in the interface.

For similar reasons you are heavily encouraged to code your interface separately as a C# interface and then implement it on your class, rather than using the default public interface of the class as here. You then should not change that interface: you would implement a new one if it needed to change.

For more on this see:

https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.classinterfaceattribute(v=vs.110).aspx

https://msdn.microsoft.com/en-us/library/system.runtime.interopservices.classinterfacetype(v=vs.110).aspx

However, we can build our library to use early bound interfaces, which means intellisense will be available. To do this we need to add an attribute from the System.Runtime.InteropServices namespace as below:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
 
namespace DotNetLibrary
{
    [ClassInterface(ClassInterfaceType.AutoDual)]
    public class DotNetClass
    {
        public DotNetClass()
        {
        }
        public string DotNetMethod(string input)
        {
            return "Hello " + input;
        }
    }
}

If you change your code as above it will expose an ‘AutoDual’ interface to COM. This means it is still exposing the late-bound interface as before, but now also exposes an early-bound interface. This means intellisense will work.

To get this working:

1. Save your workbook and close Excel.  Excel will lock the DotNetLibrary dll and prevent Visual Studio from rebuilding it unless you close it.  Remember you need to save your new code module.  Unless you are using a version of Excel prior to Excel 2007 you need to change the type: save it as type Excel Macro-Enabled Workbook (*.xlsm).

2. Go back into Visual Studio, change the DotNetClass as shown above, and rebuild the library.  Note there’s a new ‘using’ statement as well as the attribute line.

3. Re-open your Excel spreadsheet.  Once again if you are using a recent version of Excel there is an extra step: you need to explicitly enable macros.  A warning bar will appear beneath the ribbon saying the ‘Macros have been disabled’.  Click the ‘Enable content’ button and that should disappear.

4. Get the VBA code window up again (see item 5 above).

5. Excel can get confused about the interface changes unless you re-reference the library. To do this go to Tools/References. The DotNetLibrary reference should be near the top of the list now. Uncheck it and close the window. Now open the window again, find the library in the list, and re-check it (trust me, you need to do this).

6. Now run the code and it should still work (put a breakpoint in the routine and hit F5).

7. Enter a new line in the routine after the ‘MsgBox’ line, and type ‘testClass.’. When you hit the ‘.’ you should get an intellisense dropdown which shows that DotNetMethod is available. See below.

Intellisense in Excel

Let me re-iterate that this works and is fine for development, but for release code you are better off using the default late binding interfaces unless you understand the full versioning implications. That is, you should remove the ClassInterface attribute from your code when you do a release.

Deployment

In the example here we are using Visual Studio to register our .NET assembly on the workstation so that Excel can find it via COM interop. However, if we try to deploy this application to client machines we’re not going to want to use Visual Studio.

Microsoft have provided a command-line tool, regasm.exe, which can be used to register .NET assemblies for COM interop on client workstations. It can also be used to generate a COM type library (.tlb) separate from the main library (.dll), which is considered good practice in general.  You can find regasm.exe in the .NET framework folder: C:WindowsMicrosoft.NETFrameworkv4.0.30319 or similar.

As usual with .NET assemblies you have the choice of strong-naming your assembly and installing it in the GAC, or of not strong-naming it and including it in a local path. If you have strong-named your assembly and installed it in the GAC all you need to do is bring up an administrator Visual Studio command prompt and run:

regasm DotNetLibrary.dll

If you have not strong-named your assembly you need to tell regasm.exe where it is so that it can find it to register it. To do this you need to run the command below, where c:ExcelDotNet is the path where DotNetLibrary.dll can be found. This works fine, although it will warn you that you should really strong-name your assembly:

regasm /codebase c:ExcelDotNetDotNetLibrary.dll

Note that you can unregister an assembly with the /u option of regasm.

For more detail on this see https://docs.microsoft.com/en-us/dotnet/framework/tools/regasm-exe-assembly-registration-tool

Debugging into .NET from Excel

You may want to debug from Excel into your class library. To do this:

1. Using Visual Studio bring up the Properties window for the class library.

2. Go to the Debug tab and select the ‘Start external program’ option under ‘Start Action’. In the textbox alongside enter the full path including file name to Excel.exe for the version of Excel you are using (usually in Program Files (x86)Microsoft OfficerootOffice16 or Program FilesMicrosoft OfficeOffice).

3. On the same Debug tab under ‘Command line arguments’ enter the full path including file name to your test workbook (the .xlsm file). Once you’re done it should something like below::

Project Properties for Excel

4. Now put a breakpoint in the code (in our example the sensible place is in method DotNetMethod) and hit F5 in the .NET project. The .NET code should compile and Excel should start with your workbook opened. If you now run the VBA code to call the .NET library again, as above, you should find that the code will break at the breakpoint you set in the .NET code.

Possible Problem with these Examples

One problem we have had with these examples is that Excel can get confused about which version of the .NET Framework to load if you have more than one version installed. If this happens you will get an automation error when you try to instantiate .NET objects at runtime from Excel. The .NET types will appear correctly in the Excel object browser.

The workaround for this is to tell Excel explicitly that the version of the .NET Framework that you are using is supported. To do this create a text file called Excel.exe.config and put it in the same directory as Excel.exe itself. The file should contain the text below (with the version number replaced with the .NET Framework version you are using):

<?xml version="1.0"?>
<configuration>
  <startup>
    <supportedRuntime version="v2.0.50727"/>
  </startup>
</configuration>

References:

Index page from MSDN

https://docs.microsoft.com/en-us/dotnet/framework/interop/exposing-dotnet-components-to-com

More on COM Interop from COM clients into .NET:

https://www.codeproject.com/Articles/7587/Exposing-COM-interfaces-of-a-NET-class-library-for

Guidelines for COM Interoperability from .NET

https://blogs.msdn.microsoft.com/heaths/2005/03/09/guidelines-for-com-interoperability-from-net-2/

Excel/.NET versioning problems

http://krgreenlee.blogspot.com/2006/01/software-running-excel-with-net-11.html

Calling A FORTRAN DLL From Excel VBA


Introduction


According to Wikipedia: “Fortran (derived from Formula Translating System) is a general-purpose, imperative programming language that is especially suited to numeric computation and scientific computing. Originally developed by IBM in New York City in the 1950s for scientific and engineering applications, Fortran came to dominate this area of programming early on and has been in continuous use for over half a century in computationally intensive areas such as numerical weather prediction, finite element analysis, computational fluid dynamics, computational physics, and computational chemistry. It is one of the most popular languages in the area of high-performance computing and is the language used for programs that benchmark and rank the world’s fastest supercomputers”.

Only the above paragraph is probably not enough to describe the importance of the Fortran language in engineering and the scientific world. Literally, thousands of lines of Fortran code have been written in the last 50+ years by several developers throughout the world. And, now, the big question is: why not take advantage of the code already written in Fortran and use it from Excel/VBA? Is it possible to do that?

Well, I have some good news, the answer is YES! So, in this post, I will try to provide you some insights about how to do it. In short, the idea is to build a Dynamic-link library (DLL) file that will include the necessary Fortran functions and subs, and, then, call these functions/subs (DLL file) from VBA.


Fortran code


The first step in this tutorial is to build the DLL file. I used the Fortran code that you will find below; the FRICTIONFACTOR function is actually a Fortran version of an old VBA function that I developed some years ago. By the way, sorry for the uppercase in the code, but it is an old habit that I acquired when I was writing the Fortran code for my M.Sc. thesis (actually Fortran became case-insensitive from Fortran 90 and onwards).

FUNCTION  FRICTIONFACTOR (ROUGHNESS, DIAMETER, VELOCITY,VISCOSITY)
    !DEC$ ATTRIBUTES DLLEXPORT:: FRICTIONFACTOR

    !-----------------------------------------------------------------------------
    !Calculates the friction factor of a pipe using Churchill's equation (1977).
    !This equation is valid for all types of flows (from laminar to turbulent).
    !
    !Written By:    Christos Samaras
    !Date:          21/03/2014
    !E-mail:        [email protected]
    !Site:          https://www.myengineeringworld.net
    !-----------------------------------------------------------------------------

    IMPLICIT NONE

    !Declaring the necessary variables.
    REAL*8::  FRICTIONFACTOR,ROUGHNESS, DIAMETER, VELOCITY, VISCOSITY ,REYNOLDS, A, B, C, D

    !Calculate the Reynolds number (diameter in m, velocity in m/s, kinematic viscosity in m2/s).
    REYNOLDS = (DIAMETER * VELOCITY) / VISCOSITY

    !Calculate the intermediate variables A and B.
    A=((2.457 * LOG(1. / ((ROUGHNESS / (3.7 * DIAMETER)) + ((7. / REYNOLDS) ** 0.9)))) ** 16)
    B = (37530. / REYNOLDS) ** 16.

    !Apply the equation.
    FRICTIONFACTOR = 8. * ((((8. / REYNOLDS) ** 12.) + ((A + B) ** (-3./2.))) ** (1./12.))
    
    RETURN

END FUNCTION FRICTIONFACTOR

SUBROUTINE DOUBLEARRAY (ELEMENTS, INARRAY, OUTARRAY)
    !DEC$ ATTRIBUTES DLLEXPORT:: DOUBLEARRAY

    !---------------------------------------------------------------------
    !Receives as input a one-dimensional array and it doubles tis values.
    !
    !Written By:    Christos Samaras
    !Date:          23/03/2014
    !E-mail:        [email protected]
    !Site:          https://www.myengineeringworld.net
    !---------------------------------------------------------------------
    
    IMPLICIT NONE
    
    !Declaring the necessary variables.
    INTEGER*4, INTENT(IN) :: ELEMENTS
    INTEGER*4, INTENT(IN) :: INARRAY(ELEMENTS)
    INTEGER*4, INTENT(OUT) :: OUTARRAY(ELEMENTS)
    INTEGER*4:: I
    
    !Double the values of the input array.
    OUTARRAY = 2*INARRAY

    RETURN

END 

Important notes

1. To build the DLL file, I used the old Compaq Visual Fortran (version 6.6) compiler. I am sure that you can find a much more recent compiler (for example an Intel Fortran Compiler).

2. Be careful of the attributes required for the DLL export, otherwise, the Fortran functions/subs will not be “visible” from VBA:

!DEC$ ATTRIBUTES DLLEXPORT:: FRICTIONFACTOR
!DEC$ ATTRIBUTES DLLEXPORT:: DOUBLEARRAY  

The above lines might be slightly different for your compiler, but in any case, they must be included in the code (as comments).

3. Be aware of DLL dependencies. If the DLL file is going to be used in other computers (that haven’t the same Fortran compiler installed), ensure that the DLL is “independent”. For example, to achieve this in Compaq Visual Fortran I had to go to Project -> Settings -> Fortran tab -> select Libraries from the dropdown menu Category, select the Single-Threaded from the Use run time library dropdown menu, and, finally, press the OK button (see the picture below). Otherwise, the DLL file that the compiler built required the MSVCRTD.dll file to run. You can check all the DLL dependencies using a great freeware tool called Dependency Walker.

Single Threaded Library


VBA code


And here is the VBA code that “calls” the compiled Fortran code from the MySamble.dll file.

Option Explicit
Option Base 1

'---------------------------------------------------------------------------
'This module contains two examples of calling a FORTRAN dll from Excel/VBA.
'The first one uses a FORTRAN function straight from the worksheet,
'while the second one calls a FORTRAN sub from a VBA sub.

'Written By:    Christos Samaras
'Date:          25/03/2014
'E-mail:        [email protected]
'Site:          https://www.myengineeringworld.net
'---------------------------------------------------------------------------
                
'Declaring the function and the sub from dll file.
'NOTE: if the dll file is NOT in the same folder with the workbook, use its full path instead. Example:
'Public Declare Function FRICTIONFACTOR Lib "C:UsersChristosDesktopMySample.dll"
Public Declare Function FRICTIONFACTOR Lib "MySample.dll" _
                            (ROUGHNESS As Double, _
                            DIAMETER As Double, _
                            VELOCITY As Double, _
                            VISCOSITY As Double) As Double

Public Declare Sub DOUBLEARRAY Lib "MySample.dll" _
                        (ELEMENTS As Long, _
                        INARRAY As Long, _
                        OUTARRAY As Long)

Sub CallDoubleArray()

    'Declaring the necessary variables.
    Dim i           As Long
    Dim LastRow     As Long
    Dim ArrayIn()   As Long
    Dim ArrayOut()  As Long
             
    'Activate the Sub sheet and find the last row.
    With Sheets("Sub")
        .Activate
        LastRow = .Cells(.Rows.Count, "B").End(xlUp).Row
    End With
    
    'If there is at least one value proceed.
    If LastRow >= 4 Then
        
        'Resize the arrays.
        ReDim ArrayIn(1 To LastRow - 3)
        ReDim ArrayOut(1 To LastRow - 3)
        
        'Populate the input array.
        For i = 4 To LastRow
            ArrayIn(i - 3) = ActiveSheet.Range("B" & i)
        Next i
        
        'Call the FORTRAN Sub.
        Call DOUBLEARRAY(LastRow - 3, ArrayIn(1), ArrayOut(1))
        
        'Pass the results back into sheet.
        For i = 4 To LastRow
            ActiveSheet.Range("D" & i) = ArrayOut(i - 3)
        Next i
        
    Else
        
        'Empty column, inform the user.
        MsgBox "Please enter at least one value on B column and retry!", vbExclamation, "No data"
        ActiveSheet.Range("B4").Select
        
    End If

End Sub 

Important notes

1. It is essential to call the function/sub included on the DLL file using the correct data type. Here is a list of Fortran data types, along with the corresponding VBA/VB 6.0 data types.

Fortran data types VBA/Visual Basic data types
INTEGER*2 Integer
INTEGER*4 Long
REAL Single
REAL*4 Single
DOUBLE PRECISION Double
REAL*8 Double
LOGICAL*2 Integer
LOGICAL*4 Boolean/Long
CHARACTER*n String*n (Passed ByVal)

2. Note the usage of Option Base 1 at the beginning of the code. Contrary to VBA, arrays in Fortran are NOT zero-based, so we use this expression to avoid array sizing problems between the two languages.

3. To avoid calling the DLL file using its full path I used the following workbook open event, which changes the current directory to the folder where the workbook is located:

Option Explicit

Private Sub Workbook_Open()

    ChDir (ThisWorkbook.Path)
    Application.CalculateFull

End Sub 

If the DLL file is NOT in the same folder as the workbook, use its full path. For example:

Public Declare Function FRICTIONFACTOR Lib "C:UsersChristosDesktopMySample.dll" 

4. The second example (with the sub), although it might look trivial, actually shows you how to transfer arrays between VBA and Fortran. Note that in the following line we use the first element of each array:

Call DOUBLEARRAY(LastRow - 3, ArrayIn(1), ArrayOut(1)) 

Epilogue


Fortran is not a dead/outdated programming language. It’s still being used by many developers/scientists/engineers. My suggestion is to take advantage of the code already written in Fortran and don’t re-inventing the wheel. I know that the mixing of Fortran and VBA is probably a little advanced topic, but the above example, along with the side notes will help you overcome many of the obstacles that you will find on your way.


Downloads


Download

The zip file contains an f90 file with the sample Fortran code, the DLL file, and a workbook with the VBA code. The Excel file can be opened with Excel 2007 or newer. Please enable macros before using it.

Page last modified: October 1, 2021

Author Image

Hi, I am Christos, a Mechanical Engineer by profession (Ph.D.) and a Software Developer by obsession (10+ years of experience)! I founded this site back in 2011 intending to provide solutions to various engineering and programming problems.

Add Content Block

In the last blog post, we created a simple dynamic link library for Windows that was easily callable from C, including Microsoft’s Visual C++. As stated in the article, the resulting DLL was entirely standards-compliant: it utilized proper calling conventions and it exported the functions we wished to expose. Therefore, we can take another step and call said DLL from Microsoft Office Excel.

Excel spreadsheets are often used for data analysis, and, occasionally, when non-trivial analyses are necessary, Fortran may make life a bit easier. Our Fortran DLL contained three functions for computing three types of averages, but countless, more complicated use cases exist. To keep this explanation simple, though, we’ll proceed with interfacing with our averaging procedures.

A Fortran DLL, or any DLL for that manner, isn’t directly interfaced with Excel. We first need a small amount of glue in the form of Visual Basic for Applications, or VBA, code. This BASIC code is necessary to define the interface to the DLL, transform the input data properly, and expose functions to the Excel worksheet. To get started on writing this glue, we need to open Excel’s VBA editor, accessible either with the Alt-F11 hotkey or clicking «Visual Basic» under the Developer menu. This should open the Visual Basic for Applications editor:

VBAEdit

Next, we’ll need a new «Module» for our routines, which can be added by selecting «Module» from the Insert menu. This option should present a BASIC editor for defining our routines.

The first step is to create prototype calls for our Fortran subroutines. Recall our definition of the mean subroutine was:

      function mean(xc, n) bind(c)
      use ISO_C_BINDING
      implicit none
cGCC$ ATTRIBUTES STDCALL, DLLEXPORT :: mean

      real(kind=c_float)::mean

      type(c_ptr), value::xc
      integer(kind=c_int), value::n

The equivalent VBA declaration would be:

Private Declare PtrSafe Function mean Lib "C:workspaceExcelDllfortran-library.dll" _
    (ByRef x As Single, ByVal n As Integer) As Single

The above prototype has a few details that require explaining. First, we’ve declared this function with the «PtrSafe» attribute as required by 64-bit Excel; if a user is running 32-bit Excel, this attribute should not be included. Second, the prototype includes the full path to the DLL in question, and this path should be changed appropriately. Third, our array, x in the VBA prototype and xc in the Fortran function declaration, is not declared as an array in VBA. Rather, when using the «ByRef» attribute, we’re effectively passing the memory address of the first element of the array. Subsequent elements are assumed to be stored immediately after the first in a true C-compatible, one-dimensional array, so the above is sufficient. Visual Basic for Applications treats arrays somewhat differently than C does, so we’re not using a true VBA array as a parameter.

Our remaining Fortran functions are prototyped in VBA as:

Private Declare PtrSafe Function median Lib "C:workspaceExcelDllfortran-library.dll" _
    (ByRef x As Single, ByVal n As Integer) As Single

Private Declare PtrSafe Function mode Lib "C:workspaceExcelDllfortran-library.dll" _
    (ByRef x As Single, ByVal n As Integer, ByRef y As Single) As Integer

To use our functions on a spreadsheet, we need to create some VBA functions to call these routines properly and format the data accordingly. Taking the mean function as our initial example, we would want to define a worksheet-callable function, wsMean, as such in VBA:

Function wsMean(x As range) As Single
    Dim n As Integer
    Dim p() As Single

    p = Range2Array(x, n)

    wsMean = mean(p(1), n)

End Function

A worksheet function for computing the mean will accept a range of cells and return a single mean value for these given cells. The first step, though, is to convert the range of cells to a true VBA array. In our case, we can write a small, reusuable function that accepts a range and returns a one-dimensional array of values, Range2Array:

Function Range2Array(x As range, n As Integer)
    Dim res() As Single
    n = x.Rows.Count

    ReDim res(1 To n)

    For irow = 1 To n
        res(irow) = x.Cells(irow, 1).Value
    Next irow

    Range2Array = res
End Function

Much of the code above is specific to Excel and VBA, but it is basically reading the value of each cell and assigning each cell’s numeric value to an element of a properly-sized array. One might note that this routine only translates a single column. The user, though, can quickly extend this to multiple columns.

Returning to our wsMean wrapper, the call to the Fortran mean routine was:

wsMean = mean(p(1), n)

We appear to be passing only the first element of the array. However, recall that the function definition actually declared this argument as «ByRef,» meaning that VBA should pass the address of this argument, which is also the address of our entire array p. Therefore, our Fortran routine will receive the equivalent of a C pointer to a block of memory containing our one-dimensional array.

The median function can be wrapped accordingly:

Function wsMedian(x As range) As Single
    Dim n As Integer
    Dim p() As Single

    p = Range2Array(x, n)

    wsMedian = median(p(1), n)

End Function

Note that wsMedian is written nearly identical to wsMean since they both accept an array and a length, and return a single number. Our Fortran mode function is slightly more complex since it returns results in an array passed as an argument. The wrapper wsModes appears below:

Function wsModes(x As range) As String
    Dim n As Integer
    Dim p() As Single

    Dim res() As Single
    Dim resString() As String
    Dim rescount As Integer

    p = Range2Array(x, n)

    ReDim res(1 To n)

    rescount = mode(p(1), n, res(1))

    If (rescount = 0) Then
        wsModes = "None"
    Else
        ReDim resString(1 To rescount)
        For i = 1 To rescount
            resString(i) = Str(res(i))
        Next i

        wsModes = Join(resString, ", ")
    End If

End Function

In wsModes we need to define a results array to pass to our Fortran function. If the Fortran mode function returns a result of zero modes, this function will actually populate a cell with the string «None.» If valid modes are returned, a text list of modes is created to populate a single cell.

Our three worksheet functions, wsMean, wsMedian, and wsModes, are now all callable directly from an Excel cell:

wsCall

The resulting spreadsheet can now indirectly call our Fortran procedures, passing data from spreadsheet cells and properly populating resulting cells.

For time-consuming calculations, this exposure of Fortran procedures to the worksheet itself might be a poor choice because Excel often recomputes sheets when values change. The user might be better off inserting a button that triggers calls to Excel in those cases to limit the amount of computing necessary to refresh any given worksheet.

A macro-enabled spreadsheet along with the original Fortran DLL code is attached below:

  • averaging.f
  • averages.xlsm

The VBA code in the Excel spreadsheet will need to be updated with the proper path to the Fortran DLL since it currently refers to a specific, absolute path on our demonstration system. Once updated, though, Excel should be able to interface with a Fortran DLL quite easily.

Like this post? Please share to your friends:
  • Call word from excel
  • Call word for letters
  • Call vba excel описание
  • Call symbol in word
  • Call out text in word