2009-11-17 19 views
5

Nel mio codice C# sto provando a recuperare una matrice di strutture da una legacy DLL C++ (il codice che non posso cambiare).Marshalling di una matrice di strutture da C++ a C#?

In quel codice C++, la struttura è definita in questo modo:

struct MyStruct 
{ 
    char* id; 
    char* description; 
}; 

Il metodo che chiamo (get_my_structures) restituisce un puntatore a una matrice di strutture myStruct:

MyStruct* get_my_structures() 
{ 
    ... 
} 

C'è un altro metodo che restituisce il numero di strutture, quindi so quante strutture vengono restituite. MyStruct

Nel mio codice C#, ho definito in questo modo:

[StructLayout(LayoutKind.Sequential)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPStr)] // <-- also tried without this 
    private string _id; 
    [MarshalAsAttribute(UnmanagedType.LPStr)] 
    private string _description; 
} 

La firma di interoperabilità si presenta così:

[DllImport("legacy.dll", EntryPoint="get_my_structures")] 
public static extern IntPtr GetMyStructures(); 

Infine, il codice che recupera la matrice di strutture MyStruct assomiglia questo:

int structuresCount = ...; 
IntPtr myStructs = GetMyStructures(); 
int structSize = Marshal.SizeOf(typeof(MyStruct)); // <- returns 8 in my case 
for (int i = 0; i < structuresCount; i++) 
{ 
    IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i); 
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct)); 
    ... 
} 

il problema è che solo la prima struttura (uno alla o ffset zero) viene eseguito correttamente il marshalling. Quelli successivi hanno valori falsi nei membri _id e _description. I valori non sono completamente trascinati, o così sembra: sono stringhe da altre posizioni di memoria. Il codice stesso non si blocca.

Ho verificato che il codice C++ in get_my_structures() restituisce dati corretti. I dati non vengono cancellati o modificati per errore durante o dopo la chiamata.

Visto in un debugger, C++ layout della memoria dei dati restituiti assomiglia a questo:

0: id (char*)   <---- [MyStruct 1] 
4: description (char*) 
8: id (char*)   <---- [MyStruct 2] 
12: description (char*) 
16: id (char*)   <---- [MyStruct 3] 
... 

[Aggiornamento 18/11/2009]

Ecco come il codice C++ prepara queste strutture (il codice reale è molto più brutto, ma questo è un primo sufficiente approssimazione):

static char buffer[12345] = {0}; 
MyStruct* myStructs = (MyStruct*) &buffer; 
for (int i = 0; i < structuresCount; i++) 
{ 
    MyStruct* ms = <some other permanent address where the struct is>; 
    myStructs[i].id = (char*) ms->id; 
    myStructs[i].description = (char*) ms->description; 
} 
return myStructs; 

Certo, il codice di cui sopra fa un po ' brutto casting e copia i puntatori grezzi in giro, ma sembra ancora farlo correttamente. Almeno questo è quello che vedo nel debugger: il precedente (statico) buffer contiene tutti questi puntatori char char * memorizzati uno dopo l'altro, e puntano a posizioni valide (non locali) in memoria.

L'esempio di Pavel mostra che questo è davvero l'unico posto in cui le cose possono andare storte. Proverò ad analizzare cosa succede in quei punti "finali" in cui le stringhe sono realmente, non nelle posizioni in cui i puntatori vengono memorizzati.

risposta

0

È necessario utilizzare UnmanagedType.LPTStr per char *. Anche un StringBuilder è raccomandato per un const char * non: E una specifica CharSet:

[StructLayout(LayoutKind.Sequential, Charset = CharSet.Auto)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private StringBuilder _id; 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private StringBuilder _description; 
} 

Per quanto riguarda la dichiarazione DllImport, hai provato

[DllImport("legacy.dll", EntryPoint="get_my_structures")] 
public static extern MarshalAs(UnmanagedType.LPArray) MyStruct[] GetMyStructures(); 

?

Inoltre, se il precedente non funziona, lasciano a IntPtr e cercare di Mashal le struct restituiti in questo modo:

for (int i = 0; i < structuresCount; i++) 
{ 
    MyStruct ms = (MyStruct) Marshal.PtrToStructure(myStructs, typeof(MyStruct)); 
    ... 
    myStructs += Marshal.SizeOf(ms); 
} 
+0

Se provo a utilizzare StringBuilder, ottengo ArgumentException quando provo a farlo: int itemSize = Marshal.SizeOf (typeof (MyStruct)); Il messaggio di errore è "Tipo" Non è possibile eseguire il marshalling di MyStruct come struttura non gestita, non è possibile calcolare dimensioni o offset significativi ". – vladimir

+0

@vladimir: stai usando 'UnmanagedType.LPTStr'?Inoltre: prova a specificare il 'CharSet' come gli altri suggeriti. – fretje

+0

