【问题标题】:PInvoke, pointers and array copyPInvoke、指针和数组复制
【发布时间】:2011-02-04 08:05:26
【问题描述】:

我们正在 c#、.net 4.0、Win7 x64 上构建一个应用程序,目标是 x32。

我们在应用程序中使用了第 3 方库。我们知道这个库是用 C++ 编写的。但是,为了让 c# 开发人员使用这个库,他们使用 P/Invoke 对其进行了包装,这就是我们调用 API 函数的方式。

其中一个 API 调用如下:

ReadFromDevice(int deviceAddress, int numBytes, Byte[] data);

此函数从外部设备读取 numBytes 的数据并将其放在 data[] 中。如您所见,它希望看到一个 C# 字节数组作为第三个参数。现在,我们的问题是,我们想将数据读取到预先声明的数组中的任意位置。例如:

Byte[] myData = new Byte[1024*1024*16];
ReadFromDevice(0x100, 20000, &myData[350]) // Obviously not possible in C#

如果我们使用 C/C++,这将是微不足道的。鉴于底层 API 是用 C++ 编写的,我觉得我们应该也可以在 c# 中执行此操作,但是我不知道如何在 c# 中执行此操作。也许我们可以不通过提供的 P/Invoke 接口调用底层库并编写自定义接口?

任何想法都将不胜感激。

问候,

【问题讨论】:

    标签: c# .net pointers pinvoke bytearray


    【解决方案1】:

    虽然这里的其他答案很接近,但没有一个是完全完整的。

    首先,您只需声明自己的 p/invoke 声明。这就是 p/invoke 的美妙之处;从来没有一种方法可以做到这一点。

    [DllImport("whatever.dll")]
    unsafe extern static void ReadFromDevice(int deviceAddress, int numBytes, byte* data);
    

    现在你可以调用它了

    unsafe static void ReadFromDevice(int deviceAddress, byte[] data, int offset, int numBytes)
    {
        fixed (byte* p = data)
        {
            ReadFromDevice(deviceAddress, numBytes, p + offset);
        }
    }
    

    【讨论】:

      【解决方案2】:

      您可以使用指针,但需要在检查不安全模式的情况下构建。

      Byte[] myData = new Byte[1024*1024*16];
      fixed( Byte * pB = &myData[350])
      {
         ReadFromDevice(0x100, 20000,pB  ) 
      } 
      

      但首先你需要将导出的方法签名更改为。

      ReadFromDevice(int deviceAddress, int numBytes, Byte * data);
      

      【讨论】:

      • 没有从byte*byte[] 的转换,这段代码甚至无法编译。如果他能够控制 C# 源代码,那将是一回事,但听起来不像他从原始问题中所做的那样。
      • 也就是说,再次阅读问题后,确实听起来 OP 也可以访问 C++ 库,因此您应该简单地建议他添加自己的 DllImport 并更改接受byte*而不是byte[]的签名,那么这将起作用。删除反对票。
      • " 我们正在 c#、.net 4.0、Win7 x64 上构建一个应用程序,目标是 x32。我们在我们的应用程序中使用了第 3 方库。我们知道这个库是用 C++ 编写的。但是,为了让 c# 开发人员使用这个库,他们使用 P/Invoke 对其进行了包装,这就是我们调用 API 函数的方式。”所以他们可以控制 C# 并且正在为 c++ 库编写一个包装器!
      【解决方案3】:

      您仍然可以在 c# 中执行指针运算。

      如果您重新声明您的 dll 导入(针对 C++ 库)以使用 IntPtr 传递字节数组,则可以使用 Add 方法将其增加 350(这实际上返回一个新的 intptr)

      这里有一个类似的例子:http://msdn.microsoft.com/en-us/library/system.intptr.add.aspx#Y811

      【讨论】:

        【解决方案4】:

        就这么简单:

        ReadFromDevice(int deviceAddress, int numBytes, ref byte data);

        ...

        
        Byte[] myData = new Byte[1024*1024*16];
        ReadFromDevice(0x100, 20000, ref myData[350]) // Obviously possible in C#
        

        【讨论】:

        • 但是您更改了 ReadFromDevice() 的声明。我没有那个代码;来自 3rd 方库。
        • 如果是 .net,您始终拥有代码 xD。这看起来像一个 dllimport 只需打开反射器并将 2 行复制到您的应用程序中。
        • 第三方库提供了 C++ 编译文件(显然不能更改)和 C# 包装类。请参阅我的答案以获得明显的解决方案。
        • 我没有关注。这是反射器的输出:public int ReadFromDevice(int deviceAddress, int numBytes, byte[] data) { return libDevicePINVOKE.ReadFromDevice(this.handle, deviceAddress, numbytes, data); } 然后呢?
        • 然后读出句柄(如果没有其他方法使用反射)并反编译libDevicePINVOKE.ReadFromDevice
        【解决方案5】:

        这个问题的解决方案似乎很明显。

        虽然您确实可以简单地创建自己的包装类和 DllImport 并编写自己的包装类,但解决方案比这更容易。

        ReadFromDevice(int deviceAddress, int numBytes, Byte[] data); 
        

        在您发布数据的上下文中是一个输出参数。所以解决方案是向它发送一个适当大小的空字节数组,然后将填充的数组放入现有数组中。

        您所要做的就是确定您事先知道的返回数组的大小,然后填充和替换。

        我会直截了当地告诉您,您使用现有数组的想法可能只有在您发送数组中特定元素的地址时才有效(这当然是可能的),但您仅限于 C++ 库的实际内容期望这可能只是 Byte 数组本身的地址。

        【讨论】:

        • 关键是要避免这些复制操作,因为它们的成本很高(就时间而言。此应用程序处理超过 30MB/秒的数据)。如果我们按照您描述的方式进行操作,那么对于我们从外部设备读取的每个数据,第 3 方 API 都会将数据复制到小数组(ReadFromDevice 调用中的第三个参数),然后,我们将不得不这样做另一个复制操作将小数组中的数据写入我们的大数组。因此,每从外部设备读取 30MB 的数据,就会有 60MB 的数据移动。出于性能原因,我们希望写入一次数据。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-04-18
        • 2015-08-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-17
        相关资源
        最近更新 更多