Using DLLs, COM and APIs

 LC  The operating system and its subsystems provide Application Programming Interfaces (API) for programs to use their functions. Lite-C can call API functions either based on external Dynamic Link Libraries (DLLs), or on the Component Object Model (COM). DLLs are modules that contain functions and data (Details about writing DLLs can be found under Plugin DLLs). A DLL is loaded at runtime by its calling modules (.EXE or another DLL). When a DLL is loaded, it is mapped into the address space of the calling process.

DLLs can contain two kinds of functions: exported and internal. The exported functions can be called by other modules. Internal functions can only be called from within the DLL where they are defined. Although DLLs can export data, its data is usually only used by its functions. DLLs provide a way to modularize applications so that functionality can be updated and reused more easily. They also help reduce memory overhead when several applications use the same functionality at the same time, because although each application gets its own copy of the data, they can share the code.

The Microsoft® Win32® application programming interface (API) is implemented as a set of dynamic-link libraries, so any process that uses the Win32 API uses dynamic linking.

Declaring a DLL or Windows API Function

Before an API function from an external DLL can be called, a function prototype must be declared, just as any other function. Example:
long WINAPI MessageBox(HWND,char *,char *,long);

The function prototype - which is in fact a function pointer - must then be initialized with the function address. There are three methods: static initialization in the api.def. (for functions that are often used), static initialization by PRAGMA_API (for functions only defined in a particular header) and dynamic initialization by DefineApi (for functions that are only used in a particular appliction). The most common static API functions are defined in the api.def file. It's just a plain text file, so it can easily be modified. Open api.def in the Gamestudio resp. lite-C folder, and add a line to it in the style (FunctionName;ModuleName!ProcName). FunctionName is your declared function, ModuleName is the name of the DLL, and ProcName is the name of the function within that DLL (which needs not necessarily be identical to your function name). Example:

MessageBox;user32!MessageBoxA

For initializing a function in a certain header file, add a PRAGMA_API definition to the function declaration, followed by a static definition just as in the api.def. Example:

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

You can find examples of this way to declare external DLL functions in the include\windows.h and the include\d3d9.h header files.

For dynamically initializing an API function at runtime , either use the DefineApi call, or load the DLL and retrieve the function address. The function prototype can be used as a function pointer. Examples:

// 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");

By default, api.def contains a selection of C standard functions . The opengl.h and d3d9.h headers contain a selection of OpenGL and DirectX Graphics functions. If you need a certain function that is not included, you can add it easily as described under Converting C++ Code to lite-C.

Using C++ classes and the COM model

The Component Object Model (COM) is a way for software components to communicate with each other. It's a binary and network standard that allows any two components to communicate regardless of what machine they're running on (as long as the machines are connected), what operating systems the machines are running (as long as it supports COM), and what language the components are written in. COM provides location transparency: it doesn't matter to you when you write your components whether the other components are in-process DLLs, local EXEs, or components located on some other machine.

When installing a COM DLL, it's classes are registered with 128-bit identification numbers, so-called GUIDs (Global Unique IDentifier). Classes are like structs but contain not only variables but also functions (methods). For accessing a COM class, you're using its GUID to request a pointer to its method table, a so called interface. This pointer can then be used to call the methods of the class. The Windows API offers the function CoCreateInstance() for requesting an interface to a COM class:

HRESULT CoCreateInstance (
  REFCLSID rclsid, // GUID of the class
  LPUNKNOWN pUnkOuter, // Aggregation, usually NULL
  DWORD dwClsContext, // Server type, such as CLSCTX_ALL
  REFIID riid, // GUID of the interface
  LPVOID * ppv // adresse of an interface pointers 
);

So for using a COM class you need to know the class GUID and the interface GUID, and then call CoCreateInstance(). For a normal Windows program (Legacy Mode), you need to open the COM library before with CoInitialize(); this is not necessary in Pure Mode where COM is already initialized. The interface pointer contains a pointer to the function table (Vtbl) that can be used to call all methods of the class. Any COM class contains the three standard IUnknown methods - QueryInterface(), AddRef(), and Release() - as well as any number of class specific methods. For example, here's the lite-C code for defining a COM class that contains two specific methods, Func1() and 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;

Note that each of the methods has an additional parameter called "This". You have to pass the This pointer parameter explicitly in C, but it can be passed automatically in lite-C. Any additional parameters come after This, as above. The interface is then typedef'd as a structure that contains a pointer to the vtable. Example for using the above IFoo class:

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

IFoo *pIFoo;

int WinMain()
{
    ...
// open the COM interface (only in Legacy Mode!)
    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 (replace the question marks with the real GUIDs)
    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();   
}

For calling methods on COM objects, you can use either a C++-style or 'C'-style syntax. Example:
 pIFoo->Func1(); // C++ style
pIFoo->lpVtbl->Func1(pIFoo); // C style

Make sure that your Vtbl contains all methods in the correct order. As lite-C does not support class inheritance, add all inherited methods directly after the three standard methods, and after them the specific methods . Example for a DirectX class:

typedef struct ID3DXMeshVtbl
{
// standard 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); long __stdcall GetFVF(void* This);
long __stdcall GetDeclaration)(void* This, D3DVERTEXELEMENT9*);
long __stdcall GetNumBytesPerVertex(void* This);
long __stdcall GetOptions(void* This);
long __stdcall GetDevice(void* This, LPDIRECT3DDEVICE9* ppDevice);
long __stdcall CloneMeshFVF(void* This, DWORD Options, DWORD FVF, LPDIRECT3DDEVICE9 pD3DDevice,void* ppCloneMesh);
long __stdcall CloneMesh(void* This, DWORD Options, D3DVERTEXELEMENT9 *pDeclaration, LPDIRECT3DDEVICE9 pD3DDevice, void* ppCloneMesh);
long __stdcall GetVertexBuffer(void* This, LPDIRECT3DVERTEXBUFFER9* ppVB);
long __stdcall GetIndexBuffer(void* This, LPDIRECT3DINDEXBUFFER9* ppIB);
long __stdcall LockVertexBuffer(void* This, DWORD Flags, void** ppData);
long __stdcall UnlockVertexBuffer(void* This);
long __stdcall LockIndexBuffer(void* This, DWORD Flags, void** ppData);
long __stdcall UnlockIndexBuffer(void* This);
long __stdcall GetAttributeTable(void* This, D3DXATTRIBUTERANGE *pAttribTable, DWORD* pAttribTableSize); long __stdcall ConvertPointRepsToAdjacency(void* This, DWORD* pPRep, DWORD* pAdjacency);
long __stdcall ConvertAdjacencyToPointReps(void* This, DWORD* pAdjacency, DWORD* pPRep);
long __stdcall GenerateAdjacency(void* This, FLOAT Epsilon, DWORD* pAdjacency); long __stdcall UpdateSemantics(void* This, D3DVERTEXELEMENT9 *); // ID3DXMesh specific 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(); ...

See also:

Pointers, Structs, Functions ► latest version online