article

Loading and calling DLLs at runtime

Email
Submitted on: 1/1/2015 12:55:00 PM
By: James Mistry (from psc cd)  
Level: Advanced
User Rating: By 2 Users
Compatibility: Delphi 5
Views: 2665
 
     I have seen a few methods of calling DLLs at runtime that have required the programmer to know the name of the DLL and function signature (arguments) before compilation, but this is no good if you want to call a function you know nothing about. For some reason the topic is very poorly documented by Microsoft so, armed with some old C++ code and a couple of beers I set out to make a function that would call an export from a DLL with absolutely no information required at design time. The DLL's file name, the name of the function and the arguments are all supplied at runtime. Supports most integer data types as well as Single and PChar (see article for details).

 
				



DLL By Name


{
DLL By Name for Delphi
by James Mistry
Copyright 2002, James Mistry. All rights reserved.
DLL By Name allows you to access a DLL's function by specifying the DLL
name, the function name and the arguments. DLL By Name handles most data
types including Single and is ideal for creating a DLL-based plugin system
or implementing DLL support in a scripting language.
This was inspired by a C++ version by Adam Straughan.
Unlike C++, however, this function could easily be implemented into a DLL
and accessed from other languages.
You're free to do what you like with the code as long as the original
copyright stays on if you distribute it.
Credit to me is not compulsory in compiled code, but it would be nice - I
don't get much motivation.
You should have seen my victory dance when I got this to work: never has a
fat man moved so fast...
Well, if you think this is complicated I'd advise you stay away from the
VB version unless you enjoy programming in hexadecimal ASM (ughh).
Known Bugs/limitations:
1. In C++, Char and Char* (pointer) can be used. However, only PChar would work with Char* in Delphi, not
^PChar so I'm left with an equivalent for Char*, but not Char. I'm not a C++ programmer by birth so if anyone else knows
a data type I can use as an equivalent to Char, I'd appreciate an e-mail (jm@sandown-software.com).
2. At the moment the function isn't configured for use with custom types but a few adaptations (maybe in the next
version) would fix that.
3. The only Real data type presently supported is Single. Because no other Real types only occupy 4 bytes in memory,
they wouldn't fit into the temporary buffer I assign using a Longword. However, a couple of small changes should
enable larger types. I'll implement this into version 2.
}

uses
Windows, Dialogs;
type //This is an enumeration type
ARGTYPE = (ARG_NONE = -1, // Terminator or no arguments
ARG_UI1 = 0, // unsigned char
ARG_I1, // signed char
ARG_UI2, // unsigned short
ARG_I2, // signed short
ARG_UI4, // unsigned long
ARG_I4, // signed long
ARG_R4, // float
ARG_PUI1, // unsigned char* (pointer)
ARG_PI1, // signed char* (pointer)
ARG_PUI2, // unsigned short* (pointer)
ARG_PI2, // signed short* (pointer)
ARG_PUI4, // unsigned long* (pointer)
ARG_PI4, // signed long* (pointer)
ARG_PR4); // float* (pointer)
type
DLL_ARG = record //Implements data types defined in ARGTYPE
case eType: ARGTYPE of //Here we have the equivalent of absolute addressing in types (union structs in C++)
//eType is treated as a member of DLL_ARG and implicitly declared in the case statement
ARG_UI1: (ucVal: PChar); //I don't know what data type to use for this: it's supposed to be the same as a C++ char*
ARG_PUI1: (pucVal: PChar);
ARG_I1: (cVal: ShortInt);
ARG_PI1: (pcVal: ^ShortInt);
ARG_UI2: (usVal: Word);
ARG_PUI2: (pusVal: ^Word);
ARG_I2: (sVal: SmallInt);
ARG_PI2: (psVal: ^SmallInt);
ARG_UI4: (ulVal: Longword);
ARG_PUI4: (pulVal: ^Longword);
ARG_I4: (lVal: Integer);
ARG_PI4: (plVal: ^Integer);
ARG_R4: (fltVal: Single);
ARG_PR4: (pfltVal: ^Single);
end;

