Buchempfehlung
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
Windows-Programmierung. Das Entwicklerhandbuch zur WIN32-API
"Der" Petzold, das über 1000 Seiten starke Standardwerk zum Win32-API - besonders nützlich u. a. bei der GUI-Programmierung in FreeBASIC! [Mehr Infos...]
FreeBASIC-Chat
Es sind Benutzer im FreeBASIC-Chat online.
(Stand:  )
FreeBASIC bei Twitter
Twitter FreeBASIC-Nachrichten jetzt auch über Twitter erhalten. Follow us!

Tutorial

PlugIn Programmierung mit DLL / so

von MitgliedThePuppetMasterSeite 1 von 1

Aufgrund der Komplexität habe ich mal die beschreibung im sourcecode vorgenommen.


Die Anwendung, welche Plugins verwaltet

'########################################################################################################################################
'Eine Anwendung mit PlugIn's
'########################################################################################################################################
'Dies ist die Hauptanwendung. Sie beinhaltet alles um Plugin's (DLL's / SO's) zu laden, zu verwalten und funktionen in ihr aufzurufen
'########################################################################################################################################





'########################################################################################################################################
'Diese Struktur beinhaltet Funktion's definitionen (deklarationen) aus dieser Anwendung.
'Damit eine DLL auf Funktionen von dieser Anwendung aufrufen kann, müssen wir die Pointer auf die Funktionen der DLL mitteilen.
'Damit das nicht zu kompliziert wird, und wir es übersichtlicher und einfacher zu händeln haben, packen wie die Definitionen in ein UDT
Type Plugin_CallBack_Type
    V_T                         as UByte        'Leider MUSS man in ein Type eine reguläre Variable (UByte, Byte, Integer, ....) packen
                                                'Ohne eine normale variable, und nur mit funktions / sub deklarationen würde der code
                                                'nicht kompilieren und fehlermeldungen ausspucken.

    'Hier definieren wir eine Funktion aus diesem programm.
    'Sie wird später beim "PluginLoad" an die DLL übergeben.
    V_AnwendungDo               as Function (V_Was as String, V_Womit as String) as String
End Type

'Eine Variable definieren, welche dieses UDT speichert und die Pointer zu den Funktionen
Dim Shared Plugin_Callback as Plugin_CallBack_Type





'########################################################################################################################################
'Das ist ein Array, welches die Plugins verwalten wird.
'Dazu erzeugen wir variablen, die uns die identifikation der DLL ermöglichen und die deklarationen der Funktionen / Sub's
'Wenn wir auf funktionen in einer DLL zugreifen wollen, müssen diese vordeklariert werden. Damit das ganze bei mehreren DLLs, vorallem
'wenn wir nicht wissen, wie viele es sind, nicht zu einem Variablenchaos kommt, erzeugen wir ein UDT, das alle grundlegenden Funktionen
'beinhaltet, auf die wir zugreifen wollen / können
Type Plugin_Type
    'Zum überprüfen, ob dieses ArrayFeld noch frei bzw. belegt ist.
    V_InUse                     as UByte

    'Eine Bezeichnung zum einfacheren ansprechen
    V_Name                      as String

    'Der Handler ist ein Pointer auf die DLL selbst. (Rückgabe von DyLibLoad). Das wird zum entladen und zum linken der Funktionen benötigt
    V_Handler                   as Any Ptr

    'Jetzt definieren wir die "Prototypen" (auch deklaration genannt), die in der DLL vorkommen.
    'Diese müssen vordefiniert werden und sollten (grundsätzlich bei plugins) in jeder Plugin-DLL identisch sein
    V_PluginFunktion_Init       as Sub      (CallbackInterface as Plugin_CallBack_Type Ptr)
    V_PluginFunktion_GetDesc    as Function () as String
    V_PluginFunktion_Do         as Function (V_Was as String, V_Womit as String) as String
End Type

'Das Array, welches die Plugin's festhalten wird.
Dim Shared PluginD()            as Plugin_Type
Dim Shared PluginC              as UInteger





'########################################################################################################################################
'Diese fuktion stellt uns eine einfache möglichkeit bereit, ein Plugin zu laden. Benötigt wird nur der Name des Plugins
Function Plugin_Load(V_PluginName as String) as UInteger
'Zuerst einmal den Namen in kleinschrift speichern. (Das macht ihn Linux und Windows fähig, sofern die Plugin's IMMER in kleinschrift kompiliert werden)
Dim S as String = LCase(V_PluginName)

'Anschliessend Durchlaufen wir das PluginArray, um zu überprüfen, ob wir dieses schon einmal geladen hatten
For X as UInteger = 1 to PluginC
    'Array schon belegt?
    If PluginD(X).V_InUse = 1 Then
        'Ist dieses belegte Array das gesuchte?
        If PlugInD(X).V_Name = S Then
            'Dann geben wir die Indexnummer zurück.
            Return X
        End If
    End If
