指定した関数名の関数ポインタを取得したい

[戻る]
らら 2007/07/20(金) 12:20:58
いつもお世話になります。 
関数名が文字列型引数に入っている状態で、その関数のポインタを
取得&実行しようとしています。
以下のようにして、DLLの関数ポインタを取得することができますが、
同じようにEXE本体にある関数の関数ポインタも取得したいのですが
わかりません。
ヘルプにより、GetProcAddress()は、DLLの関数に対してでないと
使用できないみたいですし。
よろしくお願いします。

//↓DLLの関数ポインタ取得は以下で上手く行きます。
typedef BOOL (WINAPI *LPFNDLLFUNC1)();
void CallDllFunction( LPCTSTR  lpszProcName )
{
    HINSTANCE hDLL;               // Handle to DLL
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer

    hDLL = LoadLibrary("MyDLL");
    if (hDLL != NULL)
    {
        lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress
(hDLL,                                                 
        lpszProcName );
        if (lpfnDllFunc1)
           {
            lpfnDllFunc1();
        
         }
        FreeLibrary(hDLL)
    }
}

//↓同じように関数名を文字列変数に入れて、このように実行したい。
void CallExeFunction( LPCTSTR  lpszProcName )
{
    LPFNDLLFUNC1 lpfnDllFunc1;    // Function pointer

    //↓もちろんこれでは、関数ポインタの取得に失敗します。
    lpfnDllFunc1 = (LPFNDLLFUNC1)GetProcAddress(
                ::AfxGetInstanceHandle(), 
                lpszProcName );
    if (lpfnDllFunc1)
       {
        lpfnDllFunc1();
    }    
}


mfc使用、VC6またはVC8でコンパイルしています。
よろしくお願い致します。

Blue 2007/07/20(金) 12:50:50
逆に質問なんですが、なぜそんな処理が必要なんでしょうか?
関数ポインタの配列をあらかじめ作っておくのではだめなのでしょうか?
# C++/CLIならばリフレクションで可能ですが。

サンプル)
#include <stdio.h>
#include <string.h>

int add(int n, int m) { return n + m; }
int sub(int n, int m) { return n - m; }
int pro(int n, int m) { return n * m; }
int div(int n, int m) { return n / m; }
int mod(int n, int m) { return n % m; }

int calc(const char* target, int n, int m)
{
    static const struct
    {
        const char* name;
        int (*func)(int, int);
    }
    funclist[] =
    {
        { "add", add },
        { "sub", sub },
        { "pro", pro },
        { "div", div },
        { "mod", mod }
    };

    int i;
    for (i = 0; i < sizeof(funclist) / sizeof(funclist[0]); ++i)
    {
        if (!strcmp(funclist[i].name, target))
            return (*funclist[i].func)(n, m);
    }
    return 0;
}

int main(void)
{
    int n = 10, m = 3;

    printf("%d + %d = %d\n", n, m, calc("add", n, m));
    printf("%d - %d = %d\n", n, m, calc("sub", n, m));
    printf("%d * %d = %d\n", n, m, calc("pro", n, m));
    printf("%d / %d = %d\n", n, m, calc("div", n, m));
    printf("%d %% %d = %d\n", n, m, calc("mod", n, m));

    return 0;
}

シーケンシャルリサーチなので要素数が多い場合、マップリストを使うなり
工夫してください。

tetrapod 2007/07/20(金) 13:05:37
限りなく Blue 氏に御意なのだが、とりあえず技術的興味のためだけにサンプル
動作確認済み

extern "C" __declspec(dllexport) int __stdcall TestFunc(void) {
    std::cout << "Welcome to TestFunc\n";
    return 0;
}

int _tmain(int argc, TCHAR* argv[], TCHAR* envp[]) {
    AfxWinInit(::GetModuleHandle(NULL), NULL, ::GetCommandLine(), 0);
    int (__stdcall *p)(void)=GetProcAddress(AfxGetInstanceHandle(), _T
("_TestFunc@0"));
    p();
    return 0;
}

