2009-04-06 7 views
2

Ich versuche, eine nicht verwaltete C-DLL zum Laden von Bilddaten in eine C# -Anwendung zu verwenden. Die Bibliothek hat eine ziemlich einfache Schnittstelle, in der Sie eine Struktur übergeben, die drei Rückrufe enthält, einen, um die Größe des Bildes zu erhalten, einen, der jede Pixelreihe empfängt, und schließlich einen, der aufgerufen wird, wenn der Ladevorgang abgeschlossen ist. Wie dies (C# verwaltet Definition):Fixieren eines Delegaten innerhalb einer Struktur vor der Übergabe an nicht verwalteten Code

[System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)] 
public struct st_ImageProtocol 
{ 
    public st_ImageProtocol_done Done;  
    public st_ImageProtocol_setSize SetSize;  
    public st_ImageProtocol_sendLine SendLine; 
} 

Die Typen Start st_ImageProtocol sind delgates:

public delegate int st_ImageProtocol_sendLine(System.IntPtr localData, int rowNumber, System.IntPtr pixelData); 

Mit der Test-Datei, die ich die SetSize verwenden sollte einmal aufgerufen werden, dann wird die SendLine erhalten 200 Mal aufgerufen (einmal für jede Pixelreihe im Bild), wird schließlich der Fertig-Callback ausgelöst. Was tatsächlich passiert ist, dass die SendLine 19 Mal aufgerufen wird und dann eine AccessViolationException ausgelöst wird, die behauptet, dass die Bibliothek versucht hat, auf geschützten Speicher zuzugreifen.

Ich habe Zugriff auf den Code der C-Bibliothek (obwohl ich die Funktionalität nicht ändern kann) und während der Schleife, wo es die SendLine-Methode aufruft es keinen neuen Speicher zuweist oder freigibt, so meine Annahme ist, dass die Delegate selbst ist das Problem und ich muss es anheften, bevor ich es übergebe (ich habe derzeit keinen Code innerhalb des Delegaten selbst, außer einem Zähler, um zu sehen, wie oft er aufgerufen wird, also bezweifle ich, dass ich irgendetwas auf der verwalteten Seite breche). Das Problem ist, dass ich nicht weiß, wie ich das machen soll; Die Methode, die ich verwendet habe, um die Strukturen in nicht verwaltetem Raum zu deklarieren, funktioniert nicht mit Delegaten (Marshal.AllocHGlobal()) und ich kann keine andere geeignete Methode finden. Die Delegaten selbst sind statische Felder in der Program-Klasse, daher sollten sie nicht als Garbage Collected betrachtet werden, aber ich nehme an, dass die Runtime sie verschieben kann.

This blog entry by Chris Brumme sagt, dass die Delegierten müssen nicht vor dem in den nicht verwalteten Code übergeben gepinnt werden wird:

Klar, dass die nicht verwalteten Funktionszeiger auf eine feste Adresse beziehen. Es wäre eine Katastrophe, wenn der GC das verlagern würde! Dies führt dazu, dass viele Anwendungen ein Pinning-Handle für den Delegaten erstellen. Das ist völlig unnötig. Der nicht verwaltete Funktionszeiger verweist auf einen systemeigenen Code-Stub, den wir dynamisch erzeugen, um den Übergang & Marshalling durchzuführen. Dieser Stub existiert in einem festen Speicher außerhalb des GC-Heaps.

Aber ich weiß nicht, ob dies gilt, wenn der Delegat Teil einer Struktur ist. Es bedeutet jedoch, dass es möglich ist, sie manuell zu pinnen, und ich bin daran interessiert, wie ich dies oder irgendeinen besseren Vorschlag machen kann, warum eine Schleife 19 mal laufen würde und plötzlich scheitern würde.

Danke.


Edited Johans Fragen ... Antwort

Der Code, der die Struktur ordnet wie folgt:

_sendLineFunc = new st_ImageProtocol_sendLine(protocolSendLineStub); 

_imageProtocol = new st_ImageProtocol() 
        { 
          //Set some other properties... 
          SendLine = _sendLineFunc 
        }; 

int protocolSize = Marshal.SizeOf(_imageProtocol); 
_imageProtocolPtr = Marshal.AllocHGlobal(protocolSize); 
Marshal.StructureToPtr(_imageProtocol, _imageProtocolPtr, true); 

Wo die _sendLineFunc und die _imageProtocol Variablen beide statischen Felder des Programms sind Klasse. Wenn ich die Interna dieses richtig verstehe, bedeutet das, dass ich einen nicht verwalteten Zeiger an eine Kopie der _imageProtocol-Variable in die C-Bibliothek übergebe, aber diese Kopie enthält einen Verweis auf die statische _sendLineFunc. Dies sollte bedeuten, dass die Kopie nicht vom GC berührt wird - da sie nicht verwaltet wird - und der Delegat nicht erfasst wird, da er noch im Bereich ist (statisch).

Die struct tatsächlich in die Bibliothek als Rückgabewert von einem anderen Callback übergeben werden, aber als Zeiger:

private static IntPtr beginCallback(IntPtr localData, en_ImageType imageType) 
{ 
    return _imageProtocolPtr; 
} 

