将简单的C# DLL转换为COM互操作组件

84

如何将一个C# DLL转化为COM互操作DLL,以便VB6应用程序可以访问?

4个回答

105

这是我希望在StackOverflow中找到但没找到的答案。实际上,将一个简单的C# dll转换为COM dll非常容易。

创建C# dll

创建一个带有C#类项目的解决方案。该类应具有属性/方法的接口和事件的接口。按照MSDN - 示例COM类(C#编程指南)中所述,为类和接口分配GUID属性。也可以参见:MSDN - 如何:引发由COM Sink处理的事件

在“项目属性”>“应用程序”选项卡中>单击“程序集信息”按钮>勾选“使程序集COM可见”。这会使类中所有公共方法都变为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"设置为Yes。

      将.dll文件添加到组件的文件部分。不要选中"自注册"属性。右键单击.dll文件,然后选择"设置密钥文件"。

      将.tlb文件添加到组件的文件部分。选中"自注册"属性。

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

      就这样。


你的回答真的很有帮助。 - Nagendra Upwanshi
当我尝试在VB6 IDE中引用DLL时,我收到错误消息:“无法添加对指定文件的引用”。我应该引用DLL还是TLB文件? - Ewerton
非常棒的答案,我错过了-tlb -codebase这部分。我为此苦恼了几个小时,直到找到这个答案。谢谢! - Rahul Kishore
非常好的答案。对我来说关键是“使汇编COM可见”,这是我在其他任何在线教程中都没有看到过的。做得很好。谢谢。 - Darthchai
错误 MSB3213:无法注册类型库 "……\CsComLib.tlb"。访问 OLE 注册表时出错。 - Zhang
显示剩余4条评论

13

作为对@Kieren Johnstone的回答的扩展,这里提供一个关于类修改需要进行的实际代码示例:

源代码:

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

}

希望这能为您节省一些时间


谢谢提供示例!如果您尝试通过CreateObject访问类,您将如何在VB6中调用GetDellAsset - Panzercrisis
@Panzercrisis 你好,请看一下这个链接 https://dev59.com/CHRC5IYBdhLWcg3wS_EF - Matas Vaitkevicius
@Matas Vaitkevicius,您能否提供一些使用事件的资源或示例。因为我无法弄清楚如何处理COM事件。我在这里写了一个问题 https://stackoverflow.com/questions/61060831/create-custom-activex-controls-for-sap-b1 - xurca

8

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

  1. Create a solution for a C# Class Library (.Net Framework), not Class Library (.Net Standard) and name it for example BankServerCSharp. Choose this name wisely, because it will be the main part of the ProgIDs of your CoClasses and the namespace name in C++. This name will also be listed in the References dialog box of C# and VBA.

  2. Delete the boilerplate code and add two files Bank.cs and Account.cs. Insert the following code:

    //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. Build the project with the configuration Release/Any CPU. The output is the managed DLL BankServerCSharp.dll located in the \bin\release folder.

  4. Now you must register your managed COM DLL. Don't try regsvr32, there is a special program called regasm for managed COM DLLs. Regasm has a version for 32-bit and for 64-bit apps. Open a command prompt as administrator and change to C:\Windows\Microsoft.NET\Framework\v4.0.30319. This folder contains the regasm.exe app to register the managed COM DLL as if it would be a native 32-bit COM DLL.

  5. Type RegAsm.exe /tlb /codebase path_to_your_bin_release_folder\BankServerCSharp.dll. You must register your DLL on any computer in this way. Don’t forget the /tlb switch that creates the type library. The compiler will comment the switch /codebase with some warnings that you can ignore. The DLL is registered in the WoW64 part of the registry and can be used by native (unmanaged) 32-bit apps.

  6. Now repeat the registration for usage of the managed COM DLL by 64-bit apps. Change to C:\Windows\Microsoft.NET\Framework64\v4.0.30319 and type the same command as before.

  7. You can speed up the registration on your own PC by running Visual Studio with administrative rights and adding the following post-build events:

    %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:在“工具/参考”下选中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;
}

0

微软公司在这里提供了一个快速的操作指南here。但是,您需要注册dll。要使您的C# dll更像一个简单的C dll(C#未托管导出),请使用this方法,该方法在here中有详细描述。


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