C#和VBA的Side-By-Side COM互操作

7
我不是在谈论从C#调用VBA COM...而是另一种方式!我想做的是在MS Access中使用VBA调用C#库,而无需注册DLL。我已经尝试了一段时间的并行互操作,但没有成功,最终我意识到mdb.manifest可能不能替代exe.manifest(这很明显,但我一直试图保持乐观)。我的问题是:是否有可能让VBA加载一个并行的COM组件?或者,是否有其他方法可以在Access中使用未注册的C#库?(在你问之前,我的原因是:我绝对不会被授权访问客户的Windows注册表--这就是为什么它首先是在Access中编写的。而且,我很快将需要在C#应用程序中实现相同的功能,而不愿意重复执行)。
4个回答

12

除了已有的答案之外,使用 .NET 4.0,在不注册COM的情况下,在您的VBA项目中使用C# dll实际上非常简单。

编辑: 我刚试过了在C:\windows\Microsoft.NET\Framework\v2.0.50727中的mscorlib.tlbmscoree.tlb --加载一个在3.5编译的程序集-- 它完全可以正常工作。因此显然您不需要.NET 4.0。

以下是如何在您的VBA项目中使用C# dll的示例。它略微修改自这个答案。

1)在您的VBA项目中添加对以下类型库的引用(工具->引用):

C:\windows\Microsoft.NET\Framework\v4.0.30319\mscorlib.tlb
C:\windows\Microsoft.NET\Framework\v4.0.30319\mscoree.tlb

(如果您正在运行 64 位 Office,请使用 Framework64 文件夹)

2)在您的 C# 项目中,请确保向您的类添加 [ComVisible(true)] 属性:

using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace VB6FuncLib
{
    [ComVisible(true)]
    public class VB6FuncLib
    {
        public VB6FuncLib()
        { }
        public void test()
        {
            MessageBox.Show("Test Successful");
        }
    }
}

您无需勾选“注册COM互操作”选项。那只适用于构建标准的COM对象。除非您希望整个程序集可见 (这也会消除需要使用COMVisible属性),否则也不必勾选“使程序集对COM可见”选项。

3)在您的VBA代码中,添加一个新模块,并加入此代码:

Sub Test()
    Dim Host As mscoree.CorRuntimeHost
    Set Host = New CorRuntimeHost
    Host.Start
    Dim Unk As IUnknown
    Host.GetDefaultDomain Unk
    Dim AppDomain As AppDomain
    Set AppDomain = Unk
    Dim ObjHandle As ObjectHandle
    Set FS = CreateObject("Scripting.FileSystemObject")
    Path = FS.GetParentFolderName(CurrentDb().Name)
    Set ObjHandle = AppDomain.CreateInstanceFrom(Path & "\VB6 Function Library.dll", "VB6FuncLib.VB6FuncLib")
    Dim ObjInstance As Object
    Set ObjInstance = ObjHandle.Unwrap
    ObjInstance.test
    Host.Stop
End Sub

4) 将DLL复制到与您的Office项目相同的文件夹中,并在VBA中运行Test()子程序。

注意:

需要注意的是,这种技术的局限性之一是,如果.DLL存储在远程网络共享上,它将无法工作。一个简单的解决方案是将其复制到每台PC上使用的同一本地文件夹中。另一个解决方案是将二进制文件包含在您的Access应用程序/VBA项目中,并让MS-Access导出它们。可以通过将它们存储在表格或电子表格中的Base64中,然后将它们转换并导出为二进制来实现这一点。

我能够通过创建一个类型库与DLL配合(使用tlbexp),并在我的VBA项目中添加对TLB的引用来使早期绑定(因此Microsoft IntelliSense)工作,但它确实使问题有些复杂,因为它需要您的VBA应用程序知道DLL和TLB文件在哪里(还需要有人确保它们在那里)。


3
你不必拥有exe文件来使用SxS,SxS是激活上下文的另一个名称。如果你能将相关的win32调用导入到vba中(你可以的),那么你就可以使用激活上下文API来加载你的清单文件。
关于这个主题的更多信息和一些示例可以在这里找到。

我研究了使用那个API。然而具有讽刺意味的是,微软创建了一个避免注册但需要注册本身的包,并留下了我之前的同样问题 - 如何将它安装到我的客户机上?虽然Microsoft.Windows.Actctx适用于Windows 2K3+,但它仅包含在服务器安装中。这里有另一个SO问题抱怨:https://dev59.com/MkfSa4cB1Zd3GeqPBfWd。MSDN在这里支持它:http://msdn.microsoft.com/en-us/library/aa375644(VS.85).aspx。 - tordal
据我所知,激活上下文 API 是 Kernel32.lib 的一部分,这意味着您不需要安装任何东西,它已包含在任何支持 SxS 的操作系统中,例如 WinXP SP2 及更高版本。我自己在 WinXP SP3 上完成了这项工作,并且没有进行任何特殊的安装。我不知道 Microsoft.Windows.Actctx,但我可以百分之百确定,您可以通过从 Kernel32.lib 导入相关函数并直接调用它们来直接调用 API。就像这个示例代码中所展示的那样:http://www.mazecomputer.com/sxs/help/sxsapi3.htm - Alex Shnayder
哦,我明白了。在网上看到的所有VBA操作示例都使用Actctx对象而不是API,这就是我最初得出它无法在代码中完成的唯一原因。显然,它可以用两种方式实现(并且称之为相同的东西),只是为了让我困惑。我不应该遇到太多麻烦,成功运行起来也更稳定,比修改IL更好,所以当我完成后,我会移动接受的解决方案标志。谢谢你的帮助! - tordal
很高兴能够帮忙。我知道这非常令人困惑,唯一的原因是因为我现在也在做类似的事情,而且在掌握术语之前我必须深入挖掘。 - Alex Shnayder

1
问题在于,要使用SxS,您需要拥有exe来设置配置以加载SxS程序集。您没有“拥有”Access,虽然您可以放置正确的配置文件以使其加载您的.NET COM内容而无需注册,但这不是一个“好公民”的做法。
如果您使用shimming技巧,可以设置一个未管理的DLL(或带有dllexport的黑客C#类库,请参见this),其中包含一个导出函数,该函数将加载.NET框架,创建COMVisible DispInterface托管类型的实例并返回它(该方法应返回IDispatch)。然后编写一个VBA声明到您的DLL导出函数(声明为返回Object)。如果您不理解这个过程,最好不要尝试... :) 我以前在类似的情况下做过这件事,并且它确实有效,但我没有样本可以指向您。

这非常聪明,而且实际上很有道理。之前我在搜索时没有找到任何关于如何通过黑客手段强制C#导出方法的信息。我已经让这部分工作正常运行了,我认为让它生成一个入口点并返回一个对象也不应该太困难。感谢您的建议! - tordal

0

C#库不是普通的DLL。它们更类似于COM库,需要在使用之前进行注册(就像ActiveX控件一样),特别是当从非.NET代码中调用时。

(当然,除非事情已经改变...)


我的印象是C# DLL可以未注册使用,来源于这里:http://msdn.microsoft.com/en-us/library/ms973915.aspx,其中有一个C# .NET服务器和一个非.NET客户端。我相信这基本上等同于我想做的事情,除了客户端是VB6可执行文件而不是VBA应用程序之外。我是否还遗漏了什么?谢谢! - tordal
你可能会遇到一个难题,那就是需要为客户端创建清单文件,因为VBA不是一个独立的程序,无法生成清单文件。清单看起来很简单,但我不能确定如何正确关联它。也许你可以让VBA执行一个独立的客户端来满足你的需求? - Ioan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接