【问题标题】:Turn a simple C# DLL into a COM interop component将简单的 C# DLL 转换为 COM 互操作组件
【发布时间】:2011-10-28 21:13:48
【问题描述】:

如何将 C# DLL 制作成可供 VB6 应用程序使用的 COM 互操作 DLL?

【问题讨论】:

    标签: c# .net com interop com-interop


    【解决方案1】:

    COM 服务器互联网上的大多数示例只包含一个 CoClass,并且声称该 CoClass 必须具有公共构造函数。在这种情况下确实如此,但是普通服务器有多个 CoClass,其中只能创建一个,而不可创建 CoClass 的实例是可创建 CoClass 的属性。例如,考虑具有可创建 CoClass Application 的 Word 对象模型,该模型具有 Documents 属性,而该属性又包含 CoClass Document 的实例。以下服务器有两个 CoClass,一个带有公共构造函数,一个带有私有构造函数。

    1. 为 C# 类库 (.Net Framework) 而不是类库 (.Net Standard) 创建解决方案,并将其命名为例如 BankServerCSharp。明智地选择此名称,因为它将是 CoClass 的 ProgID 和 C++ 中的命名空间名称的主要部分。此名称也将列在 C# 和 VBA 的“引用”对话框中。

    2. 删除样板代码并添加两个文件 Bank.cs 和 Account.cs。插入以下代码:

      //Account.cs
      using System.Runtime.InteropServices;
      
      namespace BankServerCSharp
      {
        [ComVisible(true)]  // This is mandatory.
        [InterfaceType(ComInterfaceType.InterfaceIsDual)]
        public interface IAccount
        {
          double Balance { get; } // A property
          void Deposit(double b); // A method
        }
      
        [ComVisible(true)]  // This is mandatory.
        [ClassInterface(ClassInterfaceType.None)]
        public class Account:IAccount
        {
          private  double mBalance = 0;
          private Account() { }     // private constructor, coclass noncreatable
      
          public static Account MakeAccount() { return new Account(); }
          //MakeAccount is not exposed to COM, but can be used by other classes
      
          public double Balance  { get {  return mBalance; } }
          public void Deposit(double b) { mBalance += b; }
        }
      }
      
      //Bank.cs
      using System.Runtime.InteropServices;
      
      namespace BankServerCSharp
      {
        [ComVisible(true)]  // This is mandatory.
        [InterfaceType(ComInterfaceType.InterfaceIsDual)]
        public interface IBank
        {
          string BankName  {  get;  set;  }      // A property
          IAccount FirstAccount { get; }         // Another one of type IDispatch
        }
      
        [ComVisible(true)]  // This is mandatory.
        [ClassInterface(ClassInterfaceType.None)]
        public class Bank:IBank
        {
          private string Name = "";
          private readonly Account First;
      
          public Bank() { First = Account.MakeAccount(); }
      
          public string BankName  {
            get {   return Name; }
            set {   Name= value; }
          }
      
          public IAccount FirstAccount  {
            get { return First; }
          }
        }
      }
      
    3. 使用配置 Release/Any CPU 构建项目。输出是位于 \bin\release 文件夹中的托管 DLL BankServerCSharp.dll。

    4. 现在您必须注册您的托管 COM DLL。不要尝试 regsvr32,有一个名为 regasm 的特殊程序用于托管 COM DLL。 Regasm 有一个适用于 32 位和 64 位应用程序的版本。以管理员身份打开命令提示符并更改为 C:\Windows\Microsoft.NET\Framework\v4.0.30319。此文件夹包含 regasm.exe 应用程序,用于注册托管 COM DLL,就像它是本机 32 位 COM DLL 一样。

    5. 键入RegAsm.exe /tlb /codebase path_to_your_bin_release_folder\BankServerCSharp.dll您必须以这种方式在任何计算机上注册您的 DLL。不要忘记创建类型库的 /tlb 开关。编译器将注释开关 /codebase 并带有一些您可以忽略的警告。该 DLL 在注册表的 WoW64 部分中注册,可由本机(非托管)32 位应用程序使用。

    6. 现在重复注册以供 64 位应用程序使用托管 COM DLL。更改为 C:\Windows\Microsoft.NET\Framework64\v4.0.30319 并输入与之前相同的命令。

    7. 您可以通过管理权限运行 Visual Studio 并添加以下构建后事件来加快在您自己的 PC 上的注册:

      %SystemRoot%\Microsoft.NET\Framework\v4.0.30319\RegAsm.exe /tlb /codebase "$(TargetPath)"
      %SystemRoot%\Microsoft.NET\Framework64\v4.0.30319\RegAsm.exe /tlb /codebase "$(TargetPath)"
      

    您现在可以像使用本机非托管 COM DLL 一样使用您的 DLL。使用 VBA 测试您的 DLL:在 Tools/References 下勾选 BankServerCSharp。如果未显示,则注册失败。一个简单的测试子:

    Sub TestSOExampleNew()
     On Error GoTo Oops
    
       Dim BiBiBaBa As New BankServerCSharp.Bank 'New!
       BiBiBaBa.BankName = "Big Bird Bad Bank"
       Dim Account As BankServerCSharp.Account   'No New!
       Set Account = BiBiBaBa.FirstAccount
       Account.Deposit 2000
       MsgBox BiBiBaBa.BankName & ". First client's balance: " & Account.Balance
    
       Exit Sub
    
     Oops:
       MsgBox "Sorry, an unexpected error occurred!"
    End Sub
    

    要在 C++ 中测试您的托管 COM DLL,请创建一个新的控制台应用程序,插入以下代码并构建为 Release/x64 或 Release/x86:

    #include "stdafx.h"
    #import "D:\Aktuell\CSharpProjects\BankServerCSharp\BankServerCSharp\bin\Release\BankServerCSharp.tlb"
    //this is the path of my C# project's bin\Release folder
    
    inline void TESTHR(HRESULT x) { if FAILED(x) _com_issue_error(x); };
    
    int main()
    {
      try
      {
        TESTHR(CoInitialize(0));
        BankServerCSharp::IBankPtr BankPtr = nullptr;
        TESTHR(BankPtr.CreateInstance("BankServerCSharp.Bank"));
        BankPtr->BankName = L"Ernie First Global Bank";
        BankServerCSharp::IAccountPtr AccountPtr = BankPtr->FirstAccount;
        TESTHR(AccountPtr->Deposit(200.09));
        wprintf(L"Name: %s, Balance: %.2f\n", (LPCWSTR)BankPtr->BankName, AccountPtr->Balance);
      }
      catch (const _com_error& e)
      {
        CStringW out;
        out.Format(L"Exception occurred. HR = %lx, error = %s", e.Error(), e.ErrorMessage());
        MessageBoxW(NULL, out, L"Error", MB_OK);
      }
    
      CoUninitialize();// Uninitialize COM
      return 0;
    }
    

    【讨论】:

      【解决方案2】:

      作为@Kieren Johnstone's answer 的扩展,一个关于你需要做的类修改的实用代码示例:

      发件人:

      public class ApiCaller 
      {
      
          public DellAsset GetDellAsset(string serviceTag, string apiKey)
          {
           ....
          }
      }
      
      public class DellAsset
      {
          public string CountryLookupCode { get; set; }
          public string CustomerNumber { get; set; }
          public bool IsDuplicate { get; set; }
          public string ItemClassCode { get; set; }
          public string LocalChannel { get; set; }
          public string MachineDescription { get; set; }
          public string OrderNumber { get; set; }
          public string ParentServiceTag { get; set; }
          public string ServiceTag { get; set; }
          public string ShipDate { get; set; }
      
      }
      

      收件人:

      [Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83F")]
      [ComVisible(true)]
      public interface IComClassApiCaller
      {
          IComClassDellAsset GetDellAsset(string serviceTag, string apiKey);
      
      }
      
      [Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA71"),
      InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
      [ComVisible(true)]
      public interface IComClassApiCallerEvents
      {
      }
      
      [Guid("0D53A3E8-E51A-49C7-944E-E72A2064F938"),
          ClassInterface(ClassInterfaceType.None),
          ComSourceInterfaces(typeof(IComClassApiCallerEvents))]
      [ComVisible(true)]
      [ProgId("ProgId.ApiCaller")]
      public class ApiCaller : IComClassApiCaller {
      
          public IComClassDellAsset GetDellAsset(string serviceTag, string apiKey)
          {
              .....
          }
      }
      
      
      [Guid("EAA4976A-45C3-4BC5-BC0B-E474F4C3C83E")]
      [ComVisible(true)]
      public interface IComClassDellAsset
      {
           string CountryLookupCode { get; set; }
           string CustomerNumber { get; set; }
           bool IsDuplicate { get; set; }
           string ItemClassCode { get; set; }
           string LocalChannel { get; set; }
           string MachineDescription { get; set; }
           string OrderNumber { get; set; }
           string ParentServiceTag { get; set; }
           string ServiceTag { get; set; }
           string ShipDate { get; set; }
      
      }
      
      [Guid("7BD20046-DF8C-44A6-8F6B-687FAA26FA70"),
      InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
      [ComVisible(true)]
      public interface IComClassDellAssetEvents
      {
      }
      
      [Guid("0D53A3E8-E51A-49C7-944E-E72A2064F937"),
          ClassInterface(ClassInterfaceType.None),
          ComSourceInterfaces(typeof(IComClassDellAssetEvents))]
      [ComVisible(true)]
      [ProgId("ProgId.DellAsset")]
      public class DellAsset : IComClassDellAsset
      {
          public string CountryLookupCode { get; set; }
          public string CustomerNumber { get; set; }
          public bool IsDuplicate { get; set; }
          public string ItemClassCode { get; set; }
          public string LocalChannel { get; set; }
          public string MachineDescription { get; set; }
          public string OrderNumber { get; set; }
          public string ParentServiceTag { get; set; }
          public string ServiceTag { get; set; }
          public string ShipDate { get; set; }
      
      }
      

      希望这可以为您节省一些时间

      【讨论】:

      【解决方案3】:

      这是我想在 StackOverflow 中找到但找不到的答案。事实证明,将简单的 C# dll 转换为 COM dll 相当容易。

      创建 C# dll

      使用 C# 类项目创建解决方案。该类应该有一个属性/方法的接口和一个事件的接口。如MSDN - Example COM Class (C# Programming Guide) 中所述,将 GUID 属性分配给类和接口。另请参阅:MSDN - How to: Raise Events Handled by a COM Sink

      在项目属性 > 应用程序选项卡 > 程序集信息按钮 > 选中“使程序集 COM-Visible”。这使得 COM 类中的所有公共方法都可见。

      在项目属性 > 构建选项卡 > 将“平台目标”设置为 x86。

      这就是创建 DLL 所需要做的一切。调用DLL需要先注册。

      在您的开发机器上注册 DLL

      您可以通过以下方式之一注册 DLL:

      • 检查项目属性 > 构建选项卡 > “注册 COM 互操作”。这将在您构建 DLL 时自动注册它。
      • 使用 RegAsm 手动注册 DLL。这允许您在您选择的目录中注册 DLL,而不是在构建目录中。这是我使用的方法。

        • 不要检查项目属性>构建选项卡>“注册COM互操作”
        • 将 DLL 复制到要注册的目录中
        • 以管理员权限打开命令外壳并键入

          RegAsm.exe -tlb -codebase mydll.dll
          

          RegAsm.exe 可以在“C:\Windows\Microsoft.NET\Framework\v2.0.50727”中找到,而“mydll.dll”是您的 DLL 的名称; tlb 表示“创建类型库”; codebase 表示“将目录位置写入注册表,假设它没有放在 GAC 中”。

          RegAsm 将显示程序集应该是强命名的警告。你可以忽略它。

          此时,您应该可以在 VB6 中添加对 COM DLL 的引用,使用 Intellisense 查看它,并像普通 COM DLL 一样运行它。

      使用 InstallShield 安装 DLL

      如果您使用 InstallShield 将 DLL 与应用程序的其余部分一起安装,请执行以下操作。

      在 InstallShield 中,将新组件添加到组件列表中。请记住将组件与功能相关联。 将组件属性“.NET COM Interop”设置为是。

      将 .dll 文件添加到组件的文件部分。 不要检查“自助注册”属性。 右键单击 .dll 文件并选择“设置密钥文件”。

      将 .tlb 文件添加到组件的文件部分。 检查“Self-Register”属性。

      目标 PC 上需要存在正确版本的 .Net Framework。

      就是这样。

      【讨论】:

      • 我使用的是 InstallSheild 11.5,但没有看到“.NET COM Interop”选项。 11.5 附带的帮助文件确实说它应该在那里,所以我不知道我是在看错误的视图还是什么。您能否更新您的答案以包括该属性的设置位置?
      • 使用 InstallShield 2011,在组织/组件树中有一个 .NET 设置部分,其中包括 .NET COM 互操作设置。
      • 你的回答真的很有帮助。
      • 当我尝试在 VB6 IDE 中引用 DLL 时收到错误:“无法添加对指定文件的引用”。我应该引用 DLL 还是 TLB 文件?
      • 很棒的答案,我错过了-tlb -codebase 上的部分。我为此苦恼了几个小时,直到我找到了这个。谢谢!
      【解决方案4】:

      微软有一个快速的方法here。但是,您需要注册 dll。要使您的 C# dll 更像一个简单的 C dll(C# 非托管导出),请使用 this 方法,该方法将得到详细描述 here

      【讨论】:

        猜你喜欢
        • 2012-07-19
        • 1970-01-01
        • 2022-01-22
        • 1970-01-01
        • 2011-07-27
        • 2010-12-14
        • 2013-12-16
        • 1970-01-01
        • 2010-11-20
        相关资源
        最近更新 更多