Next

'Wurde das Plugin nicht gefunden, wurde es offensichtlich noch ncith geladen.
'Darum prüfen, ob die DLL überhaupt existiert. (Die DLL muss im selben verzeichniss liegen, wie das programm auch. Andernfalls muss der Pfad
'mit angegeben werden!
#IF Defined(__FB_Linux__)
    'Für linux suchen wir nach der .so datei. Wird sie nicht gefunden, dann 0 zurück geben und funktion verlassen. (0 = fehler)
    If Dir("lib" & S & ".so", -1) = "" Then Return 0
#ELSEIF Defined(__FB_Win32__)
    'Für windows suchen wir nach der .dll datei. Wird sie nicht gefunden, dann 0 zurück geben und funktion verlassen. (0 = fehler)
    If Dir(S & ".dll", -1) = "" Then Return 0
#ELSE
    'Andere Betriebssysteme werden nicht unterstützt
    #ERROR "Platform not supportet!"
#ENDIF

'Eine Struktur vorbereiten. Sie wird usn temporär die Daten speichern, welche wir später in das Array kopieren. Das spart uns viel arbeite
'falls fehler beim laden auftreten, und wir ein erzeugtes array-element löschen müssten, was so nicht nötig werden würde.
'auserdem spart es etwas zeit.
Dim TPLI as Plugin_Type

With TPLI
    'Damit wir später keine Probleme beim setzen dieser Struktur in das Array bekommen, setzten wir hier schon einmal
    'das "In Benutzung"'s flag auf 1
    .V_InUse        = 1

    'Erstmal den namen hinterlegen
    .V_Name         = S

    'Jetzt versuchen wir die DLL zu laden.
    #IF Defined(__FB_Linux__)
        'Einmal für Linux
        .V_Handler      = DyLibLoad("lib" & .V_Name & ".so")
    #ELSEIF Defined(__FB_Win32__)
        'und für windows
        .V_Handler      = DyLibLoad(.V_Name & ".dll")
    #ELSE
        'Andere Betriebssysteme werden nicht unterstützt
        #ERROR "Platform not supportet!"
    #ENDIF

    'Anschliessend überprüfen, ob das laden erfolgreich war. Wenn nicht, dann mit 0 verlassen
    If .V_Handler = 0 Then Return 0

    'Andernfalls scheint alles ok zu sein, und wir können anfangen die Funktionen aus der DLL heraus zu verlinken.
    'Dazu speichern wir den Pointer der uns DyLibSymbol zurück gibt, in die Funktionsdeklaration des UDT's.
    'DyLibSymbol verlangt von und einmal den Pointer auf die DLL selbst (.V_Handler) und einmal den Namen der Funktion in der DLL in "".
    'Dabei muss der Name IMMER! Gross geschrieben sein!!
    .V_PluginFunktion_Init          = DyLibSymbol(.V_Handler, "PLI_INIT")
    'Da es sein kann, das die Funktion nicht gefunden wurde, oder aus anderen gründen, warum auch immer. Prüfen wir, ob die Funktion
    'erfolgreich verlinkt wurde. Wenn das nicht der Fall war, dann wird hier die Funktion verlassen.
    '
    '
    '============================
    '=!= VORSICHT =!=
    'Nachdem die Dll erzeugt wurde, kann man nicht mehr so einfach die DLL ignorieren! Muss aus irgend einem grund das laden abgebrochen werden
    'dann MUSS die DLL zerstört werden. Sonnst kann das Programm beim erneutem laden der DLL problematisch reagieren!
    '=!= VORSICHT =!=
    '============================

    If .V_PluginFunktion_Init       = 0 Then DyLibFree(.V_Handler): Return 0

    'Das ganze wiederhohlen wir mit allen funktionen, die das Grundgerüst des Plugin-Systems bilden.
    .V_PluginFunktion_GetDesc       = DyLibSymbol(.V_Handler, "PLI_GETDESC")
    If .V_PluginFunktion_GetDesc    = 0 Then DyLibFree(.V_Handler): Return 0

    .V_PluginFunktion_Do            = DyLibSymbol(.V_Handler, "PLI_DO")
    If .V_PluginFunktion_Do         = 0 Then DyLibFree(.V_Handler): Return 0

    'Wenn wir hier angelangt sind, scheint alles problemlos geladen worden zu sein.
    'Damit wäre das gröbste geschaft.

    'Jetzt sind wir in der Lage Funktionen in einer DLL aufzurufen, die wir im UDT hinterlegt haben.
    'Damit die DLL jedoch auch auf Funktionen in dieserm Programm zugreifen kann, müssen wir Ihr diese Funktionen mitteilen.
    'Hierzu haben wir eine Funktion im Plugin definiert ("PLI_INIT") die als Parameter den von uns erzeugten "Plugin_CallBack_Type" UDT
    'entgegen nimmt. Dieser wird als Pointer übergeben, damit die DLL auf die Funktionen in diesem Programm zugreifen kann. Referenzierung.

    'Damit die DLL nun diese Struktur empfangen kann, rufen wir die "PLI_INIT" Funktion im Plugin auf, und übergeben die "Plugin_Callback" Variable
    'als Pointer
    'Dazu erzeugen wir eine temporäre variable als Ptr, welche die Pointer aufnimmt.
    'Natürlich könnte man sie direkt übergeben. Allerdings würde dies ein sicherheitsrisiko darstellen.
    'Eine schadhafte DLL könnte beim INIT vorgang die Pointer-adressen in der struktur manipulieren
    'und damit alle später geladenen Plugins mit fehlerhaften Callbacks versorgen.
    'Um diesem problem vorzubeugen werden die Pointer zwischenkopiert. Dadurch bleieb die oriinale erhalten und eine schadhafte DLL
    'würde so nur seine eigenen Pointer-kopien zerstören.
    Dim TPC as Plugin_Callback_Type Ptr = @Plugin_Callback
    .V_PluginFunktion_Init(TPC)
