在.NET中通过后期绑定实现类型安全的COM组件调用问题
我需要在x64进程中调用x32库,查资料后决定用COM组件实现。我用C#创建了三个项目:
- 编译为X64的WinForms项目
- 编译为AnyCPU的BaseDll项目(仅包含一个接口
BaseInter,里面有someMethod方法) - 编译为x32且勾选“Register for COM interop”的Dll32项目(引用BaseDll并实现
BaseInter接口;仅在此项目中添加了ComVisible和Guid特性)
手动在注册表的Wow6432Node路径下添加了DllSurrogate属性,完成了Dll32组件的注册,确保x64进程能调用x32组件。
WinForms项目中用以下代码调用时可以正常运行:
Type ComType = Type.GetTypeFromCLSID(new Guid("85840403-68F7-4B39-B015-7ED3506FD06B")); dynamic c2 = Activator.CreateInstance(ComType); string dd = c2.someMethod("asd"); MessageBox.Show(dd);
但我希望引用BaseDll后用强类型转换调用:
BaseInter c2 = (BaseInter) Activator.CreateInstance(ComType);
执行这段代码时抛出异常:
System.InvalidCastException: 'Unable to cast COM object of type 'System.__ComObject'
to interface type 'BaseDll.BaseInter'. This operation failed because the
QueryInterface call on the COM component for the interface with IID
'{40E88E31-DFEA-357E-8A97-E5F0077666C2}' failed due to the following error:
No such interface supported (Exception from HRESULT: 0x80004002 (E_NOINTERFACE)).'
当WinForms编译为x32时,强类型转换代码可以正常运行,请问是不是因为x64编译的WinForms里的接口和x32的Dll32项目中的接口不一致导致的?
更新内容
BaseInter代码
public interface BaseInter { string someMethod(String ime); }
Dll32代码
[ComVisible(true)] [Guid("85840403-68F7-4B39-B015-7ED3506FD06B")] public class JaviSe : BaseInter { public string someMethod(string ime) { return "Hi from 32 " + ime; } }
手动添加的注册表项
REGEDIT4 [HKEY_CLASSES_ROOT\Wow6432Node\Dll32.JaviSe] @="Dll32.JaviSe" [HKEY_CLASSES_ROOT\Wow6432Node\Dll32.JaviSe\CLSID] @="{85840403-68F7-4B39-B015-7ED3506FD06B}" [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{85840403-68F7-4B39-B015-7ED3506FD06B}] @="Dll32.JaviSe" "AppID"="{85840403-68F7-4B39-B015-7ED3506FD06B}" [HKEY_CLASSES_ROOT\Wow6432Node\AppID\{85840403-68F7-4B39-B015-7ED3506FD06B}] "DllSurrogate"="" [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{85840403-68F7-4B39-B015-7ED3506FD06B}\InprocServer32] @="mscoree.dll" "ThreadingModel"="Both" "Class"="Dll32.JaviSe" "Assembly"="Dll32, Version=1.0.0.0, Culture=neutral, PublicKeyToken=143740cba1c5c559" "RuntimeVersion"="v4.0.30319" "CodeBase"="file:///C:/Users/default.DESKTOP-VNP7D7O/source/repos/SurogateTests/Dll32/bin/Debug/Dll32.dll" [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{85840403-68F7-4B39-B015-7ED3506FD06B}\InprocServer32\1.0.0.0] "Class"="Dll32.JaviSe" "Assembly"="Dll32, Version=1.0.0.0, Culture=neutral, PublicKeyToken=143740cba1c5c559" "RuntimeVersion"="v4.0.30319" "CodeBase"="file:///C:/Users/default.DESKTOP-VNP7D7O/source/repos/SurogateTests/Dll32/bin/Debug/Dll32.dll" [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{85840403-68F7-4B39-B015-7ED3506FD06B}\ProgId] @="Dll32.JaviSe" [HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{85840403-68F7-4B39-B015-7ED3506FD06B}\Implemented Categories\{62C8FE65-4EBB-45E7-B440-6E39B2CDBF29}]
问题核心是你没有给BaseInter接口指定固定的GUID,且没有标记它为COM可见。
当x64的WinForms项目引用BaseDll时,.NET会自动为BaseInter生成一个IID;而x32的Dll32项目实现该接口时,又会生成另一个不同的IID。跨进程(x64到x32通过DllSurrogate本质是跨进程调用)时,COM要求两边使用完全一致的接口IID才能完成类型转换,这就是强类型转换失败的原因——而dynamic调用通过后期绑定方法,不依赖接口IID匹配,所以能正常运行。
要解决这个问题,需要做以下两步:
为
BaseInter接口添加ComVisible(true)和固定的Guid特性
修改BaseInter的代码:[ComVisible(true)] [Guid("请替换为你自己生成的固定GUID")] public interface BaseInter { string someMethod(String ime); }注意:这个GUID要固定,不能每次编译自动生成。可以用Visual Studio的「工具→创建GUID」工具生成(选择格式5)。
重新注册Dll32组件
先卸载旧的COM注册(执行命令regasm /u Dll32.dll),然后重新生成Dll32项目(确保勾选「Register for COM interop」),或者手动执行regasm /codebase Dll32.dll完成注册。
修改后,x64的WinForms项目和x32的Dll32项目会使用同一个接口IID,跨进程调用时强类型转换就能正常工作了。
另外补充:BaseDll项目最好设置为强签名,避免因程序集标识问题导致的COM调用异常。可以在项目属性的「签名」选项卡中勾选「签署程序集」,创建一个强名称密钥文件。
内容的提问来源于stack exchange,提问作者Bojan Vukasovic

