2010-04-30 12 views
6

Ho una funzione C++ che produce un elenco di rettangoli che sono interessanti. Voglio essere in grado di estrarre questa lista dalla libreria C++ e tornare all'applicazione C# che la sta chiamando.Come è possibile eseguire il marshalling di un vettore <int> da una dll C++ a un'applicazione C#?

Finora, sto codificare i rettangoli in questo modo:

struct ImagePatch{ 
    int xmin, xmax, ymin, ymax; 
} 

e quindi la codifica alcuni vettori:

void MyFunc(..., std::vector<int>& rectanglePoints){ 
    std::vector<ImagePatch> patches; //this is filled with rectangles 
    for(i = 0; i < patches.size(); i++){ 
     rectanglePoints.push_back(patches[i].xmin); 
     rectanglePoints.push_back(patches[i].xmax); 
     rectanglePoints.push_back(patches[i].ymin); 
     rectanglePoints.push_back(patches[i].ymax); 
    } 
} 

L'intestazione per l'interazione con C# sembra (e lavora per un gruppo di altre funzioni):

extern "C" { 
    __declspec(dllexport) void __cdecl MyFunc(..., std::vector<int>& rectanglePoints); 
} 

Ci sono alcune parole chiave o altre cose che posso fare per ottenere quel set di rettangoli fuori? Ho trovato this article per il marshalling di oggetti in C#, ma sembra troppo complicato e troppo sottostimato. Un vettore di numeri interi è il modo giusto per farlo, o c'è qualche altro trucco o approccio?

+1

Questo non è davvero "marshalling". Marshalling prenderebbe il tuo oggetto C++, scrivendo alcuni dati binari che lo rappresentano, e avendo C# leggere quei dati binari per costruire un oggetto corrispondente nell'altro ambiente. Stai provando a passare un argomento dal codice C# al codice C++ e il codice C++ lo modifica. –

+0

OK, allora come posso fare in modo che quello che ottengo dal lato C++ sia di lunghezza arbitraria? – mmr

+0

Bene, se vuoi evitare di scrivere un oggetto .NET "corretto" gestito in C++, come descritto in quell'articolo di MSDN, restituisci un buffer malloced da C++ (e liberalo da C# quando hai finito con esso); riprogettare la tua API in modo che C# possa passare in un buffer da riempire, e una lunghezza, e il codice C++ può in qualche modo dire a C# quanto deve essere grande quel buffer; ridisegnare l'API in modo da richiamare da C++ in C# una volta per ogni valore e aggiungerla a una raccolta C#. Non ne so abbastanza di C# (o di interfaccia con altre lingue da C#) per giudicare quale sia più facile/migliore. –

risposta

0

Sono abbastanza sicuro che non puoi farlo. Devi essere in grado di tradurre il codice C++ direttamente in una classe C#, quindi dovrai almeno replicare gli interni della classe vettoriale per eseguirne correttamente il marshalling. Sono anche abbastanza sicuro che non sarai in grado di spostare i riferimenti attraverso i confini, dovrai usare IntPtr (puntatori grezzi). L'approccio che conosco funziona consiste nel marshalling di una matrice raw delle strutture.

+0

So che posso spostare i riferimenti oltre il limite-- Lo faccio in altre funzioni con int & e simili. Sul lato C#, basta mettere la parola chiave 'ref' nella firma della funzione quando si usa dllimport. – mmr

+1

Si noti inoltre che gli oggetti C++ come il vettore non devono attraversare un limite del modulo: non è garantito che DLL o exe differenti utilizzino la stessa identica versione di STL. – Michael

+0

Quindi come faccio a ottenere un po 'di Ints (la cui quantità esatta di cui non so) in C#? – mmr

3

STL è una libreria specifica in C++, quindi non è possibile ottenerla direttamente come un oggetto in C#.

L'unica cosa garantita per std :: vector è che & v [0] punta al primo elemento e tutti gli elementi giacciono linearmente in memoria (in altre parole, è proprio come un array C in termini di memoria layout)

Quindi maresciallo come array di int ... che non dovrebbe essere difficile - Ci sono molti esempi sul web.

Aggiunto

Supponendo che si passa solo i dati da C++ a C#:

C# non è in grado di gestire un oggetto vettoriale C++, quindi non cercare di passarlo per riferimento: invece il codice C++ deve restituire un puntatore a un array di int ...

Se non si ha intenzione di utilizzare questa funzione da più thread, è possibile utilizzare l'archiviazione statica:

int *getRects(bool bClear) 
{ 
    static vector<int> v; // This variable persists across invocations 
    if(bClear) 
    { 
     v.swap(vector<int>()); 
    } 
    else 
    { 
     v.clear(); 
     // Fill v with data as you wish 
    } 

    return v.size() ? &v[0] : NULL; 
} 

chiama getRects (true) se i dati restituiti sono di dimensioni significative, quindi si rilascia la memoria in v.

Per semplicità, invece di distribuire la dimensione dei dati vettoriali, basta mettere un valore sentinella alla fine (come dire -1) così il codice C# può rilevare dove finisce il dato.

+0

Ma come funzionerebbe? Se faccio il marshal come array, come cresce la matrice sul lato C++ da push-back? Quella memoria non è stata allocata, quindi ogni elemento oltre il primo fallisce. Se prelezzo l'array sul lato C#, allora potrei anche usare un array piuttosto che un vettore, e potrebbe essere cattivo se ho molti rettangoli. – mmr

+0

Vedere la modifica e il codice sopra ... –

+0

@rep_movsd questo funziona ugualmente bene con qualcosa che non è un int, come una struttura? –

0

Sì. Puoi. In realtà, non solo, std::string, std::wstring, qualsiasi classe standard C++ o le tue classi possono essere sottoposte a marshalling o istanza e chiamate da C# /. NET.

Il wrapping di un std::vector<any_type> in C# è effettivamente possibile con il solo normale P/Invoke Interop, tuttavia è complicato. anche un std::map di qualsiasi tipo può essere fatto in C# /. NET.

public class SampleClass : IDisposable 
{  
    [DllImport("YourDll.dll", EntryPoint="ConstructorOfYourClass", CharSet=CharSet.Ansi,   CallingConvention=CallingConvention.ThisCall)] 
    public extern static void SampleClassConstructor(IntPtr thisObject); 

    [DllImport("YourDll.dll", EntryPoint="DestructorOfYourClass", CharSet=CharSet.Ansi,   CallingConvention=CallingConvention.ThisCall)] 
    public extern static void SampleClassDestructor(IntPtr thisObject); 

    [DllImport("YourDll.dll", EntryPoint="DoSomething", CharSet=CharSet.Ansi,  CallingConvention=CallingConvention.ThisCall)] 
    public extern static void DoSomething(IntPtr thisObject); 

    [DllImport("YourDll.dll", EntryPoint="DoSomethingElse", CharSet=CharSet.Ansi,  CallingConvention=CallingConvention.ThisCall)] 
    public extern static void DoSomething(IntPtr thisObject, int x); 

    IntPtr ptr; 

    public SampleClass(int sizeOfYourCppClass) 
    { 
     this.ptr = Marshal.AllocHGlobal(sizeOfYourCppClass); 
     SampleClassConstructor(this.ptr); 
    } 

    public void DoSomething() 
    { 
     DoSomething(this.ptr); 
    } 

    public void DoSomethingElse(int x) 
    { 
     DoSomethingElse(this.ptr, x); 
    } 

    public void Dispose() 
    { 
     if (this.ptr != IntPtr.Zero) 
     { 
      // The following 2 calls equals to "delete object" in C++ 
      // Calling the destructor of the C++ class will free the memory allocated by the native c++ class. 
      SampleClassDestructor(this.ptr); 

      // Free the memory allocated from .NET. 
      Marshal.FreeHGlobal(this.ptr); 

      this.ptr = IntPtr.Zero; 
     } 
    } 
} 

Si prega di consultare il link sottostante,

C#/.NET PInvoke Interop SDK

(Io sono l'autore del tool SDK)

volta che hai il C# classe wrapper per la classe C++ pronto, è facile da implementare ICustomMarshaler in modo da poter eseguire il marshalling dell'oggetto C++ da .NET.

http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.icustommarshaler.aspx

+1

Sei qui nel regno del comportamento indefinito. Fallo bene, usa C++/CLI. –

+0

Grazie per i commenti.Ma una volta che conosci i passaggi per avvolgere una classe C++, non è affatto complicato, ho aggiunto un semplice esempio per completare la mia risposta. C++/CLI non è così amichevole per lo sviluppatore, specialmente la sintassi. – xInterop

+0

Congratulazioni, hai scritto un corso con potenziale perdita di memoria, potenziale doppio libero e chiamata a funzioni interne senza il sistema di supporto corretto. Brutta cattiva idea Inoltre, una soluzione C++/CLI che sia * corretta * sarebbe 1/3 del codice. –

Problemi correlati