ctypes Modul kann nicht mehrere DLLs laden

Python in C/C++ embedden, C-Module, ctypes, Cython, SWIG, SIP etc sind hier richtig.
Antworten
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Hallo liebe Community,

ich versuche seit Tagen ein Problem zu lösen, jedoch bisher ohne Erfolg und langsam gehen mir die Alternativen aus :(. Ich nutze das ctypes Modul und lade der Reihe nach DLL-Dateien, die ich während der Laufzeit verwenden möchte.

Ich habe via

Code: Alles auswählen

from ctypes import CDLL
eine Instanz der Klasse CDLL erzeugt und rufe dann immer cdll.loadlibrary() auf. Die erste DLL die ich übergebe wird ordnungsgemäß geladen und ich kann sie verwenden, jedoch wenn ich die Instanz zerstöre wird die DLL nicht entladen. Sobald ich eine neue Instanz erzeuge und dann wieder cdll.loadlibrary() aufrufe wird wieder die vorherige DLL geladen anstatt die die ich aktuell übergeben habe. Ich habe schon versucht über die win32api für python mit loadlibrary() und freelibrary() das ganze selber zu handlen, aber es funtzt auch nicht. Ich hoffe Ihr habt eine Lösung, sonst ist eine Menge Arbeit für die Katz. Sollte der Sachverhalt nicht ganz klar sein, poste ich gerne noch Quellcode

Vielen Dank im voraus und haltet die Ohren steif ;)

MfG

marvel82
BlackJack

@marvel82: Der Sachverhalt ist nicht klar. Bitte mehr Infos.
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Folgender Pseudocode sollte weiterhelfen:

Code: Alles auswählen

from ctypes import CDLL

...
cdll = CDLL()
cdll.LoadLibrary("erste.dll") 
cdll.machirgendwas()
del cdll
...
cdll = CDLL()
cdll.LoadLibrary("zweite.dll") 
cdll.machwiederwas()
del cdll
...
cdll = CDLL()
cdll.LoadLibrary("dritte.dll") 
cdll.machwas()
del cdll
...
BlackJack

@marvel82: Pseudocode der falsch ist, also nicht funktionieren kann, hilft nicht so viel weiter. Und eine Fehlerbeschreibung wäre neben lauffähigem Quelltext auch nicht schlecht. Das da führt jedenfalls zu einem `TypeError` weil bei `CDLL()` der Name der Bibliothek fehlt.

Die ``del``-Anweisungen sind überflüssig, die bewirken nicht wirklich etwas, ausser eventuell die Letzte. Aber mit ``del`` versuchen wollen Speicher zu verwalten, sollte man sich gar nicht erst angewöhnen. Das löscht keine Objekte sondern Namen. Das kann eventuell, und irgendwann einmal dazu führen, dass auch Speicher von Objekten freigegeben wird, aber ob und wann das passiert, ist nicht garantiert.
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Danke für die konstruktive Kritik. Mit dem Pseudocode habe ich improvisiert, da ich erst morgen an den Quellcode rankomme und dachte der Sachverhalt wäre auch so verständlich. Ich werde morgen dann eine neue Beschreibung meines Problems inklusive Quellcode angeben.

Gruss

marvel82
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Guten Morgen zusammen,

hier der passende Quellcode zu meinem oben beschriebenen Problem

Code: Alles auswählen

#wrapper.py
from ctypes import CDLL, c_wchar_p, c_int, c_bool, c_wchar, POINTER

