如何调用一个函数,该函数用一个int向量取几个int

How do I call a function that takes several ints with a vector of ints?

本文关键字:int 函数 一个 向量 几个 何调用 调用      更新时间:2023-10-16

问题

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

#include <cstdio>
extern "C" {
__declspec(dllexport) void print(int val) { printf("%in", 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中进程的名称,可能是"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并依赖于系统(有些东西告诉我这在64-bit上不起作用)。我该如何做得更好?

类似于:

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

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

好的,得到了:-

  • 您正在堆栈上创建空间,然后将偏移量传递给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;
    }

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

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

同样,对不起,我应该明确指出DLL中的函数名在编译时对程序来说是未知的。

在C(以及C++)中做到这一点的唯一方法是声明所有可能的函数签名,然后根据参数的数量调用适当的签名,而不需要使用汇编。

接下来需要注意的是,在64位构建中,"int"不会削减它——基本参数大小将是64位类型。由于这是一个windows问题,我将使用windowssdk中的类型来确保它在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.