是否需要将数组参数固定到.NET中的COM RCW中?

时间:2014-05-07 05:16:02

标签: c# .net com com-interop

我正在调用IDiaSourceFile::get_checksum成员函数,该函数具有以下自动生成的.NET签名:

void get_checksum(uint cbData, out uint pcbData, byte[] pbData);

请注意,参数pbData是out参数。当有人打电话给get_checksum时,我是否需要担心固定在这里传入的数组?

(背景:

我继承了一些看起来像这样的代码:

unsafe
{
    fixed (byte* p = scratch_hash)
    {
        sourceFile.get_checksum(c, out c, scratch_hash);
    }
}

scratch_hash定位为p,然后实际上从不使用p。我之前没见过这样的东西,并给出了周围代码的状态我怀疑这里的引脚是完全没必要的

2 个答案:

答案 0 :(得分:2)

根据目前的定义:

void get_checksum(uint cbData, out uint pcbData, byte[] pbData);

您无需固定pbData。但是,您需要为返回的数据预先分配一个数组,但事先并不知道大小。如果通过cbData传递的数组大小不够大,并且您当前的方法签名不允许找出缓冲区大小,则该方法将失败。

original C++ declaration确实允许:

HRESULT get_checksum ( 
   DWORD  cbData,
   DWORD* pcbData,
   BYTE   data[]
);
  

data [in,out]填充校验和字节的缓冲区。如果   此参数为NULL,然后pcbData返回字节数   必需的。

因此,更有效的方法可能是声明并使用它:

void get_checksum(
    uint cbData, 
    out uint pcbData, 
    IntPtr data);

// get size
uint size;
obj.get_checksum(0, out size, IntPtr.Zero);

// get data
var buff = new byte[size];
unsafe
{
    fixed (byte* p = buff)
    {
        uint cbData;
        obj.get_checksum(size, out cbData, (IntPtr)p);
        if (size != cbData)
            throw new InvalidOperationException("cbData");
    }
}

如果您不想(或不能)使用unsafe代码,可以选择以下代码:

// get size
uint size;
obj.get_checksum(0, out size, IntPtr.Zero);

// get the data
byte[] buff;
var p = Marshal.AllocHGlobal((int)size);
try
{
    uint cbData;
    obj.get_checksum(size, out cbData, p);
    if (size < cbData)
        throw new InvalidOperationException("cbData");
    buff = new byte[cbData];
    Marshal.Copy(p, buff, 0, (int)cbData);
}
finally
{
    Marshal.FreeHGlobal(p);
}

答案 1 :(得分:0)

不,在这种情况下你不必这样做 - 你甚至不能这样做。

但是,如果遇到性能问题,可能需要更改该签名,因为编组器会将字节数组传递给本机方法做很多工作 - 它必须分配新内存,将托管字节数组复制到那,然后再次释放内存。

这意味着您发布的代码完全是胡说八道,因为虽然它确实修复了scratch_hash数组,但它仍然没有使用该固定指针。

如果你只是偶尔调用一次方法并且字节数组相对较小,那么可以安全地忽略它。但是,如果您确实发现这会对您的应用程序造成不必要的压力,那么修复可能有所帮助 - 但您必须更改签名:

unsafe void get_checksum(uint cbData, out uint pcbData, byte *pbData);

然后你可以使用它:

fixed (byte *p = &scratch_hash[0])
  sourceFile.get_checksum(c, out c, p);

所有这些说明,请注意修复托管对象可能会导致自己的问题(例如,如果您的对象位于堆顶部,它几乎会导致任何堆压缩的可能性)。应该谨慎地进行修复,并且只能进行极短的时间 - 唯一的另一个合理的选择是尽早分配对象并将它们保持在一起(例如,使用异步I / O时,需要一个固定的引用,通常需要很长时间当应用程序运行时 - 所以只需尽早分配缓冲区,你就会很好。)

另外,我仍然觉得签名有点疯狂。你能分享头文件的签名定义吗?