用 C# 编写的 COM 对象 - 获取类,但不获取方法

Posted

技术标签:

【中文标题】用 C# 编写的 COM 对象 - 获取类,但不获取方法【英文标题】:COM object written in C# - Get class, but not methods 【发布时间】:2015-03-19 00:04:26 【问题描述】:

我用 C# 编写了一个简单的 COM 对象,只有一个方法,称为 GetMac。我无法让它工作。我正在尝试从旧版 Borland C++ Builder 4 (BCB4) 应用程序访问它,我知道它很旧,并且不再使用太多,但我可以从中访问其他 COM 对象。

Borland 开发机器运行的是 Windows XP,所以我将 C# COM 对象以 .NET 4.0 框架为目标。我将 DLL 和 PDB 文件从 C# Visual Studio 机器复制到 XP 机器。我通过以下命令注册了它:

"%WINDIR%\Microsoft.NET\Framework\v4.0.30319\regasm.exe" TRSDotNetCOM.dll /tlb /nologo /codebase

我可以通过以下代码行很好地实例化 COM 对象(类):

Variant TDN = CreateOleObject("TRSDotNetCOM.TRSCOM_Class");

如果我更改名称字符串,它不起作用,所以我知道这部分是正确的。

但是,当我尝试按如下方式调用该方法时:

MacV = TDN.OleFunction(funcNameV,counterV,macKeyV);

...我得到一个运行时异常(不幸的是,BCB4 对 OLE 调用的异常处理存在问题,因此调试器给我的唯一信息是“发生异常”)。

由于我能够以相同的方式从同一个 BCB4 应用程序调用其他 COM 对象,我认为问题不在于我的 C++ 代码。我认为这是 C# 创建的 COM DLL 或其注册的问题。

为了探索这一点,我使用 Microsoft OLE/COM 对象查看器来浏览我的系统以查找 OLE 对象。正如预期的那样,我能够找到我的对象为“TRSDotNetCOM.TRSCOM_Class”。

我是使用 OLE/COM 对象查看器的新手,所以我希望我看到的是正确的东西:

当我展开课程时,我看到以下内容:

我右键单击 _Object 并选择“查看”,然后选择“查看类型信息”。然后,右侧窗格显示:

[   uuid(65074F7F-63C0-304E-AF0A-D51741CB4A8D),   hidden,   dual,   nonextensible,
    custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "System.Object")

] dispinterface _Object 
    properties:
    methods:
        [id(00000000), propget,
            custom(54FC8F55-38DE-4703-9C4E-250351302B1C, "1")]
        BSTR ToString();
        [id(0x60020001)]
        VARIANT_BOOL Equals([in] VARIANT obj);
        [id(0x60020002)]
        long GetHashCode();
        [id(0x60020003)]
        _Type* GetType(); ;

当我展开左侧的树时,我看到的是:

我没有在其中的任何地方看到我的方法“GetMac”。所以,我认为该方法对 COM 不可见,或者它没有通过 regasm 注册。

这是 COM 对象的来源:

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace TRSDotNetCOM

    [Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f")]
    public interface TRSCOM_Interface
    
        [DispId(1)]
        string GetMac(string counter, string macKey);
    

    // Events interface Database_COMObjectEvents 
    [Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface TRSCOM_Events
    
    


    [Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
    ClassInterface(ClassInterfaceType.None),
    ComVisible(true),
    ComSourceInterfaces(typeof(TRSCOM_Events))]
    public class TRSCOM_Class : TRSCOM_Interface
    
        public TRSCOM_Class()
        
        

        [ComVisible(true)]
        public string GetMac(string counter, string macKey)
        
            // convert counter to bytes
            var counterBytes = Encoding.UTF8.GetBytes(counter);
            // import AES 128 MAC_KEY
            byte[] macKeyBytes = Convert.FromBase64String(macKey);
            var hmac = new HMACSHA256(macKeyBytes);
            var macBytes = hmac.ComputeHash(counterBytes);
            var retval = Convert.ToBase64String(macBytes);
            return retval;
        

    

我确实确定并进入项目属性并选中“注册 COM 互操作”复选框。我还使用“sn”实用程序生成了一个安全名称文件,并将该文件加载到设置的“签名”部分。

所以...

1) 我是否在 OLE/COM 对象查看器中为我的方法寻找正确的位置?

2) 如果是这样,为什么我的方法不可见或未注册?

3) 有什么想法可能是错误的吗?

更新:这是 Joe W 和 Paulo 建议的更新代码。 (但它仍然不起作用)

using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;

