DLLs, COM und APIs

 LC  Das Betriebssystem und seine einzelnen Komponenten stellen zur Verwendung ihrer Funktionen Programmiereroberflächen bereit (Application Programming Interfaces, kurz API). Lite-C kann API-Funktionen auf der Basis Dynamischer Verknüpfungs-Bibliotheken (Dynamic Link Libraries, kurz DLLs) oder des sogenannten Component Object Models (COM) verwenden. DLLs sind Module, die Funktionen und Daten beinhalten (Näheres zum Erzeugen von DLLs finden Sie im Kapitel über Plugin DLLs). Eine DLL wird zur Laufzeit von seinen aufrufenden Modulen geladen (.EXE oder DLL). Ist die DLL geladen, wird es in den Adressbereich des Aufrufprozesses gemappt.

DLLs können zwei Arten von Funktionen definieren: exportierte und interne. Die exportierten Funktionen können von anderen Modulen aufgerufen werden, interne Funktionen hingegen nur innerhalb der DLL in der sie definiert sind. Auch wenn DLLs Daten exportieren können, werden ihre Daten für gewöhnlich ausschliesslich von ihren eigenen Funktionen benutzt. DLLs bieten einen Weg, Anwendungen so anzupassen, dass sich ihre Funktionalität einfacher upgedated und wiederverwenden läßt. Auch helfen sie, wenn mehrere Anwendungen gleichzeitig dieselben Funktionsweisen verwenden, Speicherplatz zu sparen, denn obwohl jede Anwendung ihre eigene Datenkopie bekommt, können sie den Code teilen.

Das Microsoft® Win32® Windows-API ist als ein Satz dynamischer-Link-Bibliotheken eingesetzt, so dass jedweder Prozess, der Win32 API benutzt, dynamisches Linking verwendet.

Angabe einer DLL- oder Windows-API-Funktion

Bevor eine API-Funktion von einem externen DLL aufgerufen werden kann, muß zunächst, wie jede andere Funktion auch, ein Funktions-Prototyp erstellt werden Beispiel:

long WINAPI MessageBox(HWND,char *,char *,long);

Der Funktionsprototyp - der eigentlich ein Funktions-Pointer ist - muß dann mit der Funktionsadresse initialisiert werden. Es gibt drei Methoden: statisches Initialisieren in der api.def (für oft verwendete Funktionen), statisches Initialisieren per PRAGMA_API (für Funktionen die lediglich in einem bestimmten Header definiert sind) und dynamisches Initialisieren via DefineApi (für Funktionen, die nur in einer bestimmten Anwendung benutzt werden). Die am häufigsten vorkommenden statischen API-Funktionen sind in der Datei api.def definiert. Öffnen Sie api.def im Gamestudio- bzw. lite-C-Ordner und fügen Sie eine Zeile im Stil von (FunctionName;ModuleName!ProcName) hinzu. FunctionName ist Ihre angegebene Funktion, ModuleName ist der Name der DLL und ProcName ist der Name der Funktion innerhalb dieser DLL (der nicht unbedingt mit Ihrem Funktionsnamen identisch sein muß). Beispiel

MessageBox;user32!MessageBoxA

Zum Initialisieren einer Funktion in einer bestimmten Header-Datei fügen Sie der Funktionsangabe eine PRAGMA_API-Definition hinzu, der eine statische Definition folgt, ganz swie in api.def. Beispiel:

long WINAPI MessageBox(HWND,char *,char *,long);
#define PRAGMA_API MessageBox;user32!MessageBoxA

Beispiele für diese Art externe DLL-Funktionen anzugeben finden Sie in den Header-Dateien von include\windows.h und include\d3d9.h.

Zum dynamischen Initialisieren einer API-Funktion zur Laufzeit, verwenden Sie entweder den Aufruf DefineApi oder Sie laden das DLL und holen die Funktionsadresse zurück. Der Funktions-Prototyp kann als Funktions-Pointer verwendet werden. Beispiele:

// Example1:
long WINAPI MessageBox(HWND,char *,char *,long);
MessageBox = DefineApi("user32!MessageBoxA");

// Example2:
long WINAPI MessageBox(HWND,char *,char *,long);
long h = LoadLibrary("user32");
MessageBox = GetProcAddress(h,"MessageBoxA");

Per default enthält api.def eine Sammlung von C-Standard-Funktionen. Die Header opengl.h und d3d9.h enthalten eine Auswahl von OpenGL- und DirectX-Graphikfunkitonen. Brauchen Sie eine bestimmte Funktion, die nicht enthalten ist, läßt sich diese einfach wie unter Converting C++ Code to lite-C. beschrieben hinzufügen.

Anwenden von C++ Klassen und dem COM-Modell

Das Compontent Object Model (COM) ist ein Weg auf dem Softwarekomponenten miteinander kommunizieren. Es handelt sich um einen Standard, der es jedweden zwei Komponenten ermöglicht miteinander zu kommunizieren, ganz egal auf welchem Rechner sie laufen (solange die Rechner miteinander verbunden sind), welche Betriebssysteme auf den Rechnern laufen (solange sie COM unterstützen) und in welchen Sprachen die Komponenten geschrieben sind. COM stellt sogenannte location transparency zur Verfügung: es kann Ihnen beim Schreiben Ihrer Komponenten also gleichgültig sein, ob die anderen Komponenten DLLs sind, lokale EXEs oder Komponenten, die sich auf anderen Rechnern befinden.

