2012-06-20 25 views
8

Ho lavorato a un'applicazione di codice prototipo che viene eseguita in C# e utilizza classi e funzioni dal vecchio codice C++ (nella forma di una DLL importata). Il requisito del codice è quello di passare un oggetto di classe alla DLL C++ non gestita (da C#) e far sì che venga memorizzato/modificato per il recupero in seguito dall'applicazione C#. Ecco il codice che ho finora ...Passare un oggetto classe C# dentro e fuori una classe DLL C++

semplice C++ DLL Classe:

class CClass : public CObject 
{ 
public: 
    int intTest1 
}; 

C++ funzioni DLL:

CClass *Holder = new CClass; 

extern "C" 
{ 
    // obj always comes in with a 0 value. 
    __declspec(dllexport) void SetDLLObj(CClass* obj) 
    { 
     Holder = obj; 
    } 

    // obj should leave with value of Holder (from SetDLLObj). 
    __declspec(dllexport) void GetDLLObj(__out CClass* &obj) 
    { 
     obj = Holder; 
    } 
} 

C# classe e Wrapper:

[StructureLayout(LayoutKind.Sequential)] 
public class CSObject 
{ 
    public int intTest2; 
} 

class LibWrapper 
{ 
    [DLLImport("CPPDLL.dll")] 
    public static extern void SetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
     CSObject csObj); 
    public static extern void GetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
     ref CSObject csObj); 
} 

C# Funzione Chiama alla DLL:

class TestCall 
{ 
    public static void CallDLL() 
    { 
     ... 
     CSObject objIn = new CSObject(); 
     objIn.intTest2 = 1234; // Just so it contains something. 
     LibWrapper.SetDLLObj(objIn); 
     CSObject objOut = new CSObject(); 
     LibWrapper.GetDLLObj(ref objOut); 
     MessageBox.Show(objOut.intTest2.ToString()); // This only outputs "0". 
     ... 
    } 
} 

Nulla ma i valori indesiderati sembrano essere disponibili all'interno della DLL (proveniente dall'oggetto C# passato). Credo che mi manca qualcosa con il marshalling di classe o un problema di memoria/puntatore. Cosa mi manca?

Modifica: Ho modificato il codice precedente per riflettere le modifiche alle definizioni metodo/funzione, in C#/C++, suggerite da Bond. Il valore (1234) che viene passato viene recuperato dal codice C# correttamente ora. Ciò ha esposto un altro problema nella DLL C++. Il valore 1234 non è disponibile per il codice C++. Invece l'oggetto ha un valore di 0 all'interno della DLL. Vorrei utilizzare le funzioni C++ predefinite per modificare l'oggetto dalla DLL. Qualsiasi altro aiuto è molto apprezzato. Grazie!

+0

__declspec (dllexport) SetDLLObj void (CClass obj) { Holder = &obj; } sarà mal di puntatore di CClass locale in modo si può ottenere spazzatura couse di questo. – user629926

+0

sembra che il titolare stia memorizzando il valore inserito nel codice C++ per il recupero successivo. Posso dare un'occhiata a questo, ma la mia preoccupazione principale in questo momento è tirare un valore dall'oggetto passato nella DLL. – notsodev

risposta

5

Bond era corretto, non riesco a passare un oggetto tra codice gestito e non gestito e continuo a conservare le informazioni memorizzate.

Alla fine ho semplicemente chiamato le funzioni C++ per creare un oggetto e passare il puntatore nel tipo IntPtr di C#. Posso quindi passare il puntatore a qualsiasi funzione C++ di cui ho bisogno (a condizione che sia esternamente) da C#. Questo non era esattamente ciò che volevamo fare, ma servirebbe al suo scopo nella misura in cui ne abbiamo bisogno.

Ecco C# il wrapper che sto usando ad esempio/riferimento. (Nota: sto usando StringBuilder al posto di int intTest del mio esempio sopra. Questo è ciò che volevamo per il nostro prototipo. Ho semplicemente usato un numero intero nell'oggetto classe per semplicità.):

class LibWrapper 
{ 
    [DllImport("CPPDLL.dll")] 
    public static extern IntPtr CreateObject(); 
    [DllImport("CPPDLL.dll")] 
    public static extern void SetObjectData(IntPtr ptrObj, StringBuilder strInput); 
    [DllImport("CPPDLL.dll")] 
    public static extern StringBuilder GetObjectData(IntPtr ptrObj); 
    [DllImport("CPPDLL.dll")] 
    public static extern void DisposeObject(IntPtr ptrObj); 
} 

