2009-09-30 37 views
5

sto chiamando una funzione di DLL C e la necessità di fornire le seguenti struct C:Marshalling array di stringhe a char ** in C#

typedef struct 
{ 
    char  *mTableId; 
    char  **mFieldNames; 
    int  mNumFields; 
    char  *mFilter; 
    char  *mSort; 
    int  mOffset; 
    int  mMaxRecords; 
    char  *mTargetRecordFilter; 
    int  mSurroundingRecordsCount; 
    int  *mOwnerIds; 
    int  mNumOwnerIds; 
    gsi_bool mCacheFlag; 
} SAKESearchForRecordsInput; 

Il problema è con char ** mFieldNames; Ho provato a smistamento automatico in questo modo:

[MarshalAs (UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeConst = 9)] [] mFieldNames public String;

In questo modo viene visualizzato un errore in Marshal.SizeOf(): non è possibile calcolare la dimensione corretta. Quindi ho deciso di gestire manualmente i puntatori. In realtà è solo un puntatore alla serie di stringhe C. Ecco il mio codice che sta portando a

System.AccessViolationException: Tentativo di leggere o scrivere memoria protetta. Questo è spesso un'indicazione che un'altra memoria è corrotta.

Quindi ho rovinato i puntatori da qualche parte. Il codice mi sembra OK, dov'è l'errore?

C#:

[StructLayout(LayoutKind.Sequential)] 
unsafe public class SAKESearchForRecordsInput { 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public String mTableId; 
    //[MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPTStr, SizeConst = 9)] // HARDCODED!?! 
    //public String[] mFieldNames;  // char  **mFieldNames; 
    public IntPtr mFieldNames; 
    public int mNumFields; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public String mFilter; 
    [MarshalAs(UnmanagedType.LPTStr)] 
    public String mSort; 
    public int mOffset; 
    public int mMaxRecords; 
    //[MarshalAs(UnmanagedType.LPTStr)] 
    public IntPtr mTargetRecordFilter; 
    public int mSurroundingRecordsCount; 
    public IntPtr mOwnerIds; 
    public int mNumOwnerIds; 
    public gsi_bool mCacheFlag; 
} 

    [DllImport("saketestd.dll")] 
    unsafe static extern void* sakeSearchForRecords(
    IntPtr sake, 
    IntPtr input, //SAKESearchForRecordsInput * 
    SAKERequestCallback callback, //SAKERequestCallback 
    IntPtr userData); 

    unsafe public bool sakeSearchForRecordsE() { 
    bool ret = false; 
    try { 
    searchInput.mTableId = "bbdx_score"; 
    //searchInput.mFieldNames = mFieldNames.to; 
    searchInput.mFilter = "num_ratings = 0 AND filestore > 0"; 
    searchInput.mSort = ""; 
    searchInput.mOffset = 0; 
    searchInput.mMaxRecords = 1; 
    //searchInput.mTargetRecordFilter = ""; 
    searchInput.mSurroundingRecordsCount = 0; 
    searchInput.mOwnerIds = IntPtr.Zero; 
    searchInput.mNumOwnerIds = 0; 
    searchInput.mCacheFlag = true; 

    int sakeSize = Marshal.SizeOf(sake); 
    debug.AddLine(this.getMethodName() + ": sizeof(sake): " + sakeSize); 
    IntPtr pSake = Marshal.AllocHGlobal(sakeSize); 
    Marshal.StructureToPtr(sake, pSake, true); 

    int inputSize = Marshal.SizeOf(searchInput); 
    debug.AddLine(this.getMethodName() + ": sizeof(input): " + inputSize); 
    IntPtr pInput = Marshal.AllocHGlobal(inputSize); 
    Marshal.StructureToPtr(searchInput, pInput, true); 

    IntPtr[] mFieldNamesPtr; 
    int i; 
    if (true) { // IntPtr[] 
    mFieldNamesPtr = new IntPtr[mFieldNames.Length]; 
    i = 0; 
    foreach (string str in mFieldNames) { 
     mFieldNamesPtr[i++] = Marshal.StringToHGlobalAnsi(str); 
    } 
    //searchInput.mFieldNames = mFieldNamesPtr; 
    } else { 
    //searchInput.mFieldNames = mFieldNames; 
    } 
    searchInput.mNumFields = mFieldNames.Length; 

    void* pRequestInternal = null; 
    void* p = mFieldNamesPtr[0].ToPointer(); 
    searchInput.mFieldNames = (IntPtr)p; 
    pRequestInternal = sakeSearchForRecords(
     pSake, 
     pInput, 
     new SAKERequestCallback(this.sakeSearchForRecordsCB), 
     IntPtr.Zero 
    ); 


    sake = (SAKEInternal)Marshal.PtrToStructure(pSake, typeof(SAKEInternal)); 
    if (searchRequest == null) { 
    debug.AddLine(this.getMethodName() + ": mStartRequestResult: " + sake.mStartRequestResult); 
    } else { 
    ret = true; 
    this.searchRequest = (SAKERequestInternal)Marshal.PtrToStructure(
     new IntPtr(pRequestInternal), 
     typeof(SAKERequestInternal) 
    ); 
    searchInput = (SAKESearchForRecordsInput)Marshal.PtrToStructure(
     pInput, 
     typeof(SAKESearchForRecordsInput) 
    ); 

    if (true) { 
     i = 0; 
     foreach (string str in mFieldNames) { 
     Marshal.FreeHGlobal(mFieldNamesPtr[i++]); 
     } 
    } 

    PrintStruct ps = new PrintStruct(sake); 
    debug.AddLine(this.getMethodName() + ": sake: " + ps); 
    ps = new PrintStruct(searchRequest); 
    debug.AddLine(this.getMethodName() + ": searchRequest: " + ps.print_r()); 
    ps = new PrintStruct(searchInput); 
    debug.AddLine(this.getMethodName() + ": searchInput: " + ps.print_r()); 
    } 
    Marshal.FreeHGlobal(pSake); 
    Marshal.FreeHGlobal(pInput); 
    } catch (Exception ex) { 
    debug.Text += ex.ToString(); 
    } 
    return ret; 
    } 
