ダイナミックリンク


DLLを作る

Windows の極意は Win32 API にありと思いますが
そのなかでも、重要な機能の一つとしてダイナミックリンクがあります

DLL(Dynamic Link Libraries) は、プログラムが実行時にアクセス可能な
様々な機能、リソースをまとめた、一つの実行ファイルの部類です

通常、プログラムがアクセスできるサービス(つまり、関数やプロシージャ)は
コンパイル時にリンクさせた、コンパイラが提供する標準関数、自分で作成した関数
そして、あとは OS が提供しているソフトウェア割り込みなどの機能でした

しかし、Windows では、OS が提供するサービスの概念をより発展させ
アプリケーションが、異なるファイルの関数を呼び出すことを可能としてます
これが DLL であり、私たちがこれまで使ってきた Windows API も DLL がほとんどです

例えば、カーネルならば KRNEL32.DLL、GDI は GDI32.DLL というようになっています
DLL の拡張子は、一般的に *.DLL ですが、*.EXE などもあります
DLL ファイルは拡張子は自由ですが、*.DLL 以外の場合は
その DLL ファイルの関数を参照する時、明示的に DLL をメモリにロードする必要があります
DLL ファイルであることをユーザーに隠蔽したい時は、拡張子を変えるだけでも効果があるでしょう

DLL は、実行ファイルの部類ですが、直接実行されることはありません
何らかのプロセスが、DLL を呼び出して、プロセスのメモリ空間に配置します
これは大きなメリットがあり、例えばある機能を異なるアプリケーションで共有できますし
DLL は、広い意味では Windows を拡張する手段であると考えても良いでしょう

DLL ファイルをプログラムする方法は、通常のプログラムと同じです
WINDOWS.H ヘッダファイルをインクルードして、何らかの処理をまとめた関数を作ります
ただし、プロセスとして実行されることはないので WinMain() 関数は必要ありません
その代わり、DllEntryPoint() というエントリーポイントを持っています
BOOL WINAPI DllEntryPoint(
	HINSTANCE hinstDLL ,
	DWORD fdwReason , LPVOID lpvReserved
);
hinstDll には、この DLL のインスタンスが格納されています
fdwReason は、この関数が呼び出された原因を表す定数が格納されています
lpvReserved は予約されている引数で、常に 0 です

fdwReason には、次のいずれかの定数が格納されています

定数解説
DLL_PROCESS_ATTACH DLL が、現在のプロセスのアドレス空間にアタッチしようとしていることを示します
プロセスの起動時や、プロセスが LoadLibrary() 関数を呼び出したときに
このフラグが通知されます
DLL_THREAD_ATTACH 現在のプロセスが、新しいスレッドを作成しようとしていることを示します
既存のスレッドが、新しくロードされた DLL に対して
エントリポイントを呼び出すことはありません
DLL_THREAD_DETACH 既存のスレッドが終了しようとしていることを示します
DLL_THREAD_ATTACH が通知されていないのに
DLL_THREAD_DETACH が通知されることがあります
たとえば、スレッドの起動後に LoadLibrary() 関数が呼び出された場合です
DLL_PROCESS_DETACH DLL が、呼び出し側プロセスのアドレス空間から
デタッチしようとしていることを示します
プロセスが FreeLibrary() 関数を呼び出したときや
正常終了したときに、このフラグが通知されます
なお、DLL のデタッチ時に、プロセス内の個々のスレッドに対する
DLL_THREAD_DETACH が通知されることはありません

fdwReason が DLL_PROCESS_ATTACH の時に
関数が TRUE を返すと成功、FALSE を返すと失敗を意味します
それ以外の場合であれば、戻り値は無視されます

DllEntryPoint() という名前はプレースホルダです
DLL ファイルのエントリーポイントは、通常リンカのオプションで指定することができます
おそらく、多くの環境では DllEntryPoint() か DllMain() をデフォルトとしているでしょう
この関数は、DLL の初期化や終了処理の手段として提供されています

