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**

}

DLLstd::string func;内のprocの名前(おそらく"print"または"add")と、std::vector<int> args;そのサイズがターゲット関数の引数の数であるaがあるとします。それに応じて正しい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

アセンブリに頼らずに 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;
}

または、テンプレート化されたアプローチを使用すると、proc をより簡単に呼び出すことができます。

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 に答える
0

私は、唯一の解決策は、Mehrdad によってここで提案されているように LibFFI を使用すること(残念ながら、MSVC をサポートしていません) を使用するか、Assembly を使用し続けて 32 ビットと 64 ビットの異なるバージョンを維持することであるという一般的な感覚を得ています (これはやってみます)。

Chris Becke によって提案されたすべてのメソッド シグネチャを生成することは、おもちゃの言語が単なる int 以外の型を持つため、実際的ではありません。

繰り返しになりますが、DLL 内の関数名がコンパイル時にプログラムに認識されないことを明確にする必要がありました。

于 2012-08-22T05:51:46.020 に答える