#==============================================================================
class CmdDLL(object):
    """Wrapper-Klasse für Application-DLL"""        
   
    #--------------------------------------------------------------------------
    def __init__(self):
    #--------------------------------------------------------------------------
        """Konstruktor
        """
        self._cmddll = None        
  
    #--------------------------------------------------------------------------
    def __del__(self):
    #--------------------------------------------------------------------------
        """Destruktor
        """                                   
 
    #--------------------------------------------------------------------------
    def load_dll(self, appdll=None):
    #--------------------------------------------------------------------------
        
        # Application-DLL laden
        try:
            self._cmddll = CDLL(appdll)  
        except: 
            return False

        # Typen für Parameterliste und Returnwert festlegen
        self._cmddll.initial_update.argtypes = [c_wchar_p, c_wchar_p]
        self._cmddll.initial_update.restype = c_bool

        self._cmddll.new_document.argtypes = [c_wchar_p]
        self._cmddll.new_document.restype = c_bool

        self._cmddll.load_document.argtypes = [c_wchar_p]
        self._cmddll.load_document.restype = c_bool

        self._cmddll.execute_command.argtypes = [c_wchar_p]
        self._cmddll.execute_command.restype = c_int

        self._cmddll.exists.argtypes = [c_wchar_p]
        self._cmddll.exists.restype = c_bool

        self._cmddll.get_value.argtypes = [c_wchar_p]
        self._cmddll.get_value.restype = c_wchar_p

        self._cmddll.set_value.argtypes = [c_wchar_p, c_wchar_p]
        self._cmddll.set_value.restype = c_int

        self._cmddll.save_document.restype = c_bool

        self._cmddll.save_document_as.argtypes = [c_wchar_p]
        self._cmddll.save_document_as.restype = c_bool

        self._cmddll.close_document.restype = c_bool

        self._cmddll.get_last_error.restype = c_wchar_p

        self._cmddll.generate_documentation.argtypes = [c_wchar_p]
        self._cmddll.generate_documentation.restype = c_bool

        # Funktionsnamen für die Funktionen, die einfach an die DLL
        # weitergeleitet werden.
        self.new_document = self._cmddll.new_document
        self.load_document = self._cmddll.load_document
        self.execute_command = self._cmddll.execute_command
        self.exists = self._cmddll.exists
        self.get_value = self._cmddll.get_value
        self.set_value = self._cmddll.set_value
        self.save_document = self._cmddll.save_document
        self.save_document_as = self._cmddll.save_document_as
        self.close_document = self._cmddll.close_document
        self.get_last_error = self._cmddll.get_last_error
        self.generate_documentation = self._cmddll.generate_documentation

        return True
        

Code: Alles auswählen

#------------------------------------------------------------------------------
def startTest(appdll_path, logicdll_file, reference_files_path):
#------------------------------------------------------------------------------
...
 # Wrapper erzeugen und Application-DLL laden
    cmd = CmdDLL()
    ret = cmd.load_dll(appdll_path)
    if not ret:
        log(u"Fehler: Konnte '%s' nicht laden." %appdll_file)
        return
...

Nochmal als Zusammenfassung:

Ich erzeuge während der Laufzeit Instanzen der Klasse CmdDLL, rufe dann die Funktion load_dll() auf und übergebe ihr die gewünschte dll. das klappt auch wunderbar, nur sobald die Funktion startTest() abgearbeitet ist und ich sie zu einem späteren zeitpunkt wieder aufrufe mit anderen argumenten wird bei der erzeugung einer neuer instanz der Klasse CmdDLL die gleiche dll geladen, obwohl ich eine völlig andere übergebe. deshalb bin ich davon ausgegangen das die vorher geladene dll nicht ordnungsgemäss entladen wird, obwohl ich die instanz zerstöre. ich hoffe es ist jetzt klarer :)

mfg

marvel82
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Hallo nochmal,

ich habe eine Lösung gefunden !

Das Problem bei dem Modul "ctypes" ist das es die DLL erst entladen kann sobald der gesamte Prozess abgearbeitet, sprich Pyhton beendet wurde. Fakt ist das man mit der aktuellen Implementierung von ctypes keine Möglichkeit hat während der Laufzeit unterschiedliche DLLs zu laden. Abhilfe kann das Modul "multiproccessing" verschaffen, indem man jeden Funktionsaufruf in eigene Prozesse auslagert.

Ich hoffe meinen Erkenntnis bringt euch etwas ;)

MfG

marvel82
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