Grundsätzlich gibt es einen anderen struct-Typen, der die Bilddateinamen und die Funktionszeiger auf diesen Rückruf hält Die Bibliothek ermittelt, welcher Bildtyp in der Datei gespeichert ist, und verwendet diesen Rückruf, um die richtige Protokollstruktur für den angegebenen Typ anzufordern. Mein Dateinamen Struktur wird in der gleichen Weise wie das Protokoll einer oben angegebenen und verwaltet werden, so ist es wahrscheinlich die gleichen Fehler enthält, aber da dieser Delegierte nur einmal aufgerufen wird und rief schnell Ich habe keine Probleme mit ihm noch nicht gehabt.


Edited für ihre Antworten

Danke an alle zu aktualisieren, aber nach ein paar weiteren Tagen auf dem Problem und machen keine Fortschritte Ausgaben habe ich beschlossen, es beiseite zu legen. Falls jemand interessiert ich ein Tool für die Nutzer der Lightwave 3D-Rendering-Anwendung und ein nettes Feature die Möglichkeit, die verschiedenen Bildformate anzuzeigen schreiben versucht hat, die Lichtwellenstützen (einige davon sind ziemlich exotische) gewesen wäre. Ich dachte, dass der beste Weg dies zu tun wäre, einen C# -Wrapper für die Plugin-Architektur zu schreiben, die Lightwave für die Bildbearbeitung verwendet, so dass ich ihren Code verwenden könnte, um die Dateien tatsächlich zu laden. Leider nach einer Reihe von Plugins, gegen meine Lösung versuchen, hatte ich eine Vielzahl von Fehlern, die ich nicht verstehen konnte oder beheben und meine Vermutung ist, dass die Lichtwelle nicht die Methoden in üblichen Weise auf den Plugins nicht nennen, wahrscheinlich um die Sicherheit zu verbessern von laufendem externen Code (wilder Stich in der Dunkelheit, gebe ich zu). Vorläufig werde ich die Bildfunktion fallen lassen und wenn ich mich entscheide, sie wiederherzustellen, werde ich sie auf andere Weise angehen.

Nochmals vielen Dank, habe ich viel gelernt durch diesen Prozess, auch wenn ich nicht das Ergebnis bekommen habe ich wollte.

Antwort

0

Es wäre interessant, ein wenig mehr zu wissen:

  • Wie erstellen Sie die ImageProtocol Struktur? Ist es eine lokale Variable oder ein Klassenmitglied oder weisen Sie es in nicht verwaltetem Speicher mit Marshal.AllocHGlobal zu?

  • Wie ist es zu der C-Funktion gesendet? Direkt als Stapelvariable oder als Zeiger?


Ein wirklich kniffliges Problem! Es fühlt sich so an, als ob die Delegatendaten vom GC verschoben werden, der die Zugriffsverletzung verursacht. Die interessante Sache ist, dass der Delegat-Datentyp ein Referenzdatentyp ist, der seine Daten auf dem GC-Heap speichert. Diese Daten enthalten Dinge wie die Adresse der aufzurufenden Funktion (Funktionszeiger), aber auch eine Referenz auf das Objekt, das die Funktion enthält. Dies sollte bedeuten, dass, obwohl der tatsächliche Funktionscode außerhalb des GC-Heaps gespeichert ist, die Daten, die den Funktionszeiger enthalten, im GC-Heap gespeichert sind und daher vom GC verschoben werden können. Ich dachte an dem viel gestern Abend Problem aber noch nicht mit einer Lösung kommen ....

+0

Danke für die Fragen - Ich habe meine Frage mit einigen weiteren Details bearbeitet –

0

Sie nicht genau sagen, wie der Rückruf in der C-Bibliothek deklariert wird. Wenn es nicht ausdrücklich __stdcall deklariert wird, werden Sie Ihren Stack langsam beschädigen. Sie werden sehen, dass Ihre Methode aufgerufen wird (wahrscheinlich mit umgekehrten Parametern), aber irgendwann wird das Programm abstürzen.

Soweit ich weiß gibt es keine andere Möglichkeit, als das Schreiben einer anderen Callback-Funktion in C, die zwischen dem C# -Code und der Bibliothek sitzt, die einen __cdecl Rückruf wünscht.

2

Ich hatte ein ähnliches Problem bei der Registrierung eines Callback-Delegaten (es würde heißen, dann poof!). Mein Problem war, dass das Objekt mit der Methode, die delegiert wurde, GC'ed wurde. Ich habe das Objekt an einem globaleren Ort erschaffen, damit es nicht GC'ed wird.

Wenn so etwas nicht, hier funktioniert es einige andere Dinge zu sehen:

Als weitere Informationen, werfen Sie einen Blick auf GetFunctionPointerForDelegate aus der Klasse Marshal. Das ist eine andere Möglichkeit, dies zu tun. Stellen Sie nur sicher, dass die Delegierten nicht GC'ed sind. Deklarieren Sie sie dann anstelle von Delegaten in Ihrer Struktur als IntPtr.

Das löst möglicherweise nicht das Pinning, aber werfen Sie einen Blick auf fixed Schlüsselwort, auch wenn das nicht für Sie arbeiten kann, da Sie mit einer längeren Lebensdauer als für das, was normalerweise verwendet wird, beschäftigen.

Betrachten Sie schließlich stackalloc zum Erstellen von Nicht-GC-Speicher. Diese Methoden erfordern die Verwendung von unsafe und können daher andere Einschränkungen für Ihre Assemblies darstellen.

0

Wenn die c-Funktion eine __cdecl Funktion ist, dann müssen Sie das Attribut [UnmanagedFunctionPointer (CallingConvention.Cdecl)] vor der Delegatendeklaration verwenden.