Go to QuArK Web Site
DLL loading
Updated 20 Jul 2021
Upper levels:
QuArK Information Base
4. The Source Code
4.4. Specific Topics

 4.4.10. DLL loading

 [ Prev - Up - Next ] 

I am assuming you have access to the C/C++ source code for the DLL file you want to load in Delphi, or at least a list of all the exported functions and their arguments. I'm also assuming you're reasonably Delphi-experienced, and know 'enough' about C.

It's smart to create a separate Delphi .pas file for each .DLL file, since these files tend to get rather long with only dull code. It would create a big mess doing this inside another .pas file, and it's simply a good coding practice to keep different things separated.

See QkDevIL.pas or QkFreeImage.pas for more/better example code. GL1.pas does the same, but a bit more complicated; it's better when bulk-loading functions though. And DX9.pas with the files it calls are only for those not faint-hearted!


 Index


 Loading the library

DanielPharos - 05 Apr 2018   [ Top ] 

All Delphi functions that need to call one or more of the .DLL files functions, first call a function in the DLLfile.pas file to load the DLL file. This function has a counter (TimesLoaded) that counts the amount of times the DLL was called to load this way. If it's zero (i.e. it's the first time we need to load the DLL file), we actually load it, and set the counter to 1. If it's already 1 or higher, we only increase the counter; there's no loading to be done, since that already happened. This LoadDLLfile function returns a boolean ('true' or 'false') indicating success or failure when loading the DLL file and all of its functions.

First, we've need to load the DLL file. We can do this by calling Windows' LoadLibrary(DLLfilename). It takes one argument: the DLL filename. Most of QuArK's DLL files are in the dlls directory; it's best to place your DLL file there too (unless the DLL file is part of another program people need to install). LoadLibrary returns a handle to where the DLL file is loaded in memory.
So the call would be something like this:

DLLLib := LoadLibrary('dlls/DLLfile.dll');

