如何使用 COM 公开 .netstandard2.0 库以便在 VB6 中使用?

How do I expose a .netstandard2.0 library with COM for use in VB6?

我有一个 dotnet 核心库、一个框架 4.7.2 库和一个 vb6 应用程序。

我想写一个公共库供他们访问,所以选择.netstandard2.0

我尝试了 .netstandard2.0 库和 vb6 之间的 4.7.2 框架包装器库。

不过我运行变成了assembly binding problems

看着the docs我明白了

In .NET Core, the process for exposing your .NET objects to COM has been significantly streamlined in comparison to .NET Framework.

但是没有提到.netstandard2.0

即使我的项目使用的是 .netstandard2.0,我还是决定尝试遵循文档

我得到了有关生成 COM 主机的说明,在这种情况下,应该构建输出文件 ProjectName.dll、ProjectName.deps.json、ProjectName.runtimeconfig.json 和 ProjectName.comhost.dll。

但是 ProjectName.comhost.dll 和 ProjectName.runtimeconfig.json 不会创建。

我在此 dotnet standard issue 中看到 Microsoft 计划在 "Preview 4"

中提供工具支持

我是运行宁VS 16.4.5

[更新]

我决定尝试制作一个 .net 核心包装器库并为 com 启用它。

我能够通过 nuget 包将我的 .netstandard 添加到包装库(我使用 azure devops 构建 .netstandard 库)

当我构建包装器库时,.dll、.deps.json、.pdb、.runtimeconfig.dev.json 和 .runtimeconfig.json 文件是在 bin\Debug\netcoreapp3.1 中创建的文件夹。

但是 none 的 .netstandard 库文件出现在 bin\debug 文件夹中。

我将 .netstandard 库和 .netcore 包装器库复制到同一个文件夹并且 运行

regsvr32 MyCoreComWrapper.comhost.dll  

但是没有创建我需要能够从 VB6 使用的 .tlb 文件

我在文档中注意到以下内容

Unlike in .NET Framework, there is no support in .NET Core for generating a COM Type Library (TLB) from a .NET Core assembly. The guidance is to either manually write an IDL file or a C/C++ header for the native declarations of the COM interfaces.

我找到了一些信息 on github,但希望能得到制作 .tlb 的分步指南

我考虑过使用后期绑定,但不确定如何将它与 com 库一起使用。

[更新]

我在 GitHub 上放了一个示例项目,其中包括一些 VB6 文件。 使用 VB6 引用框架库引用的 .tlb。

当我尝试 运行 我得到

Could not load file or assembly 'Microsoft.EntityFrameworkCore, Version=3.1.2.0, 
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.

所以我将框架测试项目中的所有文件复制到我的 vb6 文件夹中,重建并 运行。

然后我得到了错误

Could not load file or assembly 'Microsoft.Extensions.DependencyInjection.Abstractions, Version=3.1.0.0, 
Culture=neutral, PublicKeyToken=adb9793829ddae60' or one of its dependencies. The system cannot find the file specified.

我看到文件 Microsoft.Extensions.DependencyInjection.dll 存在,文件版本为 3.100.220.6706

关于 .NET 标准,我可能是错的,但我 认为 这在这里不适用,因为 COM 互操作的东西比 .NET 标准更高级别瞄准;我们只能谈论用于 COM 互操作的 .NET Core 或 .NET Framework。

如果要生成类型库,您的选择很少。

到目前为止,最简单的方法就是使用 .NET Framework。您想要创建类型库这一事实已经否定了 .NET Core 的优势,因为几个 COM,尤其是 "Automation" 功能是 Windows-only。至少在 .NET Core 5 出来之前,使用框架是没问题的。

也就是说,如果您出于业务原因使用 .NET Core 但仍然需要 COM 支持,包括类型库,那么基于 this GitHub comment,您应该能够编译自己的 IDL。请注意,这需要您安装 C++ 构建工具,因为 MIDL 编译器并不是真正独立的东西,您可以在没有其他 C++ 构建工具的情况下获得它。

It is strongly suggested to have had read the documentation on how .NET Core handles COM activation.

假设拥有 C++ 构建工具对您来说不是障碍,步骤如下:

1) 创建一个 .idl 文件,以 IDL 格式定义所有 COM 接口。这需要在 .NET 接口和 COM 接口之间进行一些转换。以下是您需要如何在 IDL 中定义的 C# 接口和 COM 接口之间进行转换的部分示例:

[
  Guid("<some gooey>"),
  InterfaceType(ComInterfaceType.InterfaceIsDual)
]
public interface IFoo 
{
   string Bar { get; }
   string Baz(int Fizz);
}

将被翻译成 IDL:

[
  uuid(<assembly gooey>),
  version(1.0)
]
library myFoo
{
  [
    uuid(<some gooey>),
    object,
    dual
  ]
  interface IFoo : IDispatch {
    [propget] HRESULT Bar([out, retval] BSTR* retVal);
    HRESULT Baz([in] long Fizz, [out, retval] BSTR* retVal);
  }
}

一旦您定义了 .idl 文件并且它是一个准确的表示,您就可以使用 MIDL 将 .idl 文件编译成 .tlb 文件。通常类似于 midl foo.idl /tlb: foo.tlb。您应该使用 MIDL language reference to help you write the .idl file. As a quick way to get started, you could copy your C# interfaces to a .NET framework project, use tlbexp, then use oleview (available via Visual Studio Developer Command Prompt) or olewoo 查看生成的 IDL 文件以开始使用。

