2009-12-28 11 views
14

Ci scusiamo per l'introduzione dettagliata che segue. Ho bisogno di informazioni da qualcuno che conosce gli interni di P/Invoke meglio di me.C# P/Invoke: strutture di marshalling contenenti puntatori di funzioni

Ecco come sono le strutture di marshalling contenenti i puntatori di funzione da C a C#. Vorrei sapere se è il modo più pulito e/o più efficiente di farlo.

sto interfacciamento con una DLL nativa scritto in C che fornisce il seguente punto di ingresso:

void* getInterface(int id); 

Devi passare getInterface(int) uno dei seguenti valori enum:

enum INTERFACES 
{ 
    FOO, 
    BAR 
}; 

che restituisce un puntatore a una struttura contenente i puntatori a funzioni come:

typedef struct IFOO 
{ 
    void (*method1)(void* self, int a, float b); 
    void (*method2)(void* self, int a, float b, int c); 
} IFoo; 

A ND ecco come lo si utilizza in C:

IFoo* interface = (IFoo*)getInterface(FOO); 
interface->method1(obj, 0, 1.0f); // where obj is an instance of an object 
            // implementing the IFoo interface. 

In C# ho una classe Library che mappa il punto getInterface(int) immissione con P/Invoke.

class Library 
{ 
    [DllImport("MyDLL"), EntryPoint="getInterface", CallingConvention=CallingConvention.Cdecl)] 
    public static extern IntPtr GetInterface(int id); 
}; 

Poi ho definito:

struct IFoo 
{ 
    public M1 method1; 
    public M2 method2; 


    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void M1(IntPtr self, int a, float b); 

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate void M2(IntPtr self, int a, float b, int c); 
} 

e sto usando in questo modo:

IntPtr address = Library.GetInterface((int)Interfaces.FOO); 
IFoo i = (IFoo)Marshal.PtrToStructure(address, typeof(IFoo)); 

i.method1(obj, 0, 1.0f): // where obj is an instance of an object 
         // implementing the IFoo interface. 

Ho le seguenti domande:

  1. è la mappatura del intera struttura meno efficiente rispetto alla mappatura di un singolo po inter all'interno della struttura usando Marshal.GetDelegateForFunctionPointer()?

    Dal momento che la maggior parte non hanno bisogno di tutti i metodi esposti da un'interfaccia, che posso fare (testato e funziona):

    unsafe 
    { 
        IntPtr address = Library.GetInterface(id); 
        IntPtr m2address = new IntPtr(((void**)address.toPointer())[1]); 
    
        M2 method2 = (M2)Marshal.GetDelegateForFunctionPointer(m2address, typeof(M2)); 
    
        method2(obj, 0, 1.0f, 1); 
    } 
    
  2. Quando la mappatura dell'intera struttura contemporaneamente utilizzando Marshal.PtrToStructure(), c'è un meno modo verboso di quello che ho descritto? Voglio dire meno verboso di dover definire i tipi di delegati per ogni metodo, ecc?


EDIT: Per ragioni di chiarezza e completezza, nei frammenti di codice di cui sopra, obj è un'istanza ottenuto con il punto void* createObject(int type) voce.


EDIT2: Uno dei vantaggi di metodo 1) è che Marshal.GetDelegateForFunctionPointer() è disponibile solo a partire dal .NET Framework 2.0. Tuttavia, Marshal.PrtToStructure() è sempre stato disponibile. Detto questo, non sono sicuro che valga la pena di garantire la compatibilità 1.0 al giorno d'oggi.


Edit3: ho cercato di ispezionare il codice generato usando Reflector ma non dà molte informazioni dal momento che tutti i dettagli interessanti sono fatte in funzioni di supporto come PtrToStructureHelper e non sono esposti. Quindi, anche se potessi vedere che cosa è fatto all'interno del framework, allora il runtime ha l'opportunità di ottimizzare le cose e non so esattamente cosa, perché e quando :)

Tuttavia, ho confrontato i due approcci descritti nella mia domanda L'approccio Marshal.PtrToStructure() è stato più lento di un fattore di circa il 10% rispetto all'approccio Marshal.GetDelegateForFunctionPointer(); quello con una struttura contenente IntPtr s per tutte le funzioni che non sono di interesse.

Ho anche confrontato il Marshal.GetDelegateForFunctionPointer() con la mia marshaller laminati: Allineo un struct rappresenta lo stack di chiamate, perno nella memoria, passare il suo indirizzo al lato nativo cui uso un trampolino codificato in asm modo che gli usi funzione chiamata l'area di memoria come stack di parametri (ciò è possibile poiché la convenzione di chiamata xpassa tutti i parametri di funzione nello stack). I tempi erano equivalenti.

risposta

2

Non conosco quella risposta alla tua domanda 1. Mi aspetto che lo Marshal.PtrToStructure() sia implementato in termini di altri primitivi maresciallo, quindi sarebbe più efficiente utilizzare semplicemente il singolo Marshal.GetDelegateForFunctionPointer. Ma è solo una supposizione - vale quello che hai pagato per questo.

