Übergeben Sie FILE * in Python / ctypes

9

Ich habe eine Bibliotheksfunktion (in C geschrieben), die Text erzeugt, indem die Ausgabe in FILE * geschrieben wird. Ich möchte das in Python (2.7.x) mit Code umwandeln, der eine temporäre Datei oder eine Pipe erstellt, diese in die Funktion übergibt, das Ergebnis aus der Datei liest und es als Python-String zurückgibt.

Hier ist ein vereinfachtes Beispiel, um zu veranschaulichen, wonach ich suche:

%Vor%

Python-Wrapper:

%Vor%

Ich frage mich, was meine beste Wette für MAGIC_HERE() ist.

Ich bin versucht, einfach ctypes zu verwenden und einen libc.fdopen() -Wrapper zu erstellen, der ein Python c_void_t zurückgibt und dann in die Bibliotheksfunktion übergibt. Ich bin der Meinung, dass das in der Theorie sicher sein sollte - ich frage mich nur, ob es Probleme mit diesem Ansatz oder einem existierenden Python-System gibt, um dieses Problem zu lösen.

Auch das wird in einem lang andauernden Prozess laufen (nehmen wir einfach "für immer" an), so dass alle durchgesickerten Dateideskriptoren problematisch werden.

    
Brian McFarland 23.10.2015, 20:13
quelle

1 Antwort

3

Beachten Sie zunächst, dass FILE* eine stdio-spezifische Entität ist. Es existiert nicht auf Systemebene. Die Dinge, die auf Systemebene existieren, sind Deskriptoren (abgerufen mit file.fileno() ) in UNIX ( os.pipe() gibt bereits einfache Deskriptoren zurück) und handle (abgerufen mit msvcrt.get_osfhandle() ) in Windows. Daher ist es eine schlechte Wahl als Interbibliothek-Austauschformat, wenn mehr als eine C-Laufzeit aktiv sein kann. Sie werden Probleme bekommen, wenn Ihre Bibliothek gegen eine andere C-Laufzeit als Ihre Kopie kompiliert wird Python: 1) binäre Layouts der Struktur können sich unterscheiden (z. B. aufgrund von Alignment oder zusätzlichen Membern zu Debugging-Zwecken oder sogar unterschiedlichen Schriftgrößen); 2) In Windows sind Dateideskriptoren, mit denen die Struktur verknüpft ist, ebenfalls C-spezifische Entitäten, und ihre Tabelle wird intern von einer C-Laufzeit verwaltet 1 .

Außerdem wurde I / O in Python 3 überarbeitet, um es von stdio zu entwirren. Also, FILE* ist dem Python-Geschmack (und wahrscheinlich auch den meisten Nicht-C-Geschmacksrichtungen) fremd.

Nun, was Sie brauchen, ist

  • rate irgendwie, welche C-Laufzeit du brauchst, und
  • nennen Sie fdopen() (oder gleichwertig).

(Einer der Python-Mottos ist "mach das richtige Ding einfach und das falsche Ding schwer", immerhin)

Die sauberste Methode besteht darin, genau die Instanz zu verwenden, mit der die Bibliothek verknüpft ist (beten Sie, dass sie dynamisch mit ihr verknüpft ist oder dass kein exportierendes Symbol aufgerufen wird)

Zum ersten Punkt konnte ich keine Python-Module finden, die die Metadaten der geladenen dynamischen Module analysieren könnten, um herauszufinden, mit welchen DLLs sie verknüpft sind (nur ein Name oder gar ein Name + Version reicht nicht aus, Sie wissen, aufgrund der möglichen mehreren Instanzen der Bibliothek auf dem System). Obwohl es definitiv möglich ist, da die Informationen über sein Format weit verbreitet sind.

Für den zweiten Punkt ist es ein triviales ctypes.cdll('path').fdopen ( _fdopen für MSVCRT).

Zweitens können Sie ein kleines Hilfsmodul erstellen, das mit derselben (oder einer mit der Laufzeit kompatiblen) Laufzeit wie die Bibliothek kompiliert wird und die Konvertierung von dem oben genannten Deskriptor / Handle für Sie übernimmt. Das ist effektiv eine Problemumgehung zum Bearbeiten der Bibliothek.

Schließlich gibt es die einfachste (und schmutzigste) Methode, die Pythons C-Laufzeit-Instanz verwendet (so dass alle obigen Warnungen vollständig gelten) über die Python-C-API, die über ctypes.pythonapi . Es nutzt die Vorteile von

  • die Tatsache, dass die fileartigen Objekte von Python 2 Wrapper über stdio s FILE* sind (Python 3's nicht)
  • PyFile_AsFile API, die das umgebrochene FILE* ( Beachten Sie, dass in Python 3 fehlt )
    • Für ein eigenständiges fd müssen Sie zuerst ein dateiähnliches Objekt erstellen (so dass ein FILE* zurückgegeben wird;))
  • die Tatsache, dass id() eines Objekts seine Speicheradresse ist (CPython-spezifisch) 2

    %Vor%

Beachten Sie, dass Sie mit den fd s- und C-Zeigern die richtigen Objektlebensdauern von Hand sicherstellen müssen!

  • dateiähnliche Objekte, die von os.fdopen() zurückgegeben werden, schließen den Deskriptor auf .close()
    • so doppelte Deskriptoren mit os.dup() , wenn Sie sie brauchen, nachdem ein Dateiobjekt geschlossen wurde / Garbage Collected
  • Wenn Sie mit der C-Struktur arbeiten, passen Sie die Referenzzahl des entsprechenden Objekts mit PyFile_IncUseCount() / PyFile_DecUseCount() .
  • stellt keine anderen I / O auf den Deskriptoren / Dateiobjekten sicher, da dies die Daten beschädigen würde (zB seit Aufruf von iter(f) / for l in f , internes Caching ist unabhängig von stdio 's Caching) / li>
ivan_pozdeev 23.10.2015, 20:42
quelle

Tags und Links