下一步是创建注册表项,以便您的 CLSID 可以引用类型库。您需要准备好程序集的 GUID,它也必须用作 .idl 文件中的 libraryuuid

使用 IFoo 界面示例,您需要创建类似于以下的注册表(使用 .reg 格式以便于 sharing/comprehension 并假设按用户安装,而不是按机器安装):

Windows Registry Editor Version 5.00

[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}]
@="IFoo"

[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\ProxyStubClsid32]
@="{00020424-0000-0000-C000-000000000046}"

[HKEY_CURRENT_USER\Software\Classes\Interface\{<some gooey>}\TypeLib]
@="{assembly gooey}"
"Version"="1.0"

您还需要根据需要在 CLSIDInterfaceTypeLibRecord 中创建注册表。 This article provides a good overview of all registry keys but keep in mind it's assuming .NET framework, not .NET Core, so not all keys are applicable, especially under the CLSID branch.

请注意,当您 运行 regsvr32 时,它通常会在 CLSIDInterface 分支中创建密钥,但您需要添加 [= Interface 分支下的 28=] 键以及 TypeLib 分支的入口。如果您想支持 CreateObject 功能,您还需要创建 ProgId 键。

最初,您可以只从一个 .reg 文件开始,您可以手动更新和维护该文件,但如果您有多个对象,则需要自动执行此操作。这也可以通过 DllRegisterServer 调用来管理,这样当您执行 regsvr32 时,它会负责注册密钥。另一方面,您现在正在用注册码污染您的代码库。有些人选择使用安装程序来写入注册表项。

希望对您有所帮助!

问题是由于 运行 来自 VB6(IDE 或编译的 .exe 文件)时程序集绑定解析失败。

解决步骤如下:

  • 编译VB工程,假设编译后的文件为Project1.exe.
  • 将所有 .NET 程序集(包括 x86 和 x64 目录,如果本地化版本很重要,还包括语言目录)复制到已编译的 VB6 文件
  • 旁边
  • 现在运行Project1.exe,你会得到这样的错误:

错误显然是 Project1.exe 文件之外的程序集版本与引用程序集的版本不匹配(不是您自己创建的引用,而是嵌入在这些程序集中的引用...)。当您启动 .NET 程序时,您看不到这一点,因为解析是一个非常复杂的过程,它取决于很多参数(并且它在 .NET Core、Framework、Standard、nugets 等方面没有任何改善)。

要进一步检查是否存在不匹配错误,您还可以使用 SDK 中的 Fuslogvw.exe (Assembly Binding Log Viewer) 工具。

现在我们知道这是程序集版本不匹配的问题,您可以做的是在 Project1.exe 旁边创建一个名为 Project1.exe.config 的文件,然后向其中添加 assembly binding redirects

配置它的最简单方法是将所有可能的版本重定向到包含您的程序的目录中的版本,因此在您的情况下(以及截至今天,所有这些都可以发展......),它可能是这样的,可能对于您直接或间接引用的 每个 程序集:

<configuration>
  <runtime>
    <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
      ...
      <dependentAssembly>
        <assemblyIdentity name="Microsoft.Extensions.DependencyInjection.Abstractions" publicKeyToken="adb9793829ddae60" />
        <!-- 3.1.2.0 is the version of the assembly you ship -->
        <bindingRedirect oldVersion="0.0.0.0-65535.65535.65535.65535" newVersion="3.1.2.0" />
      </dependentAssembly>
      ...
    </assemblyBinding>
  </runtime>
</configuration>

不幸的是,有很多附属程序集,用正确的信息创建所有重定向有点乏味,所以我创建了一个工具来创建一个 .config 文件,其中自动为所有 .给定目录中的 NET 程序集:https://github.com/smourier/BindingRedirectGenerator.

如果您希望它也适用于 VB6 IDE,则必须在 VB6.exe.config 文件中使用相同的过程 VB6.exe

给自己的提醒

  • 使用演示 UI 访问原始 DLL 以确认调用有效。 (如果你不能让它工作,请跳到使用 BindingRedirectGenerator 为单元测试项目制作 App.Config)

  • 在com可见项目中添加单元测试以确认 调用有效。

  • 将两个项目创建的所有dll复制到 发布文件夹

对于每个 com 可见的 dll 运行 作为管理员

c:\windows\microsoft.net\framework\v4.0.30319\regasm /verbose /codebase /tlb:MyLibrary.tlb c:\myproject\releasedlls\MyLibrary.dll
  • 将 BindingRedirectGenerator 安装到 c:\brg say

  • 在命令提示符下将目录更改为 c:\brg

      BindingRedirectGenerator c:\myproject\releasedlls App.config
    
  • 将 App.config 重命名为 MyVB6Project.exe.config 并将其复制到与 MyVB6Project.exe

    相同的文件夹中

如果要在 vb6 运行 ide

中,请记住为 vb6.exe 文件夹设置文件

把整个过程写成脚本方便以后使用(我用的是.bat)

关注 nuget 中的内容 app.config

注意构建时的黄色警告!