こんにちはゲストさん。会員登録(無料)して質問・回答してみよう!

解決済みの質問

DLL関数を使ったプログラム

 DLL関数を使ったプログラムを動かしたんですが、ERRORが解決できず
困っています。DLLを作成したのはいいのですが、それをLoadLibraryで読み込もうとしても指定したプロシージャが見つからないというエラーになるようです。使用している環境はVisual Studio.NET2003です。
 以下はDLLのソースです。
// plug.c //
#include <windows.h>
#include <stdio.h>

__declspec(dllexport) void CALLBACK TestFunc()
{
   printf("DLLのTestFunc()関数が実行されました。\n");
}
これをビルドするとplug.dllとplug.expとplug.libが作成されました。
 そしてこれを使用したプログラムが
// stab.c //
#include <windows.h>
#include <stdio.h>

typedef void (*TestFunc)(void);

void main()
{
    HMODULE hModule;
    DWORD error;
    TestFunc funcPointer;

    hModule = LoadLibrary(TEXT("plug"));
    error = GetLastError();//error値が127
    funcPointer = (TestFunc)GetProcAddress(hModule,TEXT("TestFunc"));
    funcPointer();
    FreeLibrary(hModule);

    getchar();
}
LoadLibrary関数を使用してアプリのメモリ空間にDLLを読み込もうとしているんですが、ここでハンドルが正確に渡されていないみたいなんです。どうしてこうなるのか分かりません。わかる方いらっしゃったらよろしくお願いします。

投稿日時 - 2008-12-01 21:51:49

QNo.4522469

困ってます

質問者が選んだベストアンサー

このようなBBSで聞かれるよりは、
MSDNをご自分で調べられた方が遥かに
自分のためになると思いますが。。。

まず、モジュール名ですが、
LoadLibraryは第1パラメータでファイル名の拡張子を省略した場合、
既定の拡張子として「.DLL」が追加されます。
実行モジュールの取得に失敗した場合、
拡張エラー番号は126になります。

まずは今回の127というエラーが本当にLoadLibraryの後直ぐに
取得された拡張エラーなのかを教えてください。
#GetLastErrorは直前に実行した関数のエラーしか見れないので、
#タイミング的に書き換わっている可能性があります。

エラー的に127が返されたということは、
GetProcAddressのエラーである可能性が非常に高いです。

もしもGetProcAddress関数のエラーで返されたエラーならば、
DLL側がdefファイルを使用してビルドされていない可能性があります。

DEFファイルを使用しない場合、VC++はエクスポートする関数名を
修飾するためDLL側で用意した関数の関数名を指定するだけでは
アドレスを取得できません。

取得側はGetProcAddressに"_TestFunc@0"というような
修飾された「完全に明確な名前」を指定する必要があります。
#修飾子名はビルドするたびに異なる可能性があります。

これを回避する為の一番手っ取り早い方法は、
plug.defというファイルをプロジェクトに取り込み、
エクスポートする関数名を限定する方法があります。
--- plug.def -------------------------------
LIBRARY plug
EXPORTS
TestFunc @1
---ここまで----------------------------------

参考URL:http://msdn.microsoft.com/ja-jp/library/cc429241.aspx

投稿日時 - 2008-12-02 14:39:48

補足

plug.def(モジュール定義)をプロジェクトに取り込んでTestFuncで実行したところERRORなく動作しました。defファイルを取り込んだお陰ですね。ありがとうございます。
しかし、GetLastError()で値を見てみると、値が127のままです。
何故こうなるのかはとても気になるのですが、一応は普通に動いています。

投稿日時 - 2008-12-02 21:48:37

このQ&Aは役に立ちましたか?

5人が「このQ&Aが役に立った」と投票しています

回答(17)

ANo.17

> しかし、GetLastError()で値を見てみると、値が127のままです。
>何故こうなるのかはとても気になるのですが、一応は普通に動いています。

GetLastErrorですが、これは直前の「エラー」に関しての取得です。
つまり、関数に成功した場合に0が返ってくることにはなっていないのです。
http://msdn.microsoft.com/ja-jp/library/cc428944.aspx
非常にわかりずらいのですが、特定の関数以外の関数は、成功した時にはSetLastErrorは呼ばないのです。ということは、それ以前の関数呼び出しでのエラーの値がそのまま残っているということです。

なので、GetLastErrorを見る前に、関数が成功したかどうかの判定をする必要があるのです。

投稿日時 - 2008-12-03 09:33:42

補足

なるほど。特定の関数以外の関数は、成功したときはSetLastErrorは呼ばないんですね。

投稿日時 - 2008-12-03 11:07:10

ANo.16

> 解決策として『extern "C"』をつけることです。
No13でも書きましたが、
Cリンゲージで修飾子抑制が有効なのは
あくまでも呼び出し規約が、
デフォルトの「__cdecl」の場合です。

呼び出し規約が__stdcallなどの場合、
Cリンゲージ指定であったとしても、
エクスポート名は始まりに「_」、
終わりに「@パラメータバイト数」
というCの修飾が行われます。

