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

时间:2011-08-17 12:12:10

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

如何将C#DLL转换为可由VB6应用程序使用的COM互操作DLL?

5 个答案:

答案 0 :(得分:81)

这是我想在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

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

在项目属性&gt;构建标签&gt;将“平台目标”设置为x86。

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

在开发计算机上注册DLL

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

  • 检查项目属性&gt;构建标签&gt; “注册COM Interop”。这将在您构建DLL时自动注册DLL。
  • 使用RegAsm手动注册DLL。这允许您在您选择的目录中注册DLL,而不是在构建目录中。这是我使用的方法。

    • 检查项目属性&gt;构建标签&gt; “注册COM Interop”
    • 将DLL复制到要注册的目录
    • 打开具有管理员权限并键入

      的命令shell
      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中,将新组件添加到“组件”列表中。请记住将Component与Feature关联。 将组件属性“.NET COM Interop”设置为Yes。

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

将.tlb文件添加到Component的Files部分。 检查“自注册”属性。

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

就是这样。

答案 1 :(得分:4)

作为@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; }

}

希望这能节省你一些时间

答案 2 :(得分:4)

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

  1. 为C#类库(.Net Framework),而不是类库(.Net Standard)创建解决方案,并将其命名为BankServerCSharp。明智地选择此名称,因为它将成为CoClasses的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. 使用配置“发行版/任何CPU”构建项目。输出是位于\ bin \ release文件夹中的托管DLL BankServerCSharp.dll。

  4. 现在,您必须注册托管的COM DLL。不要尝试regsvr32,对于托管的COM DLL,有一个名为regasm的特殊程序。 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开关。编译器将用一些您可以忽略的警告注释开关/代码库。该DLL在注册表的WoW64部分中注册,并且可以由本机(非托管)32位应用程序使用。

  6. 现在,重复注册64位应用程序对托管COM DLL的使用。更改为C:\ Windows \ Microsoft.NET \ Framework 64 \ 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:在“工具/参考”下,勾选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;
}

答案 3 :(得分:1)

Microsoft快速了解here。但是,您需要注册dll。要使您的C#dll更像一个简单的C dll(C#Unmanaged Exports),请使用this方法,详细描述here

答案 4 :(得分:0)

COM Interop是.NET Framework公共语言运行时(CLR)中包含的一项技术,它使组件对象模型(COM)对象能够与.NET对象进行交互,反之亦然。 COM Interop旨在提供对现有COM组件的访问,而无需修改原始组件,反之亦然。 更多信息: http://www.writeulearn.com/com-interop-using-csharp/