3

问题

我正在尝试为玩具语言编写解释器,并且我希望它能够调用位于 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"or "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
    }
}

然而,这显然不是一个好的解决方案,因为它使用汇编并且依赖于系统(有人告诉我这不适用于 64 位)。我怎样才能做得更好?

4

4 回答 4

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] );
}

这整件事对我来说似乎有点可疑,但我希望无论如何都能帮助你=)

于 2012-08-22T04:21:36.217 回答
0

我普遍认为,唯一的解决方案是按照 Mehrdad的建议使用 LibFFI (不幸的是,它不支持 MSVC),或者继续使用 Assembly 并维护 32 位和 64 位的不同版本(这就是我会努力做到的)。

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

再次,抱歉 - 我应该明确指出 DLL 中的函数名称在编译时不为程序所知。

于 2012-08-22T05:51:46.020 回答
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.
于 2012-08-22T05:24:27.733 回答
0

好的,我知道了:-

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

如果堆栈不是唯一的要求,那么在堆中传递数据的地址是很好的。

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

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;
    }
于 2012-08-22T04:24:46.513 回答