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

How to call .NET methods from Excel VBA?

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

我使用工具 -> 引用添加了对 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/hosting-the-net-4-runtime-in-a-native-process/

我的ExcelVBA技术还不够翻译那几行代码

这是关于从 Excel(或 VBA)调用 .Net 的 3 种主要方法的 canonical 答案。

这三种方式都适用于 .Net 4.0。

1. XLL

第 3 方供应商 Add-In Express 提供 XLL 功能,但它免费且易于使用 Excel-DNA 作者在这里 https://whosebug.com/users/44264

这是 Excel-DNA 页面的摘录:https://excel-dna.net/

Introduction

Excel-DNA is an independent project to integrate .NET into Excel. With Excel-DNA you can make native (.xll) add-ins for Excel using C#, Visual Basic.NET or F#, providing high-performance user-defined functions (UDFs), custom ribbon interfaces and more. Your entire add-in can be packed into a single .xll file requiring no installation or registration.

Getting Started

If you are using a version of Visual Studio that supports the NuGet Package Manager (including Visual Studio 2012 Express for Windows Desktop), the easiest way to make an Excel-DNA add-in is to:

Create a new Class Library project in Visual Basic, C# or F#. Use the Manage NuGet Packages dialog or the Package Manager Console to install the Excel-DNA package:

PM> Install-Package Excel-DNA

Add your code (C#, Visual Basic.NET or F#):

using ExcelDna.Integration;
public static class MyFunctions
{
    [ExcelFunction(Description = "My first .NET function")]
    public static string SayHello(string name)
    {
        return "Hello " + name;
    }
}

Compile, load and use your function in Excel:

=SayHello("World!")

2。自动化插件

Eric Carter 的这篇文章展示了如何做到这一点,这篇文章缺少大量图像,所以我复制/粘贴了整篇文章并重新创建了图像以供保存。

REF:https://blogs.msdn.microsoft.com/eric_carter/2004/12/01/writing-user-defined-functions-for-excel-in-net/

Excel enables the creation of user defined functions that can be used in Excel formulas. A developer must create a special kind of DLL called an XLL. Excel also allows you to write custom functions in VBA that can be used in Excel formulas. Unfortunately, Excel does not support or recommend writing an XLL that uses managed code. If you are willing to take your chances that your XLL might not run in current or future versions of Excel, there are solutions available that enable this scenario—search the web for “managed XLL”.

Fortunately, there is an easier way to create a user defined function that doesn’t require you to create an XLL dll. Excel XP, Excel 2003, and Excel 2007 support something called an Automation Add-in. An Automation Add-in can be created quite simply in C# or VB.NET. I’m going to show you an example in C#.

First, launch Visual Studio and create a new C# class library project called AutomationAddin for this example.

Then, in your Class1.cs file, enter the code shown below. Replace the GUID with your own GUID that you create by using Generate GUID in the Tools menu of Visual Studio.

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();
    }  
  }
}

With this code written, show the properties for the project by double clicking on the properties node under the project in Solution Explorer. Click on the Build tab and check the check box that says “Register for COM Interop”. At this point you have an extra step if you are running on Windows Vista or higher. Visual Studio has to be run with administrator privileges to register for COM interop. Save your project and exit Visual Studio. Then find Visual Studio in the Start menu and right click on it and choose “Run as Administrator”. Reopen your project in Visual Studio. Then choose “Build” to build the add-in.

Now launch Excel and get to the Automation servers dialog by following these steps:

  1.  Launch Excel and click the Microsoft Office button in the top left corner of the window. 
    
  2.  Choose Excel Options.
    
  3.  Click the Add-Ins tab in the Excel Options dialog. 
    
  4.  Choose Excel Add-Ins from the combo box labeled Manage.  Then click the Go button.
    
  5.  Click the Automation button in the Add-Ins dialog.
    

You can find the class you created by looking for AutomationAddin.MyFunctions in the list of Automation add-ins:

Now, let’s try to use the function MultiplyNTimes inside Excel. First create a simple spreadsheet that has a number, a second number to multiple the first by, and a third number for how many times you want to multiply the first number by the second number. An example spreadsheet is shown here:

Click on an empty cell in the workbook below the numbers and then click on the Insert Function button in the formula bar. From the dialog of available formulas, drop down the “Or select a category” drop down box and choose “AutomationAddin.MyFunctions.

Then click on the MultiplyNTimes function as shown here:

When you press the OK button, Excel pops up a dialog to help you grab function arguments from the spreadsheet as shown here:

Finally, click OK and see your final spreadsheet as shown here with your custom formula in cell C3.


3.从 Excel VBA

调用 .Net

REF:Calling a .net library method from vba

使用 Automation.AddIn 项目中的代码,我们可以轻松地从 Excel VBA.

中调用 MultiplyNTimes 函数

首先从 Excel 添加对 DLL 的引用,为此您需要在 VB 编辑器中。按 Alt + F11,然后单击“工具”菜单和“参考文献”:

Select AutomationAddIn DLL:

添加VB调用.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 的接口:

最后,“Andrew Whitechapel”发表了一些关于 Excel 和 .Net 的优秀 MSDN 文章 - google them

我不确定这只是巧合还是因为我提出了相关问题os。所以向我展示了你的问题,我想我也可以贡献一些东西。

在使用 VBA 和 DLL 时,我目前看到的 most 解决方案告诉我注册 DLL 并使其 com/gac 可见。如果您在您的 PC 上执行此操作,那绝对没问题,但如果您要分发您的 VBA 应用程序,您并不真的想在他们的系统中安装 DLL。您可能没有权限,或者您真的不想经历 install/uninstall 过程或搞乱引用问题。

但是您可以使用一些 windows API 动态加载 dll。

DLL

现在的问题是如何从 vba 访问 .NET dll?如果您的客户混合了 os 体系结构 x86 x64,您需要相应地处理它。假设我们正在处理 32 位 office/Excel.

如果您创建了一个 .NET dll 并想从 VBA 访问它,它将抛出类似于 "Can't find the dll entry point" 的错误消息。值得庆幸的是 Robert Giesecke has created an abstract wrapper 这将允许您通过 VBA.

创建简单的 DLL 消耗品

A template 可以在这里找到。

所有你需要做的

  1. 在 visual studio
  2. 中创建一个新的 class 项目
  3. 将项目平台设置为 x86(32 位),否则
  4. 在主 class.
  5. 中创建您的方法
  6. 创建另一个 class,它将 return 您的主要 class 作为对象(return 到 vba)
  7. (按照他网站上的模板)

假设您已经按照他的模板创建了一个测试方法,如下所示。

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

和您的非托管导出 class:

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 代码是你的,你可以用上面的模板再次编译它。

我在上面的模板上取得了成功,并为 vba 创建了一个 .NET 非阻塞通知你可以在这里看看:non-blocking "toast" like notifications for Microsoft Access (VBA)

这是您的解决方案,针对 .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 = 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 文件相同的文件夹中。

可以从 Soraco 的网站下载 DLL:https://soraco.co/products/qlm/QLMCLRHost.zip

许可 LGPL-2.1

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

默认策略阻止 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