用 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.exe
和tlbexp.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图形对象类是不是要自己编写?
尝试使用 COM-interop 从 C# 库中附加 vb6 中的对象时,获取“无法将 'Field' 转换为 'Field' 类型(同一类)”