非公開関数を名前で呼ぶことは無理
メンバ関数を呼ぶのも実用上無理

こういう非実用なコードを書くくらいなら、正しく関数テーブルや仮想関数とか使うべし

Blue 2007/07/20(金) 13:29:07
>__declspec(dllexport)
あぁその記述を入れておけばよかったのですね。

ちなみに、本題と関係ないけど
>    int (__stdcall *p)(void)=GetProcAddress(AfxGetInstanceHandle(), _T
("_TestFunc@0"));
GetProcAddressの2番目の引数は LPCSTR 型なので _Tマクロはつかってはだめですね。

[MSDN]GetProcAddress
http://msdn2.microsoft.com/en-us/library/ms683212.aspx

らら 2007/07/20(金) 13:40:56
ありがとうございます!
"_TestFunc@0" の、「_」と「@0」がよく分かってないのですが・・・。

tetrapod 2007/07/20(金) 14:01:17
> GetProcAddressの2番目の引数は LPCSTR 型
うぎょぎょ
昔のソースコードを見直しておこう・・・みんなには内緒で。

> "_TestFunc@0" の、「_」と「@0」がよく分かってないのですが・・・。
名前修飾というやつだ
http://msdn2.microsoft.com/ja-jp/library/46t77ak2(VS.80).aspx

再度言っておこう。こんな制約の多い方法を実用に供するのは意味が無い。
単なる技術的興味のためのみのサンプルである、と。

らら 2007/07/20(金) 14:31:54
「_」はおまじないで、@の後ろは引数のバイト数ですか。
引数をつけてみると、コンパイルエラーになります。書き方おかしいのでしょうか・・・

extern "C" __declspec(dllexport) int __stdcall TestFunc( char c ) 
{
  //処理省略
  return 0
}

呼び出すところ↓

    int (__stdcall *p)(char)=GetProcAddress(AfxGetInstanceHandle(), 
        _T("_TestFunc@1"));
    p('a');


ちなみにBlueさんの最初の質問の答えですが、
独自の簡易スプリクト言語のようなものを作るハメになり、
テキストで書かれた関数名などを裏方で呼び出したりすることも
必要になってしまったというわけです。

tetrapod 2007/07/20(金) 14:53:09
> 引数をつけてみると、コンパイルエラーになります。
よくわからないなら素直に関数テーブルとするべし。
最初に挙げられてる Blue 氏のサンプルがとても適切だと思う。
行くなら王道を行こう。わざわざ邪道を選んで迷いの森に入る必要は無い。

でもやっぱり技術的興味のためだけにサンプル

extern "C" __declspec(dllexport) int __stdcall TestFunc2(char x) {
    std::cout << "Welcome to TestFunc2(" << x << ")\n";
    return 0;
}
呼び出すほうは
    int (__stdcall *q)(char)=reinterpret_cast<int (__stdcall*)(char)>
        (GetProcAddress(AfxGetInstanceHandle(), "_TestFunc2@4"));
    q('a');

なぜに1ではなく4になるかは Microsoft に訊いておくんなまし。
まあぶっちゃけ効率向上のためなんだけど。

らら 2007/07/20(金) 15:26:18
[[解決]]
ありがとうございます。
でも、なんだか複雑になってきましたね・・・。
関数が多くなりそうだというのもあり、
GetProcAddressの方で使うことが出来たほうが都合いいかとか
考えていましたが、
なぜ@の後ろのバイト数がcharなのに4なのかとか不明だし
コンパイラによって代わったらどうしようとかの不安と、

同関数をDLLからも呼び出せるように作っていた(こちらは文字列
の関数名ではなくて普通に関数呼び出し)のですが、
DLL側でのリンクエラーが出て上手く行かなくなったので、
引数パターンが数種類あることについては、どうにかできる
として、Blueさんの提案していただいた配列の方でやってみる
ことにします。

