2

我一直在谷歌上转来转去,我可以找到各种讨论,很多建议,但似乎没有任何效果。我有一个 ActiveX 组件,它将图像作为字节数组。当我进行 TLB 导入时,它带有以下签名:

int HandleImage([MarshalAs(UnmanagedType.Struct)] ref object Bitmap);

我如何将 byte[] 传递给它?

还有另一个函数可以返回具有类似签名的数据,它之所以有效,是因为我可以传入“null”。返回的类型是字节[1..size](非零有界字节[])。但即使我尝试传入返回的内容,它仍然会出现类型不匹配异常。


更多细节:

我一直在编辑 IDispatch 接口签名中的方法(使用 ILSpy 从自动生成的互操作程序集中提取接口)。我几乎尝试了以下每种组合,它总是得到类型不匹配异常:

  1. 添加和删​​除“参考”
  2. 将参数数据类型更改为“byte[]”或“Array”
  3. 编组为 [MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_UI1)]。在玩了一段时间 MarshalAs 之后,我开始确信 IDispatch 不使用这些属性。

还尝试按原样使用“ref object”接口,并传递不同的类型:byte[], Array.CreateInstance(typeof(byte) (我认为它们是相同的,但我发现有人建议它,所以它不能伤害尝试)。

下面是一个 Delphi 代码示例,它创建了一个适当的数组来传递:

var
   image: OLEVariant;
   buf: Pointer;

image := VarArrayCreate([0, Stream.Size], VarByte);
Buf  := VarArrayLock(image);
Stream.ReadBuffer(Buf^, Stream.Size);
VarArrayUnlock(image);

这是执行相同操作的 C 代码。我想如果我不能让它在 C# 中工作,我可以通过托管 C++ 调用它,尽管我宁愿将所有东西都放在一个项目中:

long HandleImage(unsigned char* Bitmap, int Length)
{
    VARIANT vBitmap;
    VariantInit (&vBitmap);
    VariantClear(&vBitmap);

    SAFEARRAYBOUND bounds[1];
    bounds[0].cElements = Length;
    bounds[0].lLbound = 1;

    SAFEARRAY* arr = SafeArrayCreate(VT_UI1, 1, bounds);
    SafeArrayLock(arr);
    memcpy(arr->pvData, Bitmap, Length);
    SafeArrayUnlock(arr);
    vBitmap.parray = arr;
    vBitmap.vt = VT_ARRAY | VT_UI1;

    long result;
    static BYTE parms[] = VTS_PVARIANT;
    InvokeHelper(0x5e, DISPATCH_METHOD, VT_I4, (void*)&result, parms,
        &vBitmap);

    SafeArrayDestroy(arr);
    VariantClear(&vBitmap);

    return result;
}
4

1 回答 1

0

我终于想出了如何用 100% C# 代码做到这一点。显然,微软从未考虑过有人可能会使用带有此签名的方法来传递数据,因为它正确地编组到另一个方向(它正确地以字节 [] 的形式返回)。

此外,ICustomMarshaler 不会在 IDispatch 调用中被调用,它永远不会命中自定义封送处理程序中的断点(获取它的实例的静态方法除外)。

Hans Passant 在这个问题中的回答让我走上了正轨: Calling a member of IDispatch COM interface from C#

那里的 IDispatch 副本不包含 IUnknown 上的“Invoke”方法,但可以使用 System.Runtime.InteropServices.ComTypes 中的类型酌情将其添加到接口:http: //msdn.microsoft.com/en -us/library/windows/desktop/ms221479%28v=vs.85%29.aspx

这意味着您可以 100% 控制编组参数。Microsoft 没有公开 VARIANT 结构的实现,因此您必须定义自己的:http: //limbioliong.wordpress.com/2011/09/19/defining-a-variant-structure-in-managed-code-第2部分/

Invoke 的输入参数是一个变体数组,因此您必须将它们编组到一个非托管数组,并且有一个变体输出参数。

那么现在我们有了一个变体,它应该包含什么?这就是自动编组失败的地方。它需要一个指向另一个变体的指针,而不是直接嵌入指向 SAFEARRAY 的指针,并且该变体应该指向 SAFEARRAY。

您可以通过 P/Invoking 这些方法构建 SAFEARRAY:http: //msdn.microsoft.com/en-us/library/windows/desktop/ms221145%28v=vs.85%29.aspx

所以主要变体应该是 VT_VARIANT | VT_BYREF,它应该指向另一个变种 VT_UI8 | VT_ARRAY,它应该指向通过 SafeArrayCreate() 生成的 SAFEARRAY。然后应该将最外层的变体复制到内存块中,并将其 IntPtr 发送到 Invoke 方法。

于 2013-08-22T22:59:56.467 回答