また、DLL ファイルを作成する時は、開発環境の設定も変える必要があります
Microsoft Visual C++ であれば、ファイルメニューの「新規作成」の「プロジェクト」タブから
"Win32 Dynamic-Link Library" を選択します
あとは、DLL 用のソースを関連付けてビルドするだけです

Borland C++ Compiler の場合、コマンドラインオプションで -WD を選択します
例えば "bcc32 -WD test.c" と指定してコンパイルが成功すれば test.dll が作成されます
さっそく、以下のソースプログラムを DLL ファイルとしてコンパイルしてみてください
/*dll_test.c*/
#include <windows.h>

#define PRC_ATT TEXT("DLL が読み込まれました")
#define PRC_DET TEXT("DLL が解放されました")
#define TRD_ATT TEXT("既存プロセスが新しいスレッドを作成しています")
#define TRD_DET TEXT("既存のスレッドが終了しようとしています")

#define TITLE TEXT("Kitty on your lap")

int WINAPI DllEntryPoint(HINSTANCE hInstance , DWORD fdwReason , PVOID pvReserved) {
	switch(fdwReason) {
	case DLL_PROCESS_ATTACH:
		MessageBox(NULL , PRC_ATT , TITLE , MB_OK);
		break;
	case DLL_PROCESS_DETACH:
		MessageBox(NULL , PRC_DET , TITLE , MB_OK);
		break;
	case DLL_THREAD_ATTACH:
		MessageBox(NULL , TRD_ATT , TITLE , MB_OK);
		break;
	case DLL_THREAD_DETACH:
		MessageBox(NULL , TRD_DET , TITLE , MB_OK);
	}
	return TRUE;
}
WinMain() 関数がないということを除けば、通常の Win32 API プログラムと変わりません
この DLL は、プロセスやスレッドに呼び出されると、ダイアログボックスを表示します

実行ファイルが、DLL を読み込むには LoadLibrary() 関数を使います
この関数を使えば、いつでも明示的に DLL ファイルを読み込むことができます

HINSTANCE LoadLibrary(LPCTSTR lpLibFileName);

lpLibFileName には、実行可能モジュールの名前を指定します
拡張子を省略した場合は .DLL が文字列の末尾に、自動的に付加されます
拡張子を付けてほしくないのであれば、文字列の末尾に . を付けます

フルパスで指定した場合、見つからなければ関数は失敗します
ファイル名だけであれば、関数は次の順番で検索します

1.アプリケーションがロードされたディレクトリ
2.カレントディレクトリ
3.システムディレクトリ
4.Windows ディレクトリ
5.環境変数 PATH に記述されている各ディレクトリ

関数が成功するとモジュールのハンドルが、失敗すると NULL が返ります
また、DLL には参照カウンタと呼ばれる内部カウンタが存在し
参照カウンタが 0 になった時は、Windows は DLL をメモリから解放します

LoadLibrary() 関数は、成功した時に DLL の参照カウンタを 1 加算します
この関数は、DLL がすでに読み込まれている時でも、ハンドルを得るために使うことができます

DLL が不用になったら FreeLibrary() 関数を使います
この関数を呼び出すと、DLL 参照カウンタが 1 減算します

BOOL FreeLibrary(HMODULE hLibModule);

hLibModule には、読み込まれている DLL モジュールのハンドルを指定します
ここには、LoadLibrary() 関数が返したインスタンスハンドルを指定できます
関数が成功すれば 0 以外、失敗すれば 0 が返ります
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	static HINSTANCE hDLL;
	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		hDLL = LoadLibrary(TEXT("DLL_TEST.DLL"));
		return 0;
	case WM_LBUTTONUP:
		if (hDLL) FreeLibrary(hDLL);
		return 0;
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
このプログラムは、DLL_TEST.DLL ファイルを実行時に読み込み
マウスでウィンドウのクライアント領域を左クリック、またはプロセスを終了させると
読み込んだ DLL をメモリから解放します

もちろん、このプログラムを実行する時は DLL_TEST.DLL というファイルが必要です
DLL_TEST.DLL をあらかじめ作成し、プログラムから検索できる位置に配置しておかなければ
このプログラムは、ウィンドウを標示するだけで何もしないでしょう
それは、LoadLibrary() 関数が NULL を返すからです

