سؤال

Building off of my marshalling helloworld question, I'm running into issues marshalling an array allocated in C to C#. I've spent hours researching where I might be going wrong, but everything I've tried ends up with errors such as AccessViolationException.

The function that handles creating an array in C is below.

__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;
}

The C# code:

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);
}

The first human struct gets marshalled fine. I get the access violation exceptions after the first one. I feel like I'm missing something with marshalling structs with struct pointers inside them. I hope I have some simple mistake I'm overlooking. Do you see anything wrong with this code?

See this GitHub gist for full source.

هل كانت مفيدة؟

المحلول

  // Populate the array of IntPtr

This is where you went wrong. You are getting back a pointer to an array of pointers. You got the first one correct, actually reading the pointer value from the array. But then your for() loop got it wrong, just adding 4 (or 8) to the first pointer value. Instead of reading them from the array. Fix:

    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);
    }

Or much more cleanly since marshaling arrays of simple types is already supported:

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

I found the bug by using the Debug + Windows + Memory + Memory 1. Put humansPtr in the Address field, switched to 4-byte integer view and observed that the C code was doing it correctly. Then quickly found out that ptrs[] did not contain the values I saw in the Memory window.

Not sure why you are writing code like this, other than as a mental exercise. It is not the correct way to go about it, you are for example completely ignoring the need to release the memory again. Which is very nontrivial. Parsing CSV files in C# is quite simple and just as fast as doing it in C, it is I/O bound, not execute-bound. You'll easily avoid these almost impossible to debug bugs and get lots of help from the .NET Framework.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top