Ist eine COM DLL auf Ihrem Rechner installiert, so sind ihre Klassen in der Registry mit 128-Bit-Identifikationsnummern registriert, sogenannten GUIDs (Global Unique IDentifier). Klassen sind wie structs, enthalten jedoch nicht nur Variablen, sondern auch Funktionen (Methoden). Zum Benutzen einer COM-Klasse fordert man mit Hilfe ihrer GUID einen Zeiger auf deren Funktionstabelle an, ein sogenanntes Interface. Mittels des Zeigers kann man dann die Methoden der Klasse aufrufen. Die Windows-API bietet eine Funktion zur Anforderung von COM-Klassen mit dem Namen CoCreateInstance(), wobei das vorgestellte Co auf COM hindeutet:

HRESULT CoCreateInstance (
  REFCLSID rclsid, // GUID der Klasse
  LPUNKNOWN pUnkOuter, // Aggregation, normalerweise NULL
  DWORD dwClsContext, // Server-Typ, z.B. CLSCTX_ALL
  REFIID riid, // GUID des Interface
  LPVOID * ppv // Adresse eines Interface-Pointers 
);

Um eine COM-Klasse zu benutzen, müssen Sie also die GUID der Klasse und die GUID ihres Interface wissen und damit CoCreateInstance aufrufen. Der Interface-Pointer enthält einen Pointer auf eine Funktionstabelle (Vtbl), mit der Sie dann alle Methoden der Klasse aufrufen können. Jede COM-Klasse enthält drei Standard-Methoden - QueryInterface(), AddRef(), und Release() - sowie beliebig viele klassenspezfische Methoden. Hier beispielhaft der lite-C-Code zur Definition einer COM-Klasse mit zwei spezifischen Methoden, Func1() und Func2():

typedef struct IFooVtbl
{    
    HRESULT __stdcall QueryInterface(void* This,IID *riid,void** ppvObject);        
    DWORD   __stdcall AddRef(void* This);    
    DWORD   __stdcall Release(void* This);    
    HRESULT __stdcall Func1(void* This);    
    HRESULT __stdcall Func2(void* This,int);
} IFooVtbl;

typedef interface IFoo { IFooVtbl *lpVtbl; } IFoo;
        

Beachten Sie, daß jede der Funktionen einen zusätzlichen Parameter namens "This" hat. Diesen This-Pointer müssten Sie explizit in C übergeben, in lite-C läßt er sich aber automatisch übergeben. Wie oben gezeigt, kommt jeder weitere Parameter nach diesem This. Das Interface wird dann als eine Struktur, die einen Pointer zu der Funktionstabelle vtable enthält, typedefiniert. Beispiel zum Anwenden der obigen IFoo-Klasse:

#include <litec.h>
#include <com.h>

IFoo *pIFoo;

int WinMain()
{
    ...
// open the COM interface
    HRESULT hr = CoInitialize(NULL);
    if (hr!=S_OK)
    {
       printf("CoInitialize Failed: %x\n\n", hr);
       return 0;
    } else 
       printf("CoInitialize succeeded\n");
    
// define the GUIDs (Fragezeichen durch echte GUIDs ersetzen)
    GUID CLSID_MyObject;
    IID IID_IFoo;
    IIDFromStr("{????????-????-????-????-????????????}",&CLSID_MyObject);
    IIDFromStr("{????????-????-????-????-????????????}",&IID_IFoo);

// get a pointer to the class function table    
    hr = CoCreateInstance(&CLSID_MyObject, NULL, CLSCTX_ALL,&IID_IFoo, &pIFoo);
    if (hr!=S_OK) {
       printf("CoCreateInstance Failed: %x\n\n", hr);
       return 0;
     } else 
       printf("CoCreateInstance succeeded\n");

// use the class
    pIFoo->Func1();
    pIFoo->Func2(12345);

// close the interface and exit
    pIFoo->Release();
    CoUninitialize();   
}

Sie können zum Aufrufen von Methoden bei COM-Objekten entweder eine Syntax im C++-Stil oder im 'C'-Stil benutzen. Beispiel:

 pIFoo->Func1(); // C++ style
pIFoo->lpVtbl->Func1(pIFoo); // C style

Da lite-C keine Klassenvererbung unterstützt, fügen Sie einfach, so es welche gibt, sämtliche ererbten Methoden der Klasse hinzu. Beispiel für eine DirecX-Klasse:

typedef struct ID3DXMeshVtbl
{
// IUnknown methods
long __stdcall QueryInterface(void* This, REFIID iid, LPVOID *ppv);
long __stdcall AddRef(void* This);
long __stdcall Release(void* This); // methods inherited from ID3DXBaseMesh long __stdcall DrawSubset(void* This, long AttribId); long __stdcall GetNumFaces(void* This); long __stdcall GetNumVertices(void* This); // ID3DXMesh methods
long __stdcall LockAttributeBuffer(void* This, long Flags, long** ppData);
long __stdcall UnlockAttributeBuffer(void* This)
long __stdcall Optimize(void* This, long Flags, long* pAdjacencyIn, long* pAdjacencyOut,
long* pFaceRemap, LPD3DXBUFFER *ppVertexRemap,
void* ppOptMesh) } ID3DXMeshVtbl; typedef interface ID3DXMesh { ID3DXMeshVtbl * lpVtbl; } ID3DXMesh; ... ID3DXMesh* pMesh; ... pMesh->DrawSubSet(0); long num = pMesh->GetNumFaces(); ...

Siehe auch:

Pointers, Structs, Functions

► Aktuelle Version Online