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). 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 %f\n",fRet);
    	else
    		printf("returned %d\n",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?

    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



Comments

  • hiding dll exported functions

    Posted by Dan_Henderson on 09/30/2004 09:42pm

    is there any way to export dll functions only when a certain condition exists? here's the scenario: 1. program calls an exported dll function, passing a "code". 2. dll checks the value of the code, and if it is ok, other exported functions will become available, and if the code is incorrect those functions would remain unavailable. Any ideas would be appreciated.

    Reply
  • Windows NT/XP

    Posted by Legacy on 09/10/2003 12:00am

    Originally posted by: albert drent

    There is a similar Delphi implementation by James Mistry. It is based on the C program. However, in NT and XP the function simply doesn't work. Does anybody know if this is also the case with the C function?

    Albert Drent

    Reply
  • Saving a passed argument in DLL

    Posted by Legacy on 08/04/2003 12:00am

    Originally posted by: Nikhil Khedkar

    Hi,
    I have a MFC application. It loads a DLL and invokes a method in it. In this method few arguments are passed of type long, LPSTR, COM interface pointer. I can access these arguments in the method in which I have passed them. I store them in some DLL variables. But when I try to access them in some other DLL method, these variables are set to the default value i.e. to NULL. How do you save the state of the variables, is anything special is needed for COM interface pointer?
    - Nikhil

    Reply
  • Help

    Posted by Legacy on 07/28/2003 12:00am

    Originally posted by: Erum Raza

    I use to call function from my activex dll from VB application, i use createobject to create the dll object, the tASK MANAGER shows that application increase in memory size, thats obivious but when i use set object=nothing then why that memory isn't returned to window system, it is showing same memory and some times 2-3 bytes less.
    Plz help me friends.

    Reply
  • Calling a dll function

    Posted by Legacy on 06/09/2003 12:00am

    Originally posted by: anjali

    Hello,
    
    I have one regular dll which exports few functions. I have loaded the dll in my VC application and trying to call the functions. GetProcAddress returns me some valid address. But when I give call to that function, it fails.

    Can anybody please tell me what is the problem or where I am going wrong ?

    anjali

    • Calling convention

      Posted by Rakeshsoni on 04/28/2005 03:30am

      There may be a problem with the calling convention. Make sure you are calling the function the same way you declared it.

      Reply
    Reply
  • The value of ESP was not saved across a function call

    Posted by Legacy on 05/30/2003 12:00am

    Originally posted by: bing yang

    Dear sir:

    When i run c++ dll, i got error message below. Is there anyone can help me?

    Thanks

    bing yang


    Debug error:
    Moudle
    File: i386\chkesp.c
    The value of ESP was not saved across a function call .This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention.

    • calling convention

      Posted by Rakeshsoni on 04/28/2005 04:25am

      The message shown by Windows is quite clear and correct. This is happening because the way the function was defined is not the same as the way you are calling for example WINAPI is missing or the return type is incorrect or something of that sort.

      Reply
    Reply
  • dll promblem

    Posted by Legacy on 05/22/2003 12:00am

    Originally posted by: Joey Bate

    I am trying to download this game for Windows 2000. Its saying that i dont have thedinput8.dll file or the dsound.dll file and its keep on doing that when i download them. Is there something i can buy to fix this promblem?�?

    sincerly
    Joey Bate

    Reply
  • call c++ dll from oracle form

    Posted by Legacy on 05/15/2003 12:00am

    Originally posted by: bing yang

    I want to add a button in oracle form to call c++ dll. But i have no experience and idea about it. Is anyone know it please repley?
    
    

    Thanks
    bing

    Reply
  • Calling a DLL from C++ without lib file or sources = HOWTO?

    Posted by Legacy on 04/06/2003 12:00am

    Originally posted by: thenext_1

    The problem is as simple as water.
    I have a dll (precisely ZLIB.DLL)

    How I can call its exports (which I know) and pass them parameters?
    Do I have to mess around with getprocaddress....?

    Is there a visualbasic-like way to declare a dll function and call it?

    thanks

    Reply
  • How to get the export function address of a DLL by Ordinal??

    Posted by Legacy on 01/14/2003 12:00am

    Originally posted by: Eric.Wu

    Normally,use a function name for get the address of a export-function in DLL Library.
    But,If you don't know the name of the function,can you get it's address by IT'S Ordinal Number???SOS??!

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds