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

发布于 2024-11-30 04:22:01 字数 48 浏览 0 评论 0原文

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

How do I make a C# DLL into a COM interop DLL that can be consumed by a VB6 application?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

北渚 2024-12-07 04:22:01

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

创建 C# dll

使用 C# 类项目创建解决方案。该类应该有一个属性/方法的接口和一个事件的接口。将 GUID 属性分配给类和接口,如 MSDN - 示例 COM 类(C# 编程)中所述指南)。另请参阅:MSDN - 如何:引发已处理的事件通过 COM 接收器

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

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

这就是创建 DLL 所需要做的全部工作。要调用DLL,您需要注册它。

在开发计算机上注册 DLL

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

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

    • 请勿检查项目属性 >构建选项卡> “注册 COM 互操作”
    • 将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 中,将新组件添加到组件列表中。请记住将组件与功能相关联。
将组件属性“.NET COM Interop”设置为“是”。

将 .dll 文件添加到组件的“文件”部分。
不要选中“Self-Register”属性。
右键单击 .dll 文件并选择“设置密钥文件”。

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

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

就是这样。

This is the answer I wanted to find in StackOverflow but couldn't. It turns out to be fairly easy to turn a simple C# dll into a COM dll.

To create the C# dll

Create a solution with a C# class project. The class should have an interface for the properties/methods and an interface for the events. Assign GUID attributes to the class and interfaces as described in MSDN - Example COM Class (C# Programming Guide). Also see: MSDN - How to: Raise Events Handled by a COM Sink.

In Project Properties > Application tab > Assembly Information button > check "Make assembly COM-Visible". This makes all public methods in the class COM visible.

In Project Properties > Build tab > Set "Platform target" to x86.

That's all you need to do to create the DLL. To call the DLL, you need to register it.

Registering the DLL on your development machine

You can register the DLL one of these ways:

  • Check Project Properties > Build Tab > "Register for COM Interop". This will automatically register the DLL when you build it.
  • Manually register the DLL with RegAsm. This allows you to register the DLL in the directory of your choice, rather than in the build directory. This is the method I used.

    • Do not check Project Properties > Build Tab > "Register for COM Interop"
    • Copy the DLL to the directory where you want to register it
    • Open a command shell with administrator rights and type

      RegAsm.exe -tlb -codebase mydll.dll
      

      RegAsm.exe can be found in "C:\Windows\Microsoft.NET\Framework\v2.0.50727", while "mydll.dll" is the name of your DLL; tlb means "create a type library";
      codebase means "write the directory location to the Registry, assuming it is not being placed in the GAC".

      RegAsm will display a warning that the assembly should be strong-named. You can ignore it.

      At this point, you should be able to add a reference to the COM DLL in VB6, see it with Intellisense, and run it just like a regular COM DLL.

Installing the DLL with InstallShield

If you are using InstallShield to install the DLL along with the rest of your application, do the following.

In InstallShield, add a new Component to the Components list. Remember to associate the Component with a Feature.
Set component property ".NET COM Interop" to Yes.

Add the .dll file to the Files section of the Component.
Do not check the "Self-Register" property.
Right-click on the .dll file and select "Set Key File".

Add the .tlb file to the Files section of the Component.
Check the "Self-Register" property.

The correct version of the .Net Framework needs to exist on the target PC.

That's it.

总攻大人 2024-12-07 04:22:01

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

}

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

As en extension to @Kieren Johnstone's answer a practical code example on class modifications you need to do:

From:

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

}

To:

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

}

Hope this saves you some time

执手闯天涯 2024-12-07 04:22:01

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

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

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

    //Account.cs
    使用 System.Runtime.InteropServices;
    
    命名空间 BankServerCSharp
    {
      [ComVisible(true)] // 这是强制性的。
      [接口类型(ComInterfaceType.InterfaceIsDual)]
      公共接口 IAccount
      {
        双平衡{得到; } // 一个属性
        无效存款(双b); // 一个方法
      }
    
      [ComVisible(true)] // 这是强制性的。
      [类接口(类接口类型.无)]
      公开课账号:IAccount
      {
        私人双 mBalance = 0;
        private Account() { } // 私有构造函数,组件类不可创建
    
        公共静态帐户MakeAccount() { 返回新帐户(); }
        //MakeAccount不暴露给COM,但可以被其他类使用
    
        公共双余额{获取{返回mBalance; } }
        public void Deposit(double b) { mBalance += b; }
      }
    }
    
    //银行.cs
    使用 System.Runtime.InteropServices;
    
    命名空间 BankServerCSharp
    {
      [ComVisible(true)] // 这是强制性的。
      [接口类型(ComInterfaceType.InterfaceIsDual)]
      公共接口 IBank
      {
        字符串银行名称 { get;放; } // 一个属性
        IAccount 第一个帐户 { 获取; } // 另一种 IDispatch 类型
      }
    
      [ComVisible(true)] // 这是强制性的。
      [类接口(类接口类型.无)]
      公开课银行:IBank
      {
        私有字符串名称=“”;
        私人只读帐户优先;
    
        公共银行() { First = Account.MakeAccount(); }
    
        公共字符串银行名称{
          获取{返回名称; }
          设置{名称=值; }
        }
    
        公共 IAccount 第一个帐户 {
          获取{首先返回; }
        }
      }
    }
    
  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;
}

Most examples in the internet of COM servers contain only one CoClass, and it is claimed that this CoClass must have a public constructor. This is true in this case, but normal servers have more than one CoClass, of which only one can be created, whereas the instances of the non-creatable CoClasses are properties of the creatable CoClass. For example, consider the Word object model with the creatable CoClass Application that has the Documents property which in turn consists of instances of the CoClass Document. The following server has two CoClasses, one with a public constructor and one with a private constructor.

  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)"
    

You can now use your DLL like a native unmanaged COM DLL. Test your DLL with VBA: Under Tools/References tick BankServerCSharp. If it is not shown, the registration failed. A simple test sub:

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

To test your managed COM DLL in C++, create a new Console Application, insert the following code and build as Release/x64 or 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文