从64位.NET程序中使用32位COM服务器

5

我在使用COM互操作时遇到了一些问题,情况如下:

一个32位的COM Exe服务器(用C++编程)提供了一个类和一些成员函数,处理第三方硬件(由于制造商不支持64位,这个硬件也将COM Exe服务器与32位绑定在一起)。

我想在64位的.NET(C#)应用程序中使用32位的COM Exe服务器...起初,我尝试在Visual Studio 2010中添加对Exe服务器的引用,它创建了一个Interop-DLL。这个Interop-DLL为我提供了必要的函数,其中一个被声明为:

int Initialize(ref string callingApplicationPath);

在C++中,原始声明如下:

LONG Class::Initialize(BSTR* callingApplicationPath)

在IDL中,代码应该像这样:

[id(1)] LONG Initialize([in] BSTR* callingApplicationPath);

然而,当我想通过Interop-DLL从C#调用此函数时,它会抛出BadImageFormatException异常。看起来Interop-DLL是一个32位DLL(也许有可能生成一个64位DLL?)。
我的下一步尝试是使用以下代码实例化Exe Server:
Type type = Type.GetTypeFromProgID("OurCompany.Class");
Object o = Activator.CreateInstance(type);
Object[] args = { Marshal.StringToBSTR(str) };
Object result = type.InvokeMember("Initialize", BindingFlags.InvokeMethod, null, o, args);

另一方面,这段代码会向我抛出一个TargetInvocationException异常(更具体地说:0x80020005(DISP_E_TYPEMISMATCH))。不幸的是,我无法从C#中找出需要传递给函数的类型......我尝试了Marshal类中的所有StringToXXX函数,但似乎没有任何作用 :/ 我想我可能错过了一些简单的东西,但我看不到是什么。
非常感谢您的帮助!
最好的祝福
克里斯蒂安

你尝试过启动进程监视器并查看实例化完成时发生了什么吗?也许它找不到某些注册表项,或者某些进程权限不足?进程监视器可以帮助解决这个问题。 - sharptooth
我明白了。将BSTR*作为“in”参数传递的意义是什么?为什么不直接使用BSTR? - sharptooth
我不确定是否可以省略 "in",但在IDL中看到这样的声明非常奇怪。通常的方法是只传递BSTR - 然后 string 会自动转换为它。 - sharptooth
@Christian:好的,但是将BSTR*用作“in”参数看起来真的很奇怪。 - sharptooth
@sharptooth:好的,我会与我的同事核实一下,看看我们是否可以将IDL声明更改为[id(1)] LONG Initialize(BSTR callingApplicationPath)。感谢您的帮助! - Christian
显示剩余4条评论
3个回答

1

默认情况下,.NET字符串通过COM互操作被编组到C++中的LPTSTR。因此,您必须使用MarshalAs属性显式地编组任何其他类型的非托管字符串(包括BSTR)到.NET字符串,并从.NET字符串中编组。


尝试一下

 int Initialize([MarshalAs(UnmanagedType.BStr)] ref string callingApplicationPath);

谢谢您的回答!但是我该如何将这段代码整合到我的C#程序中呢?在我问题的最后一个代码示例中,我没有声明任何函数签名,而是使用了InvokeMember。我问题的第一行代码是从自动生成的Interop.DLL中获取的。 - Christian
我会手动更改自动生成的Interop.DLL。 - weismat
我使用.NET Reflector检查了生成的Interop-DLL,并发现它使用了您提供的签名。但是,通过这个DLL调用COM Exe Server时,我是在进程内调用它的,而这会失败,因为我的C#应用程序是64位的,而Interop-DLL + COM Exe Server都是32位的。这就是为什么我尝试通过InvokeMember以进程外方式调用函数,但不幸的是没有成功 :/ - Christian
你唯一可以尝试的就是检查32位机器上每个结构体的大小。我曾经处理过32位和64位之间的问题,发现使用sizeof比较每个位时出现了大多数问题。我还会将int更改为int32等。 - weismat
感谢您的帮助,尽管它实际上并没有解决问题 ;) 仍然给您点赞,因为您的答案让我有了更深入的见解。 - Christian

1

IDL声明

[id(1)] LONG Initialize([in] BSTR* str);    

没有意义。当您将BSTR作为in参数传递时,只需按“按值”传递即可:

[id(1)] LONG Initialize([in] BSTR str);

那么在 C# 代码中,您将不需要执行任何特殊操作 - 只需传递 string,就会自动执行封送。

当然,您还需要更改方法实现签名。


0
由于.NET使用的公共语言运行时,只有少数情况需要使用托管代码区分32位和64位。但是这仅适用于.NET环境。如果您尝试访问非托管资源,则位格式很重要,因为所有地址(导出接口)都相当静态且未编译为64位。

仍然可以使用相当简单的构造来实现您的任务;创建一个32位.NET包装器,并通过WCF连接到您的64位应用程序。我建议创建一个混合模式C ++包装器到您的COM /非托管服务器,并将基于“纯”CLR(C#,VB.NET等)编写的WCF层放置为连接点到您的主应用程序。

谢谢你的回答,但我非常希望将层数尽可能地降低。由于Sharptooth的帮助,我成功地解决了最初的问题 :) - Christian
没事了,我不知道你也能访问未托管的代码部分,因为你提到了第三方硬件 ;) - Jaster

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