VBA调用C++动态链接库¶
发布于:2017-11-30 | 分类:python/vba/cpp
Excel的VBA可以使用Declare Function
语句直接调用C++动态链接库,本文介绍此过程中可能涉及的参数传递、导入路径、链接库位数等方面的问题。
基本语句¶
' 64 bit dll
Private Declare PtrSafe Function [function_name] Lib ["path_to_dll"] ([parameters_list]) As [type]
' 32 bit dll
Private Declare Function [function_name] Lib ["path_to_dll"] ([parameters_list]) As [type]
- 以上示例单个函数的导入,同理导入同一动态链接库中的其他函数
Private
限定在module
中使用动态链接库函数,不允许直接在公式栏使用;否则可以直接应用于单元格公式PtrSafe
表明针对64-bit版本Excel软件path_to_dll
指定加载DLL的路径,可以有两种方式:- 使用绝对路径,必须为字符串常量,不允许拼接动态路径
- 写明DLL文件名,然后从默认位置加载
DLL加载路径¶
为避免硬编码绝对路径,采用Declare
方式引用动态链接库函数时一般只指定其文件名,然后尽量将其所在目录加载到默认搜索路径中去。例如引用Excel同级bin
文件夹下的动态链接库文件bar.dll
:
Private Declare PtrSafe Function foo Lib "bar.dll" () As Double
方式一:当前工作目录¶
Excel 当前工作目录 并不一定是Excel文件所在目录,而是通过ChDir()
函数来动态设置。需要注意的是,所有打开的Excel工作簿共享同一个工作目录,并且某些手动操作例如浏览文件可能改变当前工作目录,
因此,这种方式存在一定风险。我们只能通过每次激活当前工作簿时都重置当前工作目录到目标位置来尽可能避免不期望的工作目录改变:
Private Sub Workbook_Activate()
Dim DLLPath As String
' set current path to load DLL
DLLPath = ThisWorkbook.Path & "\bin"
ChDrive Left$(ThisWorkbook.Path, 1)
If Dir(DLLPath, 16) <> Empty Then ChDir DLLPath
End Sub
以上代码中ChDrive
进入单个字符表示的盘符,例如C盘、D盘;一旦不满足此条件,例如处在网络文件夹\\server\this\that
,则会出错。所以,要么避免网络路径,要么将网络路径映射到单字符的盘符。
方式二:PATH环境变量¶
更稳定的方式是将动态链接库所在路径加到PATH
环境变量中去。好处是无需增加额外的代码,不足是需要设置环境变量后才能正确使用。
方式三:LoadLibrary
动态加载¶
Declare
声明中不能使用动态路径,但我们可以借助Windows API LoadLibrary
来实现。主要步骤为:
- 调用
LoadLibrary
加载动态拼接路径表示的DLL - 按照常规方式
Declare
声明和使用DLL中的API - 调用
FreeLibrary
释放DLL
' --------
' module: ThisWorkbook: load/free DLL when open/close workbook
' --------
Private Declare Function LoadLibrary Lib "kernel32" Alias "LoadLibraryA" (ByVal lpLibFileName As String) As Long
Private Declare Function FreeLibrary Lib "kernel32" (ByVal hLibModule As Long) As Long
Public hModule As Long
' load DLL when open workbook
Private Sub Workbook_Open()
Dim DLLPath As String
DLLPath = ThisWorkbook.path & "\bin\bar.dll"
hModule = LoadLibrary(DLLPath)
If hModule <= 0 Then MsgBox "load dll error"
End Sub
' release DLL whe close
Private Sub Workbook_BeforeClose(Cancel As Boolean)
If hModule > 0 Then FreeLibrary hModule
End Sub
通过上面的代码正确加载了bar.dll
,因此其他模块中即可按常规方式使用函数foo
:
Private Declare Function foo Lib "bar.dll" () As Double
Sub test()
Debug.Print foo()
End Sub
参数类型对应¶
VBA
与C++
中基本参数类型的对应关系:
C++ | VBA |
---|---|
int | Long |
bool | Integer |
float | Single |
double | Double |
char* | String |
需要注意的是,对于C++
中数组类型的变量,VBA
中需要声明为引用Byref
,使用时传入数组参数的第一个元素即可,参考例子:
// C++ test.dll
int __stdcall foo(float*, float[]);
' VBA
Declare Function foo lib "path/to/test.dll" (Byref a!, Byref b!) As Long
Dim a(2), b(3)
call foo(a(0), b(0))
其中!
是VBA
中float
类型的简写,其他的还有:
Integer(%)
Long(&)
float(!)
double(#)
string($)
函数名称¶
C++
中函数名混肴机制将导致编译后的函数名与源码中的不同,为了避免此类转换,可以设置.def
文件,基本格式参考:
LIBRARY [library name]
EXPORTS
[user_defined_exported_name_1] = [function_name_in_source_code_1]
[user_defined_exported_name_2] = [function_name_in_source_code_2]
以上内容中[]
表示需要填入的内容([]
本身并不需要)
然后在Visual Studio
中编译前通过Configuration
->Properties
->Linker
->InputModule Definition File
指定自定义的.def
文件即可。
Excel版本与动态链接库版本¶
Excel软件(而非操作系统)位数必须与编译动态链接库时选择的平台位数一致,否则导入函数时将会提示:
Error 48: file can not be found.
此前一个案例涉及Qt
库的QNetwork
模块,相关的两点延伸:
-
为了成功编译出32-bit和64-bit的动态链接库,需要同时安装32-bit和64-bit的
Qt
库,在编译时指定相应版本的库 -
QNetwork
的https访问需要用到SSL认证,因此需要提供相应库函数,例如libeay32.dll
和ssleay32.dll
,同时要求相应的位数必须一致
另外,32位/64位Excel之前迁移代码需要注意:
-
32位的指针地址类型
Long
对应64位的LongPtr
-
对于32位Excel,
Declare
声明的API同时会VBA和工作表单元格有效;对64位Excel,直接声明的API需要经过VBA函数的包装才能正确应用于单元格公式