今回に限ってはNo11さんが言っている通り、
DEFファイルを使用する以外にないと思います。

投稿日時 - 2008-12-02 19:27:13

ANo.15

★追記。
・『参考URL』をどうぞ。
 ここにDLLの注意点が書かれています。

参考URL:http://www.interq.or.jp/www-user/wanderer/program/memo/lecture/compiler.html#linker

投稿日時 - 2008-12-02 18:35:29

補足

 参考URLありがとうございます。DLLに関しては、まだ勉強不足という事もあってか初歩的なことも分からないです。ただ、皆さんのお陰で解決しつつあるわけですし、そろそろこの問題を締め切ろうと思います。

投稿日時 - 2008-12-03 11:19:07

ANo.14

★アドバイス
・『plug.c』をC言語としてコンパイルするか、
 C++としてコンパイルして DLL を作成するかで関数名が異なります。
 C++ではオーバーロードという機能があるため関数名の末尾などに
 勝手に一意の文字列が追加されます。
 ネットで『マングリング』を検索するといろいろと情報が出てきます。
 http://eternalwindows.jp/windevelop/dll/dll06.html
・解決策として『extern "C"』をつけることです。
 DLLソースと使う側のソースでC言語か、C++かを統一させます。
 矛盾があると『マングリング』によって見つかりません。注意。

// plug.c
#include <stdio.h>
#include <windows.h>

extern "C" __declspec(dllexport) void TestFunc()
{
 printf("DLLのTestFunc()関数が実行されました。\n");
}

// stab.c
#include <stdio.h>
#include <windows.h>

extern "C"{
 typedef void (*TestFunc)(void);
}

参考URL:http://eternalwindows.jp/windevelop/dll/dll06.html

投稿日時 - 2008-12-02 18:31:59

補足

リンケージ指定をやろうとすると、エラーになります。
エラーは( errorC2059:構文エラー:'文字列' )です。
cファイルで記述しているからエラーがでるんじゃないかと思うのですが。

投稿日時 - 2008-12-02 22:09:12

ANo.13

> VC++ではリンケージ指定ではエクスポート名が装飾されるのを
> 避けることは出来ません関数名のままエクスポートできるのは
> defファイルのみです、それ以外の方法はありません
呼び出し規約を変更するすることで、
defファイルを使用しなくても、
修飾子名をはずすことができる場合があります。

>No10さんのC関数名の修飾をはずす方法か、
と書きましたが、今回の質問者さんは 呼び出し規約を
CALLBACK としているのでデフォルトだと__stdcallの呼び出しと
なるのでdefファイルでしかエクスポートできないかもしれません。

ためしに、__stdcallでCリンゲージ指定を行うと、
エクスポート名は「_TestFunc@0」となりました。
ちなみにデフォルトの__cdeclの呼び出し規約では
エクスポート名は「TestFunc」となりました。

投稿日時 - 2008-12-02 17:08:58

ANo.12

>その方法を試したところERRORが起きました。
起こったことはともかくどういう内容だったかが重要では?

リンゲージの差異によるエラーだと思うので、
No10さんのC関数名の修飾をはずす方法か、
DEFファイルを使用することで解決できると思います。

DEFファイルやC関数名修飾については以下を参照。
http://msdn.microsoft.com/ja-jp/library/d91k01sh.aspx

投稿日時 - 2008-12-02 16:41:59

ANo.11

VC++ではリンケージ指定ではエクスポート名が装飾されるのを避けることは出来ません
関数名のままエクスポートできるのはdefファイルのみです、それ以外の方法はありません

# 装飾名の所為で取得できてないのは最初から確定してるからLoadLibraryの事はもう考えるな

参考URL:http://msdn.microsoft.com/ja-jp/library/56h2zst2.aspx

投稿日時 - 2008-12-02 16:19:32

ANo.10

> その方法を試したところERRORが起きました。
> cファイルだったからだと思うのですが。リンケージ指定はC++だと出来るんですけどね。
extern "C"{
__declspec(dllexport) void __cdecl TestFunc(void);
}

__declspec(dllexport) void __cdecl TestFunc(void)
{
   printf("DLLのTestFunc()関数が実行されました。\n");
}


typedef void __cdecl (*TestFunc)(void);

だと、どうでしょうか?

投稿日時 - 2008-12-02 14:58:22

ANo.9

>フルパスにしても同じことが起こりました。
>あと、hModuleに入った値ですが0x10000000です。
そのときGetLastError()の値は0 (この操作は正しく終了しました。)になっていたはずです。
LoadLibrary()が成功すればGetLastErrorは0になります。
パスの指定も原因のひとつだったという事が考えられます。

投稿日時 - 2008-12-02 14:49:00

ANo.8

> plug.defというファイルをプロジェクトに取り込み、
言葉足らずですね。
defファイルを取り込むのは「DLL側のプロジェクト」です。
DLLのリビルドが必要です。

投稿日時 - 2008-12-02 14:42:11

ANo.6

> hModuleの値は0x10000000でした。関数自体は失敗していなんでしょうか。