DLLLib should be a HMODULE (which is the same as Cardinal, but please use HMODULE for future compatibility), declared at the top of the DLLfile.pas, in the implementation part (other parts of the program shouldn't need direct access to the loaded DLL file).

If LoadLibrary returns 0, something went wrong when trying to load the DLL file (file not found, or corrupt file or something along those lines). So now we raise an error, and exit. (Remember to NOT increase the TimesLoaded at the start, but at the end: if we didn't load anything, we don't want to increase TimesLoaded, since that will only cause trouble when trying to unload later!)

if DLLLib = 0 then
begin
    Exception.Create('Unable to load dlls/DLLfile.dll');
    Exit;
end;

By the way, the code examples here are just generic stuff: you should use the proper exit strategy and error-raising that should be used in the section of QuArK you're working in. Don't forget to set the correct Results! The easiest way of getting it right is simply to copy one of the DLL loading .pas files (like QkDevIL.pas) and change the functions, variable-names and constants to suit your DLL file. Don't forget to add the new .pas file to the .dpr file!


 Getting the functions

DanielPharos - 05 Apr 2018   [ Top ] 

Now that the DLL file is loaded, we need to load its functions. This is done using Windows' GetProcAddress API (Application Programming Interface) call. Its arguments are a handle to a DLL file in memory, and the name of the function we want to load, and it returns a pointer to that function. This is where things get tricky.

First, we need to know the name of the function as it is stored in the DLL file. But, there is something called: decoration. Some DLL files use this, and it will appear as a @X behind the function name, where X is a non-negative integer number. This is used to identify between different functions with the same name: the X stands for the total amount of bytes the arguments the function takes are, so you can have several functions that are named the same, but one takes more (or different) arguments than the others.

Often, these decorations are not documented, so you'll need to open up the DLL file in a text editor, search for the function name and simply look behind it to see if there's an @X attached. If so, use that. Sometimes, there's also an underscore '_' prepended to the name; so check that too while you're there. You can also try to guess the amount of bytes the arguments of the function will take up, but using a text editor is much easier (just be sure you take the @X of the right version of the function).

Second, we need to know the calling convention. DLL files (and C++) support multiple calling conventions. This convention defines what the caller function should do, and what the callee function should do. It's mostly low-level programming stuff, like who cleans up the stack. The only thing we need to know is that we need to get it right, because if we don't do it right, QuArK might start crashing randomly (serious, this happened to me! It simply crashed at the next function call in the program, because the stack was corrupted).

Most used are cdecl and stdlib. cdecl is the default C(++) one, while stdlib is a Windows one that's a bit faster. It should be stated which one to use in the C++ source code of the DLL file, where the functions get exported (near 'EXTERN_C' and 'DLL_EXPORT'). It's probably the same for all calls, but just make sure it is. Note it down; we need it for the next step.

The final piece to this puzzle is 'Delphi-ing' the functions: making a Delphi version of the C++ arguments. We need to set-up the functions and procedures so GetProcAddress can fill them up. This can be done in either the interface or implementation section, depending on whether we need the function to be available outside of this .pas file, but most of the time, everybody simply dumps it in the interface-part so they're all packed together.

DelphiFunction := GetProcAddress(DLLLib, 'functionname');
if DelphiFunction = Nil then
begin
    Exception.Create('Unable to load functionname from dlls/DLLfile.dll');
    Exit;
end;

Now we translate the C++ function into a Delphi one. If the DLL function returns a value, it's a function in Delphi. If not, it's a procedure. The arguments should be in the same order as in C++. The names of both the functions and the arguments don't have to be the same, but I suggest you leave them the same, unless of course the name is a keyword in Delphi (simply adding an 'x' or something in front of the name should take care of that). Behind the function/procedure's semicolon we add the calling convention ('cdecl', 'stdlib', ...) with another semicolon.


 What C++ variable-types map onto what Delphi variable-types?

DanielPharos - 20 Jul 2021   [ Top ] 

The Delphi help comes to the rescue, but only partially. For instance, find the article called 'Integer Types' (using Delphi 6/7 here, but it should be in every version, with possibly a different name). It gives a list of Delphi integer variable types, with their range, amount of bytes (or bits) and if they are signed or unsigned. For convenience, the results are summarized in the table below.

C typeDelphi type
int==Integer
signed int==Integer
unsigned int==Cardinal
byte==ShortInt
signed byte==ShortInt
unsigned byte==Byte
short==SmallInt
signed short==SmallInt
unsigned short==Word
long==LongInt
signed long==LongInt
unsigned long==Longword
char==Char
signed char==Char
unsigned char==Byte
?==int64
void==? (Byte?)

Note: Some of these only apply to a Win32 32-bit application: Cardinal might be different in 64-bit Delphi, and the same goes for the C++ Int. However, since QuArK is 32-bit, and most DLL files are too, we don't need to be too concerned, but it is something to keep in mind.

Pointers to variables can simply be translated into a Delphi pointer to a variable of the translated type. For instance, a char* can simply be translated into a PChar (a pointer to a Char). void* is special, and I suggest using PByte for this because it's often a pointer to a buffer of unknown content, so we probably want to read/parse it byte-for-byte. However, Delphi (for historical reasons) uses PChar, so often using that keeps the code cleaner.

Remember that it is important to use the right datatypes, to prevent security issues like integer overflows.


 Unloading the library

DanielPharos - 05 Apr 2018   [ Top ] 

Well, that's all the loading stuff. But we also need to do some clean up when QuArK shuts down, or when the DLL file is no longer needed.

UnloadDLLfile is what we want to use. When a function is done using the DLL file, it calls UnloadDLLfile to decrease the TimesLoaded counter, and when the counter hits 0, that means the DLL file is no longer being used and we can now unload it. There are cases where this is not what we want, and we want to keep the DLL file loaded anyway. You will have to handle those situations however you see fit (and please add some comment as to 'why'!).

Actually, we check if the counter hits 1, and then do the unload. If everything went OK, then we decrease the counter. We unload by simply calling Windows' FreeLibrary with the HMODULE of the DLL file as the only argument. It returns a boolean whether the unloading was successful or not. Do the usual error-raising if needed. Done!

if FreeLibrary(DLLLib) = false then
begin
    Exception.Create('Unable to unload dlls/DLLfile.dll');
    Exit;
end;

Well, just one more thing. To make error diagnostics easier, it's best to set all the functions and procedures that we loaded to nil when we unload them, so QuArK gives us a clean crash when we try to call any of those. Also, setting the HMODULE to zero is not a bad idea.



Copyright (c) 2022, GNU General Public License by The QuArK (Quake Army Knife) Community - https://quark.sourceforge.io/

 [ Prev - Top - Next ]