2009-09-30 26 views
5

Voy a llamar a una función DLL C y la necesidad de proporcionar la siguiente estructura C:Marshalling matriz de cadenas a char ** en 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; 

El problema es con char ** mFieldNames; He tratado de cálculo de referencias de forma automática la siguiente manera:

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

De esta manera me sale un error en Marshal.SizeOf() - no se puede calcular el tamaño correcto. Entonces decidí tratar con punteros manualmente. De hecho, es solo un puntero a la matriz de cadenas C. Este es mi código que conduce a

System.AccessViolationException: Intenté leer o escribir en la memoria protegida. Esto a menudo es una indicación de que otra memoria está corrupta.

Así que he metido los punteros en alguna parte. El código me parece bien, ¿dónde está el error?

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

Actualización: se rompe en sakeSearchForRecords(); – Slawa

Respuesta

7

La mejor manera de Mariscal punteros de cadena desagradables, especialmente punteros dobles dentro de una estructura es simplemente usar un IntPtr.

public IntPtr mFieldNames; 

Esto Marshal correctamente, aunque con un tipo no tan útil. Sin embargo, si comprende la estructura de IntPtr, es muy fácil obtener las cadenas resultantes.

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

El único inconveniente es que tendrá que liberar manualmente la memoria

+0

Si quisiera usar tu función (tengo una similar si miras mi código (usando una matriz)), ¿cómo puedo obtener IntPtr desde la "Lista "? IntPtr pList = &list; // ?? – Slawa

1

Una mejor manera es simplemente usar código no seguro con sbyte que es el mismo que el c-char (-128 a 127) 1 byte Puede escribir usted mismo algunas funciones externas como alloc_txt, free_txt, etc. para asignar y liberar del montón. Principalmente cuando escribo con interoperabilidad uso un código inseguro porque IntPtr te da la dirección pero aún tienes que usar funciones externas para obtener miembros en la estructura a la que apunta o si una primitiva tiene métodos de Marshal para extraer el valor.

La única vez que tiene que declarar que una estructura de C# es insegura es si está utilizando punteros reales que no es pero está utilizando MarshalAs en su lugar. Aún así, preferiría que utilizaras punteros inseguros a través de MarshalAs (UnmanagedType.?), Lo que le permite tratar directamente con los miembros.

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