c# - 将结构数组从 C++ 编码到 C#?

标签 c# .net c++ interop marshalling

在我的 C# 代码中,我试图从遗留 C++ DLL(我无法更改的代码)中获取结构数组。

在该 C++ 代码中,结构定义如下:

struct MyStruct
{
    char* id;
    char* description;
};

我正在调用的方法 (get_my_structures) 返回指向 MyStruct 结构数组的指针:

MyStruct* get_my_structures()
{
    ...
}

还有另一种返回结构数量的方法,所以我知道返回了多少结构。

在我的 C# 代码中,我这样定义了 MyStruct:

[StructLayout(LayoutKind.Sequential)]  
public class MyStruct
{
  [MarshalAsAttribute(UnmanagedType.LPStr)]    // <-- also tried without this
  private string _id;
  [MarshalAsAttribute(UnmanagedType.LPStr)]
  private string _description;
}

互操作签名如下所示:

[DllImport("legacy.dll", EntryPoint="get_my_structures")]
public static extern IntPtr GetMyStructures();

最后,获取 MyStruct 结构数组的代码如下所示:

int structuresCount = ...;
IntPtr myStructs = GetMyStructures();
int structSize = Marshal.SizeOf(typeof(MyStruct));    // <- returns 8 in my case
for (int i = 0; i < structuresCount; i++)
{
    IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i);
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct));
    ...
}

问题是,只有第一个结构(偏移量为零的结构)被正确编码。后续的在 _id 和 _description 成员中有虚假值。这些值并没有完全被丢弃,或者看起来是这样:它们是来自其他内存位置的字符串。代码本身不会崩溃。

我已验证 get_my_structures() 中的 C++ 代码确实返回了正确的数据。数据不会在通话期间或通话后被意外删除或修改。

在调试器中查看,返回数据的 C++ 内存布局如下所示:

0: id (char*)           <---- [MyStruct 1]
4: description (char*)
8: id (char*)           <---- [MyStruct 2]
12: description (char*)
16: id (char*)          <---- [MyStruct 3]
...

[2009 年 11 月 18 日更新]

这是 C++ 代码准备这些结构的方式(实际代码要丑得多,但这是一个足够接近的近似值):

static char buffer[12345] = {0};
MyStruct* myStructs = (MyStruct*) &buffer;
for (int i = 0; i < structuresCount; i++)
{
    MyStruct* ms = <some other permanent address where the struct is>;
    myStructs[i].id = (char*) ms->id;
    myStructs[i].description = (char*) ms->description;
}
return myStructs;

不可否认,上面的代码进行了一些丑陋的转换并复制了原始指针,但它似乎仍然正确地做到了这一点。至少那是我在调试器中看到的:上面的(静态)缓冲区确实包含所有这些一个接一个存储的裸 char* 指针,它们指向内存中的有效(非本地)位置。

Pavel 的示例表明这确实是唯一可能出错的地方。我将尝试分析字符串真正所在的那些“结束”位置发生了什么,而不是指针存储的位置。

最佳答案

我无法重现您的问题,这使我怀疑它确实是 C++ 方面的问题。这是我尝试的完整源代码。

dll.cpp - 用 cl.exe/LD 编译:

extern "C" {

struct MyStruct
{
    char* id;
    char* description;
};

__declspec(dllexport)
MyStruct* __stdcall get_my_structures()
{
    static MyStruct a[] =
    {
        { "id1", "desc1" },
        { "id2", "desc2" },
        { "id3", "desc3" }
    };
    return a;

}

}

test.cs - 使用 csc.exe/platform:x86 编译:

using System;
using System.Runtime.InteropServices;


[StructLayout(LayoutKind.Sequential)]  
public class MyStruct
{
  [MarshalAsAttribute(UnmanagedType.LPStr)]
  public string _id;
  [MarshalAsAttribute(UnmanagedType.LPStr)]
  public string _description;
}


class Program
{
    [DllImport("dll")]
    static extern IntPtr get_my_structures();

    static void Main()
    {
        int structSize = Marshal.SizeOf(typeof(MyStruct));
        Console.WriteLine(structSize);

        IntPtr myStructs = get_my_structures();
        for (int i = 0; i < 3; ++i)
        {
            IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i);
            MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct));

            Console.WriteLine();
            Console.WriteLine(ms._id);
            Console.WriteLine(ms._description);
        }
    }
}

这会正确打印出所有 3 个结构。

您能展示填充结构的 C++ 代码吗?您可以直接从 C++ 调用它并获得正确结果的事实并不一定意味着它是正确的。例如,您可能返回一个指向堆栈分配结构的指针。那么,在进行直接调用时,您会得到一个技术上无效的指针,但数据可能会保留下来。在执行 P/Invoke 编码时,当 P/Invoke 数据结构尝试从那里读取值时,堆栈可能会被 P/Invoke 数据结构覆盖。

关于c# - 将结构数组从 C++ 编码到 C#?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1748169/

相关文章:

c++ - 在 DirectX11 游戏上叠加

c# - BindingFlags.InvokeMethod 是什么意思?

c# - 在 Windows Server 2008 上部署 .Net Web 服务失败

c# - 如何确定新的推力输入将当前坐标移动到指定坐标位置

.net - 我需要编写什么代码来生成此代码?

.net - 我应该在我的版本中分发 log4net 吗?

c# - 打开 Windows 资源管理器到 Combined-GUID 文件夹

c++ - std::bind 参数到没有对象的成员函数

c++ - 为什么信号量被释放但 WaitForSingleObject() 仍然卡住?

c# - 如何使用 C# 配置组策略?