public static void CallDLL() 
{ 
    try 
    { 
     IntPtr ptrObj = Marshal.AllocHGlobal(4); 
     ptrObj = LibWrapper.CreateObject(); 
     StringBuilder strInput = new StringBuilder(); 
     strInput.Append("DLL Test"); 
     MessageBox.Show("Before DLL Call: " + strInput.ToString()); 
     LibWrapper.SetObjectData(ptrObj, strInput); 
     StringBuilder strOutput = new StringBuilder(); 
     strOutput = LibWrapper.GetObjectData(ptrObj); 
     MessageBox.Show("After DLL Call: " + strOutput.ToString()); 
     LibWrapper.DisposeObject(ptrObj); 
    } 
    ... 
} 

Naturalmente il C++ esegue tutte le modifiche necessarie e l'unico modo per C# per accedere al contenuto è, più o meno, richiedendo i contenuti desiderati -C++. Il codice C# non ha accesso ai contenuti della classe non misti in questo modo, rendendo un po 'più lungo il codice su entrambe le estremità. Ma funziona per me.

Questo è i riferimenti che ho usato per venire con la base della mia soluzione: http://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class

Speriamo che questo può aiutare alcuni altri a risparmiare più tempo di quanto ho fatto cercando di capirlo!

0

Se si utilizza la classe proprio da C#, è necessario utilizzare GCHandle per quella http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle%28v=vs.110%29.aspx

edit:

CClass *Holder; 

extern "C" 
{ 
    // obj always comes in with a 0 value. 
    __declspec(dllexport) void SetDLLObj(CClass* obj) 
    { 
     (*Holder) = (*obj); 
    } 

    // obj should leave with value of Holder (from SetDLLObj). 
    __declspec(dllexport) void GetDLLObj(__out CClass** &obj) 
    { 
     (**obj) = (*Holder); 
    } 
} 
+0

Questo è interessante perché ho avuto la sensazione che fosse qualcosa legato all'accesso alla memoria tra i due e che non ho corso su GCHandle. Ma usando questo avrei bisogno di usare un tipo IntPtr al posto di passare il mio CSObject. C'è un modo per convertire la classe CSObject in un IntPtr? Altrimenti non posso passare il contenuto della lezione. Grazie! – notsodev

+0

Sostituisci CClass con CClass * o void * e puoi utilizzarlo come IntPtr sul lato C#. Hai cambiato i suoi valori sul lato C++? Anche i marshal della classe C# della classe C vengono automaticamente come CClass *. Prova a dichiarare la tua classe come struct e usa – user629926

+0

public static extern void GetDLLObj ( out CSObject csObj); – user629926

4

Credo che si dovrebbe dichiarare il vostro metodo di ritorno come questo

__declspec(dllexport) void getDLLObj(__out CClass* &obj) 

e rispettivamente il prototipo C#

public static extern void GetDLLObj([MarshalAs(UnmanagedType.LPStruct)] 
     ref CSObject csObj); 

il in entrata metodo dovrebbe prendere un puntatore a CClass troppo, il prototipo C# è ok.

+0

Viene visualizzato un errore "puntatore a riferimento illegale" durante il tentativo di compilare la DLL. Inoltre, la definizione della funzione C++ come riferimento fornisce un errore di "membro di accesso privato connot in classe" per la classe pubblica. Se definisco la funzione di ritorno come un puntatore alla classe, esso cambia l'output nell'app C#, tuttavia la junk di classe che ho menzionato passata nel DL cambia invece in 0. Potrebbe essere correlato a un altro problema? – notsodev

+0

Errore mio, deve essere un riferimento al puntatore o puntatore al puntatore - Ho corretto il mio codice di esempio. È necessario utilizzare il riferimento al puntatore nel proprio getDLLObj e solo un puntatore nel SetDLLObj e correggere il prototipo C# per getDLLObj aggiungendo la parola chiave ref sul parametro. – Bond

+0

Grazie a Bond, i tuoi suggerimenti hanno aiutato a rimuovere i valori indesiderati memorizzati nella DLL. Tuttavia, ho ancora il mio problema con il passaggio di un valore 0, dove dovrebbe essere 1234. Credi che ci sia ancora un problema tra la memoria non matta e quella gestita come @ user629926. Apprezzo il tuo aiuto e ogni altro pensiero sarebbe fantastico! – notsodev