2016-10-11 3 views
0

Ich schreibe einen C# -Wrapper für eine native Bibliothek. Es enthält dieses Callback-Funktion:C# nativer Rückruf mit Zeiger auf Strukturparameter

typedef application_event_result(*application_event_ptr)(application_request* request); 

Der Parameter als solche definiert ist:

typedef struct { 
    uint32_t query; 
    const char* client; 
    bool  isAuthenticated; 
    bool  isGuest; 
} application_request; 

I die C# Rückruf definiert haben Delegierten wie folgt aus:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)] 
    public delegate application_event_result application_event([MarshalAs(UnmanagedType.Struct)] 
     ref application_request request); 

Die Struktur in C#:

[StructLayout(LayoutKind.Sequential)] 
public struct application_request 
{ 
    public UInt32 query; 

    [MarshalAs(UnmanagedType.LPStr)] 
    public string client; 

    [MarshalAs(UnmanagedType.I1)] 
    public bool isAuthenticated; 

    [MarshalAs(UnmanagedType.I1)] 
    public bool isGuest; 
} 

Das alles scheint zu funktionieren. Der Rückruf in C# wird ausgelöst, und die Mitglieder der Struktur haben die erwarteten Werte.

Bei der Rückkehr zu systemeigenem Code wird jedoch eine Heap-Korruptionsausnahme ausgelöst (0xc0000374).

Offensichtlich möchte ich das vermeiden.

Wenn ich die C# -Rückrufsignatur so ändern, dass IntPtr anstelle des Parameters "ref application_request" verwendet wird, und Marshal es manuell mit dem folgenden Code ausführen, funktioniert es.

var request = Marshal.PtrToStructure<application_request>(requestptr); 

Aber ich möchte die Unterschrift so genau wie möglich haben und nicht den Marshaler selbst verwenden müssen.

Gibt es eine Möglichkeit für mich, die Unterschrift des Callback-Delegierten zu ändern, damit .net die Struktur automatisch konvertieren kann?

+0

Der Fehler tritt auf, weil die Größe des Rückgabeparameters nicht konsistent ist. IntPtr ist vier Bytes, also müssen Sie in C# den Rückgabeparameter als vier Bytes deklarieren. Die Position des Zeigers muss auch im statischen Speicherbereich liegen, damit sowohl der c- als auch der C# -Code zusammenarbeiten können. Ein IntPtr funktioniert, da er sich in nicht verwaltetem statischem Speicher befindet. Ein C# -Objekt wird verwaltet, das einen Fehler verursacht, wenn es in nicht verwaltetem Speicherbereich platziert wird. – jdweng

+0

Das Problem besteht nicht mit dem Rückgabewert, sondern mit dem Eingabeparameter pointer-to-struct. Gibt es eine Möglichkeit, das zu markieren, so dass es in nicht verwalteten Platz gebracht wird? – RasmusW

+0

@jdweng der Rückgabewert sieht aus wie ein Enum Nein? –

Antwort

1

Ihr Problem ist das char* Mitglied der struct. Der C# -M Marshaler geht davon aus, dass er für die Freigabe dieses Speichers zuständig ist. Dies geschieht durch den Aufruf CoTaskMemFree. Ich denke, es ist ziemlich klar, dass der Speicher nicht durch den C# -Code überhaupt zerstört werden soll.

Marshal, dass Mitglied als IntPtr statt:

[StructLayout(LayoutKind.Sequential)] 
public struct application_request 
{ 
    public UInt32 query; 

    public IntPtr client; 

    [MarshalAs(UnmanagedType.I1)] 
    public bool isAuthenticated; 

    [MarshalAs(UnmanagedType.I1)] 
    public bool isGuest; 
} 

Innerhalb Ihrer Callback-Methode, die Sie den Wert der Zeichenfolge durch den Aufruf Marshal.PtrToStringAnsi lesen kann.

Sie schließen dies ab, indem Sie die Mitglieder privat machen und sie über Eigenschaften verfügbar machen. Dadurch können Sie die Konvertierung von Zeiger in Zeichenfolge kapseln.

+0

Super, das funktioniert. Und danke für die gute Idee, das IntPtr in eine Eigenschaft zu packen, die die Konvertierung macht: public string Client => Marshal.PtrToStringAnsi (Client); Ich wusste nicht, dass die Struktureigenschaften privat sein könnten und der C# Marshaller sie immer noch finden konnte. – RasmusW