"Le stringhe sono membri validi delle strutture, tuttavia i buffer StringBuilder non sono validi nelle strutture." -> http://msdn.microsoft.com/en-us/library/s9ts558h.aspx#Mtps_DropDownFilterText –

0

Io di solito finiscono per lavorare queste cose per tentativi ed errori. Assicurati di aver impostato la proprietà CharSet sul tuo StructLayout, e vorrei provare UnmanagedType.LPTStr, sembra funzionare meglio per char *, anche se non sono sicuro del perché.

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Auto)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private string _id; 
    [MarshalAsAttribute(UnmanagedType.LPTStr)] 
    private string _description; 
} 
0

Penso, anche, oltre alle risposte date, che è necessario fornire la lunghezza così, cioè [MarshalAsAttribute (UnmanagedType.LPTStr), SizeConst =, ArraySubType = System.Runtime.InteropServices. UnmanagedType.AnsiBStr)]

questa è una prova ed errore per ottenere questo diritto, anche, un'altra cosa da considerare, in alcune chiamate WinAPI che si aspettano un parametro di stringa, di solito un parametro ref, potrebbe valere la pena di provare anche la classe StringBuilder ... Nulla altro ti viene in mente su questo oltre ai punti che ho menzionato qui ... Spero che questo aiuti, Tom

+0

char * è terminato con null, quindi non fornirei la lunghezza. Ecco perché abbiamo UnmanagedType.LPWStr e UnmanagedType.LPTStr. Se dovessi fornire la lunghezza, dovresti anche renderla davvero grande da non troncare alcun dato. Non una soluzione elegante. – ParmesanCodice

1

Vorrei cambiare la struttura. Invece di stringhe, ecc, utilizzare IntPtr:

 
[StructLayout(LayoutKind.Sequential)] 
public class MyStruct 
{ 
    private IntPtr _id; 
    private IntPtr _description; 
} 

Poi ogni valore del C# array possono essere dispiegate manualmente stringa utilizzando Marshal.PtrToString tenendo conto charset ecc

+0

Sembra un approccio praticabile, ma sfortunatamente non sembra affrontare il problema che ho. Anche con i membri IntPtr, questi membri hanno ancora gli stessi valori (non validi) in tutte le strutture diverse dalla prima. – vladimir

3

non riesco a riprodurre il problema, che mi porta a sospettare che sia davvero sul lato C++ delle cose. Ecco il codice sorgente completo per il mio tentativo.

dll.cpp - compilare con cl.exe /LD:

extern "C" { 

struct MyStruct 
{ 
    char* id; 
    char* description; 
}; 

__declspec(dllexport) 
MyStruct* __stdcall get_my_structures() 
{ 
    static MyStruct a[] = 
    { 
     { "id1", "desc1" }, 
     { "id2", "desc2" }, 
     { "id3", "desc3" } 
    }; 
    return a; 

} 

} 

test.cs - compilare con csc.exe /platform:x86:

using System; 
using System.Runtime.InteropServices; 


[StructLayout(LayoutKind.Sequential)] 
public class MyStruct 
{ 
    [MarshalAsAttribute(UnmanagedType.LPStr)] 
    public string _id; 
    [MarshalAsAttribute(UnmanagedType.LPStr)] 
    public string _description; 
} 


class Program 
{ 
    [DllImport("dll")] 
    static extern IntPtr get_my_structures(); 

    static void Main() 
    { 
     int structSize = Marshal.SizeOf(typeof(MyStruct)); 
     Console.WriteLine(structSize); 

     IntPtr myStructs = get_my_structures(); 
     for (int i = 0; i < 3; ++i) 
     { 
      IntPtr data = new IntPtr(myStructs.ToInt64() + structSize * i); 
      MyStruct ms = (MyStruct) Marshal.PtrToStructure(data, typeof(MyStruct)); 

      Console.WriteLine(); 
      Console.WriteLine(ms._id); 
      Console.WriteLine(ms._description); 
     } 
    } 
} 

viene stampato correttamente tutte 3 le strutture.

Puoi mostrare il codice C++ che riempie le strutture? Il fatto che tu possa chiamarlo direttamente dal C++ e ottenere risultati corretti non significa necessariamente che sia corretto. Ad esempio, potresti restituire un puntatore a una struttura allocata allo stack. Quando si effettua una chiamata diretta, si otterrebbe un puntatore tecnicamente non valido, ma i dati probabilmente verrebbero mantenuti. Quando si esegue il marshalling P/Invoke, lo stack può essere sovrascritto dalle strutture di dati P/Invoke dal punto in cui tenta di leggere i valori da lì.

+0

Ho aggiornato la mia domanda, aggiungendo il codice C++. Dato che hai dimostrato che non c'è niente di sbagliato nella parte di interoperabilità stessa (né sul lato C#), è logico concludere che deve essere il codice C++. – vladimir

Problemi correlati