一応・・・DLLから本体の関数について、
//本体側に置く関数は、こんな風な書き方で↓
BOOL __declspec(dllexport) FunctionInExe()
{
    //処理省略
    return TRUE;
}
そして、本体のコンパイルで自動的に出来たhontai.libを
DLL側の設定でリンクのライブラリに書き足すことで実装
しておりました。

//DLL内の関数が呼び出しているところ。↓
BOOL WINAPI FunctionInDll()
{
    BOOL FunctionInExe();

    BOOL res = FunctionInExe();
    return TRUE;
}

Blueさん、tetrapodさん、
丁寧にご指導いただき、ありがとうございました!!

RAPT [HomePage] 2007/07/21(土) 13:25:28
関数名を文字列で比較し、実行する方法として、COM の IDispatch 方式を使う
という手もありますね。
IDispatch は、GetIDsOfNames で名前を渡すと対応する序数を返し、
Invoke で序数を指定して関数を実行する ActiveX の基本的な構造を定義します。

らら 2007/07/21(土) 18:11:12
PARTさんありがとうございます。
GetIDsOfNameでWebで調べてみたりしましたけど、
私には難しそうです・・・

RAPT [HomePage] 2007/08/01(水) 23:20:05
IDispatch な考え方、という意味で、IDispatch を実装しろ、という訳ではないです。
参考までに、IDispatch 風な実装例です。

// VC++6 で動作確認済

#include <windows.h>
#include <tchar.h>
#include <iostream>


void foo( TCHAR x )
{
    std::cout << _T( "foo called - " ) << x << std::endl;
}


int bar( LPCTSTR str )
{
    std::cout << _T( "bar called - " ) << str << std::endl;
    return 99;
}


bool GetFuncIDFromName( LPCTSTR lpszName, DWORD* pdwID )
{
    if ( lpszName == NULL || pdwID == NULL ) {
        return false;
    }
    else if ( lstrcmp( lpszName, _T( "foo" ) ) == 0 ) {
        *pdwID = 0;
        return true;
    }
    else if ( lstrcmp( lpszName, _T( "bar" ) ) == 0 ) {
        *pdwID = 1;
        return true;
    }
    else {
        return false;
    }
}


bool Invoke( DWORD dwFuncID, LPVOID* vpResult = NULL, LPVOID vpParam = NULL )
{
    switch ( dwFuncID ) {
    case 0:
        foo( reinterpret_cast< TCHAR >( vpParam ) );
        return true;

    case 1:
        if ( vpResult == NULL ) {
            return false;
        }
        *vpResult = reinterpret_cast< LPVOID >( bar( static_cast< LPCTSTR >( 
vpParam ) ) );
        return true;

    default:
        return false;
    }
}


// 使い方の例
int main()
{
    DWORD dwFuncID;
    if ( GetFuncIDFromName( _T( "foo" ), &dwFuncID ) ) {
        Invoke( dwFuncID, NULL, reinterpret_cast< LPVOID >( _T( 'A' ) ) );
    }
    if ( GetFuncIDFromName( _T( "bar" ), &dwFuncID ) ) {
        LPVOID vpReturn = NULL;
        if ( Invoke( dwFuncID, &vpReturn, _T( "test string" ) ) ) {
            int nValue = reinterpret_cast< int >( vpReturn );
            std::cout << _T( "bar - return value : " ) << nValue << std::endl;
        }
    }
    return 0;
}

RAPT [HomePage] 2007/08/01(水) 23:23:58
ついでに、実行例

foo called - A
bar called - test string
bar - return value : 99

らら 2007/08/02(木) 13:24:44
実装例ありがとうございます。よくわかりました。
なるほど、そういう使い方できるんですね。
PARTさん、ありがとうございました。

古いスレッドには追加発言できません。