通过IDispatch将接口数组从C#COM服务器传递到C++



我正在尝试开发一个客户端只能通过IDispatch访问的C#COM服务器。我的COM对象的某些方法向其他COM对象返回一组接口。

当我尝试使用早期绑定(例如通过这个答案中提供的代码(来实现这一点时,它不会出现任何问题。然而,当我尝试通过IDispatch装饰我的类型以使用后期绑定时,请求失败,出现0x80020005:类型不匹配。其他非接口类型可以通过IDispatch毫无问题地返回,只是COM接口无法正确传输。

给定以下C++客户端

CoInitialize(NULL);
IMyRootClassPtr ptr(__uuidof(MyRootClass));
try
{
auto entities = ptr->GetEntities();
}
catch (_com_error &err)
{
wprintf(L"The server throws the error: %sn", err.ErrorMessage());
wprintf(L"Description: %sn", (PCWSTR)err.Description());
}

以下代码适用于早期绑定

C#:

[ComVisible(true)]
public interface IMyRootClass
{
IEmailEntity[] GetEntities();
}
[ComVisible(true)]
public class MyRootClass : IMyRootClass // some class to start with
{
public IEmailEntity[] GetEntities()
{
List<IEmailEntity> list = new List<IEmailEntity>();
for (int i = 0; i < 10; i++)
{
EmailEntity entity = new EmailEntity();
entity.Body = "hello world " + i;
list.Add(entity);
}
return list.ToArray();
}
}
[ComVisible(true)]
public interface IEmailEntity
{
string Body { get; set; }
}
public class EmailEntity : IEmailEntity
{
public string Body { get; set; }
}

通过进行以下更改

  • [InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]添加到IMyRootClass
  • [ClassInterface(ClassInterfaceType.None)]添加到MyRootClass

我认为这现在应该通过IDispatch工作,但我得到了上面提到的类型不匹配错误。我花了一下午的时间调试错误的确切来源。对.NET Framework的Dispatch调用似乎成功了,返回了VT_ARRAY | VT_DISPATCH(2009(类型的VARIANT,这是意料之中的事,但最终结果的验证似乎在下失败了

main
_com_dispatch_method
_com_invoke_helper
VariantChangeType
VariantChangeTypeEx

我已经看了它在oleaut32.dll中的分解,在这个阶段,我只是得出结论,这一定是Windows中的一个错误。有人可能有其他建议吗?

请记住,IDispatch/late绑定是为特殊客户端(例如:VB/VBA/VBScript/JScript(创建的,从纯C/C++客户端使用它总是很痛苦。

对于原始定义,以下是IMyRootClass的定义方式(您可以在RegAsm生成的.tlb文件上使用Windows SDK中的OleView工具阅读(:

interface IMyRootClass : IDispatch {
[id(0x60020000)]
HRESULT GetEntities([out, retval] SAFEARRAY(IEmailEntity*)* pRetVal);
};

它将在#import之后这样结束,在C/C++标头级别:

IMyRootClass : IDispatch
{
//
// Wrapper methods for error-handling
//
SAFEARRAY * GetEntities ( );
//
// Raw methods provided by interface
//
virtual HRESULT __stdcall raw_GetEntities (
/*[out,retval]*/ SAFEARRAY * * pRetVal ) = 0;
};

其中GetEntities实际上只是围绕IUnknown/早期绑定接口的一个小包装代码:

inline SAFEARRAY * IMyRootClass::GetEntities ( ) {
SAFEARRAY * _result = 0;
HRESULT _hr = raw_GetEntities(&_result);
if (FAILED(_hr)) _com_issue_errorex(_hr, this, __uuidof(this));
return _result;
}

这就是为什么";双";接口很好(不确定为什么您只想要IDispatch(,因为它们允许两个世界都可以轻松访问。

现在,如果您像问题中那样更改定义,则不再定义IMyRootClassCOM接口。相反,您只能获得IDL级别的dispinterface

dispinterface IMyRootClass {
properties:
methods:
[id(0x60020000)]
SAFEARRAY(IEmailEntity*) GetEntities();
};

它将在#import之后这样结束,在C/C++标头级别:

IMyRootClass : IDispatch
{
//
// Wrapper methods for error-handling
//
// Methods:
SAFEARRAY * GetEntities ( );
};

其中GetEntities实际上是一个完全不同的包装代码:

inline SAFEARRAY * IMyRootClass::GetEntities ( ) {
SAFEARRAY * _result = 0;
_com_dispatch_method(this, 0x60020000, DISPATCH_METHOD, VT_ARRAY|VT_DISPATCH, (void*)&_result, NULL);
return _result;
}

正如您在这里看到的,由于我们使用的是IDispatch,所以一切都或多或少地接近VARIANT类型(它是在同一时间发明的,同样是针对这些客户端(,按原样使用或与包装器一起使用。

这就是为什么您看到预期的返回类型是VT_ARRAY|VT_DISPATCH。也可以使用VT_ARRAY|VT_UNKNOWNVT_ARRAY|VT_VARIANT或简单的VT_VARIANT(最终包装器类型(,但无法说出VT_ARRAY of IEmailEntity*

因此,这个问题至少有两种解决方案:

1-你可以这样定义你的界面:

[ComVisible(true), InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface IMyRootClass
{
object[] GetEntities();
}
[ComVisible(true), ClassInterface(ClassInterfaceType.None)]
public class MyRootClass : IMyRootClass
{
public object[] GetEntities()
{
...
// the Cast forces the creation of an array of object => VT_ARRAY | VT_DISPATCH
return list.Cast<object>().ToArray();
}
}   

2-或者您可以使用CCD_ 23接口";手动";(不要使用包装纸(如下:

IMyRootClassPtr ptr(__uuidof(MyRootClass));
CComVariant result;
DISPPARAMS p = {};
ptr->Invoke(0x60020000, IID_NULL, 0, DISPATCH_METHOD, &p, &result, nullptr, nullptr);

在这种情况下,结果将是VT_ARRAY | VT_UNKNOWN(与第一种情况一样(,这就是包装器抛出异常的原因。comdef.h的包装器在其自动化类型支持方面比VB.等客户端更受限制

最新更新