DLLの関数を呼び出す方法は?

[戻る]
まゆたん 2001/02/09(金) 21:06:18
こんばんわ。いつも質問させてもらってます(質問ばっかり・・・)。
DLLを使ったプログラムをつくりたいんですけど、
うまくいかなくって困っています。

ダイアログのボタンをクリックすると、
    typedef void (* DLL_FUNC)();
    HINSTANCE hDll;
    DLL_FUNC foo;
    hDll=LoadLibrary("MSG2.DLL");
    if(!hDll){
        AfxMessageBox("関数がないよ♪",MB_OK);
    }
    foo = (DLL_FUNC)GetProcAddress(hDll,"MSG_EX@1");
    if(!foo)
    {
        AfxMessageBox("関数がないぞぉ。",MB_OK);
    }
という処理が走るんですが、
用意した"MSG2.dll"には"MSG_EX()"という関数を設けてるのに、
いつも「関数がないぞぉ。」というメッセージがでます。
なお、MSG_EX()は「できたね!」というメッセージを表示
させる処理を書いてるんですけれど、それもでません。

わたしとしては、DLLもその中にはお目当ての関数もあるので、
「できたね!」
と出て欲しいのですが(泣)。

呼び出し方(EXE側)が悪いのか、
呼び出され方(DLL側)が悪いのか、
わからないです。

なんだか、回を増すごとに、
説明がへたっぴーになってるような気がしますが、
解決方法・お気づきの点がある方、
どうか教えてください。
よろしくお願いします。

ku 2001/02/09(金) 22:10:49
VCの開発環境をインストールしたのならdumpbin.exeが入っています
コマンドラインで
>dumpbin /exports (パス)msg2.dll
と入力してください
エクスポート名がわかります
自分が付けた関数名の前後に?や@等が付いていたらそれがそのまま
エクスポート名として使用できます(ずいぶん長い名前になっていると思います)
この名前でGetProcAddressやれば成功すると思います

必ずdllをビルドしたら、アプリケーションをリビルドするのなら
エクスポートする関数をソースとヘッダの両方で下記のように括れば問題ありません
#ifdef __cplusplus
extern "C" {
#endif

関数

#ifdef __cplusplus
}
#endif

なぜこうするかですがC++のオーバーロードは使用した事がありますか?
int a(int a);
int a(int a, int b);
こんな感じのやつです
これをdllにしてエクスポートしたとしましょう
dllのエクスポートには引数の個数で呼び分ける機能はありません
ですから、同じエクスポート名は使用できません
これをサポートするために見えない所で修飾した結果,上記の?や@が付属されます
(?A_INT@@AAAと?A_INTINT@@AAAみたいな感じになります/規則は知らないけど)
extern "C"でくくられているとオーバーロードはできなくなりますが
修飾する必要が無くなるので?や@等が無くなります

別の方法はdefファイルを作る方法です
新しいファイル(msg2.def)というのを作成してください
内容は
EXPORTS
    MSG_EX
こんな感じでしょうか
メニューの「プロジェクト」−「プロジェクトへの追加」−「ファイル」で
msg2.defを選択してください
後はリビルドするだけ
僕はいつもこちらを使用
dllでエクスポートする関数は上記のextern "C"でも括った上にdefファイルも作成してます

ちょいと疑問,何故"MSG_EX@1"でGetProcAddressしてるんだろう?
惜しいところまでいってたということだろうか?
ちなみにメニューの「プロジェクト」−「プロジェクトの設定」の
「リンク」タブの「カテゴリ」−一般でオブジェクトライブラリモジュールに
msg2.libを追加した場合はその辺りをうまくやってくれるので
修飾云々なんてことは考えなくてもうまくリンクしてくれます
GetProcAddressを使うかVBから呼ぶとき(結局GetProcAddressを使ってるはず)
修飾された名前にされてGetProcAddressが失敗してうまく呼べなくなる事があります

おまけ,コマンドラインで
>dumpbin /imports (パス)msg2.dll
とやればどのdllにあるどんなAPIをインポートしてるかを知ることが出来ます