プログラムが、正しく DLL_TEST.DLL ファイルを読み込むことができれば
DLL の DllEntryPoint() 関数が実行され、DLL が初期化されます
DLL は、プロセスやスレッドが DLL を読み込んだり解放したりするとダイアログを表示します


関数の呼び出し

DLL に記述した関数を呼び出す作業となると、上のように単純ではなくなります
理由の一つは、リンカがどのようにして DLL を参照するかというところです

コンパイラは、様々なファイルやライブラリをコンパイル時に機械語に変換し
そして、それをリンカで繋げて、一つの目的プログラムとして完成させます
ところが、DLL は実行時に呼び出すもので、コンパイル時に結合はできません

そこで、DLL の関数には、拡張属性構文 を使います
これは、関数などの宣言に修飾子として指定することで、属性をあらわす追加構文です
この構文は ANSI ではなく、Microsoft 固有の仕様なので注意してください

DLL の関数を作るには __declspec() キーワードを用います
このキーワードは、次のような構文です

__declspec ( extended-attribute ) declarator

extended-attribute には、定義済みの記憶クラス属性を指定します
これを用いて、DLL の関数をエクスポートできるように宣言します
エクスポートを宣言するには extended-attribute に dllexport を指定します

__declspec (dllexport) 関数() {...

これで、この関数をリンカが理解できるようになります
もちろん、DLL の内部からのみ呼び出されるローカルな関数には必要ありません
次のプログラムは、DLL ソースと実行ファイルのソースの両方にインクルードされるヘッダです
/*test.h*/
#ifdef __cplusplus
#define DLL_EXPORT extern "C" __declspec (dllexport)
#else 
#define DLL_EXPORT __declspec (dllexport)
#endif

DLL_EXPORT BOOL CALLBACK GetCenter(LPRECT , LPRECT);
__cplusplus という定数は、Microsoft 固有の組み込みマクロです
コンパイルしたファイルが *.cpp の時に定義されています
プログラムは、C++ からも DLL を使えるように extern を使っています

次に DLL を作成します
次のファイルは DLL 用の C ソースファイルです
/*dll_test.c*/
#include <windows.h>
#include "test.h"

int WINAPI DllEntryPoint(HINSTANCE hInstance , DWORD fdwReason , PVOID pvReserved) {
	return TRUE;
}

DLL_EXPORT BOOL CALLBACK GetCenter(LPRECT lpParent , LPRECT lpChild) {
	if (lpParent->right < lpChild->right || lpParent->bottom < lpChild->bottom)
		return FALSE;

	lpChild->left = lpParent->right / 2 - lpChild->right / 2;
	lpChild->top = lpParent->bottom / 2 - lpChild->bottom / 2;
	lpChild->right += lpChild->left;
	lpChild->bottom += lpChild->top;
	return TRUE;
}
これをコンパイルすれば、DLL は完成です
GetCenter() 関数は lpParent で指定された長方形に対して
lpChild が lpParent の中心に配置されるように、データを書き換えます
lpChild が lpParent よりも大きかった場合は FALSE を返します

次の問題は、この GetCenter() 関数の位置を、どのようにリンカに知らせるかです
単純にソースの中に GetCenter() と書いても、ソース上には GetCenter() 関数がないので
コンパイラはヘッダで了承しても、リンカは参照先が不明なのでエラーを返します

ここで使われるのがインポートライブラリと呼ばれるファイルです
インポートライブラリは、DLL ファイルの参照情報を持つファイルで
リンカはこの情報を元に、DLL ファイル内のエクスポートされている関数を参照します
私たちが使ってきた Win32 API 関数群も、このインポートライブラリが参照していたのです

ただし、標準関数の場合はコンパイラがデフォルトでインポートライブラリを使ってくれますが
それ以外の、私たちが独自に作った DLL の関数を呼び出すためには
インポートライブラリを作成して、それをリンクして初めて参照することができます

おそらく、総合開発環境の場合は、これらの手順を一度にやってくれるでしょう
総合開発環境を使っている方は、それぞれのヘルプを参照してください
Borland C++ Compiler を使っている場合は、IMPLIB.EXE というツールがあります
これを使って、次のように *.LIB ファイルを生成してください

implib 出力ファイル名.lib 入力ファイル名.dll

インポートライブラリファイルは *.LIB という拡張子を持っています
*.LIB の作成には、他にもアプローチがありますが、詳しくはヘルプなどを参照してください
そして、bcc32 で、次のように指定します

bcc32 -W ソースファイル名.c インポートライブラリファイル名.lib

こうすれば、ソースファイルで直接 DLL の関数にアクセスできます
インポートライブラリの情報から、リンカが参照してくれるのです
#include <windows.h>
#include "test.h"

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rectParent , rectChild;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_PAINT:
		rectChild.right = 200;
		rectChild.bottom = 100;
		GetClientRect(hWnd , &rectParent);

		hdc = BeginPaint(hWnd , &ps);
		if (!GetCenter(&rectParent , &rectChild))
			Rectangle(hdc , 0 , 0 ,
				rectParent.right , rectParent.bottom);
		else Rectangle(hdc , rectChild.left , rectChild.top ,
				rectChild.right , rectChild.bottom);
		EndPaint(hWnd , &ps);
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}


