在Access VBA中使用.NET 5 COM库

3

我们有一个旧的Access工具,需要打开用C# - Winforms编写的GUI。目前我们只是启动exe文件,但如果可能的话,我希望直接加载dll。

到目前为止,我有这段代码来创建可见的COM库

using System;
using System.Runtime.InteropServices;

namespace COMs {
    [ComVisible(true)]
    [Guid("C412E308-0D12-42D1-9506-C64A7958B4F9")]
    [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    public interface IProduktionsstundenCOM {
        public void StartProduktionsstunden();
    }

    [ComVisible(true)]
    [Guid("C63D94CD-4978-40C2-AD88-799D9430683F")]
    public class ProduktionsstundenCOM : IProduktionsstundenCOM {

        public void StartProduktionsstunden() {
            throw new NotImplementedException();
        }
    }
}

在这个过程中,我构建了一个名为appname.comhost.dll的文件,并将其注册到regsvr32 appname.comhost.dll

但是,在访问引用窗口时,我发现无法找到该库,即使我浏览到它并尝试添加它,也会收到以下错误信息:"无法添加对所选文件的引用"("Verweis auf die angegebene Datei kann nicht hinzugefügt werden.")。我只能找到这篇有用的文章https://learn.microsoft.com/en-us/dotnet/core/native-interop/expose-components-to-com,它还指出.net5不能创建.tlb文件,但我不知道是否需要。

有没有办法让这个工作?


您需要在GAC中注册用C#.NET编写的自定义*.dll。 - Maciej Los
2
请按照以下步骤操作:使用CSharp或VB.Net为VBA创建DLL,或者 泛型和COM可见的.NET库 - Maciej Los
谢谢,我会在周一查看它。 - DemosKadi
好的,我已经尝试重现您的问题...看起来,灰色的“注册COM互操作”选项是由于不正确(或缺失)的.NET框架安装引起的。我建议使用Microsoft .NET Framework修复工具。如果有帮助,请告诉我。 - Maciej Los
尝试使用.NET 6自己使其工作。您发布的链接只能帮助我们到这里,因为没有调用VBA中的COM DLL的示例。我让它工作的一种方法是安装DllExport NuGet包。我希望能够消除这种依赖关系。 - Mike Lowery
显示剩余4条评论
2个回答

2
我遇到了类似的问题,并找到了以下 Github 存储库,其中显示了 IDL 文件的正确格式。它还向您展示了需要添加哪些注册表项。您可以将其用作起点。链接 您的 IDL 文件的基本结构应该如下所示:
[
    uuid(your library GUID),
    version(1.0),
    helpstring("ComTestLibrary")
]
library ComTestLibrary
{
    importlib("STDOLE2.TLB");

    [
        odl,
        uuid(your interface GUID),
        dual,
        oleautomation,
        nonextensible,
        helpstring("ComTestLibrary"),
        object,
    ]
    interface IComTest : IDispatch
    {
        [
            id(1),
            helpstring("Test")
        ]
        HRESULT Test(
            [in] double firstValue,
            [in] double secondValue,
            [in] BSTR comment,
            [out, retval] double* ReturnVal);

        // Other methods
    };

    [
        uuid(your class GUID),
        helpstring("ComTest")
    ]
    coclass ComTest
    {
        [default] interface IComTest;
    };
}

对于注册表条目,您可以向COM类添加DllRegisterServer和DllUnregisterServer函数。 在我的情况下,它们看起来像这样:

[ComRegisterFunction]
public static void DllRegisterServer(Type t)
{
      // Additional CLSID entries
      using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(@"CLSID\{" + AssemblyInfo.ClassGuid + @"}"))
      {
           using (RegistryKey typeLib = key.CreateSubKey(@"TypeLib"))
           {
                typeLib.SetValue(string.Empty, "{" + AssemblyInfo.LibraryGuid + "}", RegistryValueKind.String);
           }
      }