まゆたん 2001/02/13(火) 14:58:16
いつもありがとうございます、kuさん。
コマンドラインの打ち方がちょっとわからなかったので、
kuさんが教えてくれた「別の方法」のdefファイルを作る
やり方を試してます。

MFC AppWizard(dll)を使ってdllを作っていると、
あらかじめdefファイルがあったので、そこに

EXPORTS
    ; 明示的なエクスポートはここへ記述できます
    MSG_EX

と記述しました(MSG_EXは例の「できたね!」の関数です)。


すると、変化がありました。

メッセージがなにも出ないんです。

dllファイルが見つかり、
MSG_EXってゆう関数も見つかったとゆうことだと思います。
(見つからなかったらエラーメッセージを出してるので)

でも、カンジンのメッセージ「できたね!」も、出ないんです。

今、dllのcppファイルでは、こんなかんじで書いてます。

#ifdef __cplusplus
extern "C" {
#endif

void CMSG2App::MSG_EX()
{
    AFX_MANAGE_STATE(AfxGetStaticModuleState());
    AfxMessageBox("!",MB_OK);
}

#ifdef __cplusplus
}
#endif

exe側に、「プロジェクト」−「プロジェクトへの追加」−「ファイル」で
msg2.defを選択しても、メッセージは出なかったです(涙)。

まだどこかに記述がたりないのかなぁ・・・。

「ちょいと疑問,何故"MSG_EX@1"でGetProcAddressしてるんだろう?」
とのことですが、わたしは、EXPORTSのところに、関数を書いたとき、
その関数のIDみたいなもの(?)として、@1とかがひつようなのかな?
って思ってた(勘違いしてた)んで、書いてました(恥)。

ku 2001/02/13(火) 15:53:00
MFCのDLLは詳しくないので答えられないです
その方法ではうまく動かないと思います
クラスのメンバ関数はGetProcAddressを使って呼ぶのは難しいような気がします
難しく言うと
thisポインタをスタックに積まなくてはいけないのですが
スタックに積むフェーズが無いのです

DLLのLIBファイルを使用する方法を書いておきます
ご存じでしたら無視してください(GetProcAddressを使う方法は知りません)
(説明が面倒なので固定ドライブとフォルダ名やプロジェクト名を使用して説明します)
まず、Cドライブのルートに000というフォルダを作成してください
C:\000にMFC AppWizard(dll)でabcというプロジェクトを作成します。
StdAfx.hの最後の方に
#define __ABC_DLL_EXPORTS
を追加します。
abc.hに
#ifdef __ABC_DLL_EXPORTS
#define ABCDLL_API __declspec(dllexport)
#else
#define ABCDLL_API __declspec(dllimport)
#endif
と追加します。
CAbcAppのメンバ関数にpublic関数として
    ABCDLL_API int Abc(void);
を追加します。
abc.cppの
CAbcApp theApp;を
ABCDLL_API CAbcApp theApp;に修正します。
abc.cppに
ABCDLL_API int CAbcApp::Abc(void)
{
    AfxMessageBox("!", MB_OK);
    return 0x12345678;
}
を追加します。
で、ビルドします。

次にMFC AppWizard(exe)でC:\000にxyzをダイアログベースで作成します
プロジェクトの設定の
デバッグのカテゴリ−一般の作業ディレクトリを..\abc\debugにします。
同じくリンクのカテゴリ−一般のオブジェクト/ライブラリに..\abc\debug\abc.libを追加します。
xyz.cppにあるOnOK()の前後を下記のようにします
#include "..\abc\abc.h"
ABCDLL_API CAbcApp theApp;
void CXyzDlg::OnOK() 
{
    theApp.Abc();
}
これでビルドしてください。

一応、動くと思うんですが、いかがでしょうか

ku 2001/02/13(火) 15:55:52
DLLの関数の先頭にマクロ書き忘れちゃった
これで正しいかどうかはよく分からないです
MFCDLLを作ったの初めてなんで...

あと使い方も、もっと便利なのがありそうだし...

まゆたん 2001/02/13(火) 17:09:10
ごめんなさい。

