c# c++ sharing

Compartiendo variables entre C#y C++



sharing (3)

Lo mejor que puedes hacer es definir tu código C ++ (que asumo que no será administrado). Luego escriba un contenedor para él en C ++ / CLI. La envoltura le proporcionará una interfaz a C # y es el lugar donde puede ocuparse de marchar (mover datos de unmanagerd a un área administrada)

Estoy escribiendo un software en c # que necesita llamar muchas veces y, por muchos hilos, una función en una dll no administrada de c ++.

Tengo un archivo de C ++ como ese:

// "variables" which consist in some simple variables (int, double) // and in some complex variables (structs containing arrays of structs) extern "C" { __declspec(dllexport) int function1() { // some work depending on random and on the "variables" } }

y una clase de C # como esa

public class class1 { // "variables" <--- the "same" as the C++ file''s ones // Dll import <--- ok public void method1() { int [] result; for(int i=0; i<many_times; i++) { result = new int[number_of_parallel_tasks]; Parallel.For(0, number_of_parallel_tasks, delegate(int j) { // I would like to do result[j] = function1() }); // choose best result // then update "variables" } } }

Escribí "Me gustaría hacer ..." porque la función c ++ necesita en cada ronda tener las "variables" actualizadas, también.

Mi pregunta es:

¿Es posible compartir la memoria entre C ++ y C # para evitar pasar referencias cada vez? ¿Es sólo una pérdida de tiempo?

Leí sobre los archivos de memoria asignados. ¿Podrían ayudarme? Sin embargo, ¿conoces las soluciones más adecuadas?
Muchas gracias.


No hay ningún problema para compartir la memoria entre C # y C ++ usando P / Invoke una vez que sepa cómo funciona. Yo sugeriría leer sobre el cálculo de referencias en MSDN. También es posible que desee leer sobre el uso de palabras clave no seguras y la corrección de memoria.

Aquí hay una muestra que asume que sus variables pueden describirse como una estructura simple:

En C ++ declara tu función de la siguiente manera:

#pragma pack(1) typedef struct VARIABLES { /* Use simple variables, avoid pointers If you need to use arrays use fixed size ones */ }variables_t; #pragma pack() extern "C" { __declspec(dllexport) int function1(void * variables) { // some work depending on random and on the "variables" } }

En C # haz algo como esto:

[StructLayout(LayoutKind.Sequential, Pack=1)] struct variables_t { /* Place the exact same definition as in C++ remember that long long in c++ is long in c# use MarshalAs for fixed size arrays */ }; [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern int function(ref variables_t variables);

Y en tu clase:

variables_t variables = new variables_t(); //Initialize variables here for(int i=0; i<many_times; i++) { int[] result = new int[number_of_parallel_tasks]; Parallel.For(0, number_of_parallel_tasks, delegate(int j) { result[j] = function1(ref variables) }); // choose best result // then update "variables" }

Puede usar escenarios más complejos como asignar y liberar la estructura en c ++, usar otras formas de cálculo de referencias para recuperar los datos, como construir su propia clase para leer y escribir directamente en la memoria no administrada. Pero si puede usar una estructura simple para mantener sus variables, el método anterior es el más simple.

EDITAR: Indicadores sobre cómo manejar correctamente datos más complejos.

Así que el ejemplo anterior es, en mi opinión, la forma correcta de "compartir" datos entre C # y C ++ si se trata de datos simples, por ejemplo. una estructura que contiene tipos primitivos o matrices de tamaño fijo de tipos primitivos.

Dicho esto, en realidad hay muy pocas limitaciones en la forma en que puede acceder a la memoria utilizando C #. Para obtener más información, consulte la palabra clave no segura, la palabra clave fija y la estructura GCHandle. Y aún si tiene estructuras de datos muy complejas que contienen arreglos de otras estructuras, etc., entonces tiene un trabajo más complicado.

En el caso anterior, recomendaría mover la lógica sobre cómo actualizar las "variables" a C ++. Agregue en C ++ una función para ver algo como esto:

extern "C" { __declspec(dllexport) void updateVariables(int bestResult) { // update the variables } }

Todavía sugeriría no usar variables globales, por lo que propongo el siguiente esquema. En C ++:

typedef struct MYVERYCOMPLEXDATA { /* Some very complex data structure */ }variables_t; extern "C" { __declspec(dllexport) variables_t * AllocVariables() { // Alloc the variables; } __declspec(dllexport) void ReleaseVariables(variables_t * variables) { // Free the variables; } __declspec(dllexport) int function1(variables_t const * variables) { // Do some work depending on variables; } __declspec(dllexport) void updateVariables(variables_t * variables, int bestResult) { // update the variables } };

Cía#:

[DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern IntPtr AllocVariables(); [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern void ReleaseVariables(IntPtr variables); [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern int function1(IntPtr variables); [DllExport("YourDll.dll", CallingConvention=CallingConvention.Cdecl)] static extern void updateVariables(IntPtr variables, int bestResult);

Si aún desea mantener su lógica en C #, tendrá que hacer algo como lo siguiente: Cree una clase para guardar la memoria devuelta desde C ++ y escriba su propia lógica de acceso a la memoria. Exponer los datos a C # usando semántica de copia. Lo que quiero decir es lo siguiente, digamos que tienes en C ++ una estructura como esta:

#pragma pack(1) typedef struct SUBSTRUCT { int subInt; double subDouble; }subvar_t; typedef struct COMPLEXDATA { int int0; double double0; int subdata_length; subvar_t * subdata; }variables_t; #pragma pack()

en C # haces algo como esto

[DllImport("kernel32.dll")] static extern void CopyMemory(IntPtr dst, IntPtr src, uint size); [StructLayout((LayoutKind.Sequential, Pack=1)] struct variable_t { public int int0; public double double0; public int subdata_length; private IntPtr subdata; public SubData[] subdata { get { SubData[] ret = new SubData[subdata_length]; GCHandle gcH = GCHandle.Alloc(ret, GCHandleType.Pinned); CopyMemory(gcH.AddrOfPinnedObject(), subdata, (uint)Marshal.SizeOf(typeof(SubData))*subdata_length); gcH.Free(); return ret; } set { if(value == null || value.Length == 0) { subdata_length = 0; subdata = IntPtr.Zero; }else { GCHandle gcH = GCHandle.Alloc(value, GCHandleType.Pinned); subdata_length = value.Length; if(subdata != IntPtr.Zero) Marshal.FreeHGlobal(subdata); subdata = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(SubData))*subdata_length); CopyMemory(subdata, gcH.AddrOfPinnedObject(),(uint)Marshal.SizeOf(typeof(SubData))*subdata_length); gcH.Free(); } } } }; [StructLayout((LayoutKind.Sequential, Pack=1)] sturct SubData { public int subInt; public double subDouble; };

En la muestra anterior, la estructura todavía se puede pasar como en la primera muestra. Por supuesto, esto es solo un resumen de cómo manejar datos complejos con araays de estructuras y matrices de estructuras dentro de matrices de estructuras. Como puede ver, necesitará mucha copia para protegerse de la corrupción de la memoria. Además, si la memoria se asigna a través de C ++, será muy mala si utiliza FreeHGlobal para liberarla. Si desea evitar copiar la memoria y aún mantener la lógica dentro de C #, puede escribir un contenedor de memoria nativo con accesores para lo que quiera. Por ejemplo, tendrá un método para configurar directamente u obtener el subInt del miembro de la matriz Nth: de esta manera Guardará sus copias exactamente a lo que accede.

Otra opción sería escribir funciones específicas de C ++ para hacer el difícil manejo de datos por usted y llamarlas desde C # de acuerdo con su lógica.

Y por último, pero no menos importante, siempre puede usar C ++ con la interfaz CLI. Sin embargo, yo mismo lo hago solo si debo: no me gusta la jerga, pero para datos muy complejos, sin duda, hay que considerarlo.

EDITAR

Agregué la convención de llamada correcta a DllImport para que esté completa. Tenga en cuenta que la convención de llamadas predeterminada utilizada por el atributo DllImport es Winapi (que en windows se traduce como __stdcall), mientras que la convención de llamadas predeterminada en C / C ++ (a menos que cambie las opciones del compilador) es __cdecl.


Una forma de evitar tener que pasar una referencia a las variables / datos que requieren tanto el código C # como el código C ++ es exportar dos funciones desde la DLL nativa. Además de la función que realiza el trabajo, proporcione otra función que permita que la referencia se pase y se almacene en un puntero estático del alcance del archivo que se define en el mismo archivo .cpp como ambas funciones para que ambas puedan acceder.

Como mencionó, también puede usar un archivo asignado en la memoria (en este caso podría no ser persistente, ya que nunca debe escribirse en el disco).