      // Interface entries
      using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(@"Interface\{" + AssemblyInfo.InterfaceGuid + @"}"))
      {
           using (RegistryKey typeLib = key.CreateSubKey(@"ProxyStubClsid32"))
           {
                typeLib.SetValue(string.Empty, "{00020424-0000-0000-C000-000000000046}", RegistryValueKind.String);
           }

           using (RegistryKey typeLib = key.CreateSubKey(@"TypeLib"))
           {
                typeLib.SetValue(string.Empty, "{" + AssemblyInfo.LibraryGuid + "}", RegistryValueKind.String);
                Version version = typeof(AssemblyInfo).Assembly.GetName().Version;
                typeLib.SetValue("Version", string.Format("{0}.{1}", version.Major, version.Minor), RegistryValueKind.String);
           }
      }

      // TypeLib entries
      using (RegistryKey key = Registry.ClassesRoot.CreateSubKey(@"TypeLib\{" + AssemblyInfo.LibraryGuid + @"}"))
      {
            Version version = typeof(AssemblyInfo).Assembly.GetName().Version;
            using (RegistryKey keyVersion = key.CreateSubKey(string.Format("{0}.{1}", version.Major, version.Minor)))
            {
                // typelib key for 32 bit
                keyVersion.SetValue(string.Empty, AssemblyInfo.Attribute<AssemblyDescriptionAttribute>().Description, RegistryValueKind.String);
                using (RegistryKey keyWin32 = keyVersion.CreateSubKey(@"0\win32"))
                {
                    keyWin32.SetValue(string.Empty, Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, ".comhost.tlb"), RegistryValueKind.String);
                }

                // typelib key for 64 bit
                keyVersion.SetValue(string.Empty, AssemblyInfo.Attribute<AssemblyDescriptionAttribute>().Description, RegistryValueKind.String);
                using (RegistryKey keyWin64 = keyVersion.CreateSubKey(@"0\win64"))
                {
                    keyWin64.SetValue(string.Empty, Path.ChangeExtension(Assembly.GetExecutingAssembly().Location, ".comhost.tlb"), RegistryValueKind.String);
                }

                using (RegistryKey keyFlags = keyVersion.CreateSubKey(@"FLAGS"))
                {
                    keyFlags.SetValue(string.Empty, "0", RegistryValueKind.String);
                }
           }
      }
}

[ComUnregisterFunction]
public static void DllUnregisterServer(Type t)
{
        Registry.ClassesRoot.DeleteSubKeyTree(@"TypeLib\{" + AssemblyInfo.LibraryGuid + @"}", false);
        Registry.ClassesRoot.DeleteSubKeyTree(@"Interface\{" + AssemblyInfo.InterfaceGuid + @"}", false);
        Registry.ClassesRoot.DeleteSubKeyTree(@"CLSID\{" + AssemblyInfo.ClassGuid + @"}", false);
}

这很好。但如果您能从链接中提取一个示例并将其放入代码块中,那就更好了。 - ouflak

1
如果您想在Access中使用COM类,则需要一个TLB文件。正如您已经发现的,.NET Core不支持从编译二进制文件生成TLB文件。因此,您需要手动草拟一个IDL文件,并使用MIDL进行编译,以创建一个合适的TLB文件。
然而,有一种解决方法可以更加实用地使其工作:只需编辑您的csproj文件并为经典.NET Framework创建一个附加的构建目标即可。例如:
<PropertyGroup>
    <TargetFrameworks>net5.0;net48</TargetFrameworks>
</PropertyGroup>

请注意额外的.NET Framework 4.8目标。构建项目时,应该出现一个额外的输出目录。它包含一个二进制文件,其目标为.NET 4.8,而不是.NET Core。
接下来,在此二进制文件上运行tlbexp(类型库导出程序),以创建一个TLB文件:
tlbexp net48build.dll

这个命令会生成一个适配的TLB文件,适用于两个构建目标。然后您可以在Access中引用此TLB文件来创建已注册类的实例。

只要我只是做一些基本的东西,这个可以工作。但是,一旦我想链接我的其他(winforms)项目,我就不能再构建net48目标了,因为其他项目有net5-windows目标。我尝试将com接口制作为一个单独的项目,只在那里有额外的目标net48,并从实现中链接它,但现在我不知道如何使Access将不同的comhost.dll和tlb文件识别为相同的。 - DemosKadi
在这种情况下,您将不得不手动编写一个IDL文件,并通过midl编译器运行它。根据您的实际项目设置,您可能可以使用net48构建的初始IDL描述,并从那里继续,而不是从头开始。 - Aurora
好的,谢谢。你知道关于如何编写此案例的IDL文件有哪些好的资源吗?我搜索了一下,但并没有真正理解。虽然我甚至不确定是否会走这条路,因为它似乎比我希望的要复杂得多,而我们的产品正在运行,只是不太优雅。 - DemosKadi
官方的MIDL文档应该能够帮助您深入了解这个主题。它描述了语法,以及如何使用midl编译器生成类型库。虽然可能有更多网络资源可用,但由于此主题过时,它们可能难以找到。 - Aurora

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