「できたね!」のメッセージが表示されない理由がわかりました。
変数fooに、関数のアドレスを取得したあと、
fooを呼び出していませんでした(恥)。

わたしはてっきり、

  foo=(DLL_FUNC)GetProcAddress(hDll,"MSG_EX@1");

のところで関数が呼び出されるのかな、って思ってたんですけど、
そのあと、foo()を呼び出さないといけなかったんですね。
なんか、一人でまいあがってしまい、恥ずかしいです。



kuさん、お返事ありがとうございます。

メッセージボックスを出すだけなら、なんとかDLLから呼び出す
ことができましたけど、
kuさんが言う「難しい」とゆーのは、
例えばダイアログを表示したりすることが
難しいとゆーことでしょうか?
LIBファイルを組み込む方法、試してみたいと思います。
いつもいつも、ありがとうございます、kuさん。




ところで、今、DLLファイルをexeの方のフォルダに入れて

  LoadLibrary("MSG2.DLL");

と呼び出しているんですけど、
DLLファイルをフォルダから外に出して、
LoadLibraryの""のところにDLLのフルパスを入れ場合、
「関数がないよ♪」としかられちゃいます。
メッセージからすると、
たぶん、DLLそのものが見つかってないんだと思うんですが・・・。

また、なにか悪いことをしたのかなぁ?

2001/02/13(火) 23:43:45
> DLLファイルをフォルダから外に出して、
> LoadLibraryの""のところにDLLのフルパスを入れ場合、
> 「関数がないよ♪」としかられちゃいます。
> メッセージからすると、
> たぶん、DLLそのものが見つかってないんだと思うんですが・・・。

フルパスの指定が間違ってませんか?
例えば、C:\TESTの場合だと
LoadLibrary("C:\\TEST\\MSG2.DLL");

ほかの部分を変えていないとすれば、この様なニアミスが殆どだと思いますよ。(←経験済)

ku 2001/02/14(水) 00:00:57
メンバ関数をうまくコールできる理由は私には分かりません
きっとうまい仕掛けがあるんでしょうね
関数のコールが成功しているのならダイアログを表示するのも
うまくいくと思います

LoadLibraryはDLLのファイル名のみの指定とフルパスの指定ができます
フルパスの指定は重さんが書かれている通りです
フルパスの指定の方が優先度が高いのでフルパスで指定した場合は
指定されたファイルが無い場合はLoadLibraryが失敗します
LoadLibrary("MSG2.DLL");とファイル名のみを指定した場合は
下記の順序でMSG2.DLLを探します
(下記に該当するフォルダにMSG2.DLLが無い場合はロードできません)
1.exeがあるフォルダのmsg2.dll
2.GetCurrentDirectoryで得られるフォルダにあるmsg2.dll
3.GetSystemDirectoryで得られるフォルダにあるmsg2.dll
4.GetWindowsDirectoryで得られるフォルダにあるmsg2.dll
5.PATH環境変数内にリストされているフォルダにあるmsg2.dll

1から5まで順にmsg2.dllを探して見つからないとエラーとなります

まゆたん 2001/02/14(水) 12:36:28
[[解決]]
重さん、はじめまして。
kuさん、いつもお返事くださってありがとうございます。

みなさん、お返事くださって、とってもうれしいです。

重さんの教えてくれたとおりに、フルパスを

  "C:\\〜〜〜〜〜\\MSG2.dll"

とゆー風に、「\」を「\\」に変えたら、
「できたね!」のメッセージがでました!
フルパスの書き方がまちがっていたんですね。

これからは、VCでフルパスを書くときは、「\\」でかきますね。
ありがとうございます。重さん。

kuさん、もう一つの方法を教えてくれて、
ありがとうございます。
フルパスじゃなくって、ファイル名だけ書いたら、
特定のフォルダを探しに行ってくれるんですね。
(う〜ん、かしこいなぁ。)


原因もわかり、
無事解決できたのも、重さんとkuさんのおかげです。

本当に、ありがとうございました。

これからも、(へたな文章ですが)
よろしくお願いします。

  

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