如何使用整数向量调用一个带有多个整数的函数?

时间:2012-08-22 03:47:59

标签: c++ windows assembly

问题

我正在尝试为玩具语言编写解释器,我希望它能够调用DLL中的函数。在某些external.dll我有:

#include <cstdio>

extern "C" {

__declspec(dllexport) void print(int val) { printf("%i\n", val); }
__declspec(dllexport) int add(int a, int b) { return a + b; }

... more functions **that I don't know then names of**

}

假设我有一个std::string func;,它是DLL中proc的名称,可能是"print""add",而std::vector<int> args;的大小是参数的数量目标函数。我如何相应地调用正确的DLL函数?理想情况下,我希望能够使用GetProcAddress调用任何可以加载的函数。

我的解决方法

我目前正在使用MSVC的内联汇编程序来执行我想要的操作。这有点像:

int WorkaroundCall(const std::string& func, const std::vector<int>& args) {
    void* proc = GetProcAddress(hmod, func.c_str()); // hmod is the DLL's HMODULE
    void* spsave, * argframe;
    size_t argsize = sizeof(int) * args.size();
    const int* argdata = args.data();

    __asm {
            mov eax, esp
            sub eax, argsize
            mov argframe, eax
    }

    memcpy(argframe, argdata, argsize);

    __asm {
            mov spsave, esp
            mov esp, argframe
            xor edx, edx
            call proc
            mov esp, spsave
    }
}

然而,这显然不是一个好的解决方案,因为它使用Assembly并依赖于系统(Something告诉我这不适用于64位)。我怎样才能更好地做到这一点?

4 个答案:

答案 0 :(得分:1)

类似的东西:

#define EXTERNAL_API __declspec(dllimport)
typedef void (EXTERNAL_API* LPPRINTFN)( int );
typedef int (EXTERNAL_API* LPADDFN)( int, int );

// After loading the module you get the functions.
LPPRINTFN pfnPrint = (LPPRINTFN) GetProcAddress( hmod, "print" );
LPADDFN pfnAdd = (LPADDFN) GetProcAddress( hmod, "add" );

现在,由于您拥有这些字符串,您可能希望将它们映射到唯一值(假设地图是全局的):

typedef enum FuncType {
    Nothing = 0,
    PrintFunc = 1,
    AddFunc = 2
} EFuncType;

typedef map< string, EFuncType > TFuncNameMap;
TFuncNameMap funcNameMap;

if( pfnPrint != NULL ) funcNameMap["print"] = PrintFunc;
if( pfnAdd != NULL ) funcNameMap["add"] = AddFunc;

最后,调用(不包括对参数向量的任何边界检查):

int SlightlyBetterCall( const std::string& func, const std::vector<int>& args )
{
    TFuncNameMap::iterator iFuncId = funcNameMap.find(func);
    if( iFuncId == funcNameMap.end() )
        return -1; // return some error?

    int result = 0;

    switch( iFuncId->second ) {
        case PrintFunc:
            pfnPrint( args[0] );
            break;
        case AddFunc:
            result = pfnAdd( args[0], args[1] );
            break;
    }

    return result;
}

你真的不需要那张地图......没有什么可以阻止你去的:

if( func == "print" && pfnPrint != NULL ) {
    pfnPrint( args[0] );
}
else if( func == "add" && pfnAdd != NULL ) {
    result = pfnAdd( args[0], args[1] );
}

这整件事对我来说似乎有点怀疑,但我希望在任何情况下都可以帮助你=)

答案 1 :(得分:0)

好的了: -

  • 您正在堆栈上创建空间,然后将偏移量传递给proc

如果stack不是唯一的要求,那么传递堆中数据的地址是件好事。

但是,您可以使用数组在堆栈上分配空间。

int WorkaroundCall(const std::string& func, const std::vector<int>& args) {
        void (*proc)(int);
        void* proc = GetProcAddress(hmod, func.c_str()); // hmod is the DLL's HMODULE
        void* spsave, * argframe;
        size_t argsize = sizeof(int) * args.size();
        const int* argdata = args.data();
        int *dat= new int[argsize];    
        memcpy(dat, argdata, argsize);
        proc(dat);
        delete[] dat;
    }

答案 2 :(得分:0)

在C(以及C ++)中执行此操作的唯一方法是在不使用程序集的情况下声明所有可能的函数签名,然后根据参数的数量调用相应的签名。

接下来要注意的是,在64位构建中,'int'不会削减它 - 基本参数大小将是64位类型。由于这是一个Windows问题,我将使用windows sdk中的类型来确保它在Win32和Win64中编译并正常工作。

#include <windows.h>
typedef INT_PTR CALLBACK Tfunc0(void);
typedef INT_PTR CALLBACK Tfunc1(INT_PTR p1);
typedef INT_PTR CALLBACK Tfunc2(INT_PTR p1,INT_PTR p2);

INT_PTR CallProc(FARPROC proc, const std::vector<INT_PTR>& args) {
    switch(args.size())
    {
    case 0: return ((TFunc0*)proc)();
    case 1: return ((TFunc1*)proc)(args[0]);
    case 2: return ((TFunc2*)proc)(args[0],args[1]);
    }
    throw some_kind_of_exception;
}

或者,模板化方法可以让您更轻松地调用procs:

template<typename RetT> 
RetT CallProc(FARPROC proc){
  return (RetT)(((TFunc0*)proc)());
}

template<typename RetT,typename Param1T> 
RetT CallProc(FARPROC proc, Param1T p1){
  return (RetT)(((TFunc1*)proc)((INT_PTR)p1));
}
//etc.

答案 3 :(得分:0)

我得到的一般意义是,唯一的解决方案是按照Mehrdad的建议here使用LibFFI(不幸的是,它不支持MSVC),或继续使用Assembly并维护32的不同版本和64位(我会尝试这样做)。

按照Chris Becke的建议生成所有方法签名是不切实际的,因为玩具语言的类型不仅仅是int。

再次,抱歉 - 我应该已经明确表示编译时程序不知道DLL中的函数名称。