End With

'Zum schluss müssen wir noch Platz in einem Array-Element machen, damit wir die frisch erzeugte Struktur abspeichern können
'Dazu gehen wir erstmal alle Elemente durch, und suchen nach einem Freien Platz
Dim CIndex as UInteger
For X as UInteger = 1 to PluginC
    'Haben wir einen Freien gefunden, dann diese Indexnummer speicher und Schleife verlassen.
    If PluginD(X).V_InUse = 0 then CIndex = X: Exit For
Next

'Haben wir KEINEN freien platz gefunden,...
If CIndex = 0 Then
    '...dann erzeugen wir einen neuen

    'Dazu zählen wir die Anzahl Arrayeinträge +1
    PluginC += 1
    'Speichern die Indexnummern
    CIndex = PluginC
    'Und redimensionieren das Array neu.
    '(Alternativ kann man hier auch eine LinkedList nutzen, die weit schneller ist. Das würde aber das Tutorial zu sher aufblähen und vom Thema ablenken)
    Redim Preserve PluginD(PluginC) as Plugin_Type
    'Zum schluss noch dieses Neue Element als 'Belegt' Markieren
    PluginD(CIndex).V_InUse = 1
    'Das setzen des Flags könnte man sich sparen. Wird jedoch Mutithreading eingesetzt, muss es hier drin erfolgen, damit das Mutex danach entlockt werden kann
    'Das schützt das array for "inkonsistenz"
End If

'Jetzt haben wir definitiv einen Freien Platz, dessen Indexnummer in CIndex steht.
'Zum schluss noch die Temporäre struktur in das Array Kopieren
PluginD(CIndex) = TPLI

'Zum schluss reben wir die Indexnummer zurück und beenden die funktion (erfolgreich).
Return CIndex
End Function





'########################################################################################################################################
'Dies ist eine Funktion, welche von der DLL aus aufgerufen werden kann.
Function V_AnwendungDo(V_Was as String, V_Womit as String) as String
'Sie empfängt 2 variablen, und gibt einen veränderten test zurück
'Ganz simpel, udn nur zur demonstration
Select Case LCase(V_Was)
    Case "print":   Print "Anwendung 'Printet' V_Womit aus: " & V_Womit
    Case "return":  Return "Die Anwendung hat '" & V_Was & "' mit '" & V_Womit & "' erkannt"
End Select
End Function





'########################################################################################################################################
'Die "Sub_Main()"

'Damit das Programm funktioniert, müssen wir das Plugin einmal als "test_1.dll" / "libtest_1.so" und ...test_2... kompilieren.

'Es ist EXTREM!!!!!!!!! Wichtig, das ZUERST die Callbackstruktur-Pointer erzeugt werden, da es sonst zu Problemen
'kommen kann, wenn beim INIT die DLL schon auf Funktionen zugreift, die noch garnicht definiert wurde.
'Es reicht, wenn man dies einmal macht, da sie anschliessend nicht mehr verändert werden.
With Plugin_Callback
    'Wir kopieren den Pointer der Funktion "V_AnwendungDo" in die Variable des Callback-UDTs
    .V_AnwendungDo      = @V_AnwendungDo
End With
'Jetzt können wir die Plugins laden

