【问题标题】:Marshalling C array in C# - Simple HelloWorld在 C# 中编组 C 数组 - 简单的 HelloWorld
【发布时间】:2014-05-01 15:59:18
【问题描述】:

基于我的marshalling helloworld question,我遇到了将 C 中分配的数组编组到 C# 的问题。我花了几个小时研究我可能出错的地方,但我尝试过的一切都以错误告终,例如AccessViolationException

在 C 中处理创建数组的函数如下。

__declspec(dllexport) int __cdecl import_csv(char *path, struct human ***persons, int *numPersons)
{
    int res;
    FILE *csv;
    char line[1024];
    struct human **humans;

    csv = fopen(path, "r");
    if (csv == NULL) {
        return errno;
    }

    *numPersons = 0; // init to sane value
    /*
     * All I'm trying to do for now is get more than one working.
     * Starting with 2 seems reasonable. My test CSV file only has 2 lines.
     */
    humans = calloc(2, sizeof(struct human *));
    if (humans == NULL)
        return ENOMEM;

    while (fgets(line, 1024, csv)) {
        char *tmp = strdup(line);
        struct human *person;

        humans[*numPersons] = calloc(1, sizeof(*person));
        person = humans[*numPersons]; // easier to work with
        if (person == NULL) {
            return ENOMEM;
        }
        person->contact = calloc(1, sizeof(*(person->contact)));
        if (person->contact == NULL) {
            return ENOMEM;
        }

        res = parse_human(line, person);
        if (res != 0) {
            return res;
        }

        (*numPersons)++;
    }
    (*persons) = humans;

    fclose(csv);

    return 0;
}

C# 代码:

IntPtr humansPtr = IntPtr.Zero;
int numHumans = 0;

HelloLibrary.import_csv(args[0], ref humansPtr, ref numHumans);

HelloLibrary.human[] humans = new HelloLibrary.human[numHumans];
IntPtr[] ptrs = new IntPtr[numHumans];
IntPtr aIndex = (IntPtr)Marshal.PtrToStructure(humansPtr, typeof(IntPtr));

// Populate the array of IntPtr
for (int i = 0; i < numHumans; i++)
{
    ptrs[i] = new IntPtr(aIndex.ToInt64() +
            (Marshal.SizeOf(typeof(IntPtr)) * i));
}

// Marshal the array of human structs
for (int i = 0; i < numHumans; i++)
{
    humans[i] = (HelloLibrary.human)Marshal.PtrToStructure(
        ptrs[i],
        typeof(HelloLibrary.human));
}

// Use the marshalled data
foreach (HelloLibrary.human human in humans)
{
    Console.WriteLine("first:'{0}'", human.first);
    Console.WriteLine("last:'{0}'", human.last);

    HelloLibrary.contact_info contact = (HelloLibrary.contact_info)Marshal.
        PtrToStructure(human.contact, typeof(HelloLibrary.contact_info));

    Console.WriteLine("cell:'{0}'", contact.cell);
    Console.WriteLine("home:'{0}'", contact.home);
}

第一个 human struct 得到很好的编组。我在第一个之后得到了访问冲突异常。我觉得我在编组结构中缺少一些东西,其中包含结构指针。我希望我有一些我忽略的简单错误。你觉得这段代码有什么问题吗?

查看GitHub gist 获取完整源代码。

【问题讨论】:

    标签: c# c arrays pointers marshalling


    【解决方案1】:
      // Populate the array of IntPtr
    

    这就是你出错的地方。您正在获取指向指针数组的指针。你得到了第一个正确的,实际上是从数组中读取指针值。但是后来你的 for() 循环弄错了,只是在第一个指针值上加了 4(或 8)。而不是从数组中读取它们。修复:

        IntPtr[] ptrs = new IntPtr[numHumans];
    
        // Populate the array of IntPtr
        for (int i = 0; i < numHumans; i++)
        {
            ptrs[i] = (IntPtr)Marshal.PtrToStructure(humansPtr, typeof(IntPtr));
            humansPtr = new IntPtr(humansPtr.ToInt64() + IntPtr.Size);
        }
    

    或者更简洁,因为已经支持简单类型的编组数组:

        IntPtr[] ptrs = new IntPtr[numHumans];
        Marshal.Copy(humansPtr, ptrs, 0, numHumans);
    

    我通过使用Debug + Windows + Memory + Memory 1发现了这个错误。将humansPtr放在Address字段中,切换到4字节整数视图并观察C代码正确执行.然后很快发现 ptrs[] 不包含我在 Memory 窗口中看到的值。

    不知道您为什么要编写这样的代码,而不是作为一种心理锻炼。这不是正确的方法,例如,您完全忽略了再次释放内存的需要。这是非常不平凡的。在 C# 中解析 CSV 文件非常简单,并且与在 C 中解析一样快,它是 I/O 绑定的,而不是执行绑定的。您将轻松避免这些几乎不可能调试的错误并从 .NET Framework 中获取 lots of help

    【讨论】:

    • 谢谢!我正在编写这样的代码,因为我需要它跨平台运行。 C 代码是一个可以在嵌入式和桌面 Linux 以及 Windows 上运行的库。我在 Linux 上提供了一个 CLI,但 Windows 需要一个 GUI,我更喜欢在 Windows 上为 GUI 编写 C#。我对在 C# 中使用 C 代码相当陌生,所以我不知道所有的最佳实践。这是我实际代码的一个简单示例,其中我提供了一个函数来释放 C 中分配的内存。
    猜你喜欢
    • 1970-01-01
    • 2012-02-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-20
    • 1970-01-01
    相关资源
    最近更新 更多