发生的事情是,微软已经从.NET Core(所有版本)的COM Callable wrapper实现中移除了对ITypeInfo的支持,该对象也实现了IDispatch.https://github.com/dotnet/runtime/issues/86751这里简要讨论了这一点,实际上在文档中也提到了:
与GitHub的讨论似乎暗示的相反,这是一个巨大的突破性变化,会带来很多麻烦,很难诊断,也没有容易的解决方案.
当您try 按对象上的名称(如myadd
)调用任何成员时,dynamic
代码使用IDispatch::GetTypeInfo检索ITypeInfo
引用:
检索对象的类型信息,然后可以使用该信息
获取接口的类型信息.
HRESULT GetTypeInfo(
UINT iTInfo,
LCID LCID,
ITypeInfo**ppTInfo
);
现在,让问题更加复杂的是,您的调用代码是.NET框架,并且两者的dynamic
实现是不同的:
.NET框架(即使类型信息计数=0也引发):
internal static ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch, bool throwIfMissingExpectedTypeInfo)
{
int errorCode = dispatch.TryGetTypeInfoCount(out var pctinfo);
Marshal.ThrowExceptionForHR(errorCode); // noooooooo !
if (pctinfo == 0)
return null;
...
}
这就是最终导致System.Runtime.InteropServices.COMException: 'Typelib export: Type library is not registered. (Exception from HRESULT: 0x80131165)'
错误的原因
.NET核心(更新,不那么愚蠢):
internal static ComTypes.ITypeInfo GetITypeInfoFromIDispatch(IDispatch dispatch)
{
int hresult = dispatch.TryGetTypeInfoCount(out uint typeCount);
if (typeCount == 0)
{
// COM objects should return a type count of 0 to indicate that type info is not exposed.
// Some COM objects may return a non-success HRESULT when type info is not supported, so
// we only check the count and not the HRESULT in this case.
return null;
}
Marshal.ThrowExceptionForHR(hresult); // yeeeeeees !
...
}
有多种解决方案:
IDispatch based:如果您想继续使用dynamic
,您可以使用.NET Core客户端或使用"旧的"反射方式,如下所示(不使用ITypeInfo
,直接转到IDispatch
GetIDsOfNames
和Invoke
):
var sum = obj.GetType().InvokeMember(
"myadd",
BindingFlags.Public | BindingFlags.InvokeMethod,
null,
obj,
new object[] { 5, 6 });
IUnknown based:由于您已经定义了双ICalculator
接口(IDispatch
+IUnknown
派生),您可以使用它来代替使用后期绑定(IDispatch
)方法.
为此,系统必须知道该接口的详细信息,以便能够跨线程/进程/机器进行编组,即:它需要一个.TLB(还请确保您坚持使用already known data types,否则您将需要一个.NET无法访问的代理/存根.dll).因此,您必须1)从.NET代码导出.TLB
(类型库文件,您也可以将其嵌入.dll或.exe中),2)使用例如dscom注册它,这是一个非官方工具,可以很好地使用此示例代码(Microsoft还删除了.NET Framework regasm
中存在的.TLB生成和注册功能).确保你在x64中构建了所有东西,并在x64中使用dscom(而不是x86),类似于这样:
dscom.exe tlbexport c:\temp\core dll\bin\Debug\net6.0\Library1.dll
dscom.exe tlbregister Library1.tlb
然后像这样更改您的调用.NET Framework代码:
var calc = (ICalculator)Marshal.GetActiveObject("Library1.Calculator");
var res = calc.myadd(5, 6);
TextBox1.Text = res.ToString();
PS:还有一个更复杂的解决方案,那就是在.NET核心宿主端的对象上公开一个包装器,而不是对象本身,它将重新实现IDispatch
,但不会在.NET框架调用IDispatch::GetTypeInfo
和内部调用对象的IDispatch
实现时报告任何错误