marvel82 hat geschrieben:Fakt ist das man mit der aktuellen Implementierung von ctypes keine Möglichkeit hat während der Laufzeit unterschiedliche DLLs zu laden.
Quatsch.
marvel82 hat geschrieben:Abhilfe kann das Modul "multiproccessing" verschaffen, indem man jeden Funktionsaufruf in eigene Prozesse auslagert.
Blödsinn.
marvel82 hat geschrieben:Ich hoffe meinen Erkenntnis bringt euch etwas ;)
Ja, hoffentlich nimmt das keiner Ernst.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Hallo Leonidas,

konstruktive Antworten wie "Quatsch, Blödsinn, Ja, hoffentlich nimmt das keiner Ernst" sind natürlich sehr hilfreich. Im moment hat dein Post den Wert von "Total überflüssig", ausser das du in den Raum schmeisst das ich falsch liege. Solltest du jedoch stichhaltige Beweise haben die meine Aussage widerlegen wäre dies anhand eines Beispiels oder Quellcode von Vorteil. So habe ich erstens etwas dazugelernt und zweitens kann jeder User der ein ähnliches Problem hat das Ganze nachvollziehen. Danke im voraus

MfG

marvel82
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Hallo marvel82,
marvel82 hat geschrieben:konstruktive Antworten wie "Quatsch, Blödsinn, Ja, hoffentlich nimmt das keiner Ernst" sind natürlich sehr hilfreich. Im moment hat dein Post den Wert von "Total überflüssig", ausser das du in den Raum schmeisst das ich falsch liege.
Das war auch meine Absicht, denn das ist ja auch völlig an den Haaren herbeigezogen was du da behauptest und dann auch noch eventuell anderen empfiehst. Daher wollte ich das schnell widerlegen, bevor andere auf die Idee kommen, das ernst zu nehmen. Wenn du das nicht empfohlen hättest, dann hätte ich nicht so schnell widersprochen.
marvel82 hat geschrieben:Solltest du jedoch stichhaltige Beweise haben die meine Aussage widerlegen wäre dies anhand eines Beispiels oder Quellcode von Vorteil. So habe ich erstens etwas dazugelernt und zweitens kann jeder User der ein ähnliches Problem hat das Ganze nachvollziehen.
Finde es bischen seltsam dass du deine Aussage so einfach in den Raum stellen darfst, aber ich meine Aussage mit funktionierendem Quellcode untermauern soll. Aber hey, hier bitteschön:

Code: Alles auswählen

import ctypes
from ctypes.util import find_library

libc = ctypes.CDLL(find_library("c"))
libm = ctypes.CDLL(find_library("m"))

libc.printf("String formatting, %d\n", 42)
libm.sqrt.restype = ctypes.c_double
libm.sqrt.argtypes = [ctypes.c_double]
print libm.sqrt(23)
Ich importiere hier libc um printf mit String-Formatting zu nutzen und libm um dort die Quadratwurzelfunktion zu nutzen. Ausgabe auf dem Terminal sieht dann so aus:

Code: Alles auswählen

String formatting, 42
4.79583152331
(Ja, die Quadratwurzel ist auch richtig, habe das mit dem GNOME-Taschenrechner gegengeprüft)
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Hmm,

genauso hatte ich mir das auch vorgestellt, jedoch läd er wie bereits erwähnt nur die erste dll. sobald ich die zweite dll übergebe ist es so als würde er dies einfach ignorieren und weiterhin mit der ersten arbeiten. benutzt du ein linux, ich arbeite auf windoof 7. danke für dein beispiel.
deets

@marvel82

Es kann schlicht nicht sein, was du da behauptest. Denn ctypes ist nur ein duenner Layer ueber dem Loader und der LibFFI. Und *natuerlich* kann man diverse libs laden. Man kann sie allerdings nicht entladen, auch nicht durch del oder sonstewas. Das unterstuetzt schlicht das Betriebssystem nicht. Und damit ist das Problem aller Wahrscheinlichkeit nach in deinem speziellen Usecase zu finden. Kann es sein, dass du immer *dieselbe* DLL (bezogen auf Namen + Funktionen) laden willst - nur in verschiedenen Inkarnationen? Dann ist das so weit logisch, denn das koennen die Loader nicht. Einmal im Prozessraum, immer im Prozessraum.

