Содержание
- Доступ к библиотекам DLL в Excel
- Вызов функций и команд DLL из VBA
- Типы аргументов в C/C++ и VBA
- Аргументы строк и переменных
- Вызов функций DLL непосредственно с листа
- Вызов команд DLL непосредственно из Excel
- Память 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.
Источник
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
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
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!
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.
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::
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
Hi, I am doing something similar right now. here is what I know. Try using
the following link below.
http://www.windowsdevcenter.com/pub/a/windows/2005/04/26/create_dll.html
This next example I got off the web. I don’t have the addess but we can
discuss this offline. if want further help.
Title Make a standard DLL
Description This example shows how to make a standard DLL in Visual Basic 6.
Keywords DLL, ActiveX DLL
Categories ActiveX, Windows
This examples builds a standard DLL that you can call by using the normal
DLL calling conventions. For full details, see the article Creating a Windows
DLL with Visual Basic.
Thanks to Luke Emmet for pointing this article out.
The basic staps are:
Hack the linking process.
Make an executable program to call the linker. Reomve the default Form1 and
create the following Sub Main.
Public Sub Main()
Dim SpecialLink As Boolean, fCPL As Boolean, fResource _
As Boolean
Dim intPos As Integer
Dim strCmd As String
Dim strPath As String
Dim strFileContents As String
Dim strDefFile As String, strResFile As String
Dim oFS As New Scripting.FileSystemObject
Dim fld As Folder
Dim fil As File
Dim ts As TextStream, tsDef As TextStream
strCmd = Command
Set ts = oFS.CreateTextFile(App.Path & «lnklog.txt»)
ts.WriteLine «Beginning execution at » & Date & » » & _
Time()
ts.WriteBlankLines 1
ts.WriteLine «Command line arguments to LINK call:»
ts.WriteBlankLines 1
ts.WriteLine » » & strCmd
ts.WriteBlankLines 2
‘ Determine if .DEF file exists
‘
‘ Extract path from first .obj argument
intPos = InStr(1, strCmd, «.OBJ», vbTextCompare)
strPath = Mid(strCmd, 2, intPos + 2)
intPos = InStrRev(strPath, «»)
strPath = Left(strPath, intPos — 1)
‘ Open folder
Set fld = oFS.GetFolder(strPath)
‘ Get files in folder
For Each fil In fld.Files
If UCase(oFS.GetExtensionName(fil)) = «DEF» Then
strDefFile = fil
SpecialLink = True
End If
If UCase(oFS.GetExtensionName(fil)) = «RES» Then
strResFile = fil
fResource = True
End If
If SpecialLink And fResource Then Exit For
Next
‘ Change command line arguments if flag set
If SpecialLink Then
‘ Determine contents of .DEF file
Set tsDef = oFS.OpenTextFile(strDefFile)
strFileContents = tsDef.ReadAll
If InStr(1, strFileContents, «CplApplet», _
vbTextCompare) > 0 Then
fCPL = True
End If
‘ Add module definition before /DLL switch
intPos = InStr(1, strCmd, «/DLL», vbTextCompare)
If intPos > 0 Then
strCmd = Left(strCmd, intPos — 1) & _
» /DEF:» & Chr(34) & strDefFile & Chr(34) & _
» » & _
Mid(strCmd, intPos)
End If
‘ Include .RES file if one exists
If fResource Then
intPos = InStr(1, strCmd, «/ENTRY», vbTextCompare)
strCmd = Left(strCmd, intPos — 1) & Chr(34) & _
strResFile & _
Chr(34) & » » & Mid(strCmd, intPos)
End If
‘ If Control Panel applet, change «DLL» extension to
‘ «CPL»
If fCPL Then
strCmd = Replace(strCmd, «.dll», «.cpl», 1, , _
vbTextCompare)
End If
‘ Write linker options to output file
ts.WriteLine «Command line arguments after » & _
«modification:»
ts.WriteBlankLines 1
ts.WriteLine » » & strCmd
ts.WriteBlankLines 2
End If
ts.WriteLine «Calling LINK.EXE linker»
Shell «linklnk.exe » & strCmd
If Err.Number <> 0 Then
ts.WriteLine «Error in calling linker…»
Err.Clear
End If
ts.WriteBlankLines 1
ts.WriteLine «Returned from linker call»
ts.Close
End Sub
This program does roughly the same thing that Visual Basic does when it
creates a DLL except it adds the /DEF flag to the command.
Compile the executable.
Rename the normal Visual Basic linker from Link.exe to LinkLnk.exe. On my
system, it’s at C:Program FilesMicrosoft Visual StudioVB98.
Copy the executable program that you compiled into this directory and name
it Link.exe. When Visual Basic links the DLL, it calls this program, which
calls the renamed LinkLnk.exe program, adding the new /DEF parameter.
Export the DLL’s routines.
Create a file named .def where is the name of the DLL. In this example, the
DLL is named Fibonacci.dll so this file is called Fibonacci.def.
Add code to this file similar to the following:
NAME MathLib
LIBRARY MathMod
DESCRIPTION «Add-on Library of Mathematical Routines»
EXPORTS DllMain @1
Fibo @2
This tells the linker about the main entry point DllMain and this example’s
function Fibo, both of which are created shortly.
Build the DLL.
Create a new ActiveX DLL project.
Leave the default Class1 class alone. You will not use it but Visual Basic
needs it to it has something to compile into the ActiveX DLL.
Add a code module and insert this code:
Public Const DLL_PROCESS_DETACH = 0
Public Const DLL_PROCESS_ATTACH = 1
Public Const DLL_THREAD_ATTACH = 2
Public Const DLL_THREAD_DETACH = 3
Public Function DllMain(hInst As Long, fdwReason As Long, _
lpvReserved As Long) As Boolean
Select Case fdwReason
Case DLL_PROCESS_DETACH
‘ No per-process cleanup needed
Case DLL_PROCESS_ATTACH
DllMain = True
Case DLL_THREAD_ATTACH
‘ No per-thread initialization needed
Case DLL_THREAD_DETACH
‘ No per-thread cleanup needed
End Select
End Function
‘ Return a Fibonacci number.
Public Function Fibo(ByVal N As Integer) As Long
If N <= 1 Then
Fibo = 1
Else
Fibo = Fibo(N — 1) + Fibo(N — 2)
End If
End Function
DllMain is the DLL entry point. Fibo is a function that the DLL is exporting.
Compile the DLL. This should invoke the new Link.exe you built. If you look
in that program’s directory, you should see the log file it generates.
Build a test program to call the DLL.
Make a standard Visual Basic EXE.
Declare the routine exported by the DLL as in the following code:
Private Declare Function Fibo Lib _
«C:WebSiteHowToSrca2Fibonacci.dll» (ByVal N As _
Integer) As Long
Insert the path to the DLL on your computer.
Run the program.
That should do it. Watch out for typos. If the .DEF file doesn’t spell the
function’s name correctly, the DLL won’t compile and the error messages are
not very good.
See the article mentioned at the beginning for more detail and some
information about how the original author figured all this out.
The default policy is preventing the CLR 4 from excuting the legacy code from the CLR 2 :
Set clr = New mscoree.CorRuntimeHost
To enable the legacy execution, you can either create the file excel.exe.config
in the folder where excel.exe
is located:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup useLegacyV2RuntimeActivationPolicy="true">
<supportedRuntime version="v4.0"/>
</startup>
</configuration>
Or you can call the native function CorBindToRuntimeEx
instead of New mscoree.CorRuntimeHost
:
Private Declare PtrSafe Function CorBindToRuntimeEx Lib "mscoree" ( _
ByVal pwszVersion As LongPtr, _
ByVal pwszBuildFlavor As LongPtr, _
ByVal startupFlags As Long, _
ByRef rclsid As Long, _
ByRef riid As Long, _
ByRef ppvObject As mscoree.CorRuntimeHost) As Long
Private Declare PtrSafe Function VariantCopy Lib "oleaut32" (dest, src) As Long
''
' Creates a .Net object with the CLR 4 without registration. '
''
Function CreateInstance(assembly As String, typeName As String) As Variant
Const CLR$ = "v4.0.30319"
Static domain As mscorlib.AppDomain
If domain Is Nothing Then
Dim host As mscoree.CorRuntimeHost, hr&, T&(0 To 7)
T(0) = &HCB2F6723: T(1) = &H11D2AB3A: T(2) = &HC000409C: T(3) = &H3E0AA34F
T(4) = &HCB2F6722: T(5) = &H11D2AB3A: T(6) = &HC000409C: T(7) = &H3E0AA34F
hr = CorBindToRuntimeEx(StrPtr(CLR), 0, 3, T(0), T(4), host)
If hr And -2 Then err.Raise hr
host.Start
host.GetDefaultDomain domain
End If
VariantCopy CreateInstance, domain.CreateInstanceFrom(assembly, typeName).Unwrap
End Function
I’m not sure if this was just a coincidence or because I posted related question. SO showed me your question and I think I could also contribute something.
When working with VBA and DLL, most solutions that I’ve seen so far is telling me to register the DLL and make it com/gac visible. If you are doing this in your PC that’s absolutely fine but if you are distributing your VBA application, you don’t really want to install DLLs in their system. You might not have permission or you don’t really want to go through install/uninstall process or messing with referencing issues.
However you can load dlls dynamically using some windows APIs.
DLL
Now the question is how to access .NET dll from vba? if your clients have mixed os architecture x86 x64 you need to handle this accordingly. Lets assume we are working on 32bit office/Excel.
If you create a .NET dll and would like to access it from VBA it will throw an error message similar to «Can’t find the dll entry point». thankfully Robert Giesecke has created an abstract wrapper which will allow you to create simple DLL consumable via VBA.
A template can be found here.
All you have to do
- Create a new class project in visual studio
- Set the project platform either x86 for 32bit and otherwise
- Create your methods within a main class.
- create another class which will return your main class as object (is returning to vba)
- (follow the template from his website)
Lets assume you have followed his template and created a test method as following.
[ComVisible(true), ClassInterface(ClassInterfaceType.AutoDual)]
public class YOUR_MAIN_CLASS
{
[return: MarshalAs(UnmanagedType.BStr)]
public string FN_RETURN_TEXT(string iMsg)
{
return "You have sent me: " + iMsg + "...";
}
}
and your unmanagedexport class:
static class UnmanagedExports
{
[DllExport]
[return: MarshalAs(UnmanagedType.IDispatch)]
static object YOUR_DLL_OBJECT()
{
return new YOUR_MAIN_CLASS();
}
}
Preparing to access the dll from vba side
Add the DLL to your root folder:
#If VBA7 Then
Public Declare PtrSafe Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As LongPtr
Public Declare PtrSafe Function YOUR_DLL_OBJECT Lib "YOUR_DLL.dll" () As Object
#Else
Public Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal strFilePath As String) As Long
Public Declare Function YOUR_DLL_OBJECT Lib "YOUR_DLL.dll" () As Object
#End If
Now It’s all about loading the dll and creating & accessing objects it in vba.
that would be:
LoadLibrary (FN_APP_GET_BASE_PATH & "YOUR_DLL.dll")
dim mObj as object
set mObj = YOUR_DLL_OBJECT()
debug.print mObj.FN_RETURN_TEXT("Testing ..")
the output should be
"You have sent me: Testing ....."
Advantages
I personally don’t like installing and referencing dlls. By following above template, you don’t need to reference anything, you don’t need to install anything just load and work with your the DLL with full freedom.
NOTE: I assume the dll/.net code is yours and you can compile it again with above templates to.
I had success with above template and created a .NET non-blocking notifications for vba you can have a look here: non-blocking «toast» like notifications for Microsoft Access (VBA)
Here’s your solution, tested for .NET 2.0 and .NET 4.0, 32 bit and 64 bit, courtesy of Soraco Technologies.
The solution proposed below uses late binding and does not require registration of the .NET assemblies.
Declarations
Add the following declarations to your project:
#If VBA7 Then
Private Declare PtrSafe Function GetShortPathName Lib “Kernel32.dll” Alias “GetShortPathNameW” (ByVal LongPath As LongPtr, ByVal ShortPath As LongPtr, ByVal Size As Long) As Long
Private Declare PtrSafe Function SetDllDirectory Lib “Kernel32.dll” Alias “SetDllDirectoryW” (ByVal Path As LongPtr) As Long
Private Declare PtrSafe Sub LoadClr_x64 Lib “QlmCLRHost_x64.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown)
Private Declare PtrSafe Sub LoadClr_x86 Lib “QlmCLRHost_x86.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown)
#Else
Private Declare Function GetShortPathName Lib “Kernel32.dll” Alias “GetShortPathNameW” (ByVal LongPath As Long, ByVal ShortPath As Long, ByVal Size As Long) As Long
Private Declare Function SetDllDirectory Lib “Kernel32.dll” Alias “SetDllDirectoryW” (ByVal Path As Long) As Long
Private Declare Sub LoadClr_x64 Lib “QlmCLRHost_x64.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown)
Private Declare Sub LoadClr_x86 Lib “QlmCLRHost_x86.dll” (ByVal clrVersion As String, ByVal verbose As Boolean, ByRef CorRuntimeHost As IUnknown)
#End If ‘ WinAPI Declarations
' Declare variables
Dim m_myobject As Object
Dim m_homeDir As String
Initialization
You must initialize the m_homeDir variable to the path where the .NET assemblies are located.
For example, if you install the .NET assemblies in the same folder as the Excel or MS-Access files, you should initialize m_homeDir to:
Excel: m_homeDir = ThisWorkbook.Path
Access: m_homeDir = CurrentProject.Path
.NET Object Creation
Add the following code to your project.
Private Function GetMyObject(dllPath As String, dllClass As String) As Object
Dim LongPath As String
Dim ShortPath As String
LongPath = “\?” & m_homeDir
ShortPath = String$(260, vbNull)
PathLength = GetShortPathName(StrPtr(LongPath), StrPtr(ShortPath), 260)
ShortPath = Mid$(ShortPath, 5, CLng(PathLength – 4))
Call SetDllDirectory(StrPtr(ShortPath))
Dim clr As mscoree.CorRuntimeHost
If Is64BitApp() Then
Call LoadClr_x64(“v4.0”, False, clr)
Else
Call LoadClr_x86(“v4.0”, False, clr)
End If
Call clr.Start
Dim domain As mscorlib.AppDomain
Call clr.GetDefaultDomain(domain)
Dim myInstanceOfDotNetClass As Object
Dim handle As mscorlib.ObjectHandle
Set handle = domain.CreateInstanceFrom(dllPath, dllClass)
Dim clrObject As Object
Set GetMyObject = handle.Unwrap
Call clr.Stop
End Function
Private Function Is64BitApp() As Boolean
#If Win64 Then
Is64BitApp = True
#End If
End Function
Instantiate the .NET object
Now you are ready to instantiate your .NET object and start using it. Add the following code to your application:
m_homeDir = ThisWorkbook.Path
m_myobject = GetMyObject(m_homeDir & “yourdotnet.dll”, “namespace.class”)
The first argument is the full path to the .NET DLL.
The second argument is the fully qualified name of the requested type, including the namespace but not the assembly, as returned by the Type.FullName property.
Required DLLs
The solution requires deployment of 2 DLLs that are responsible for hosting the .NET CLR. The DLLs are expected to be deployed in the same folder as your Excel or MS-Access file.
The DLLs can be downloaded from Soraco’s web site: https://soraco.co/products/qlm/QLMCLRHost.zip
Licensing LGPL-2.1
We hereby grant you the right to use our DLLs as long as your application does not compete directly or indirectly with Quick License Manager. You can use these DLLs in your commercial or non-commercial applications.
Here is a canonical answer on the 3 main methods to call .Net from Excel (or VBA).
All three ways work in .Net 4.0.
1. XLLs
The 3rd party vendor Add-In Express offer XLL functionality, however its free and easy to use Excel-DNA the author is here https://stackoverflow.com/users/44264
Here is an extract from the Excel-DNA page: https://excel-dna.net/
Introduction
Excel-DNA is an independent project to integrate .NET into Excel. With Excel-DNA you can make native (.xll) add-ins for Excel using C#, Visual Basic.NET or F#, providing high-performance user-defined functions (UDFs), custom ribbon interfaces and more. Your entire add-in can be packed into a single .xll file requiring no installation or registration.
Getting Started
If you are using a version of Visual Studio that supports the NuGet Package Manager (including Visual Studio 2012 Express for Windows Desktop), the easiest way to make an Excel-DNA add-in is to:
Create a new Class Library project in Visual Basic, C# or F#.
Use the Manage NuGet Packages dialog or the Package Manager Console to install the Excel-DNA package:
PM> Install-Package Excel-DNA
Add your code (C#, Visual Basic.NET or F#):
using ExcelDna.Integration;
public static class MyFunctions
{
[ExcelFunction(Description = "My first .NET function")]
public static string SayHello(string name)
{
return "Hello " + name;
}
}
Compile, load and use your function in Excel:
=SayHello("World!")
2. Automation AddIns
This article by Eric Carter shows how to do it, the article is missing heaps of images so I am copy / pasting the entire article and have recreated the images for preservation.
REF: https://blogs.msdn.microsoft.com/eric_carter/2004/12/01/writing-user-defined-functions-for-excel-in-net/
Excel enables the creation of user defined functions that can be used in Excel formulas. A developer must create a special kind of DLL called an XLL. Excel also allows you to write custom functions in VBA that can be used in Excel formulas. Unfortunately, Excel does not support or recommend writing an XLL that uses managed code. If you are willing to take your chances that your XLL might not run in current or future versions of Excel, there are solutions available that enable this scenario—search the web for “managed XLL”.
Fortunately, there is an easier way to create a user defined function that doesn’t require you to create an XLL dll. Excel XP, Excel 2003, and Excel 2007 support something called an Automation Add-in. An Automation Add-in can be created quite simply in C# or VB.NET. I’m going to show you an example in C#.
First, launch Visual Studio and create a new C# class library project called AutomationAddin for this example.
Then, in your Class1.cs file, enter the code shown below. Replace the GUID with your own GUID that you create by using Generate GUID in the Tools menu of Visual Studio.
using System;
using System.Runtime.InteropServices;
using Microsoft.Win32;
namespace AutomationAddin
{
// Replace the Guid below with your own guid that
// you generate using Create GUID from the Tools menu
[Guid("A33BF1F2-483F-48F9-8A2D-4DA68C53C13B")]
[ClassInterface(ClassInterfaceType.AutoDual)]
[ComVisible(true)]
public class MyFunctions
{
public MyFunctions()
{
}
public double MultiplyNTimes(double number1, double number2, double timesToMultiply)
{
double result = number1;
for (double i = 0; i < timesToMultiply; i++)
{
result = result * number2;
}
return result;
}
[ComRegisterFunctionAttribute]
public static void RegisterFunction(Type type)
{
Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable"));
RegistryKey key = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), true);
key.SetValue("", System.Environment.SystemDirectory + @"mscoree.dll",RegistryValueKind.String);
}
[ComUnregisterFunctionAttribute]
public static void UnregisterFunction(Type type)
{
Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), false);
}
private static string GetSubKeyName(Type type, string subKeyName)
{
System.Text.StringBuilder s = new System.Text.StringBuilder();
s.Append(@"CLSID{");
s.Append(type.GUID.ToString().ToUpper());
s.Append(@"}");
s.Append(subKeyName);
return s.ToString();
}
}
}
With this code written, show the properties for the project by double clicking on the properties node under the project in Solution Explorer. Click on the Build tab and check the check box that says “Register for COM Interop”. At this point you have an extra step if you are running on Windows Vista or higher. Visual Studio has to be run with administrator privileges to register for COM interop. Save your project and exit Visual Studio. Then find Visual Studio in the Start menu and right click on it and choose “Run as Administrator”. Reopen your project in Visual Studio. Then choose “Build” to build the add-in.
Now launch Excel and get to the Automation servers dialog by following these steps:
-
Launch Excel and click the Microsoft Office button in the top left corner of the window.
-
Choose Excel Options.
-
Click the Add-Ins tab in the Excel Options dialog.
-
Choose Excel Add-Ins from the combo box labeled Manage. Then click the Go button.
-
Click the Automation button in the Add-Ins dialog.
You can find the class you created by looking for AutomationAddin.MyFunctions in the list of Automation add-ins:
Now, let’s try to use the function MultiplyNTimes inside Excel. First create a simple spreadsheet that has a number, a second number to multiple the first by, and a third number for how many times you want to multiply the first number by the second number. An example spreadsheet is shown here:
Click on an empty cell in the workbook below the numbers and then click on the Insert Function button in the formula bar. From the dialog of available formulas, drop down the “Or select a category” drop down box and choose “AutomationAddin.MyFunctions.
Then click on the MultiplyNTimes function as shown here:
When you press the OK button, Excel pops up a dialog to help you grab function arguments from the spreadsheet as shown here:
Finally, click OK and see your final spreadsheet as shown here with your custom formula in cell C3.
3. Calling .Net from Excel VBA
REF: Calling a .net library method from vba
Using the code from the Automation.AddIn project we can easily call the MultiplyNTimes function from Excel VBA.
First Add a reference to the DLL from Excel, to do this you will need to be in the VB Editor. Press Alt + F11, then click Tools menu and References:
Select the AutomationAddIn DLL:
Add VBA code to call the .Net DLL:
Sub Test()
Dim dotNetClass As AutomationAddIn.MyFunctions
Set dotNetClass = New AutomationAddIn.MyFunctions
Dim dbl As Double
dbl = dotNetClass.MultiplyNTimes(3, 2, 5)
End Sub
And hey presto!
Please note if you’re working with Classes in C# you will need to mark them with ClassInterface, with an Interface marked with ComVisible = true: Use CLR classes from COM addin in Excel VBA?
Finally there are some excellent MSDN articles about Excel and .Net by «Andrew Whitechapel» — google them