namespace TRSDotNetCOM

    [Guid("80ef9acd-3a75-4fcd-b841-11199d827e8f"),
    ComVisible(true)]
    public interface TRSCOM_Interface
    
        [DispId(1)]
        string GetMac(string counter, string macKey);
    

    // Events interface Database_COMObjectEvents 
    [Guid("67bd8422-9641-4675-acda-3dfc3c911a07"),
    ComImport,
    ComVisible(true),
    InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
    public interface TRSCOM_Events
    
    


    [Guid("854dee72-83a7-4902-ab50-5c7a73a7e17d"),
    ClassInterface(ClassInterfaceType.None),
    ComDefaultInterface(typeof(TRSCOM_Interface)),
    ComVisible(true),
    ComSourceInterfaces(typeof(TRSCOM_Events))]
    public class TRSCOM_Class : TRSCOM_Interface
    
        public TRSCOM_Class()
        
        


        public string GetMac(string counter, string macKey)
        
            // convert counter to bytes
            var counterBytes = Encoding.UTF8.GetBytes(counter);
            // import AES 128 MAC_KEY
            byte[] macKeyBytes = Convert.FromBase64String(macKey);
            var hmac = new HMACSHA256(macKeyBytes);
            var macBytes = hmac.ComputeHash(counterBytes);
            var retval = Convert.ToBase64String(macBytes);
            return retval;
        

    

【问题讨论】:

【参考方案1】:

你只漏掉了一些。

将您的接口声明为ComVisible

[ComVisible(true)]
public interface TRSCOM_Interface

如果您的程序集默认情况下已经是 COM 可见的(您可以在项目的属性中或通常在 AssemblyInfo.cs 中进行检查),您不需要这样做,但它没有害处,它会保留接口可用于regasm.exetlbexp.exe,以防您恢复此配置。

将事件接口声明为ComImport

[ComImport]
public interface TRSCOM_Events

我的猜测是这个接口是在你的 C# 项目之外定义的,可能是由 BCB4 应用程序或其模块之一。

如果我的猜测是错误的,并且您的 C# 项目是定义此接口的项目,那么[ComVisible(true)]

如果这个接口有事件方法,那么你implement then as events in the class。

最后,为避免为您的类导出另一个接口,您可能需要添加ClassInterface 属性:

[ClassInterface(ClassInterfaceType.None)]
public class TRSCOM_Class : TRSCOM_Interface

这样,您就告诉TRSCOM_Interface 是您的类默认接口,因为它是您实现的第一个接口,而regasm.exe /tlb 不会生成类接口。

根据实现接口的顺序不放心,所以也可以使用ComDefaultInterface属性:

[ClassInterface(ClassInterfaceType.None)]
[ComDefaultInterface(typeof(TRSCOM_Interface))]
public class TRSCOM_Class : TRSCOM_Interface

现在,您可以在已实现的接口列表中拥有任何顺序,而无需担心更改默认类接口。

【讨论】:

Paulo,我的班级已经有了 [ClassInterface(ClassInterfaceType.None)] 属性。我的班级没有任何事件,这就是 TRSCOM_Interface 声明为空的原因。除了我发布的来源之外,它没有在其他任何地方定义。无论如何,我继续添加您的其他建议(请参阅我在问题中添加的更新源)。但是,它仍然不起作用。我仍然遇到异常,并且在 OLE/COM 对象浏览器中仍然看不到我的 GetMac 方法。 我复制粘贴了你当前的代码,用csc /target:library test.cs编译,然后我运行tlbexp test.dll。在生成的 test.tlb 上使用 OleView,有一个 GetMac 方法需要 2 个 in BSTR 和 1 个 out BSTR。你能在这些步骤之后复制粘贴 OleView 的输出吗? 保罗,谢谢。我刚刚意识到我在 OleView 中寻找错误的位置。当我应该进入 TRSCOM_Interface 时,我正进入 _Object。当我进入那个时,我确实看到了我的 GetMac 方法。但是,仍然无法在 BCB4 中工作。 我认为您的问题中缺少某些内容。 TRSCOM_Interface 接口上有[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 属性吗?这可以解释为什么 BCB4 找不到该方法,.NET 的 IDispatch::GetIDsOfNames 会说 GetMac 是未知成员。您是在使用这个SSCCE,还是在使用自己的更大的库进行调整和测试?也许您的示例不是复制您的问题的确切 SSCCE。 Paulo,是的,我发布的源代码正是我想要开始工作的源代码。我刚刚尝试将 [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] 属性添加到 TRSCOM_Interface 声明,结果是现在,我对 Variant TDN = CreateOleObject("TRSDotNetCOM.TRSCOM_Class"); 的 C++ 调用也失败了,说“无效的类字符串”。【参考方案2】:

这是我第一次看到声明为 ComVisible 的方法。我会放弃它,而是声明 TRSCOM_Interface 接口 ComVisible。

【讨论】:

Joe W. - 好的,我刚刚尝试了你的建议。我从函数中删除了 ComVisible 并将其添加到接口声明中。我重建了 DLL,复制了它,重新注册了它,但没有任何区别。我仍然遇到异常,并且在 OLE/COM 对象查看器程序中仍然看不到任何关于我的方法的信息。 我会编写一个简单的 VB 脚本来测试它,并将 Borland 的东西排除在外。也许VB脚本错误会有更好的诊断...... Joe W,感谢您的建议。我认为这是一个很棒的下一步。但是,我不知道VB。我是否有机会获得执行此操作的说明,以及用于实例化我的对象并调用我的方法的等效 VB 代码? 不要使用 VB,而是使用 VB 脚本。创建一个 .vbs 文件: set obj = CreateObject("TRSDotNetCOM.TRSCOM_Class") MsgBox TypeName(obj) res = obj.GetMac("abc", "123") MsgBox res 你可以用 wscript.exe 或 cscript.exe 运行它这将在您的 Windows 路径上。看起来您使用 32 位 .Net 运行时注册了您的组件。因此,如果您使用的是 64 位机器,则必须调用 c:\windows\SysWOW64\cscript.exe 来运行它。 乔 W,谢谢。这是结果。第一个 MsgBox 显示“TRSCOM_Class”。然后我在下一行得到一个错误:“ComTest.vbs(3, 1) Microsoft VBScript runtime error: Object required: 'obj'”

以上是关于用 C# 编写的 COM 对象 - 获取类,但不获取方法的主要内容,如果未能解决你的问题,请参考以下文章

用C#做autocad二次开发的时候,autocad图形对象类是不是要自己编写?

从 C# 中的 C++ 代码中获取对象功能

C#高阶-反射

尝试使用 COM-interop 从 C# 库中附加 vb6 中的对象时,获取“无法将 'Field' 转换为 'Field' 类型(同一类)”

用c#实现编写esp32单片机获取DHT11温度传感器参数

C# 比较两个对象的属性值是不是有改变