ここの内容は基本編。
応用は応用編へ
【呼び出し規約を合わせろ】
呼ばれる側
__declspec(dllexport) DWORD __stdcall Function(LPCTSTR lpcString, DWORD dwCode
{
....
}
参考:http://my.opera.com/namu/blog/show.dml/51676
呼ばれる側では、上のように、関数を定義する。
赤文字で示した、__stdcallというのが呼び出し規約と呼ばれるもので、
しばしば、省略されたソースを目にすることがある。
呼び出し規約というのはよく知らないが、
スタックに変数をどうやって格納するかとか、そういう決まり事らしい。
呼び出し側と呼ばれる側で、呼び出し規約は共通である必要があり、
異なっていると、下のように怒られる。
Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call. This is usually a result of calling a function declared with one calling convention (=呼出し規約) with a function pointer declared with a different calling convention.
呼ばれる側は楽勝。
__declspec(dllexport) 返り値の型 呼び出し規約 関数名(引数)
という呪文がついている以外は、普通に関数を書いてコンパイルすればOK
(もちろん、プロジェクトはDLL用に作っておくべし)
呼び出す側
.libがいらない明示的リンクの方。
HINSTANCE hDll;
//HMODULE hDll;
typedef int ( __cdecl *OKiHo)(double b , double c);
void main(void)
{
LPCWSTR szClassName = TEXT("DLL_TEST2.dll");
hDll = LoadLibrary(szClassName);
if( ! hDll ){
printf("open");
system("pause");
return;
}
OKiHo test =(OKiHo)GetProcAddress( hDll,"warizan");
if( ! test ){
::FreeLibrary( hDll );
printf("read");
system("pause");
return;
}
int b = test(100.0, 25.0);
::FreeLibrary( hDll );
上記では、__cdecl が呼び出し規約である。
それ以外は、ネット上に情報があふれているので、詳細は割愛するが、
2つハマりポイントがある。
(1)
LoadLibraryが、Unicodeを扱えるようになった関係?で、
LoadLibrary("hogehoge.dll")みたいにできないので注意。
(2)
testという関数名を実行して、DLLの中身を実行しているが、
DLLで定義された関数名は"warizan"の方である。
つまり、この名前を利用して、関数ポインタを取得するのが、
GetProcAddressということのようだ。
(IDのようなもの?を使って取得する方法もあるらしい)
そのほか、雑学的なもの
複数の呼び出し規約に対応する方法
http://members.jcom.home.ne.jp/c-spencer/other/memo/other/program_dmwrapperdll.html
スタックへの積み方をアセンブラで操作してしまうという手法と思われる。
関数名@16
みたいなのは、HSPのヘルプにVC++のエクスポート規約に基づくエクスポート名とあった。
<以下、違う気がする>
@数字の部分は、序数値とかいうらしく、
DLL内に定義した関数1つ1つについて、個別の番号が割り振られるものです。とある。
http://www.geocities.jp/ky_webid/win32c/059.html
===
これは、序数値ではなくて、修飾関数名な気がしてきた。
__stdcall時の修飾関数名に似ている。
===
しかし、HSPのエクスポート名だと、@16固定なのはなぜなのだろうか??
⇒extern "C"の項目と、装飾つき関数名の項目も参照のこと。
呼び出し規約を省略すると
呼び出し規約はプロジェクトファイル(プロジェクトファイルに呼び出し規約を設定する項目があるらしい)や
言語に依存するようになるらしい。
http://my.opera.com/namu/blog/show.dml/51676
extern "C"とは
DLLによく、extern "C"というのが書かれている。これは何か?
一言でいうと、C++とCの間で連携するためのオプション。
これは、http://www.geocities.jp/ky_webid/cpp/language/038.htmlによると、
C言語の関数名のまま使おうとしますとある。
C++では、オーバーロードがあるので、コンパイル後の関数名は、CとC++で異なる。
ちなみに、Cはコンパイルすると、function@@YAHH@Zみたいな名前になる。
装飾つき関数名
さっきのfunction@YAHH@Zというのは、装飾つき関数名とかいうらしい。
関数宣言・定義 ⇒ 装飾つき関数名 |備考
int __cdecl test(int a) ⇒ ?test@@YAHH@Z この装飾はコンパイラのバージョンによって異なる。
int __stdcall test(int a)⇒ ?test@@YGHH@Z この装飾はコンパイラのバージョンによって異なる。
extern "C" int __cdecl ⇒test(int a) test
extern "C" int __stdcall ⇒test(int a) _test@4
extern "C"が付いたものは、オーバーロードがない分、
全体的にシンプルになっていることがわかる。
参考:
http://www.ne.jp/asahi/hishidama/home/tech/vcpp/dllusage.html
HSPでのDLL注意点
HSPのDLL呼び出し規約は?
__cdeclでも、__stdcallでもいけるみたい?これは実験で確認した。
でも、_関数名@16を見ると、extern "C" __stdcallの修飾関数名に近い気がするんだけどなあ。
最後の16は、バイト数を意味するらしい?
http://social.msdn.microsoft.com/Forums/ja-JP/vcgeneralja/thread/e6f937db-e496-461a-b206-29ddab40af93/
で、↓な書き込みを見つけた。
__cdecl 先頭に'_'が付きます_
_stdcall 先頭に'_'が付きます、最後尾に'@'が付き、
さらに引数のスタックByte数が付きます。
__fastcall 先頭に'@'が付き、最後尾に'@'が付き、
さらに引数のスタックByte数が付きます。
cfuncに注意
cfuncは、戻り値に整数値しか返すことができない。
つまり、
double warizan( double a, double b )
みたいな関数はNG.値を返すと、めちゃくちゃな値が返ってくる。
double型の結果は、ポインタを使って受け取るしかない。
型に注意
DLLで作成した関数に、double型のポインタのつもりで
未初期化の変数を渡しても、結果は
滅茶苦茶な数字が入ってしまう。
渡す前に、x = 0.0のように、
double型であることの明示が必要のようだ。
Easy3Dなどでは、特に受け取り変数の初期化をしなくても
きちんとdouble型が入ってくれるが、これに関しては理由はわからなかった。
Easy3Dの関数定義のHSPソースが2.6用なので、なんともいえなかった。