このプログラムは、DLL_TEST.DLL の GetCenter() を呼び出します
GetCenter() 関数は、第二引数の子長方形を
第一引数で指定した親長方形の中心に配置されるように、座標を変換します

インポートライブラリを用いた、このアプローチでは LoadLibrary() を使う必要はありません
必要になった時、Windows は DLL を読み込んで関数を参照します

これで、この技術がどれほどの可能性を持っているかわかったでしょう
C/C++ で作成した DLL の関数は、プログラムごとに共有することができますし
一つの実行ファイルにまとめるよりも、要素単位に分けられるので保守性を向上させられます
他の言語や開発環境で作成したプログラムから DLL にアクセスすることだって可能です

因みに、インポートライブラリを用いた場合は
DLL が存在しなかったときの例外処理が、コンパイル時に付加されているでしょう


インポートライブラリを使わない

インポートライブラリを用いてリンカに Win32 API の位置を教える方法は
コンパイル時に、確実に、簡単に関数を呼び出す方法として最適です

しかし、コンパイラが Win32 アプリケーションを作成する時に
ほとんどのケースで、デフォルト参照してくれる Win32 API 用のインポートと異なり
自作の DLL の場合は、専用のインポートライブラリを作ってリンクさせる必要がありました

通常、Windows 用の C/C++ コンパイラであれば
Microsoft の仕様に合わせ、Microsoft Visual C++ と互換を取ります
しかし、それでも、静的なリンクは開発環境に依存しています

例えば、実行時まで呼び出すべき関数名がわからない場合などは
コンパイル時に関数名を指定することは無理です
あなたが作った独自のインタプリンタや仮想マシンから
C 言語で作った DLL を呼び出す時も、インポートライブラリが使えないのでリンクできません

そこで、DLL の関数を実行時に呼び出す方法が必要になります
これを実現させるため GetProcAddress() 関数を用います

FARPROC GetProcAddress(HMODULE hModule , LPCSTR lpProcName);

hModule には、エクスポート関数を持つ DLL モジュールのハンドルを指定します
lpProcName に、関数の名前を文字列で指定します

関数名ではなく、関数の宣言順序で関数を指定することも可能です
この場合は、MAKEINTRESOURCE() マクロで関数の位置を指定してください
ただし、順序値で指定する方法は曖昧で、正しいアドレスを返さない場合があります
この時は、NULL ではなく、それでいて不正なアドレスが変えることもあるので注意が必要です
関数が指定すれば、指定した関数のアドレスが、そうでなければ NULL が返ります

