Canonical:如何从Excel VBA调用.NET方法
我找到了一种直接从VBA宏调用.NET 2代码的方法:
Dim clr As mscoree.CorRuntimeHost Set clr = New mscoree.CorRuntimeHost clr.Start Dim domain As mscorlib.AppDomain clr.GetDefaultDomain domain Dim myInstanceOfDotNetClass As Object Set myInstanceOfDotNetClass = domain.CreateInstanceFrom("SomeDotNetAssembly.dll", "Namespace.Typename").Unwrap Call myInstanceOfDotNetClass.ExecuteSomeDotNetMethod
(为了使这段代码工作,我想在Excel中使用工具 – >引用…将对mscoree.tlb和mscorlib.tlb的引用添加到Excel VBA)
但这仅适用于.NET CLR 2程序集,最高可达.NET Framework 3.5版。
现在我需要使用.NET 4。
我已经了解.NET CLR4引入了另一种与版本无关的创建运行时实例的方法,我还发现了一个用C ++编写的相当简单的代码示例: http : //dev.widemeadows.de/2014/02/ 04 /托管最净-4-运行时在-一个天然过程/
但我的Excel VBA技能还不足以将这几行代码转换为工作的VBA makro。 有人可以帮帮我吗?
以下是从Excel(或VBA)调用.Net的3种主要方法的规范性答案。
这三种方式都适用于.Net 4.0。
1. XLLs
第三方供应商Add-In Express提供XLLfunction,但其免费且易于使用的Excel-DNA 作者在这里 https://stackoverflow.com/users/44264
以下是Excel-DNA页面的摘录: https : //excel-dna.net/
介绍
Excel-DNA是一个将.NET集成到Excel中的独立项目。 使用Excel-DNA,您可以使用C#,Visual Basic.NET或F#为Excel创建本机(.xll)加载项,从而提供高性能的用户定义函数(UDF),自定义function区界面等。 您的整个加载项可以打包到单个.xll文件中,无需安装或注册。
入门
如果您使用的是支持NuGet包管理器的Visual Studio版本(包括用于Windows桌面的Visual Studio 2012 Express),则创建Excel-DNA加载项的最简单方法是:
在Visual Basic,C#或F#中创建一个新的类库项目。 使用Manage NuGet Packages对话框或Package Manager Console安装Excel-DNA包:
PM> Install-Package Excel-DNA
添加代码(C#,Visual Basic.NET或F#):
using ExcelDna.Integration; public static class MyFunctions { [ExcelFunction(Description = "My first .NET function")] public static string SayHello(string name) { return "Hello " + name; } }
在Excel中编译,加载和使用您的函数:
=SayHello("World!")
2. 自动化AddIns
Eric Carter的这篇文章展示了如何做到这一点,文章缺少大量的图像,所以我复制/粘贴整篇文章并重新创建图像以便保存。
参考: https : //blogs.msdn.microsoft.com/eric_carter/2004/12/01/writing-user-defined-functions-for-excel-in-net/
Excel允许创建可在Excel公式中使用的用户定义函数。 开发人员必须创建一种称为XLL的特殊DLL。 Excel还允许您在VBA中编写可在Excel公式中使用的自定义函数。 遗憾的是,Excel不支持或建议编写使用托管代码的XLL。 如果您愿意冒险尝试XLL可能无法在当前版本或未来版本的Excel中运行,那么可以使用解决方案来启用此方案 – 在Web上搜索“托管XLL”。
幸运的是,有一种更简单的方法来创建一个不需要您创建XLL dll的用户定义函数。 Excel XP,Excel 2003和Excel 2007支持称为自动化加载项的function。 可以在C#或VB.NET中非常简单地创建自动化加载项。 我将在C#中向您展示一个示例。
首先,启动Visual Studio并为此示例创建一个名为AutomationAddin的新C#类库项目。
然后,在Class1.cs文件中,输入如下所示的代码。 使用Visual Studio的“工具”菜单中的“生成GUID”将GUID替换为您自己创建的GUID。
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(); } } }
编写此代码后,双击解决方案资源管理器中项目下的属性节点,显示项目的属性。 单击Build选项卡,选中“Register for COM Interop”复选框。 此时,如果您在Windows Vista或更高版本上运行,则还有一个额外的步骤。 必须以管理员权限运行Visual Studio才能注册COM互操作。 保存项目并退出Visual Studio。 然后在“开始”菜单中找到Visual Studio并右键单击它并选择“以管理员身份运行”。 在Visual Studio中重新打开您的项目。 然后选择“Build”来构建加载项。
现在启动Excel并按照以下步骤进入自动化服务器对话框:
-
启动Excel并单击窗口左上角的Microsoft Office按钮。
-
选择Excel选项。
-
单击“Excel选项”对话框中的“加载项”选项卡。
-
从标有“管理”的combobox中选择“Excel加载项”。 然后单击“Go”按钮。
-
单击“加载项”对话框中的“自动化”按钮。
您可以通过在自动化加载项列表中查找AutomationAddin.MyFunctions来找到您创建的类:
现在,让我们尝试在Excel中使用MultiplyNTimes函数。 首先创建一个简单的电子表格,其中包含一个数字,第二个数字与第一个数字相乘,第三个数字表示您希望将第一个数字乘以第二个数字的次数。 此处显示了一个示例电子表格:
单击数字下方工作簿中的空单元格,然后单击公式栏中的“插入函数”按钮。 从可用公式的对话框中,下拉“或选择一个类别”下拉框并选择“AutomationAddin.MyFunctions”。
然后单击MultiplyNTimes函数,如下所示:
当您按下确定按钮时,Excel会弹出一个对话框,帮助您从电子表格中获取函数参数,如下所示:
最后,点击“确定”,然后在单元格C3中查看自定义公式所示的最终电子表格。
3.从Excel VBA调用.Net
REF: 从vba调用.net库方法
使用Automation.AddIn项目中的代码,我们可以轻松地从Excel VBA调用MultiplyNTimes函数。
首先从Excel添加对DLL的引用,为此,您需要在VB编辑器中。 按Alt + F11,然后单击工具菜单和参考:
选择AutomationAddIn DLL:
添加VBA代码以调用.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
嘿presto!
最后有一些关于Excel和.Net的优秀MSDN文章由“Andrew Whitechapel” - google他们
这是您的解决方案,经过Soraco Technologies的测试,针对.NET 2.0和.NET 4.0,32位和64位进行了测试。
下面提出的解决方案使用后期绑定,不需要注册.NET程序集。
声明
将以下声明添加到项目中:
#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
初始化
您必须将m_homeDir变量初始化为.NET程序集所在的路径。
例如,如果将.NET程序集安装在与Excel或MS-Access文件相同的文件夹中,则应将m_homeDir初始化为:
Excel:m_homeDir = ThisWorkbook.Path
访问:m_homeDir = CurrentProject.Path
.NET对象创建
将以下代码添加到项目中。
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
实例化.NET对象
现在,您已准备好实例化.NET对象并开始使用它。 将以下代码添加到您的应用程序:
m_homeDir = ThisWorkbook.Path m_myobject = GetMyObject(m_homeDir & “\yourdotnet.dll”, “namespace.class”)
第一个参数是.NET DLL的完整路径。
第二个参数是所请求类型的完全限定名,包括命名空间,但不是由Type.FullName属性返回的程序集。
必需的DLL
该解决方案需要部署2个负责托管.NET CLR的DLL。 DLL预计将部署在与Excel或MS-Access文件相同的文件夹中。
DLL可以从Soraco的网站下载: https ://soraco.co/products/qlm/QLMCLRHost.zip
LGPL-2.1的许可
只要您的应用程序不直接或间接与Quick License Manager竞争,我们特此授予您使用我们的DLL的权利。 您可以在商业或非商业应用程序中使用这些DLL。
我不确定这只是巧合,还是因为我发布了相关问题。 SO向我展示了你的问题,我想我也可以做出贡献。
使用VBA和DLL时,到目前为止我看到的大多数解决方案都告诉我注册DLL并使其显示为com / gac。 如果您在PC中执行此操作绝对没问题,但如果您要分发VBA应用程序,则实际上并不想在其系统中安装DLL。 您可能没有权限,或者您真的不想通过安装/卸载过程或搞乱引用问题。
但是,您可以使用某些Windows API动态加载dll。
DLL
现在的问题是如何从vba访问.NET DLL? 如果您的客户端具有混合的os体系结构x86 x64,则需要相应地处理此问题。 让我们假设我们正在使用32位office / Excel。
如果您创建一个.NET DLL并希望从VBA访问它,它将抛出类似于“无法找到dll入口点”的错误消息。 谢天谢地, Robert Giesecke创建了一个抽象包装器 ,它允许您通过VBA创建简单的DLL消耗品。
可在此处找到模板 。
所有你需要做的
- 在visual studio中创建一个新的类项目
- 将项目平台设置为x86(32位)和其他
- 在主类中创建方法。
- 创建另一个类,它将主类作为对象返回(返回到vba)
- (按照他网站上的模板)
让我们假设您已经按照他的模板创建了一个测试方法,如下所示。
[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 + "..."; } }
和你的unmanagedexport类:
static class UnmanagedExports { [DllExport] [return: MarshalAs(UnmanagedType.IDispatch)] static Object YOUR_DLL_OBJECT() { return new YOUR_MAIN_CLASS(); } }
准备从vba端访问dll
将DLL添加到根文件夹:
#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" () #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
现在一切都是关于加载DLL并在vba中创建和访问它的对象。 那将是:
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 ..")
输出应该是
"You have sent me: Testing ....."
优点我个人不喜欢安装和引用dll。 通过以上模板,你不需要引用任何东西,你不需要安装任何东西只需加载和完全自由地使用你的DLL。
注意 :我假设dll / .net代码是你的,你可以使用上面的模板再次编译它。
我在上面的模板上取得了成功并为vba创建了一个.NET非阻塞通知,您可以在这里查看: 非阻塞“toast”,如Microsoft Access(VBA)通知
默认策略是阻止CLR 4从CLR 2中删除遗留代码:
Set clr = New mscoree.CorRuntimeHost
要启用旧版执行,您可以在excel.exe.config
所在的文件夹中创建文件excel.exe.config
:
或者您可以调用本机函数CorBindToRuntimeEx
而不是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