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

本文介绍了一种在运行时加载并执行Win32 DLL中任意函数的方法,通过获取DLL路径、函数名及参数类型等信息,使用内联汇编实现函数调用。文章详细解释了如何对不同类型参数进行堆栈操作,并展示了如何获取返回值。
 
 
23 votes for this article.
Popularity: 5.54. Rating: 4.07 out of 5.

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.

Collapse
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

本项目采用C++编程语言结合ROS框架构建了完整的双机械臂控制系统,实现了Gazebo仿真环境下的协同运动模拟,并完成了两台实体UR10工业机器人的联动控制。该毕业设计在答辩环节获得98分的优异成绩,所有程序代码均通过系统性调试验证,保证可直接部署运行。 系统架构包含三个核心模块:基于ROS通信架构的双臂协调控制器、Gazebo物理引擎下的动力学仿真环境、以及真实UR10机器人的硬件接口层。在仿真验证阶段,开发了双臂碰撞检测算法和轨迹规划模块,通过ROS控制包实现了末端执行器的同步轨迹跟踪。硬件集成方面,建立了基于TCP/IP协议的实时通信链路,解决了双机数据同步和运动指令分发等关键技术问题。 本资源适用于自动化、机械电子、人工智能等专业方向的课程实践,可作为高年级课程设计、毕业课题的重要参考案例。系统采用模块化设计理念,控制核心与硬件接口分离架构便于功能扩展,具备工程实践能力的学习者可在现有框架基础上进行二次开发,例如集成视觉感知模块或优化运动规划算法。 项目文档详细记录了环境配置流程、参数调试方法和实验验证数据,特别说明了双机协同作业时的时序同步解决方案。所有功能模块均提供完整的API接口说明,便于使用者快速理解系统架构并进行定制化修改。 资源来源于网络分享,仅用于学习交流使用,请勿用于商业,如有侵权请联系我删除!
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值