在 c# 中使用 c lib - 包含指向数组第一个元素的指针的结构

using c lib in c# - struct containing pointer to first element of array

我必须在 C# 中使用 C 库。这是给我带来麻烦的部分。具有结构定义的 C 函数:

extern "C"__declspec(dllexport)unsigned short _stdcall read(unsigned short orderid, struct read_rb * request_ptr);

struct read_rb
{
   // in
   unsigned long C_Ref;
   unsigned char Slot_Number;
   unsigned char Index;
   // out
   unsigned char Length_s;
   unsigned char * Data_s;
   struct error _error;
};

所以 unsigned char * Data_s 是一个指向包含输出数据的数组的指针。我的 C# 代码如下:

[DllImport("dpc2lib.dll")]
private static extern ushort read(ushort orderid, [In, Out] read_rb request_ptr);

[StructLayout(LayoutKind.Sequential, Pack=1)]
public struct read_rb
{
    // in
    public uint C_Ref;
    public byte Slot_Number;
    public byte Index;
    // out
    public byte Length_s;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public byte[] Data_s;
    public error _error;
}

我在我的程序中是这样调用的

public float readData(byte slot_Number, byte index, byte data_Length)
{
    read_rb Read_rb = new read_rb();
    byte[] _dataReceived = new byte[5];
    Read_rb.Data_s = _dataReceived;
    int result = read(0, Read_rb);
}

根本行不通。当调用 read() 函数时抛出 AccessViolationEsception。消息是:Attempt to read or write Protected Memory. This is often an indicating that other memory is corrupt.

我尝试了各种方法,但我真的不知道如何处理... 感谢您的帮助!

首先你应该仔细研究C库的头文件(我没有dpc2lib的C头文件)。请注意,我的回答基于找到的 SDK 文档 here.

read_rb 结构是否真的定义了字节边界上的数据对齐(您已经设置了 Pack 成员 StructLayout 属性为 1)。如果不是,你应该定义 read_rb 不设置 Pack 成员的结构:

[StructLayout(LayoutKind.Sequential)]
struct read_rb
{
  public uint C_Ref; // in
  public byte Slot_Number; // in
  public byte Index; // in
  public byte Length_s; // inout
  public IntPtr Data_s; // out
  public error error;
}

[StructLayout(LayoutKind.Sequential)]
struct error
{
... your error struct members
}

通过省略 Pack 成员,使用默认值 0,这意味着 包装对齐设置为当前平台的默认值。 此外,您应该将 read_rb 结构的 Data_s 成员定义为 IntPtr.

定义read()函数如下:

[DllImport("dpc2lib.dll", CallingConvention=CallingConvention.StdCall)]
private static extern ushort read(ushort orderid, ref read_rb request_ptr);

ref 参数告诉 CLR 在两个方向(到本机代码和返回到 托管代码)。使用 StdCall 作为调用约定,因为 _stdcall 定义在 读取函数的头文件。

然后,使用read_rb结构和read()函数如下:

const ushort DPC2_DATA_LEN_S = ..; // See SDK documentation and header file
read_rb readRb = new read_rb();

readRb.C_Ref = ..; // Set identifier for connection.
readRb.Slot_Number = ..; // Set required slot on destination device.
readRb.Index = ..; // Set the index parameter.

// Set the length field to at least DPC2_DATA_LEN_S
// See SDK documentation for more information.
readRb.Length_s = DPC2_DATA_LEN_S; 

try
{
  // Allocate memory for data pointer.
  readRb.Data_s = Marshal.AllocHGlobal(DPC2_DATA_LEN_S);

  // Call the read function
  ushort result = read(ref readRb);

  // Check return value here.
  if (result != ../*DPC2_OK*/)
  {
    // Handle error case
  }
  else
  {
     // Use Marshal.Copy to copy the received 
     // data to a byte buffer.
     byte[] buffer = new byte[DPC2_DATA_LEN_S];
     Marshal.Copy(readRb.Data_s, buffer, 0, buffer.Length);

     // Do something with data ...
  }
}
finally
{
  // Finally, release the allocated memory.
  if(readRb.Data_s !+ IntPtr.Zero)
  {
    Marshal.FreeHGlobal(readRb.Data_s);
  }
}

借助 Marshal.Copy 函数,您可以复制 接收到的数据到托管字节数组。