如何从Excel VBA调用.NET方法?

f1tvaqid  于 2022-11-19  发布在  .NET
关注(0)|答案(4)|浏览(794)

我找到了一种直接从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

我使用“工具”-〉“引用”将对mscore.tlb和mscorlib.tlb的引用添加到Excel VBA中。
这适用于.NET CLR 2组件,最高可达.NET Framework 3.5版。
我需要让它与.NET 4一起工作。
我知道.NET CLR 4引入了另一种与版本无关的创建运行时示例的方法,并且我找到了一个用C++编写的代码示例:http://dev.widemeadows.de/2014/02/04/hosting-the-net-4-runtime-in-a-native-process/
我的Excel VBA技能不足以翻译这几行代码。

slmsl1lt

slmsl1lt1#

下面是关于从Excel(或VBA)调用.Net的3种主要方法的canonical答案。
这三种方法在.Net 4.0中都可以使用。


第三方供应商Add-In Express提供XLL功能,但其免费且易于使用的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)、自定义功能区界面等。您的整个加载项可以打包到一个.xll文件中,无需安装或注册。

入门

如果您使用的Visual Studio版本支持NuGet程序包管理器(包括Visual Studio 2012 Express for Windows Desktop),则创建Excel-DNA外接程序的最简单方法是:
使用Visual Basic、C#或F#创建新的类库项目。使用“管理NuGet包”对话框或“包管理器控制台”安装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.自动化加载项

这篇文章由埃里克卡特显示如何做到这一点,文章是失踪的图像堆,所以我复制/粘贴整篇文章,并已重新创建的图像保存。
参考: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支持自动化外接程序。使用C#或VB .NET可以非常简单地创建自动化外接程序。我将向您展示一个使用C#的示例。
首先,启动Visual Studio,并为此示例创建一个名为AutomationAddin的新C#类库项目。
然后,在Class1.cs文件中,输入下面所示的代码。将GUID替换为您自己的GUID,该GUID是通过使用Visual Studio的“工具”菜单中的“生成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();
    }  
  }
}

编写了这段代码后,显示项目的属性,方法是在解决方案资源管理器中双击该项目下的属性节点。单击“生成”选项卡并选中“注册COM互操作”复选框。如果您在Windows Vista或更高版本上运行,则此时需要执行一个额外的步骤。必须以管理员权限运行Visual Studio才能注册COM互操作。保存项目并退出Visual Studio。然后在“开始”菜单中找到Visual Studio,右键单击它并选择“以管理员身份运行”。在Visual Studio中重新打开项目。然后选择“生成”以生成外接程序。

现在启动Excel,并按照以下步骤进入“自动化服务器”对话框:
1.

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.

通过在自动化加载项列表中查找AutomationAddin.MyFunctions,可以找到所创建的类:

现在,让我们尝试在Excel中使用MultiplyNTTimes函数。首先创建一个简单的电子表格,其中包含一个数字、第二个数字(第一个数字的倍数)和第三个数字(第一个数字乘以第二个数字的次数)。下面是一个电子表格示例:

单击工作簿中数字下方的空白单元格,然后单击编辑栏中的“插入函数”按钮。从可用公式的对话框中,下拉“或选择一个类别”下拉框,然后选择“AutomationAddin. MyFunctions”。

然后单击MultiplyNTTimes函数,如下所示:
x1c4d 1x指令集
当您按下“确定”按钮时,Excel会弹出一个对话框,帮助您从电子表格中获取函数参数,如下所示:

最后,单击“确定”,查看最终的电子表格,如此处所示,其中单元格C3中包含自定义公式。


指令集

3.从Excel VBA调用.Net

使用Automation.AddIn项目中的代码,我们可以轻松地从Excel VBA中调用MultiplyNTTimes函数。
首先从Excel中添加一个对DLL的引用,为此,您需要在VB编辑器中按Alt + F11,然后单击“工具”菜单和“引用”:

选择自动加载项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

嘿,快!


指令集
请注意,如果您使用的是C#中的类,则需要使用ClassInterface标记它们,并使用ComVisible = true标记接口:Use CLR classes from COM addin in Excel VBA?
最后,还有一些关于Excel和.Net的优秀MSDN文章,作者是“Andrew Whitechapel”-谷歌一下

kcwpcxri

kcwpcxri2#

默认策略是阻止CLR 4执行CLR 2中的遗留代码:

Set clr = New mscoree.CorRuntimeHost

要启用传统执行,可以在excel.exe所在的文件夹中创建文件excel.exe.config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
  </startup>
</configuration>

或者,您可以呼叫原生函式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
w8f9ii69

w8f9ii693#

这是您的解决方案,已针对.NET 2.0和.NET 4.0(32位和64位)进行了测试,由Soraco Technologies提供。
下面建议的解决方案使用后期绑定,并且不需要注册.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 =此工作簿.路径
访问:m_homeDir =当前项目路径

.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

我们特此授予您使用我们的DLL的权利,只要您的应用程序不直接或间接与Quick License Manager竞争。您可以在商业或非商业应用程序中使用这些DLL。

klr1opcd

klr1opcd4#

我不知道这是否只是一个巧合或因为我张贴了相关的问题。所以给我看你的问题,我想我也可以贡献一些东西。
当使用VBA和DLL时,我所见过的大多数解决方案都是告诉我注册DLL并使其com/gac可见。如果您在PC上这样做,这绝对没问题,但如果您要分发VBA应用程序,您并不想在他们的系统中安装DLL。您可能没有权限,或者您并不想执行install/卸载过程或与引用问题混淆。
但是,您可以使用某些Windows API动态加载dll。

动态链接库

现在的问题是如何访问.NET dll从vba?如果你的客户端有混合操作系统架构x86 x64你需要相应地处理这个。让我们假设我们正在工作的32位办公室/Excel。
如果您创建了一个.NET dll并想从VBA访问它,它将抛出类似于“找不到dll入口点”的错误消息。谢天谢地,Robert Giesecke创建了一个abstract wrapper,它将允许您通过VBA创建简单的DLL消耗品。
A template可以在这里找到。
你只需要
1.在visual studio中创建新的类项目
1.将项目平台设置为x86(32位)或其他
1.在主类中创建方法。
1.创建另一个类,它将把你的主类作为对象返回(正在返回到vba)
1.(按照他网站上的模板)
让我们假设你已经按照他的模板,并创建了一个测试方法如下。

[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 + "...";
    }
}

和未管理的导出类:

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" () 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

现在它是所有关于加载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代码是你的,你可以用上面的模板再次编译它。

我成功地与上述模板,并创建了一个.NET非阻塞通知的vba,你可以看看这里:Microsoft Access(VBA)的非阻塞“吐司”类通知

相关问题