'2 plugins laden. Dabei ist die bezeichnung gleich dem dateinamen
'Ein wenig text ausgeben
Print "Lade ein Plugin names: 'test_1'..."
'Plugin laden
Dim PluginID_1 as UInteger = Plugin_Load("test_1")
'Prüfen, ob ladevorgang erfolgreich war.
If PluginID_1 = 0 Then Print "Konnte Plugin 'test_1' aufgrund eines Fehlers nicht laden!": End -1
'Jetzt rufen wir eine Funktion im Plugin auf, die uns eine beschreibung liefert udn ausgibt.
Print PluginD(PluginID_1).V_PluginFunktion_GetDesc()


'Das ganze nochmal für plugin nr. 2
Print "Lade ein Plugin names: 'test_2'..."
Dim PluginID_2 as UInteger = Plugin_Load("test_2")
If PluginID_2 = 0 Then Print "Konnte Plugin 'test_2' aufgrund eines Fehlers nicht laden!": End -1
Print PluginD(PluginID_2).V_PluginFunktion_GetDesc()


'Eine Funktion im Plugin_1 aufrufen, die etwas "tut"
Print PluginD(PluginID_1).V_PluginFunktion_Do("print", "Ich bin Plugin mit ID:" & Str(PluginID_1))


'Eine Funktion im Plugin_2 aufrufen, die etwas "tut"
Print PluginD(PluginID_2).V_PluginFunktion_Do("print", "Ich bin Plugin mit ID:" & Str(PluginID_2))


'Bevor das Programm beendet wird, MUSS!!!!!!!!!!!!!!! das Plugin entladen werden!, bzw die DLL ausgekoppelt!
'Das kann man natürlich auch einfacher machen, udn automatisieren. z.B. mit einer "PLI_Term" bzw "PLI_TermAll"
'Dabei sollte auch die variable von "In_Use" auf 0 zurück gestellt werden, damit beim erneuten laden die Elemente als
'Frei markiert sind.
DyLibFree(PluginD(PluginID_1).V_Handler)
DyLibFree(PluginD(PluginID_2).V_Handler)

'Dann noch sauber beenden
End 0



'  H F
' T P M

Die DLL / SO welche das Plugin selbst darstellt.

'########################################################################################################################################
'Das Plugin-Konstrukt
'########################################################################################################################################
'Dies ist das hauptkonstrukt eines Plugins. Es sind min. die funktionen nötig, die von der anwendung gelinkt werden!
'########################################################################################################################################

'ACHTUNG! .. diese Datei muss mit dem "-dylib" Flag von FBC kompiliert werden!
'fbc <dateiname>.bas -dylib





'########################################################################################################################################
'Hier muss die Struktur ebenfalls vorhanden sein, welche das Callback definiert.
Type Plugin_CallBack_Type
    V_T                         as UByte
    V_AnwendungDo               as Function (V_Was as String, V_Womit as String) as String
End Type

'Auch hier wird eine Variable definiert, die jedoch als Pointer ausgelegt wird.
'Die dient später dazu auf Funktionen in der Anwendung zu zu greifen.
Dim Shared Plugin_Callback as Plugin_CallBack_Type Ptr




'Es folgen die 3 haupt funktionen der DLL
'########################################################################################################################################
'Die INIT funktion. Sie wird beim laden der DLL aufgerufen, und enthält einen pointer auf die Callback struktur

'WICHTIG !!!! ist hier das "Export" am ende der Funktion, da diese sonst nicht von aussen zugänglich ist.

Sub PLI_INIT(CallbackInterface as Plugin_CallBack_Type Ptr) Export
'Diese Struktur wird in der DLL lokal gespeichert
Plugin_Callback = CallbackInterface
'Damit wird es uns ermöglicht aus der gesammten DLL heraus auf funktionen in der Anwendung zugreifen zu können.
'Hierbei ist darauf zu achten, das die Aufrufe Pointerentsprechend mit "->" erfolgen.
'Gleich mal eine Test-Ausgabe machen
Print Plugin_Callback->V_AnwendungDo("return", "Plugin hat INIT erhalten")
End Sub





'########################################################################################################################################
Function PLI_GETDESC() as String Export
'Nutzlose funktion Nr. 1, reines DEMO
Return "Hallo! Ich bin ein Plugin"
End Function





'########################################################################################################################################
'Nutzlose funktion nr. 2 .. auch reines demo.
Function PLI_DO(V_Was as String, V_Womit as String) as String Export
Select Case LCase(V_Was)
    Case "print":   Print "PLUGIN 'Printet' V_Womit aus: " & V_Womit
    Case "return":  Return "Das PLUGIN hat '" & V_Was & "' mit '" & V_Womit & "' erkannt"
End Select
End Function

Viel spass und HF
TPM

 

Zusätzliche Informationen und Funktionen
  • Das Tutorial wurde am 29.05.2009 von MitgliedThePuppetMaster angelegt.
  • Die aktuellste Version wurde am 13.02.2010 von RedakteurMOD gespeichert.
  Bearbeiten Bearbeiten  

  Versionen Versionen