2012-11-07 20 views
15

Sto scrivendo un software in C# che ha bisogno di chiamare molte volte e per molti thread una funzione in una DLL non gestita C++.Condivisione di variabili tra C# e C++

ho lima ++ così un C:

// "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" 
    } 
} 

e una classe C# del genere

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" 
     } 
    } 

} 

ho scritto "mi piacerebbe fare ..." perché la funzione C++ ha bisogno di ogni round per avere anche le "variabili" aggiornate.

La mia domanda è:

E 'possibile condividere la memoria tra C++ e C# in modo da evitare di passare di riferimento ogni volta? È solo una perdita di tempo?

Ho letto sui file mappati in memoria. Potrebbero aiutarmi? Tuttavia, conosci le soluzioni più appropriate?
Grazie mille.

+1

Ho upvoted la tua domanda ma dovresti davvero specificare la natura delle "variabili" per ottenere una buona risposta –

+0

Ho appena modificato. Grazie. – 888

risposta

22

Non c'è alcun problema nel condividere la memoria tra C# e C++ usando P/Invoke una volta sapere come funziona Suggerirei di leggere sul marshalling in MSDN. Si potrebbe anche voler leggere sull'utilizzo della parola chiave non sicura e sulla risoluzione della memoria.

Ecco un esempio che assume che le variabili possono essere descritti come una semplice struct:

In C++ dichiarare la funzione come segue:

#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" 
    } 
} 

In C# fare qualcosa di simile:

[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); 

E nella tua classe:

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" 
} 

È possibile utilizzare scenari più complessi, ad esempio allocare e rilasciare la struttura in C++, utilizzando altre forme di marshalling per ripristinare i dati come se si stesse costruendo la propria classe per leggere e scrivere direttamente nella memoria non gestita. Ma se puoi usare una semplice struttura per contenere le tue variabili, il metodo sopra è il più semplice.

EDIT: indicazioni su come gestire i dati in modo corretto più complessi

Così il campione di cui sopra è a mio parere il modo corretto di dati "condivisione" tra C# e C++, se si tratta di semplici dati ad es. una struttura che contiene tipi primitivi o matrici di dimensioni fisse di tipi primitivi.

Detto questo ci sono in realtà pochissime limitazioni sul modo in cui è possibile accedere alla memoria tramite C#. Per ulteriori informazioni, esaminare la parola chiave non sicura, la parola chiave fissa e la struttura GCHandle. Eppure se hai una struttura di dati molto complessa che contiene array di altre strutture ecc., Allora hai un lavoro più complicato.

Nel caso sopra suggerirei di spostare la logica su come aggiornare le "variabili" in C++. Aggiungere in una funzione C++ a cercare qualcosa di simile:

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

Vorrei ancora suggerire di non utilizzare le variabili globali in modo propongo il seguente schema. In 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 
    } 
}; 

In C#:

[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); 

Se si vuole ancora mantenere voi logica in C# si dovrà fare qualcosa di simile al seguente: Creare una classe per tenere la memoria restituita da C++ e scrivi la tua logica di accesso alla memoria. Esporre i dati a C# usando la semantica della copia. Quello che voglio dire è il seguente, Diciamo che avete in C++ una struttura come questa:

#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() 

in C# si fa qualcosa di simile

[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; 
}; 

Nell'esempio sopra la struttura può ancora essere passato come nel primo campione. Questo, naturalmente, è solo uno schema su come gestire dati complessi con araay di strutture e array di strutture all'interno di matrici di strutture. Come puoi vedere, avrai bisogno di molte copie per proteggerti dalla corruzione della memoria. Inoltre, se la memoria viene allocata tramite C++, sarà molto brutto se si utilizza FreeHGlobal per liberarlo. Se vuoi evitare di copiare la memoria e mantenere la logica all'interno di C#, puoi scrivere un wrapper di memoria nativo con accessor per qualsiasi cosa desideri. Ad esempio, avrai un metodo per impostare direttamente o ottenere il subInt del membro dell'array Nth - This in questo modo manterrai le tue copie esattamente a ciò che accederai.

Un'altra opzione potrebbe essere quella di scrivere specifiche funzioni C++ per gestire i dati difficili per te e chiamarli da C# in base alla tua logica.

E, ultimo ma non meno importante, è sempre possibile utilizzare C++ con l'interfaccia CLI. Comunque io stesso lo faccio solo se devo - non mi piace il gergo ma per dati molto complessi devi certamente considerarlo.

EDIT

ho aggiunto la convenzione di chiamata corretto al DllImport per completezza. Si noti che la convenzione di chiamata predefinita utilizzata dall'attributo DllImport è Winapi (che in Windows si traduce in __stdcall) mentre la convenzione di chiamata predefinita in C/C++ (a meno che non si cambino le opzioni del compilatore) è __cdecl.

+0

Grazie per questa chiara risposta. L'ho svalutato, ma non sono ancora soddisfatto. Forse nella mia domanda ho mancato di specificare che ho già provato con P/invoke, ma in questo modo (se non sbaglio) devo passare tutte le variabili (ok, il riferimento) ad ogni iterazione. Mi stavo chiedendo se c'è un modo per evitarlo. – 888

+1

@mcdg Non c'è alcun problema a passare il riferimento alla struttura. Non c'è un sovraccarico significativo facendo così. In molti modi è molto meglio usare un qualche tipo di struttura o classe per mantenere lo stato. In questo modo potresti diventare multi-thread e far rientrare la tua funzione mentre usi variabili globali per limitare le tue possibilità. tuttavia, dato che hai aggiornato la tua domanda con una specifica di "variabili", aggiungerò alla risposta alcuni suggerimenti su come gestire casi più complessi –

3

La cosa migliore che si può fare è definire il proprio codice C++ (che sarà non gestito presumo). Quindi scrivi un wrapper per esso in C++/CLI. Il wrapper ti fornirà un'interfaccia per C# ed è il posto dove puoi prenderti cura di marchalling (spostamento dei dati da unmanagerd ad area gestita)

+1

Perché dovrebbero utilizzare C++/CLI per creare il wrapper? Potresti scrivere il wrapper in C# abbastanza facilmente. –

+1

C++/CLI è un po 'più flessibile nella gestione di tutti i dettagli, in particolare se si desidera passare dal codice gestito a quello non gestito. C++/CLI, combinando .NET e C++, non è "facile" se non sei almeno abituato al C++ (ma questa è solo la mia opinione). Quindi attenersi a C# potrebbe essere una scelta più saggia. – PapaAtHome

+0

Ho cambiato un po 'la domanda per spiegarmi meglio. – 888

1

Un modo per evitare di dover passare un riferimento alle variabili/dati richiesti sia dal codice C++ sia dal codice C++ consiste nell'esportare due funzioni dalla DLL nativa. Oltre alla funzione che svolge il lavoro, fornire un'altra funzione che consente il passaggio del riferimento e il salvataggio in un puntatore statico del file scope definito nello stesso file .cpp come entrambe le funzioni, in modo che sia accessibile a entrambi.

Come accennato, è possibile utilizzare anche un file mappato in memoria (in questo caso potrebbe non essere persistente poiché non è necessario scrivere sul disco).

Problemi correlati