将 object/struct 中的 array/list 从 C# 库传递到 C++ MFC 应用程序

Passing array/list of object/struct from C# Library to C++ MFC application

我试图从 C++ MFC 应用程序调用的 C# 库 (.net 4.5) 传递一个对象或结构数组,但我无法获得正确的数据。另一个困难是 C++ 应用程序不知道对象计数。

我已经成功传递了简单类型 (int, string) 以及具有以下代码的结构(使用 UnmanagedExports 1.2.7):

C#

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode, Pack = 4)]
public struct ItemA
{
    // Size: 4
    public int Id;
    // Size: 100
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 50)]
    public string Name;
    // Size: 2(4)
    public bool IsEnabled;
}

[DllExport(CallingConvention = CallingConvention.Cdecl)]
static public IntPtr GetItemA(int itemId)
{
    // Check structure size
    Debug.Assert(Marshal.SizeOf(typeof(ItemA)) == 108);

    // Get ItemA from a WS
    var itemA = client.GetItemsA().First(i => i.Id == itemId);

    // Convert ItemA to structure pointer
    IntPtr buf = Marshal.AllocHGlobal(Marshal.SizeOf(itemA));
    Marshal.StructureToPtr(itemA, buf, false);
    return buf;
}

C++

#pragma pack(4)
typedef struct
{
    // Size: 4
    int Id;
    // Size: 100
    TCHAR Name[50];
    // Size: 2(4)
    bool IsEnabled;
} ItemA;

extern "C"
{
    ItemA* GetItemA(int itemId);
}

// (...)

void CMyAppDlg::OnBnClickedButtonGetItemA()
{
    // Check structure size
    static_assert(sizeof ItemA == 108, "Size does not match C# struct size");
    ItemA* structItemA = GetItemA(1);    
}

我到处搜索,但没有找到针对我的特定问题的任何功能性响应。您能否帮助为 GetItemAList 编写 C# 和 C++ 代码,它可以 return 结构数组或其他类型的 C++ 应用程序?

提前致谢!

Edit1 : 我更改了代码以解决 packing/size 结构问题(64 位编译)。

Edit2 : 我用#pragma pack 代替手动对齐。

Edit3 : 我总是坚持使用 GetItemAList。这是我在调用 c# 后出现 MemoryException 的实际 c#/c++ 代码。你能帮我解决这个问题吗**?提前致谢。

C#

[DllExport(CallingConvention = CallingConvention.Cdecl)]
static public void GetItemAList([In, Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.Struct, SizeParamIndex = 1)] ref ItemA[] myArray, ref int length)
{
    // Get ItemAList from a WS
    var itemAArray = client.GetItemsAList().ToArray();

    // Set myArray values
    length = itemAArray.Length;
    for (int i = 0; i < length; i++)
    {
         myArray[i] = itemAArray[i];
    }
}

C++

extern "C"
{
    void GetItemAList(ItemA*& myArray, int& length);
}

// (...)

void CMyAppDlg::OnBnClickedButtonGetItemA()
{
    ItemA* myArray = new ItemA[100];
    int length = 100;
    GetItemAList(myArray, length);   
}

我没有做任何检查,但你的 C++ 版本可能是用 4 字节打包编译的(假设你是用 32 位编译的)。

在这种情况下,您将在 C++ 端:

// Field,   Field size, Total size
// -------------------------------
// Id,               0,          4
// Name,            50,         54
// IsEnabled,        1,         55
// (padding)         1,         56

在那一边,您应该添加一个 static_assert 验证您是否获得了预期的大小。

static_check(sizeof ItemA == 56, "Size does not match C# struct size")

在 C# 方面,您将拥有:

// Field,   Field size, Total size
// -------------------------------
// Id,               0,          4
// Name,            50,         54
// IsEnabled,        4,         58
// (no padding)      0,         58

如果 bool 是 1 个字节,那么您的大小将改为 55。在这两种情况下,它都不会匹配 C++ 端的大小。

由于了解所有规则并且不犯任何错误以及确保在未来的维护中没有任何问题有点困难,您应该始终在代码中添加验证。

在 C# 端,典型的验证类似于:Debug.Assert(Marshal.Sizeof(typeof(ItemA)) == 58.

您还可以验证某些字段的偏移量或大小,但通常如果您手动计算的大小在两边都匹配,那么如果您使用 1 字节打包,您可能做对了。