为 VBA 和 Python 用户创建 DLL
扩展 Microsoft Office 和 Python 的功能。
下载
引言
有时,我们需要为 VBA (Visual Basic for Application) 用户开发 DLL 库,以扩展 Microsoft Office 套件(Excel、Word、PPT 和 Outlook)的功能。
如果我们需要为 Python 开发具有类似功能的库怎么办?
我只是想知道是否可以为 VBA 和 Python 都使用同一个库。
这样做的好处很明显,在一个项目中维护代码很容易。
经过一番探索,我发现 Robert Giesecke 的 Unmanaged Exports Nuget 包是实现此目的的正确工具。
我已将一些有用的功能打包到此 DLL 中,它也可以为其他人节省一些宝贵的时间。
该库仍在开发中,如果您有任何建议,请在此文章下方留言。 例如,如果 Excel 或 Python 中缺少任何需要的功能等。
Using the Code
请下载示例并解压整个文件夹 "PythonVbaDemo"。
- 在 Excel 中进行测试:打开 "VbaCallDLL.xlsm"
- 在 Python 中进行测试:使用 Visual Studio Code 打开文件夹 "PythonVbaDemo"
VBA:只需运行 VbaTestDllDemo Sub
。
Python:运行 "PythonCallDLL.py"
关注点
1. 在 VBA 中使用
DLL 有两个版本(32 位和 64 位),如何使 VBA 代码自动处理这两个版本,请参考下面的代码片段。
- 确定现有 Office 是 32 位还是 64 位:(感谢 Daniel Pineault)
Function IsOffice32Bit() As Boolean 'https://docs.microsoft.com/en-us/office/troubleshoot/office-suite-issues/numbering-scheme-for-product-guid ' p => 0=32-bit, 1=64-bit IsOffice32Bit = (Mid(Application.ProductCode, 21, 1) = 0) End Function
- 现在我们可以使用“更改当前目录”来让 VBA 知道它应该引用哪个 DLL
Sub ChangeCurDirToDllFolder() Dim sSubfolderName As String If IsOffice32Bit() Then sSubfolderName = "x86" Else sSubfolderName = "x64" End If ChDir ThisWorkbook.Path & "\" & sSubfolderName End Sub
以下是可以由 VBA 使用的函数列表
Vba_PlotFreeSpaceChart
ReloadMediaFile
StartPlayingMediaFile
StopPlayingMediaFile
Sum
InstantiateExtLib
Vba_Echo
EncryptStringTripleDES
DecryptStringTripleDES
GetLibName
GetLibVersion
GetEmpIDByUserID
GetUserIDByEmpID
有三种类型的接口
- 不返回任何数据的子程序
- 返回原始数据类型的函数,例如
Integer
、Double
等。 - 返回非原始数据类型的函数,例如
String
对于类型 1 和 2,我们可以使用以下声明直接在 VBA 中调用它们。
'
' Interface 1 & 2 declarations:
'
#If VBA7 Then
Private Declare PtrSafe Sub Vba_PlotFreeSpaceChart _
Lib "ExtLib4VbaPython.dll" (ByVal sSpaceCheckPathsInCsv As String, _
ByVal nMinFreeSpacePercent As Integer)
Private Declare PtrSafe Function Vba_Echo Lib "ExtLib4VbaPython.dll" _
(ByVal sName As String) As String
Private Declare PtrSafe Function Sum Lib "ExtLib4VbaPython.dll" _
(ByVal nNum1 As Integer, ByVal nNum2 As Integer) As Integer
Private Declare PtrSafe Function BytesToBase64 Lib "ExtLib4VbaPython.dll" _
(bytes() As Byte) As String
Private Declare PtrSafe Function Base64toBytes Lib "ExtLib4VbaPython.dll" _
(ByVal sBase64String As String) As Byte()
Private Declare PtrSafe Function EncryptStringTripleDES Lib _
"ExtLib4VbaPython.dll" (ByVal sPlainText As String) As String
Private Declare PtrSafe Function DecryptStringTripleDES Lib _
"ExtLib4VbaPython.dll" (ByVal sEncryptedText As String) As String
Private Declare PtrSafe Sub Vba_ReloadMediaFile Lib _
"ExtLib4VbaPython.dll" (ByVal sMediaFileName As String)
Private Declare PtrSafe Sub StartPlayingMediaFile Lib "ExtLib4VbaPython.dll" ()
Private Declare PtrSafe Sub StopPlayingMediaFile Lib "ExtLib4VbaPython.dll" ()
Private Declare PtrSafe Function InstantiateExtLib Lib _
"ExtLib4VbaPython.dll" (ByVal text As String) As Object
#Else
Private Declare Function InstantiateExtLib Lib _
"ExtLib4VbaPython.dll" (ByVal text As String) As Object
Private Declare Sub Vba_PlotFreeSpaceChart Lib "ExtLib4VbaPython.dll" _
(ByVal sSpaceCheckPathsInCsv As String, ByVal nMinFreeSpacePercent As Integer)
Private Declare Function Vba_Echo Lib "ExtLib4VbaPython.dll" _
(ByVal sName As String) As String
Private Declare Function Sum Lib "ExtLib4VbaPython.dll" _
(ByVal nNum1 As Integer, ByVal nNum2 As Integer) As Integer
Private Declare Function BytesToBase64 Lib "ExtLib4VbaPython.dll" _
(bytes() As Byte) As String
Private Declare Function Base64toBytes Lib "ExtLib4VbaPython.dll" _
(ByVal sBase64String As String) As Byte()
Private Declare Function EncryptStringTripleDES Lib "ExtLib4VbaPython.dll" _
(ByVal sPlainText As String) As String
Private Declare Function DecryptStringTripleDES Lib "ExtLib4VbaPython.dll" _
(ByVal sEncryptedText As String) As String
Private Declare Sub Vba_ReloadMediaFile Lib "ExtLib4VbaPython.dll" _
(ByVal sMediaFileName As String)
Private Declare Sub StartPlayingMediaFile Lib "ExtLib4VbaPython.dll" ()
Private Declare Sub StopPlayingMediaFile Lib "ExtLib4VbaPython.dll" ()
#End If
对于类型 3 接口,我们必须使用以下方法来调用它们
'
' Interface 3 calling:
'
Dim oExtLib As Object
Set oExtLib = InstantiateExtLib("Alarm01.wav")
Debug.Print oExtLib.Vba_Hello(Application.UserName)
Debug.Print oExtLib.MediaFileName
Debug.Print "Lib Name: " & oExtLib.Vba_GetLibName()
Debug.Print "Lib Version: " & oExtLib.Vba_GetLibVersion()
Debug.Print "Lib EmpID: " & oExtLib.Vba_GetEmpIDByUserID("Your User ID")
Debug.Print "Lib UserID: " & oExtLib.Vba_GetUserIDByEmpID("Your Emp ID")
2. 在 Python 中使用
以下是可以由 Python 使用的函数列表
Py_PlotFreeSpaceChart
ReloadMediaFile
StartPlayingMediaFile
StopPlayingMediaFile
Sum
InstantiateExtLib
Py_Echo
Py_EncryptStringTripleDES
Py_DecryptStringTripleDES
Py_GetLibName
Py_GetLibVersion
Py_GetEmpIDByUserID
Py_GetUserIDByEmpID
有三种类型的接口
- 不返回任何数据的子程序
- 返回原始数据类型的函数,例如
Integer
、Double
等。 - 返回非原始数据类型的函数,例如
String
对于类型 1 和 2,我们可以使用以下声明直接在 Python 中调用它们。
#
# Interface 1 & 2 declarations:
#
import ctypes
import os
from ctypes import *
from comtypes.automation import VARIANT
full_path = os.path.realpath(__file__)
sPath, sFilename = os.path.split(full_path)
sDllPath = sPath + "\\x64\\ExtLib4VbaPython.dll"
oExtLib = ctypes.cdll.LoadLibrary(sDllPath)
print ("3 + 5 = " + str(oExtLib.Sum(3, 5)))
oExtLib.Py_PlotFreeSpaceChart ('C:', 10)
对于类型 3 接口,我们必须使用以下方法来调用它们
#
# Interface 3 calling:
#
import ctypes
import os
from ctypes import *
from comtypes.automation import VARIANT
def Hello(name, oExtLib):
oExtLib.Py_Hello.argtypes = [VARIANT, POINTER(VARIANT)]
v = VARIANT()
oExtLib.Py_Hello(name, v)
return v.value
full_path = os.path.realpath(__file__)
sPath, sFilename = os.path.split(full_path)
sDllPath = sPath + "\\x64\\ExtLib4VbaPython.dll"
oExtLib = ctypes.cdll.LoadLibrary(sDllPath)
print (Hello('Wayne', oExtLib))
已知问题
播放媒体在 VBA 中有效,但在 Python 中无效,如果有人知道原因,请指出,非常感谢。
#
# Known issues: Same function in VBA call, it will play the music, but not in Python
#
import ctypes
import os
from ctypes import *
from comtypes.automation import VARIANT
full_path = os.path.realpath(__file__)
sPath, sFilename = os.path.split(full_path)
sDllPath = sPath + "\\x64\\ExtLib4VbaPython.dll"
oExtLib = ctypes.cdll.LoadLibrary(sDllPath)
oExtLib.Py_ReloadMediaFile('')
oExtLib.StartPlayingMediaFile()
致谢
- Robert Giesecke: Unmanaged Exports
- Daniel Pineault: VBA – 确定 MS Office/应用程序位数
- Microsoft: Office 2016 中产品代码 GUID 的编号方案说明
历史
- 2022 年 12 月 5 日:初始版本
<object data-extension-version="1.2.0.158" data-supports-flavor-configuration="true" id="__symantecMPKIClientMessenger" style="display: none;">__PRESENT
<object data-extension-version="1.2.0.158" data-supports-flavor-configuration="true" id="__symantecMPKIClientMessenger" style="display: none;">__PRESENT