Abhilfe wuerde uU ein umbenennen vor dem Laden bringen - das muesste man mal ausprobieren. Das ist ein Trick, wie man zB mehrere parallele Python-Interpreter in einen Prozessraum bekommt.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

marvel82 hat geschrieben:sobald ich die zweite dll übergebe ist es so als würde er dies einfach ignorieren und weiterhin mit der ersten arbeiten. benutzt du ein linux, ich arbeite auf windoof 7.
Ja, ich nutze GNU/Linux, aber das tut hier kaum was zur Sache, denn so ein Beispiel würde (gegebenfalls mit anderen DLLs) auf Windows laufen. Jetzt bist du mal an der Reihe uns einen Quellcode zu posten, der nicht funktioniert. Weil so kann man nur rumraten und das verschwendet unser aller Zeit.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Den Quellcode hatte ich bereits weiter oben gepostet, also Erzeugung einer Instanz der Wrapper-Klasse in der ctypes verwendet wird. Denke das ist soweit das wichtigste.
Leonidas
Python-Forum Veteran
Beiträge: 16025
Registriert: Freitag 20. Juni 2003, 16:30
Kontaktdaten:

Da fehlen die nötigen Angaben. Wie etwa was die beiden DLLs sind, die du zu Laden versuchst.
My god, it's full of CARs! | Leonidasvoice vs (former) Modvoice
EyDu
User
Beiträge: 4881
Registriert: Donnerstag 20. Juli 2006, 23:06
Wohnort: Berlin

marvel82 hat geschrieben:Den Quellcode hatte ich bereits weiter oben gepostet, also Erzeugung einer Instanz der Wrapper-Klasse in der ctypes verwendet wird. Denke das ist soweit das wichtigste.
Der Code ist weder lauffähig noch annähernd minimal. Wie soll man da das von dir beschriebene Verhalten tesen können?
Das Leben ist wie ein Tennisball.
marvel82
User
Beiträge: 11
Registriert: Montag 30. April 2012, 10:33

Die Dlls die ich übergebe sind von mir selber implementiert worden, also funktionen in c++ mit c schnittstelle für python.
Benutzeravatar
Hyperion
Moderator
Beiträge: 7478
Registriert: Freitag 4. August 2006, 14:56
Wohnort: Hamburg
Kontaktdaten:

marvel82 hat geschrieben:Die Dlls die ich übergebe sind von mir selber implementiert worden, also funktionen in c++ mit c schnittstelle für python.
Dann sollte es ja noch weniger ein Problem sein, diese zu veröffentlichen. Kannst ja auch den Quellcode veröffentlichen und dazu ggf. Infos zum Kompilieren. Es soll hier ja einige geben, die sich so etwas dann schon selber backen können ;-)
encoding_kapiert = all(verstehen(lesen(info)) for info in (Leonidas Folien, Blog, Folien & Text inkl. Python3, utf-8 everywhere))
assert encoding_kapiert
jerch
User
Beiträge: 1669
Registriert: Mittwoch 4. März 2009, 14:19

Auch unter C/C++ kannst Du mit dlclose resp. FreeLibrary das "Entladen" einer shared lib aus dem Speicher nicht erzwingen.

Ich kann Dein Problem in keinster Weise nachvollziehen, von Assembler-Libs über Pascal- bis C/C++-Libs lädt ctypes alles und das auch mehrfach, solange es den Containerformaten und unterstützten Aufrufkonventionen entspricht. Hast Du mal versucht, Deine Libs in C einzubinden? Vllt. fehlt irgendwo eine Directive beim Erstellen der Libs?

Ein auf das Wesentliche eingekürztes Bsp. wäre hilfreich.
Antworten