为什么在编组之后,数组中只有一个元素包含值

本文关键字:有一个 元素 包含值 数组 之后 为什么 | 更新日期: 2023-09-27 17:50:53

我正在从我的c#代码调用c++函数。我正在使用编组,但是当从c++代码返回时,在我的c#代码中只有一个元素填充了这个数组。

我的c++结构体:

typedef struct DEV_SUB_STATE_ITEM_s
{
char err_text[NAME_MAX_LENGTH];
uint32_t state;
char obj_name[NAME_MAX_LENGTH];
char name[NAME_MAX_LENGTH];
} DEV_SUB_STATE_ITEM_t;

我的c#结构:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DEVICE_Sub_State_Item
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public String err_text;
public UInt32 state;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public String obj_name;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
public String name;
}

我的c++函数原型:

int COMSpClient::GetSubSlotList (UINT32 obj_rid, DEV_SUB_STATE_ITEM_t** subSlotItems);

我的c#函数原型:

[DllImport(@"xxx_OMSpClient.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "?OMSpClient_GetSubSlotList@@YAHPAXHPAPAUDEV_SUB_STATE_ITEM_s@@@Z", CharSet = CharSet.Auto)]
public static unsafe extern Int32 GetSubSlotList(Int32 p_hHandle, UInt32 obj_rid,[MarshalAs(UnmanagedType.LPArray)] ref DEVICE_Sub_State_Item[] sub_slot_items);

在c#中的用法:

OMSpClientWrapper.DEVICE_Sub_State_Item[] sub_slots = new      OMSpClientWrapper.DEVICE_Sub_State_Item[5];
// TODO : load subordinate list!!!
OMSpClientWrapper.GetSubSlotList(this.omsp_client_handle, MyDevice.DeviceRID, ref sub_slots);

为什么在编组之后,数组中只有一个元素包含值

这是一个有点笨拙的封送函数。非托管代码分配数组并将指向该数组的指针返回给调用方。因此在签名中使用双指针。您不能使用p/invoke自动封送。

您将需要使用IntPtr,作为out参数传递,然后自己执行其余的封送。

[DllImport(...)]
public static extern int GetSubSlotList(
    IntPtr p_hHandle, 
    uint obj_rid, 
    out IntPtr sub_slot_items
);

此时,sub_slot_items指向数组的第一个元素。然后,您需要使用Marshal.PtrToStructure来读出每一项,并在读取过程中增加点。

并且您可能需要回调非托管代码以要求它释放内存。

当然,这很乱。如果您可以控制接口,那么更好的设计是让调用者分配数组。代码看起来像这样:

int COMSpClient::GetSubSlotList(
    UINT32 obj_rid, 
    DEV_SUB_STATE_ITEM_t subSlotItems[]
);

你也可能希望传递数组的长度,除非有其他原因使它为双方都知道。

在c#方面,代码将是:

[DllImport(...)]
public static extern int GetSubSlotList(
    IntPtr p_hHandle, 
    uint obj_rid, 
    [Out] DEVICE_Sub_State_Item[] sub_slot_items
);

编组到字符串比它最初看起来要烦人得多。将字符串封送到固定字节缓冲区中可能更容易,然后像这样构造字符串:

public unsafe struct DEVICE_Sub_State_Item
{
    public fixed byte err_text[50];
    public UInt32 state;
    public fixed byte obj_name[50];
    public fixed byte name[50];
    public string ErrorText
    {
        get
        {
            byte[] buffer = new byte[50];
            fixed (byte* b = err_text)
                Marshal.Copy(new IntPtr(b), buffer, 0, buffer.Length);
            return Encoding.UTF8.GetString(buffer);
        }
    }
}

实际的错误文本将作为结构体中的指针保存,只有在调用其ErrorText属性时才能正确读取并转换为字符串。

如果您决定这样做,您将需要在项目的构建选项下启用不安全代码。