Click here to Skip to main content
Click here to Skip to main content

Execute a function in any Win32 DLL - Reflection in Win32 DLL?

, 24 Dec 2004
Rate this:
Please Sign up or sign in to vote.
Crazy experiment to decide DLL path, function name, parameter types and parameter values at run time and execute that function residing in Win32 DLL.

Sample Image - ExecDLL.jpg

Introduction

Most of us know about reflection (Example: Windows COM, .NET) and its flexibilities. Have you ever wondered how to accomplish reflection in Win32 DLL? Here is the funny tool that resulted from that kind of my thought. "Uh!.. Reflection in Win32 DLL", oops!.. don't believe 100%. Don't worry, you can't accomplish perfect reflection in WIn32 DLLs in a simple way. In perfect reflection, you don't need the knowledge of source, function, parameters & types until its usage. When considering Win32 DLL, I feel late binding is some sort of reflection (don't accuse me, just an opinion). Late bound DLLs can do without the path name, function name until its usage, but need the function argument type list to compile. It seems late binding is impossible when you don't know the parameter list & type until execution.

This article is just a crazy demonstration of how you can possibly load and execute any function in a Win32 DLL without compiling the tool for the particular DLL. Here in this section, I'm directly jumping to detail explanation assuming you have sufficient knowledge about DLLs, late binding, etc.

Idea behind this

To accomplish what this tool should do on run time ...

  • Load the DLL: Since LoadLibrary has LPCTSTR type parameter for DLL name to load, just get string input from user and pass to it.
  • Get the function pointer: Here also, GetProcAddress has LPCTSTR type parameter for function name to pass, get string input from user and pass to it.
  • Pass the parameters: Here comes the problem. Compiler, in most cases, passes arguments to function through stack. Try to simulate the compiler, get the parameter types from user in addition to the parameter values. Align & push the parameters in to the stack.
  • Call the function: jump to the function using the function pointer that is obtained through GetProcAddress.
  • Get the return value: Get the return type from user. Function always returns 32 bit value (except in 64 bit applications) in a register. Get this value and type cast into the type specified by the user.

That's it. Last three steps are the exceptional procedures in this tool from normal late binding code. So I'll explain those in detail.

As I stated earlier, every parameter to a function in a DLL is passed through stack. Compiler pushes the parameters in to the stack whenever you compile an application to call a function with _stdcall, _cdecl calling conventions. Here, same thing is done on run time. I have used inline assembly to accomplish this (don't be disappointed about inline assembly, if you don't know; in Windows, it is not as much as difficult as Linux GCC inline assembly).

Stack should be 32 bit aligned, i.e., align to DWORD. So every parameter should be aligned to DWORD before pushing in to stack. After pushing, call the function using the function pointer (here also, inline assembly is necessary). The return value will be in EAX register if it is a 32 bit value. If it is 64 bit, then it will be in combination of EAX and EDX registers. Using assembly, save this to a variable. Now this variable can be type-cast to any of the user specified return type.

Code detail

The above logic is explained in this below code. This code performs the core functionality of this tool.

void CExecDLLDlg::OnBtnExec() 
{       //  this list control has the user input of parameter types & values
    CListCtrl *pcList = (CListCtrl*)GetDlgItem(IDC_LSTPARAM);
    
    char szText1[MAXCHAR_TEXT];
    char szText2[MAXCHAR_TEXT];
    int nCount = pcList->GetItemCount();

    int nArrayCount = nCount;
    struct ST_PARAM
    {
        int nType;    // if =0 then its DWORD
                // if =1 ......
        DWORD dwValue;
    } *pArray = new ST_PARAM[nArrayCount];

    DWORD dwDWord;
    DWORD *pdwDWord;
    char *lpString;
    char **lppString;

///////////// go through the list and collect /////////////////////////////
/////////////////// the arguments in a structure array ////////////////////
    for(int i=0,j=0;(i<nCount)&&(j<nArrayCount);i++,j++)
    {
        pcList->GetItemText(i,0,szText1,MAXCHAR_TEXT);
        pcList->GetItemText(i,1,szText2,MAXCHAR_TEXT);

        if(!strcmp(szText1,"Int"))
        {
            pArray[j].nType = 0;
            dwDWord = atol(szText2);
            
            pArray[j].dwValue = dwDWord;
        }
        else if(!strcmp(szText1,"Short"))
        {
            pArray[j].nType = 1;
            dwDWord = atoi(szText2);
            
            pArray[j].dwValue = dwDWord;
        }
        else if(!strcmp(szText1,"Byte"))
        {
            pArray[j].nType = 2;
            dwDWord = atoi(szText2);
            
            pArray[j].dwValue = dwDWord;
        }
        else if((!strcmp(szText1,"Int*"))||
            (!strcmp(szText1,"Short*"))||
            (!strcmp(szText1,"Byte*")))
        {
            pArray[j].nType = 3;
            pdwDWord = new DWORD;
            *pdwDWord = atol(szText2);
            
            pArray[j].dwValue = (DWORD)pdwDWord;
        }
        else if(!strcmp(szText1,"String"))
        {
            pArray[j].nType = 4;
            lpString = new char[strlen(szText2)+1];
            strcpy(lpString,szText2);

            pArray[j].dwValue = (DWORD)lpString;
        }        
        else if(!strcmp(szText1,"String*")) // not supported (not tested :D)
        {
            pArray[j].nType = 5;
            lppString = new char*[1];
            lpString = new char[strlen(szText2)+1];
            strcpy(lpString,szText2);
            
            lppString[1] = lpString;
            pArray[j].dwValue = (DWORD)lppString;
        }
        else if(!strcmp(szText1,"Struct*"))
        {// here already aligned memory address (using CStructDlg::OnOK() function )
         //will be in current list item. So get it and put it
            pArray[j].nType = 6;

            CMemInfo *pcMemInfo;
            pcMemInfo = (CMemInfo*)atol(szText2);
            
            pArray[j].dwValue = pcMemInfo->m_dwMemAddress;
        }
        else if(!strcmp(szText1,"Array*"))
        {// here already aligned memory address (using CArrayDlg::OnOK() function )
         //will be in this list item. So get it and put it
            pArray[j].nType = 7;
            
            CMemInfo *pcMemInfo;
            pcMemInfo = (CMemInfo *)atol(szText2);
            
            pArray[j].dwValue = pcMemInfo->m_dwMemAddress;
        }
        else if(!strcmp(szText1,"Struct"))
        {// here already aligned memory address (using CStructDlg::OnOK() function )
         //will be in this list item. So get it and put it
            CMemInfo *pcMemInfo;
            pcMemInfo = (CMemInfo *)atol(szText2);

            DWORD dwLen = pcMemInfo->m_dwMemLen;
            DWORD nMemCount = (dwLen/4);
            DWORD nCurCnt = i;
            nArrayCount += nMemCount;
            nArrayCount--;

            if(dwLen%4)
            {
                delete pArray;
                MessageBox("Memory is not aligned",
                    "Execute DLL",
                    MB_OK|MB_ICONERROR);
                return;
            }
            pArray = (ST_PARAM*)realloc(pArray,
                        sizeof(ST_PARAM)*nArrayCount);
            
            pdwDWord = (DWORD*)pcMemInfo->m_dwMemAddress;

            for(;j<nCurCnt+nMemCount;j++)
            {
                pArray[j].nType = 8;
                pArray[j].dwValue = *pdwDWord;
                pdwDWord++;
            }
            j--;
        }
    }
/////////////  load the DLL //////////////////////////////////////
    typedef int (__stdcall *FUNCTION)(void);
//// DLL path
    GetDlgItemText(IDC_EDTFILEPATH,szText1 ,MAXCHAR_TEXT);

//// Function name (Obtained through enumeration using Dbghelp API)
    GetDlgItemText(IDC_CMBFUNCNAME,szText2 ,MAXCHAR_TEXT);

    HMODULE hMod = LoadLibrary(szText1);
    if(!hMod)
    {
        MessageBox("DLL not found","Execute DLL",MB_OK|MB_ICONERROR);
        return;
    }
//////////// get the function pointer //////////////////////////////////
    FUNCTION proc = GetProcAddress(hMod,szText2);
    if(!proc)
    {
        MessageBox("Function not found","Execute DLL",MB_OK|MB_ICONERROR);
        return;
    }

/////////// one by one push the arguments //////////////////////////////
    for(i=nArrayCount-1;i>=0;i--) // args should be pushed in reverse order
    {
        dwDWord = pArray[i].dwValue;
        _asm
        {
            mov eax, dwDWord
            push eax
        }
    }
/////////// call the function and store the return value /////////////////
    _asm
    {
        call proc
        mov dwDWord, eax
    }
//////////////  convert and display the return value ///////////////////////////
    GetDlgItemText(IDC_CMBRETTYPE,szText1,MAXCHAR_TEXT);

    if(!strcmp(szText1,"Void"))
    {
        // do nothing
        strcpy(szText2,"");
    }
    else if((!strcmp(szText1,"Int"))
        ||(!strcmp(szText1,"Short"))
        ||(!strcmp(szText1,"Byte")))
    {
        sprintf(szText2,"%u",dwDWord);
    }
    else if(!strcmp(szText1,"Int*"))
    {
        sprintf(szText2,"%u",*((int*)dwDWord));
    }
    else if(!strcmp(szText1,"Short*"))
    {
        sprintf(szText2,"%u",*((SHORT*)dwDWord));
    }
    else if(!strcmp(szText1,"Byte*"))
    {
        sprintf(szText2,"%u",*((BYTE*)dwDWord));
    }
    else if(!strcmp(szText1,"String"))
    {
        sprintf(szText2,"%s",(char*)dwDWord);
    }    
    else if(!strcmp(szText1,"String*"))// at this moment not supported 
    {
        sprintf(szText2,"%s",(char*)dwDWord);
    }
    SetDlgItemText(IDC_EDTRETVALUE,szText2);

    FreeLibrary(hMod);

//////////////// clean up the allocated aruments //////////////////////////
/// oops !.....
    delete pArray;
}

Secondary part of the code includes aligning user defined (structure) type data and array type data.

void CStructDlg::OnOK() 
{
    ..
    ....
    align user input structure
    pass the aligned mem address
    ....
    ,,
}

void CArrayDlg::OnOK() 
{
    ..
    ....
    align user input array
    pass the aligned mem address
    ....
    ,,
}

In addition to this, you may find GetDLLFileExports interesting. This function retrieves all the exported functions in a DLL. This function needs dbghelp.h and dbghelp.lib files from Microsoft Debugging SDK.

bool GetDLLFileExports (char *szFileName, 
     unsigned int *nNoOfExports, char **&pszFunctions)
{
    ....
}

Other codes are kind of aesthetic stuff. Like getting the input from user, presenting to user, etc.

Sample tests

These zips contain a tool project and a sample DLL project to test this tool. Use TestDLL if you are squeamish to use some other DLL found in your PC. Else, just ignore this TestDLL and use Windows system DLLs. I'm not encouraging or discouraging you to use these DLLs, it's up to you.

Example 1: Take USER32.DLL found in System32 directory. This DLL has most of the GUI APIs. Select the DLL and then the function MessageBoxA. Now push (Add) the parameters one by one like in the above picture. For the handle parameter, copy the value from lower left hand edit box. Set the return type to "Int". When you click "Execute", you can see that the message box pops out like in the picture. After you click "Yes", "No" or "Cancel" on that message box, you can see that the appropriate return value comes on the return parameter edit box.

Example 2: Bit more complicated example. On the USER32.DLL, select the function PtInRect. Set the return type to "Int". Select the "Struct" type in param type combo. When a Struct dialog appears, enter two "Int" type values 10,10 and click OK. Now push (Add) the integer value that appears on the param value edit box. This is for the second parameter (POINT pt) of the selected function. Now, select "Struct*" in param type combo. When a Struct dialog appears, enter four "Int" type values 0, 0, 100 & 100 and click OK. Push (Add) the integer value that appears on the param value edit box. This is for the first parameter (const RECT *lprc). On execute, you can see the return value 1 on return value edit box.

Specifying any incorrect parameter type for a function may result in tool crash. For example, you can make the tool crash on the second example if you specify "Struct" instead of "Struct*" or vice versa.

Known issues & lacking features

  • Passing in/out parameters not tested thoroughly.
  • Can not display the returned structures & arrays.
  • Supports only Win32 DLL functions, i.e., functions specified with _stdcall calling convention.
  • Unicode parameter strings are not supported.
  • Passing and returning 64 bit values.

Planned features

  • Reading the arguments, parameters and types from XML file and execute.
  • Executing functions in a sequence.
  • Adding logging mechanism.
  • Extending this tool to do automated testing.

Conclusion

This tool is not complete to use in real time testing but very useful for learning stuffs behind the curtain. Due to time constraints at this moment, I have put a full stop to this development, and I'm not putting a full stop to those who want to modify, fix bugs or add any additional features. If you do so, please update me with a copy (if you wish). Thanks.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Ramanan.T
Software Developer (Senior)
Australia Australia
No Biography provided

Comments and Discussions

 
Question64bit version Pinmemberjung-kreidler22-Jul-11 0:06 
GeneralPassing Parameters by Reference PinmemberACordner1-Mar-11 10:18 
GeneralExcellent! PinmemberSharjith19-Mar-10 12:57 
Questionhow can i get function names with its parameters? PinmemberHari Om Prakash Sharma16-Jan-10 0:40 
Generalpassing input values by reference Pinmemberkishoresajja23-Mar-09 6:20 
Questionhow can i deal other datatype of VC++? Pinmemberqileifeng4-Feb-09 1:42 
GeneralFor Calling User DLLS Solution Pinmemberpdump200027-Oct-07 10:39 
GeneralWow! Thanks PinmemberRich Douglass21-Mar-07 9:11 
GeneralRe: Wow! Thanks PinmemberT.YogaRamanan22-Mar-07 18:21 
Generalgetting the output parameters value Pinmembervericon17-Oct-05 20:40 
GeneralThanks PinmemberThatsAlok5-Jan-05 18:58 
GeneralUse Alloca instead of new Pinmemberbrianma4-Jan-05 9:37 
GeneralRe: Use Alloca instead of new PinmemberT.YogaRamanan4-Jan-05 11:30 
GeneralRe: Use Alloca instead of new Pinmemberbrianma5-Jan-05 1:42 
GeneralPython 'ctypes' module PinmemberRichie Hindle3-Jan-05 22:37 
GeneralRe: Python 'ctypes' module PinmemberT.YogaRamanan4-Jan-05 3:10 
GeneralRe: Python 'ctypes' module PinmemberRichie Hindle4-Jan-05 3:52 
GeneralRe: Python 'ctypes' module PinmemberT.YogaRamanan4-Jan-05 11:59 
GeneralRe: Python 'ctypes' module PinmemberRichie Hindle4-Jan-05 21:30 
GeneralRe: Python 'ctypes' module PinmemberT.YogaRamanan4-Jan-05 23:01 
GeneralRe: Python 'ctypes' module PinmemberRichie Hindle4-Jan-05 23:19 
GeneralParameter Information in DLL PinmemberHubert Mayer2-Jan-05 22:03 
GeneralRe: Parameter Information in DLL PinmemberT.YogaRamanan3-Jan-05 7:02 
QuestionHow can I know the prototype? PinmemberCong Dan Luong29-Dec-04 15:09 
AnswerRe: How can I know the prototype? PinmemberT.YogaRamanan29-Dec-04 15:37 
GeneralNice work Pinmemberpatelsair29-Dec-04 5:59 
Questionpush inside for? PinmemberGoran Mitrovic26-Dec-04 20:16 
AnswerRe: push inside for? PinmemberT.YogaRamanan27-Dec-04 3:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140721.1 | Last Updated 24 Dec 2004
Article Copyright 2004 by Ramanan.T
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid