Within the top of your module contains the declarations section. This is where you will typically find your Option Statement(s) if they exist.
It is in this same area you would declare your DLL functions, which you would do outside any procedure.
In the days before the release of 64 bit MS Office, you would simply declare your DLL procedure with the following parameters:
[Bracket Statements] are optional
Function
is interchangeable withSub
— You would not use [As Type] on a sub
[Public|Private] Declare Function "PublicName" Lib LibName _
[Alias "AliasName"] [(Arguments)] [As Type].
Here is an example of the sleep API:
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
However, MS released 64-bit versions of their software, and this came with a new version of VBA, known as VBA7
. The problem is that 64-bit OS and software use different memory pointers and handles, therefore it was important that VBA knows that the declare statement is safe to run in a 64-bit environment. In order for you to let VBA know this, you would need to modify your Function’s data types to generally be of type LongPtr
for memory pointers as opposed to Long
(or quantities should be of type LongLong
.
You would inform VBA that your declaration is correct using the following statement:
[Private|Public] Declare PtrSafe Function...
Example of the same Sleep API declared as PtrSafe:
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
What if you are unsure if the end user is using the new VBA7?
Fortunately for you, If...Then
statements are available in your declarations if prefixed by #
. You can make your code work for any VBA environment by using the above declaration:
#If VBA7 Then
Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As LongPtr)
#Else
Declare Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long)
#End If
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!
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
Добрый день, уважаемые друзья.
Появилась необходимость запихать все свои наработки в VBA (макросы и функции) в файл DLL для работы на других компьютерах в офисе (не на тех, на которых пишется код).
Компьютер для написания кода — Windows 7 — 64 бита, Офис 2013 — 32 бита, MS Visio Studio 2012.
Прежде чем написать сюда, я перерыла просторы Интернет, но в полном объеме информацию так и не удалось получить. Много описанных в Интернет вариантов пробовала использовать, но финального, завершенного варианта так и не получилось создать. Ниже я опишу все пошагово, что и как создавалось.[/P]
Итак: Создаем пробную функцию в VBA, которую и будем зашивать в DLL.
Код |
---|
Public Function myPlus(ByVal x As Integer, ByVal y As Integer) As Integer Return (x + y) End Function |
Захожу в MS Visual Studio 2012 > Создать проект > Библиотека классов > Называю «Second_Project» > Жму «ОК».
Далее, в «Обозревателе решений», жму правой кнопкой на проекте > Свойства, и ставлю две галочки:
> Приложение > Сведения о сборке > Сделать сборку видимой для COM.
> Компиляция > Регистрация для СOM — взаимодействия.
Жму «ОК».
В файле Class1.vb прописываю интерфейсы и свои две тестовые функции:
Код |
---|
Public Interface ompzb52 Function myPlus(ByVal x As Integer, ByVal y As Integer) As Integer Function myMinus(ByVal x As Integer, ByVal y As Integer) As Integer End Interface Public Class Second_Project Implements ompzb52 Public Function myPlus(ByVal x As Integer, ByVal y As Integer) As Integer Implements ompzb52.myPlus Return (x + y) End Function Public Function myMinus(ByVal x As Integer, ByVal y As Integer) As Integer Implements ompzb52.myMinus Return (x - y) End Function End Class |
Жму > Построить решение (F7) и получаю четыре файла Second_Project.dll, Second_Project.tlb, Second_Project.pdb, Second_Project.xml .
Далее создаю новый документ в MS Excel, сохраняю его в xlsm и жму родной Alt + F11.
В редакторе VISUAL BASIC FOR APPLICATION жму Tools > Refrence > Browse и выбираю созданный файл «Second_Project.tlb». Жму «ОК».[/P]
Теперь подключаю файл DLL.
В редакторе VISUAL BASIC FOR APPLICATION вставляю новый модуль, в котором прописываю одну из функций:
Код |
---|
Declare Function myPlus Lib "C:UsersDUOSecond_Project.dll" (ByRef x As Integer, ByRef y As Integer) As Integer Sub test() Dim perem As New Second_Project.Second_Project MsgBox (perem.myPlus(10, 3)) End Sub |
На этом участке я тестирую функцию из файла *tlb. Жму в редакторе ф5 и все прекрасно работает. Выводится MsgBox c цифрой 13. Ок. Правильно.
Прописываю в документе функцию: =myPlus(D4;D5) Не работает. В ячейках D4 и D5 указаны числа для суммирования.
Проблема 2.
Переношу файлы Second_Project.dll и Second_Project.tlb на другой компьютер, регистрирую их батником, в котором прописываю код:
Код |
---|
copy C:UsersDUODesktopSecond_Project.dll C:WINDOWSSysWOW64 C:WINDOWSMicrosoft.NETFramework64v4.0.30319regasm C:WINDOWSsysWOW64Second_Project.dll /tlb:Second_Project.tlb /codebase PAUSE |
Код |
---|
Скопировано файлов: 1. C:UsersDUODesktop>C:WINDOWSMicrosoft.NETFramework64v4.0.30319regasm C:WINDOWSsysWOW64Second_Project.dll /tlb:Second_Project.tlb /codebaseMicrosoft .NET Framework Assembly Registration Utility 4.7.2053.0для Microsoft .NET Framework 4.7.2053.0c Корпорация Майкрософт (Microsoft Corporation). Все права защищены. RegAsm : warning RA0000 : Регистрация неподписанной сборки с использованием параметра /codebase может вызвать конфликт данной сборки с другими приложениями, которые могут быть установлены на том же компьютере. Параметр /codebase предназначен только для подписанных сборок. Присвойте сборке строгое имя и повторите регистрацию.RegAsm : Регистрация успешна.Для продолжения нажмите любую клавишу . . . |
Создаю пустой документ на другом компьютере (Windows 7 64 бита, Офис 2013 — 32 бита),
В редакторе VISUAL BASIC FOR APPLICATION жму Tools > Refrence > Browse и выбираю зарегистрированный файл «Second_Project.tlb» (В папке SysWOW64). Жму «ОК». Декларируюфункцию в файле DLL и Создаю тестовый макрос
Код |
---|
Declare Function myPlus Lib "C:WINDOWSSysWOW64Second_Project.dll" (ByRef x As Integer, ByRef y As Integer) As Integer Sub test() Dim perem As New Second_Project.Second_Project MsgBox (perem.myPlus(10, 3)) End Sub |
Жму в коде F5 и получаю ошибку ActiveX component can’t create object.
Ни макрос, ни функция, прописанная в ячейке (которая находится в файле *tlb) не работает.
Подскажите, пожалуйста, блондинке, к каком напралении копать.
Заранее благодарна.
Outline
In this post it is described in a step-by-step manner how to create a C/C++ dynamic linked library (DLL) for use with Visual Basic for Applications (VBA) in Microsoft Excel. Main emphasis is on using the DLL for scientific computations, thus it is necessary to be able to send arrays as input to the DLL and receive arrays as output. Only a very simple example will be given in order to illustrate this. It has been prioritized to show a working example rather than elaborating too much on C/C++ calling conventions, name decoration of exported function etc. For more information on these topics please consult the web (see e.g. the Further reading section below).
In order to follow the given example it is required to have a working installation of Microsoft Windows (example only tested on XP, but is assumed to work as well on Windows 2000 and Vista), including Microsoft Office (tested on Office 2000, but works as well with Office 2003 and 2007 I presume), and finally an installation of Microsoft Visual C++ 2008 (Express Edition). Visual C++ can be downloaded free of charge from Microsoft. If problems with the installation are experienced, seek assistance in the installation and setup forum.
Creating the DLL with Visual C++
Paste the code below into a text editor and save it as dllarray.cpp.
extern “C” int __declspec(dllexport) _stdcall myarray( double* pin, double* pout, int sz )
{
int i;
for (i = 0; i < sz ; i++)
{
pout[i]=pin[i]*100;
}
return 0;
}
The function “dllarray” takes two arrays as input (actually pointers to the adress of the first element of each array) along with the number of elements in the arrays (in this case the same). The function loops through the arrays and writes element i in pin times 100 to element i in pout (not really that scientific ;-). The return type of the function is an iteger (in this case 0), but error checking could be included as well with different return values. In order to compile the code open the Visual Studio 2008 Command Prompt
and change directory to the location of the file just created. Compile the source file with:
cl /c /LD dllarray.cpp and link with:
link /DLL dllarray.obj
This creates the DLL (dllarray.dll). In order to refer to the functions defined inside the dll it is necessary to extract the function names really exported (some name decoration has occured). This is achieved with the DUMPBIN tool shipped with Visual C++. On the VS2008 command line type:
dumpbin /EXPORTS dllarray.dll
This gives the following output:
Microsoft (R) COFF/PE Dumper Version 9.00.21022.08
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file myarray.dll
File Type: DLL
Section contains the following exports for myarray.dll
00000000 characteristics
481F8D73 time date stamp Tue May 06 00:42:59 2008
0.00 version
1 ordinal base
1 number of functions
1 number of names
ordinal hint RVA name
1 0 00001000 _myarray@12
Summary
2000 .data
2000 .rdata
1000 .reloc
A000 .text
From which it is inferred that the exported name of the function myarray is actually _myarray@12.
Calling the DLL from inside VBA in MS Excel
Open an empty workbook in Excel and enter the VBA editor (Alt+F11). Make a new module and paste the following code into it:
Declare Function myarray& Lib “C:dllarray.dll” Alias “_myarray@12” _
(ByRef pin As Double, ByRef pout As Double, ByVal sz As Long)
Sub test_dll_array()
Dim sz As Long
Dim pin() As Double
Dim pout() As Double
sz = 10
ReDim pin(sz – 1), pout(sz – 1)
For i = 0 To (sz – 1)
pin(i) = i
Next i
y = myarray(pin(0), pout(0), sz)
MsgBox (“Value of 4. element in pin array : ” + Str(pin(3)))
MsgBox (“Value of 4. element in pout array : ” + Str(pout(3)))
End Sub
Run the code. This should pop up two message boxes, the first showing the value of the 4. element of the “input” array and the second showing the corresponding value written to the “output” array. The following conventions are very important when passing arrays to a C/C++ DLL
- In the function declaration of the DLL the arrays should be passed “ByRef” (default) which passes the address of the first element rather than the value.
- Integers (int) in C/C++ should passed as long’s from VBA.
- The name following Declare Function is the name which is used in VBA (does not have to be the same as in the DLL), however the real (exported) name of the function in the DLL should be specified after the Alias (in this case with name decoration).
- When calling the declared function the first element should be given, not the entire array.
- When writing output to arrays in VBA from a C/C++ DLL it is by far the easiest approach to hand in the (empty) “output” arrays as input to the function. Using the pointer approach here the DLL can modify the arrays defined in VBA.
The above example with C++ can easily be modified to apply for ANSI C as well. Change the extension of the source file from *.cpp to *.c. Delete the extern “C” declaration and add the following option to the command line compiler /TC.
Further reading
http://support.microsoft.com/kb/207931
http://www.ozgrid.com/forum/showthread.php?t=63142
http://msdn.microsoft.com/en-us/library/dt232c9t%28VS.80%29.aspx
Пытаясь научиться подключать к VBA хотя бы самую тривиальную функцию на C++.
Для примера написал на C++ простую функцию Kvadrat и попытался сделать DLL. Сначала использовал Dev-C++. Создаю там проект, выбираю иконку «DLL». Появляется два файла. «dll.h» и «dllmain.cpp».
«dll.h» содержит:
C++ | ||
|
После этого в «dll.h» я дописываю следующее:
C++ | ||
|
Далее в изначально пустой файл «dllmain.cpp» я дописываю:
C++ | ||
|
Компилирую. Ошибок нет, всё ОК.
Далее в VBA Excel написал следующее (как прочитал в соседних ветках форума, это правильно):
Visual Basic | ||
|
При запуске кода выдаёт ошибку — «File not found F:CODINGCPPdll03Project1.dll«. Хотя файл там есть!
Ладно. Наверное Dev-C++ что-то делает не так.
Скачал с сайта Microsoft Visual Studio 2015. Установил. Создаю в нём проект (Консольное приложение Win32, далее галочка «Библиотека DLL». Все остальные параметры по умолчанию. Проект содержит 5 файлов: stdafx.h, targetver.h, ConsoleApplication1.cpp, dllmain.cpp, stdafx.cpp.
В файл «dllmain.cpp» дописываю свою функцию. Теперь он содержит:
C++ | ||
|
Компилирую. Ошибок нет. Меняю в своём коде VBA (см. выше) путь к файлу DLL. Запускаю. Снова ошибка:
«Can’t find DLL entry point kvadrat in F:CODINGCPPtestvsConsoleApplication1DebugCon soleApplication1.dll«.
Далее сделал всё так, как написано в ссылке. Однако и теперь ни выдаётся та же ошибка (Can’t find DLL entry point).
Что я делаю не так?