2014-05-01 46 views
6

編譯我的marshalling helloworld question,我遇到了將C中分配的數組編碼爲C#的問題。我花了好幾個小時來研究可能會出錯的地方,但是我試過的所有結果都出現了錯誤,例如AccessViolationException在C#中編組C的數組 - 簡單的HelloWorld

處理在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

回答

2
// 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); 

我發現的bug通過使用調試+的Windows +內存+內存1.將humansPtr在Address字段中,切換到4字節的整數視圖,並觀察到C代碼正確執行。然後很快發現,ptrs []不包含我在Memory窗口中看到的值。

不知道你爲什麼要寫這樣的代碼,除了作爲一種心理鍛鍊。這不是正確的方法,例如,你完全忽略了再次釋放內存的需要。這是非常平凡的。在C#中解析CSV文件非常簡單,與在C中一樣快,它是I/O綁定的,而不是執行綁定的。您將很容易避免這些幾乎不可能調試錯誤,並從.NET Framework獲取lots of help

+0

謝謝!我正在編寫這樣的代碼,因爲我需要它跨平臺運行。 C代碼是一個將在嵌入式和桌面Linux以及Windows上運行的庫。我在Linux上提供了一個CLI,但Windows需要一個GUI,我更喜歡在Windows上爲GUI編寫C#。 我在C#中使用C代碼相當新,所以我不知道所有的最佳實踐。這是我的實際代碼的一個簡單化的例子,我提供了一個函數來釋放C中分配的內存。 –