implementation
//************************ Notes ************************
//Reals CAN be returned using the Single type
//which occupies 4 bytes - the same as Longword (unsigned int in C++), the
//data type used for temporary storage in the function call.
//If you want to be able to pass/return anything bigger than Longword
//(e.g. Currency) then you're going to have to re-design the data type
//system I've used.
//It is possible to pass/return custom types but you'd have to
//look into some kind of type-checking system. The memory copying shouldn't
//cause a problem as long as you find the size of any types used. The
//safest way to do this is with SizeOf.
function CallDLLByName(szLibrary, szFunction: PChar; Arguments: array of DLL_ARG; nArgCount: Integer; var pRetVal: DLL_ARG): Integer; cdecl; //Returns error code
var
pFun: Integer; //The address of the procedure
dwTemp: Longword; //Temporary storage - big enough for all types
dwRet: Longword; //What to return
i: Integer; //Iterator for arguments
m_hDLL: Integer; //The handle to the DLL
Begin
pFun := 0;
dwRet := 0;
m_hDLL := 0;
m_hDLL := LoadLibrary(szLibrary); //Load the DLL
if m_hDLL = 0 then //Load error: DLL either wasn't found or couldn't be loaded
Begin
MessageBox(Form1.Handle, 'DLL load error.', 'Error', 48);
Result := 1; //LoadError
exit;
end;
pFun := Integer(GetProcAddress(m_hDLL, szFunction)); //Cast the return value of GetProcAddress to Integer and assign it to pFun
if pFun <> 0 then //Procedure address was obtained successfully
Begin
//Loop through the arguments in reverse
for i := High(Arguments) downto Low(Arguments) do
Begin
//Copy data to the temporary buffer
CopyMemory(@dwTemp, @Arguments[i].lVal, SizeOf(Longword));
//Note that SizeOf can return the amount of memory a specified data type
//occupies, hence SizeOf(Longword)
//Now put it on the stack (I wish I was a pixie)

asm push dwTemp end;
end;
if pRetVal.eType = ARG_R4 then
Begin
asm
call dword ptr [pFun] //Call the function
fstp dword ptr [dwRet] //Perform a store-and-pop
end;
end
else
Begin

asm //In C++ we could have just used dwRet = (pFun)() but not in Delphi
call dword ptr [pFun] //Call the function
mov dword ptr [dwRet], eax
end;
end;
//Unload the stack by looping through Arguments
for i := Low(Arguments) to High(Arguments) do
Begin
asm pop dwTemp end;
//We have to copy the data back because values might have been
//changed

CopyMemory(@Arguments[i].cVal, @dwTemp, SizeOf(Longword));
end;
if pRetVal.eType <> ARG_NONE then //Return the value if requested
Begin
pRetVal.eType := ARG_I4; //Use the entire buffer
pRetVal.lVal := dwRet;
end;
end
else
Begin

ShowMessage(szLibrary + ' does not export a function called "' + szFunction + '".');
Result := 2; //FunctionError
exit;
end;
Result := 0; //Success
end;
procedure Test;
var
GetRet: DLL_ARG;
buf: PChar;
Args: array[0..2] of DLL_ARG;
begin
Args[0].eType := ARG_UI4;
Args[1].eType := ARG_PUI1;
Args[2].eType := ARG_UI4;
Args[0].ulVal := 3912; //Replace this with the hWnd of a window in decimal
Args[2].ulVal := 20; //The buffer size for received text
Args[1].pucVal := '12345678901234567890'; //Populate the buffer
GetRet.eType := ARG_UI4; //We want a return value of type Longword
CallDLLByName('C:\WINDOWS\SYSTEM\user32.dll', 'GetWindowTextA', Args, 3, GetRet); //Call GetWindowTextA
ShowMessage(String(Args[1].pucVal)); //Show the result
end;
Begin

end
.


Report Bad Submission
Use this form to tell us if this entry should be deleted (i.e contains no code, is a virus, etc.).
This submission should be removed because:

Your Vote

What do you think of this article (in the Advanced category)?
(The article with your highest vote will win this month's coding contest!)
Excellent  Good  Average  Below Average  Poor (See voting log ...)
 

Other User Comments


 There are no comments on this submission.
 

Add Your Feedback
Your feedback will be posted below and an email sent to the author. Please remember that the author was kind enough to share this with you, so any criticisms must be stated politely, or they will be deleted. (For feedback not related to this particular article, please click here instead.)
 

To post feedback, first please login.