Come per la domanda 2. No, non c'è meno modo prolisso per fare ciò. C'è un modo PIÙ verboso. È possibile utilizzare il compilatore MIDL vecchio stile per creare una libreria di tipi per la DLL e il carico che digita la libreria. Ma le opzioni di marshalling disponibili per MIDL sono un po 'più limitate di ciò che puoi descrivere in C#. E il complatore MIDL è piuttosto difficile da lavorare, probabilmente si finirebbe per dover scrivere un'altra DLL non gestita per fare l'interp tra il codice gestito e la dll di destinazione.

+0

Informazioni su 1) Non so davvero se sia possibile verificare cosa è veramente fatto alla fine: come l'ottimizzatore può capire che viene utilizzato solo 1 delegato della struttura. Comunque almeno sembra abbastanza pulito. –

+0

@ Gregory Penso che sia improbabile. Ma potresti usare .NET Reflector per decompilare il codice e scoprirlo con certezza. –

3

Ecco cosa vorrei iniziare.

Usage:

IFoo foo = UnsafeNativeMethods.GetFooInterface(); 
foo.Method1(0, 1.0f); 

Implementazione:

internal interface IFoo 
{ 
    void Method1(int a, float b); 
    void Method2(int a, float b, int c); 
} 

internal static class UnsafeNativeMethods 
{ 
    public static IFoo GetFooInterface() 
    { 
     IntPtr self = GetInterface(InterfaceType.Foo); 
     NativeFoo nativeFoo = (NativeFoo)Marshal.PtrToStructure(self, typeof(NativeFoo)); 
     return new NativeFooWrapper(self, nativeFoo.Method1, nativeFoo.Method2); 
    } 

    [DllImport("mydll.dll", EntryPoint = "getInterface", CallingConvention = CallingConvention.Cdecl)] 
    private static extern IntPtr GetInterface(InterfaceType id); 

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    private delegate void Method1Delegate(IntPtr self, int a, float b); 

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    private delegate void Method2Delegate(IntPtr self, int a, float b, int c); 

    private enum InterfaceType 
    { 
     Foo, 
     Bar 
    } 

    private struct NativeFoo 
    { 
     public Method1Delegate Method1; 
     public Method2Delegate Method2; 
    } 

    private sealed class NativeFooWrapper : IFoo 
    { 
     private IntPtr _self; 
     private Method1Delegate _method1; 
     private Method2Delegate _method2; 

     public NativeFooWrapper(IntPtr self, Method1Delegate method1, Method2Delegate method2) 
     { 
      this._self = self; 
      this._method1 = method1; 
      this._method2 = method2; 
     } 

     public void Method1(int a, float b) 
     { 
      _method1(_self, a, b); 
     } 

     public void Method2(int a, float b, int c) 
     { 
      _method2(_self, a, b, c); 
     } 
    } 
} 
+0

Bene, questo è come lo sto facendo.È solo che questo tipo di "grazioso involucro" era irrilevante per la domanda. Quindi so che non l'ho spiegato nei dettagli, ma 'self' non è il puntatore alla struttura dell'interfaccia. È davvero un puntatore "questo". –

+0

La libreria nativa ha un punto di ingresso 'void * obj = createObject (int type)'. Lo si usa per istanziare i tipi, quindi se il tipo istanziato implementa l'interfaccia IFoo ('bool implements (void * obj, int iface)' restituisce 'true' per' obj' e 'FOO'), si è autorizzati a chiamare' ((IFoo *) getInterface (FOO)) -> method1 (obj, 0, 1.0f); ' –

+0

Questa modifica più semplice sarebbe l'aggiunta di un parametro' IntPtr self' a 'GetFooInterface'. Per un wrapper complesso come quello, creerei effettivamente 'Is ' e 'Come ' metodi per eseguire i cast. Se avessi fatto un ulteriore passo avanti, avrei creato dinamicamente delle classi di implementazione in modo tale che C# 'is' e gli operatori' as' lavorassero senza problemi. –

1

Per il punto 1:

Marshal.GetDelegateForFunctionPointer() è più semplice se la struttura contiene un sacco di puntatori a funzione e si utilizza solo alcuni. Uno svantaggio (maggiore) è che devi calcolare manualmente l'offset con il puntatore della funzione (nota che la dimensione del puntatore è diversa sulla piattaforma a 32/64 bit). Una struttura è più facile da usare ma ordina più dati.

Per il punto 2:

non credo che un approccio meno prolissa è possibile. È possibile definire i delegati solo per la funzione che si desidera utilizzare e utilizzare un delegato fittizio per i puntatori di funzione che non si desidera utilizzare. In questo modo, il marshalling funzionerà correttamente, ma si termina con una struttura contenente delegati non richiamabili.

+0

Ok sembra che ci sia un consenso: quando si fa il marshalling dell'intera struttura, si paga per quello che non si può usare. A proposito della dimensione del puntatore, 'new IntPtr ((void **) address.toPointer()) [1])' ottiene correttamente qualunque sia la dimensione. –

+0

E sì, l'unica opzione meno dettagliata che utilizzo durante il marshalling dell'intera struttura consiste nel definire solo i delegati di cui ho bisogno e utilizzare raw IntPtr per i restanti punti di ingresso –

Problemi correlati