+0

Aggiornamento: si rompe in sakeSearchForRecords(); – Slawa

risposta

7

Il modo migliore per maresciallo puntatori di stringa brutto, soprattutto doppi puntatori all'interno di una struttura è quello di utilizzare semplicemente un IntPtr.

public IntPtr mFieldNames; 

Questo sarà Maresciallo correttamente anche se con un tipo non così utile. Tuttavia, se si capisce la struttura di IntPtr, è molto semplice ottenere le stringhe risultanti.

public static List<string> GetAllStrings(IntPtr ptr, int size) { 
    var list = new List<string>(); 
    for (int i = 0; i < size; i++) { 
    var strPtr = (IntPtr)Marshal.PtrToStructure(ptr, typeof(IntPtr)); 
    list.Add(Marshal.PtrToStringUni(strPtr)); 
    ptr = new IntPtr(ptr.ToInt64()+IntPtr.Size); 
    } 
    return list; 
} 

L'unico vero problema è che si dovrà liberare manualmente la memoria

+0

Se provassi ad usare la tua funzione (ne ho una simile se guardi il mio codice (usando array)), come posso ottenere IntPtr dalla "Lista "? IntPtr pList = &list; // ?? – Slawa

1

Un modo migliore è semplicemente quello di utilizzare il codice non sicuro con sbyte che è la stessa come c-char (da -128 a 127) 1 byte. Puoi scrivere alcune funzioni esterne come alloc_txt, free_txt, ecc. Per allocare e liberare dall'heap. Principalmente quando scrivo con interop, utilizzo codice non sicuro perché IntPtr ti fornisce l'indirizzo ma devi comunque utilizzare le funzioni extern per ottenere membri nella struttura a cui punta o se un primitivo deve utilizzare metodi Marshal per estrarre il valore.

L'unica volta che è necessario dichiarare una struttura C# come non sicura è se si stanno utilizzando puntatori effettivi che non si sono ma utilizzando MarshalAs. Continuerò a preferire che utilizzi puntatori non sicuri tramite MarshalAs (UnmanagedType.?) Che ti consente di gestire direttamente i membri.

[Struct(Layout.Sequential)] 
public unsafe struct SAKESearchForRecordsInput 
{ 
sbyte*mTableId; 
sbyte**mFieldNames; 
int mNumFields; 
sbyte*mFilter; 
sbyte*mSort; 
int mOffset; 
int mMaxRecords; 
char*mTargetRecordFilter; 
int mSurroundingRecordsCount; 
int*mOwnerIds; 
int mNumOwnerIds; 
bool mCacheFlag;//?don't know what the typedef for the bytes 
};