NULLが返ってきているのではない限り、失敗ではありません。
> ここでfuncPointer にNULL(0x000000)が入っています。
ということは、LoadLibraryではなく、GetProcAddressの失敗です。
GetProcAddressの直後にGetLastErrorをするべきですね。
>Dependency Walkerという(ソフト?)ものがないです。
DLLをエクスプローラ上から右クリックしたときに、メニューに「View Dependencies」という項目はないでしょうか?

投稿日時 - 2008-12-02 14:22:43

補足

Dependency Walkerを検索したところ、フリーで配布されていました。
これをDLして早速plug.dllを調べてみると関数名が_TestFunc@0になっていました。
リンク時チェックサムが0x00000000で要求ベースアドレスが0x10000000です。
なのでLoadLibrary()呼ぶとhModuleの値は0x10000000
_TestFunc@0でGetProcAddressを呼ぶと値にはちゃんと入っていました。

投稿日時 - 2008-12-02 16:27:05

ANo.5

既に回答にもありますがGetLastErrorで原因は明らかです。hModuleには恐らくNULLが入っているでしょう?

dllはまずカレントディレクトリを検索しますが
VisualStudioの初期設定で実行した場合、
カレントディレクトリとexeの出力されたディレクトリが違います。
それが原因ではないでしょうか?

試しにplug.dllをcドライブのルートにコピーして
hModule = LoadLibrary(TEXT("C:\\plug.dll"));
とフルパスで指定してみたらどうでしょうか?

投稿日時 - 2008-12-02 11:27:26

補足

フルパスにしても同じことが起こりました。
 あと、hModuleに入った値ですが0x10000000です。
 funcPointer = (TestFunc)GetProcAddress(hModule,TEXT("TestFunc"));
 ここでfuncPointer にNULL(0x000000)が入っています。
 関数が失敗していました。
 やっぱりLoadLibraryに原因があると思います。

投稿日時 - 2008-12-02 13:53:08

ANo.4

>hModule = LoadLibrary(TEXT("plug"));
>error = GetLastError();//error値が127
GetLastErrorを見る前に、hModuleの値はどうなっているのでしょうか?


>__declspec(dllexport)でエクスポートした場合、関数名がそのままにはならなかったと思いますが…。
>内部ンク用に名前が変更されているかと思われます。
>defファイルを使用された方がよいかと。
defファイルを使う以外に、extern"C"を使う方法もありますね。
extern "C"{
__declspec(dllexport) void CALLBACK TestFunc();
}

__declspec(dllexport) void CALLBACK TestFunc()
{
   printf("DLLのTestFunc()関数が実行されました。\n");
}

でよかったかな。
extern "C"{
__declspec(dllexport) void CALLBACK TestFunc(void);
}

__declspec(dllexport) void CALLBACK TestFunc(void)
{
   printf("DLLのTestFunc()関数が実行されました。\n");
}
こっちのほうがいいかも。

投稿日時 - 2008-12-02 11:25:05

補足

 その方法を試したところERRORが起きました。
 cファイルだったからだと思うのですが。リンケージ指定はC++だと出来るんですけどね。
 hModuleの値は0x10000000でした。関数自体は失敗していなんでしょうか。

投稿日時 - 2008-12-02 14:01:04

ANo.3

Wr5

plug.dllをDependency Walkerで開いて…
TestFuncという名前の関数がエクスポートされていますか?

__declspec(dllexport)でエクスポートした場合、関数名がそのままにはならなかったと思いますが…。
内部ンク用に名前が変更されているかと思われます。
defファイルを使用された方がよいかと。

投稿日時 - 2008-12-02 01:21:08

補足

  Dependency Walkerという(ソフト?)ものがないです。
defファイルというものも、よく分からないです。
  よろしければ、それがどういう物か教えていただけると助かります。

投稿日時 - 2008-12-02 10:32:46

ANo.2

>error = GetLastError();//error値が127
#define ERROR_PROC_NOT_FOUND 127 //指定されたプロシージャが見つかりません。
と言う訳で、LoadLibrary()に指定した「plug」が見付からないのが原因です。
「plug」を「plug.dll」に変えましょう。

投稿日時 - 2008-12-01 22:58:32

補足

plug.dllに変えても結果が同じでした。
個人的にはplug.expとplug.libが関係しているのかなと思うのですが。

投稿日時 - 2008-12-02 00:59:57

ANo.1

 こんばんは。
 指定しているDLL名の拡張子が抜けているのが原因かもしれません。

 ↓拡張子を付けてみてください。
 hModule = LoadLibrary(TEXT("plug.dll"));

 ↓更に、関数ポインタにDLL側のエクスポート関数と同じ呼び出し規約を指定しましょう。
 typedef void (CALLBACK *TestFunc)(void);

投稿日時 - 2008-12-01 22:21:47

補足

修正して、typedef void (CALLBACK *TestFunc)(void);にし、
拡張子も.dllにしたんですが、funcPointer();を実行しようとすると
ERRORになります。まだプロシージャが見つけられないみたいです。
ディレクトリにはちゃんとplug.dllが入っているんですが・・・

投稿日時 - 2008-12-02 00:56:46

あなたにオススメの質問