2012-06-20 23 views
8

He estado trabajando en una aplicación de código prototipo que se ejecuta en C# y utiliza clases y funciones del código C++ anterior (en forma de una DLL importada). El requisito del código es pasar un objeto de clase a la DLL de C++ no administrada (desde C#) y hacer que sea almacenada/modificada para que la aplicación C# la recupere posteriormente. Aquí está el código que tengo hasta ahora ...Pasar un objeto clase C# dentro y fuera de una clase DLL C++

simple C++ DLL Clase:

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

C++ funciones 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# clase y Envoltura:

[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# Llamada de función a 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". 
     ... 
    } 
} 

Nada más que los valores no deseados parecen estar disponibles dentro de la DLL (proveniente del objeto C# pasado). Creo que me falta algo con el marshalling de clase o un problema de memoria/puntero. ¿Qué me estoy perdiendo?

Edit: He cambiado el código anterior para reflejar los cambios a las definiciones de método/función, en C#/C++, sugerido por Bond. El valor (1234) que se transfiere se recupera correctamente con el código C#. Esto ha expuesto otro problema en la DLL de C++. El valor 1234 no está disponible para el código C++. En cambio, el objeto tiene un valor de 0 dentro de la DLL. Me gustaría usar funciones predefinidas de C++ para editar el objeto desde el archivo DLL. Cualquier otra ayuda es muy apreciada. ¡Gracias!

+0

__declspec (dllexport) SetDLLObj vacío (obj CClass) { Holder = &obj; } habrá dolor puntero de CClass local, por lo que puede conseguir Couse chatarra de ello. – user629926

+0

parece que el Titular está almacenando el valor que ingresa al código de C++ para su posterior recuperación. Puedo echarle un vistazo, pero mi preocupación principal en este momento es extraer un valor del objeto pasado a la DLL. – notsodev

Respuesta

5

Bond era correcto, no puedo pasar un objeto entre código administrado y no administrado y aún así mantener su información almacenada.

Terminé simplemente llamando a funciones C++ para crear un objeto y pasar el puntero al tipo IntPtr de C#. Luego puedo pasar el puntero a cualquier función de C++ que necesite (siempre que sea externa) desde C#. Esto no fue exactamente lo que queríamos hacer, pero cumplirá su propósito en la medida en que lo necesitemos.

Aquí está C# la envoltura que estoy usando para ejemplo/referencia. (Nota: estoy usando StringBuilder en lugar de 'int intTest' de mi ejemplo anterior. Esto es lo que queríamos para nuestro prototipo. Solo utilicé un entero en el objeto de la clase por simplicidad.):

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

Por supuesto, el C++ realiza todas las modificaciones necesarias y la única manera para C# para acceder a los contenidos es, más o menos, mediante la solicitud de los contenidos deseados a C++. El código C# no tiene acceso a los contenidos de la clase no controlada de esta manera, lo que hace que sea un poco más largo para codificar en ambos extremos. Pero funciona para mí.

Se trata de las referencias que he utilizado para llegar a la base de mi solución: http://www.codeproject.com/Articles/18032/How-to-Marshal-a-C-Class

Esperemos que esto puede ayudar a algunos otros a ahorrar más tiempo de lo que hice tratando de averiguarlo!

0

Si se utiliza la clase sólo de C#, se debe utilizar GCHandle para que http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.gchandle%28v=vs.110%29.aspx

edición:

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

Esto es interesante porque tenía la sensación de que se trataba de algo relacionado con el acceso a memoria entre los dos y no me encontré con GCHandle. Pero al usar esto necesitaría usar un tipo de IntPtr en lugar de pasar mi CSObject. ¿Hay alguna forma de convertir la clase CSObject en IntPtr? De lo contrario, no puedo pasar el contenido de la clase. ¡Gracias! – notsodev

+0

Simplemente reemplaza CClass con CClass * o void * y puede usarlo como IntPtr en el lado C#. ¿Tiene que cambiar sus valores en el lado de C++? También Class clase CClass se convierte automáticamente en CClass *. Podría intentar declarar su clase como struct y usar – user629926

+0

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

4

creo que debe declarar su método de regresar como esto

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

y, respectivamente, el prototipo C#

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

entrante El método también debe tomar un puntero a CClass, el prototipo C# está bien.

+0

Aparece un error de "puntero a referencia ilegal" cuando intento compilar la DLL. Además, la definición de la función C++ como referencia da un error "connot access private member in class" para la clase pública. Si defino la función de retorno como un puntero a la clase, cambia la salida en la aplicación C#, sin embargo, la basura de clase que mencioné pasada a la DL cambia a valores 0 en su lugar. ¿Podría esto estar relacionado con otro problema? – notsodev

+0

Mi error, tiene que ser una referencia a un puntero o un puntero a un puntero, he arreglado el código de muestra. Debe usar la referencia al puntero en su getDLLObj y solo un puntero en SetDLLObj y arreglar su prototipo C# para getDLLObj agregando la palabra clave ref en el parámetro. – Bond

+0

Gracias, Bond, sus sugerencias ayudaron a eliminar los valores basura que se almacenan dentro de la DLL. Sin embargo, todavía tengo mi problema de que se pase un valor de 0, donde debería ser 1234. Creo que todavía hay un problema entre la memoria no administrada y la memoria administrada como @ user629926. ¡Agradezco tu ayuda y cualquier otro pensamiento sería genial! – notsodev

Cuestiones relacionadas