Runtime 'Dynamic' DLL Calling | CodeGuru

Runtime ‘Dynamic’ DLL Calling

Environment: Visual C++ (SP3), Windows NT4 (SP3) The Problem: Call a function in a DLL passing some arguments. The DLL, function and arguments are not known until run-time. The argument types are limited to standard types (i.e. char, short, long, float, char*) but not, for the sake of simplicity, doubles (due to their 8 bytes). […]

Written By
CodeGuru Staff
CodeGuru Staff
Jul 13, 1999
5 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Environment: Visual C++ (SP3), Windows NT4 (SP3)

The Problem:

Call a function in a DLL passing some arguments. The DLL, function and arguments are not known until run-time. The argument types are limited to standard types (i.e. char, short, long, float, char*) but not, for the sake of simplicity, doubles (due to their 8 bytes). Further types could be added, I think, but for now we’ll leave them alone. I’m still not sure about return types and the solution that I propose can only cope with return types that fit into 4 bytes.

Notes on the Problem

  1. I have posted this article not so much as to give a solution but, as an idea that I expect to get ripped to shreds in the hope that what comes out is neat, tidy and useable.
  2. The solution should fix the problem as it stands i.e. we can’t say, “just use COM” or “only have functions that take some complex object that can work out its own parameter list”.
  3. void func(...) style functions are for the callee not the caller.

Possible Solution

  1. We start by going against rule two and limiting the maximum number of arguments to 10. We do this by creating a structure which contains an array of ten DWORDS and pass the struct by value to the DLL. The DLL will get all the data it needs on the stack (and probably a bit to spare). typedef struct { DWORD dwARG[10]; }STACK; typedef DWORD (*PFNDLL)(STACK); void main() { HINSTANCE hDll = LoadLibrary("MyDll.dll") PFDLL pfFunc = (PFDLL)GetProcAddress(hDll,"MyFunc"); STACK args; char szBuf[] = "hello world"; DWORD dwRet; args[0] = 10; // a dword args[1] = (DWORD)szBuf; dwRet = (pfFunc)(args); } We sort of solved the problem but had to impose extra limits, so no points there.
  2. This leads to a better solution, put our data on the stack ourselves and then we can have as much or as little as we want, and potentially deal with types other than the simple ones (once we know that the idea is sound). The problem is that this uses two lines of assembler code which is pushing my assembler knowledge by two lines. As far as I can tell this works for the types supported in the demo. // see the detail in the RuntimeDll.cpp file, all error handling // has been removed for brevity // pointer to function in dll, caller of CallDll typedef __declspec(dllimport)DWORD (*pfFunc)(void); void main() { HINSTANCE hDll = ::LoadLibrary("MyDll.dll"); pfFunc pFun = NULL; DWORD dwTemp; DWORD dwRet = 0; float fRet = 0.0f; int i; bool bRetIsFloat = false; nArgCount = 4; int nArg[4]; long nA = 123456; char cB = 20; float fC = 5.678f; char* szD = "test string"; // memcpy not cast as a cast will loose data // in the case of a float memcpy(nArg+0, &nA ,sizeof(long)); memcpy(nArg+1, &cB ,sizeof(long)); memcpy(nArg+2, &fC ,sizeof(long)); memcpy(nArg+3, &szD,sizeof(long)); pFun = (pfFunc)::GetProcAddress(hDll, "MyFunc"); // load the stack (in reverse order) for( i=nArgCount-1; i>=0; i--) { // copy the data to a temporary holder // (casts can lose data) memcpy(&dwTemp, &(Arg[i].bVal), sizeof(DWORD)); // chuck it on the stack _asm push dwTemp } // call the function, data already on the stack if (bRetIsFloat) { // float returns are not passed via the EAX register but, // it seem, are on the stack so ... // ok I didn't know this bit of asm I compiled some // code and copied it once I had some idea of what // the instructions do. // call, calls a function // fstp, does a store & pop _asm { call dword ptr [pFun] fstp dword ptr [fRet] } } else { dwRet = (pFun)(); } // unload the stack for (i = 0; i<nArgCount; i++) { _asm pop dwTemp // copy the data back in case of altered values memcpy(&(Arg[i].bVal), &dwTemp, sizeof(DWORD)); } if (bRetIsFloat) printf("returned %fn",fRet); else printf("returned %dn",dwRet); } I have tested this code against a test DLL using various argument lists and return types and everything seems to work nicely, but as I don’t know much about assembler I’m not sure what problems this code may cause. If you have any comments I’d be pleased to hear them. The Demo Source code contains this basic idea wrapped up in a class CRuntimeDll and uses CRuntimeDllException.
  3. Some other, better, less hacky, solution?
Advertisement

Notes on the Demo project

The demo allows you to call a function in a DLL. Follow the example below for an explanation of how the demo app can be used.

To call the WINAPI GetWindowText(HWND hwnd,char* szBuffer,long nBufferSize) function:

  1. Set the DLL name to “user32.dll” and press “Load”, all being well the “Add” , “Call” and “Set Type” buttons should be enabled and the “Load” button should now read “Unload”.
  2. Enter “GetWindowTextA” (for non-unicode version) in the “Function” edit box.
  3. Set the “Value” edit box to that of any hwnd, get the value using Spy++, the value must be in decimal not hex!
  4. Select “unsigned long” on the “Types” list box then press “Add”
  5. Set the “Value” to “12345678901234567890” then add an “unsigned char*”.
  6. Finally for nBufferSize set “Value” to 20 and add an “unsigned long”.
  7. Press “Call”.
  8. Click on the “unsigned char*” on the Args list and the window text (the first 19 bytes) for the window you entered should appear in the “Value” box.
  9. Press “Clear” to call another function in this DLL.

The return type is always caught in a DWORD and can be converted after the function has been called. This is done by selecting a “Type” and pressing “Set Type”, the value and type are displayed at the bottom of the dialog box. The Exception to this is functions that return type float, they must be declared before the function is called, the return type will remain float until it is changed to an integer or pointer type.

Closing Notes

  1. The string to value and value to string functionality should be moved into the CRuntimeDll object as should return type setting and access.
  2. Calling some DLL’s with the wrong arguments can cause the demo to crash. I have yet to find a way to stop or catch this. Bear in mind though, that applications with this kind of functionality would normally be used by some sort of developer who expect this sort of thing 😉

Downloads

Download demo application – 7 Kb
Download demo source – 15 Kb

History

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.