この関数が返す FARPROC 型は、コールバック関数へのポインタをあらわします
GetProcAddress() が返すのは、エクスポート属性を持つコールバック関数だけです
#include <windows.h>
#include "test.h"

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rectParent , rectChild;
	static FARPROC GetCenter , Rectangle;

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		GetCenter = GetProcAddress(
			LoadLibrary(TEXT("DLL_TEST.DLL")) , TEXT("GetCenter"));
		Rectangle = GetProcAddress(
			LoadLibrary(TEXT("GDI32.DLL")) , TEXT("Rectangle"));
		return 0;
	case WM_PAINT:
		rectChild.right = 200;
		rectChild.bottom = 100;
		GetClientRect(hWnd , &rectParent);

		hdc = BeginPaint(hWnd , &ps);
		if (!GetCenter(&rectParent , &rectChild))
			Rectangle(hdc , 0 , 0 ,
				rectParent.right , rectParent.bottom);
		else Rectangle(hdc , rectChild.left , rectChild.top ,
				rectChild.right , rectChild.bottom);
		EndPaint(hWnd , &ps);
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
このプログラムは、先ほどの DLL_TEST.DLL を用います
DLL_TEST.DLL と Windows の GDI32.DLL から
動的にエクスポート関数のポインタを取得し、それを実行できます
このソースをコンパイルする時は、インポートライブラリは必要ありません


DllEntryPoint()


BOOL WINAPI DllEntryPoint(
	HINSTANCE hinstDLL ,
	DWORD fdwReason , LPVOID lpvReserved
);
DLL のエントリーポイント関数です
DllEntryPoint はプレースホルダなので、関数名は異なってもかまいません
DLL のエントリーポイントは、リンカオプションで指定する必要があります

hinstDll - この DLL のインスタンスを指定します
fdwReason - この関数が呼び出された原因を表す定数を指定します
lpvReserved - 予約されています。常に 0 です

戻り値 - 成功すれば 0 以外、失敗すれば 0

fdwReason には、以下の定数のいずれかを指定できます

定数解説
DLL_PROCESS_ATTACH DLL が、現在のプロセスのアドレス空間にアタッチしようとしていることを示します
プロセスの起動時や、プロセスが LoadLibrary() 関数を呼び出したときに
このフラグが通知されます
DLL_THREAD_ATTACH 現在のプロセスが、新しいスレッドを作成しようとしていることを示します
既存のスレッドが、新しくロードされた DLL に対して
エントリポイントを呼び出すことはありません
DLL_THREAD_DETACH 既存のスレッドが終了しようとしていることを示します
DLL_THREAD_ATTACH が通知されていないのに
DLL_THREAD_DETACH が通知されることがあります
たとえば、スレッドの起動後に LoadLibrary() 関数が呼び出された場合です
DLL_PROCESS_DETACH DLL が、呼び出し側プロセスのアドレス空間から
デタッチしようとしていることを示します
プロセスが FreeLibrary() 関数を呼び出したときや
正常終了したときに、このフラグが通知されます
なお、DLL のデタッチ時に、プロセス内の個々のスレッドに対する
DLL_THREAD_DETACH が通知されることはありません

LoadLibrary()

HINSTANCE LoadLibrary(LPCTSTR lpLibFileName);

DLL 参照カウンタを 1 加算します
指定 DLL が読み込まれていない場合、DLL をメモリに読み込みます

lpLibFileName - 実行可能モジュールの名前を指定します

戻り値 - 成功するとモジュールのハンドル、失敗すると NULL

FreeLibrary()

BOOL FreeLibrary(HMODULE hLibModule);

指定した DLL の参照カウンタを 1 減算します

hLibModule - 読み込まれている DLL モジュールのハンドルを指定します

戻り値 - 成功すれば 0 以外、失敗すれば 0

__declspec( dllexport ) declarator

Microsoft 固有の仕様
拡張属性構文です
記憶クラス属性 dllexport 属性を関数、データ、またはオブジェクトに指定します
関数はクライアントに対する DLL のインターフェイスを明示的に定義できます

GetProcAddress()

FARPROC GetProcAddress(HMODULE hModule , LPCSTR lpProcName);

DLL の指定したエクスポート関数のアドレスを取得します

hModule - エクスポート関数を持つ DLL モジュールのハンドルを指定します
lpProcName - 関数の名前を文字列で指定します

戻り値 - 関数のポインタ、失敗すると NULL



前のページへ戻る次のページへ