DLLの作り方
by K.I
2008/09/08
Index
- VS2005のC++で、DLLを作成する時の覚書き。
- 時々しか作らないけど、毎回迷うので。。
- 例として、InputBoxのような入力用Dialogを表示するDLLを作成してみる。
- ちなみに開発環境は、VisualStudio2005のVC++だが、MFCとか.NETのフレームワークは使用していない。
- いわゆるSDKのルーチンしか使ってないので、古いVC++でも同じように出来ると思う。
- 普通の関数は、プログラム本体の一部として組込まれているが、
- DLLは関数だけがコンパイルされて、dllという拡張子の別ファイルになっている。
- プログラム実行時にロードされ、あとは普通の関数と同じように実行される。
- DLLだけだと、コンパイル時にどのようなインターフェースになっているか分からないので、
- これ以外に、Cのプロトタイプを記述したヘッダファイルと、
- DLLのコンパイル時に、コンパイラが出力するDLLの入出力情報が記述されたlibファイルが使われる。
- 呼出し側のプログラムは、DLLのヘッダファイルをインクルードして、普通の関数と同様に呼出す。
- DLLのリンクは、DLLのインターフェースを記述したlibファイルを指定して、コンパイラにリンクを任すように1する。
- コンパイラは、実行時にDLLをロードして実行するようにコンパイルする。
1これは暗黙的なリンクと言う。
[top]
- 説明だけだと凄く難しそうな感じなので、試しに簡単なDLLを作ってみよう。
- MessageBoxのような出力用Dialogを表示するAPIはあるが、入力用DialogのAPIは無い。
- VisualBasicでは、InputBox関数で入力用Dialogを表示することが出来るので、それを真似て作ってみる。
- InputBoxはいろいろな機能があるみたいだが、ここでは単純な仕様で作ってみよう。
- 以下のような仕様で作成する。
- 指定したメッセージを表示して、TextBoxに入力された文字列を返す関数。
- Dialogの大きさ、TextBoxの大きさは固定。いろいろ弄れるように、リソースで作成しておく。
- VS2005のVC++は、UniCode文字セットがデフォルトだが、どうも苦手なのでマルチバイト文字セット2を使う。
- DLLのロード時に、255文字のバッファを作成して、入力された文字列をそこに入れる。
- InputBoxの戻り値として、バッファの先頭アドレスを返す。
- ファイルメニュー→新規作成→プロジェクト
- プロジェクトの種類を、Visual C++→Win32→Win32コンソールアプリケーションを選ぶ。
- プロジェクト名(仮に、inputboxとする)を指定したら、OKボタンを押す。
- アプリケーションウィザードで、次へのボタンを押して、
- アプリケーションの種類で、DLLを選ぶ。
- シンボルのエキスポートにもチェックを付けたら、完了ボタンを押して完了。
-ソースファイル
- inputbox.cpp →メインプログラム
- stdafx.cpp →このファイルは特に編集しない
- ヘッダファイル
- inputbox.h →DLL用の定義、Export用のヘッダ定義
- stdafx.h →このファイルには、includeするファイルの記述をする
- ソリューションエクスプローラで、プロジェクト(inputbox)を選択して、右クリックでプロパティを出す。
- 全ての構成にしておいて、全般を選択。文字セットをマルチバイト文字セットを使用するに変更。
- もしUniCodeが使いたいなら、tchar.hとかインクルードして、文字列処理を変更する必要がある。
- プロジェクト作成時に、シンボルのエキスポートをするようにしたので、
- これは互換性を考えて、C形式でエキスポートするように変更する。
- グローバル変数は、構造体に纏めておく。
- これは、ヘッダファイル(inputbox.h)に定義しておく
typedef struct inputbox_global {
HINSTANCE hinst;
char input_buffer[256];
char prompt[256];
} *inputbox_globalptr;
- これをDLLのメインプログラム inputbox.cppの先頭で忘れずに定義しておく。
inputbox_global g;
- ここでは、DLLで使用するライブラリのincludeを行う。
- .cppファイルの先頭には、必ず #include "stdafx.h" を入れるようにする。
- このdllは簡単なので、このファイルの必要性はあまりないが、
- DLLで使用するライブラリの定義や定数の定義を行う。
- ここでは、文字列関連のWarningの抑制の記述もしている。
- DLLのメインルーチンである、DLLMainの雛型が作成されているので、
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g.hinst = (HINSTANCE)hModule;
strcpy(g.input_buffer,"");
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
- DLLのローディング時に実行される、DLL_PROCESS_ATTACHで、初期化を行う。
- Windowsプログラムで、インスタンスハンドルはいろいろ使うので、確保しておきたい。
- DllMainの第一引数は、HINSTANCEのようなので、キャストしてグローバルに保存しておく。
- 後処理が必要な場合は、DLL_PROCESS_DETACHに記述する。
- 最初に、Dialogのリソースを作成する。
- リソースビューを右クリックして、追加→リソース→Dialogを選択して新規作成ボタンを押す
- OKボタン、キャンセルボタンを持ったDialogが表示される。(IDD_DIALOG1)
- 表示メニュー→ツールボックスで、ツールボックスウィンドウを表示。
- Static Textを選択して、スタティックテキストを、ダイアログ上に配置(IDC_STATIC1)
- Edit Conrolを選択して、エディットボックスを、ダイアログ上に配置(IDC_EDIT1)
- 作成したリソースのデータは、inputbox.rcというファイルになっている。
- 定義されたリソースIDは、resource.hに記述されているので、矛盾が無いかチェック3して、場合によっては修正、リビルドしておく。
- InputBox本体を記述する。
- これはDialogBoxにより、リソースを表示するだけ。
- ここで、DLLのロード時に保存しておいたインスタンスハンドルを使っている。
INPUTBOX_API char* CALLBACK InputBox(char *msg)
{
strcpy(g.prompt,msg);
if (DialogBox(g.hinst,MAKEINTRESOURCE(IDD_DIALOG1), NULL, (DLGPROC)InputBoxProc) == -1) {
MessageBox(NULL, "DialogBox error!", "InputBox Error", MB_OK);
strcpy(g.input_buffer,"");
}
return (g.input_buffer);
}
- 実際のInputBoxの動作は、コールバック関数InputBoxProcで記述する。
- msgはInputBoxProcに渡すために、グローバルg.promptに保存しておく。
- 入力された文字列は、グローバル g.input_bufferに入っているので、ポインタを戻す。
- DLLがロードされている間は、グローバルは保持されている(はず)
- Dialogの動作を記述する。
- といっても、最初にタイトルとプロンプトを表示するのと、
- OKが押されたら、データを入力するだけ
LRESULT CALLBACK InputBoxProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg) {
case WM_INITDIALOG:
// タイトルとプロンプトの表示
SetWindowText(hDlg,"InputBox");
SetWindowText(GetDlgItem(hDlg,IDC_STATIC1),g.prompt);
return TRUE;
case WM_COMMAND:
switch (LOWORD(wp)) {
case IDOK:
Edit_GetText(GetDlgItem(hDlg,IDC_EDIT1),g.input_buffer, sizeof(g.input_buffer));
EndDialog(hDlg, IDOK);
return TRUE;
case IDCANCEL:
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
break;
}
return FALSE;
}
2SJISとかEUC等の文字セット。WindowsではSJISということになるけど。
3リソースIDの変更を繰り返すとID番号がおかしくなることがある。
[top]
- DLLは単体では動かないので、テスト用のプログラムを作成する。
- テスト用に、非常に簡単なメインプログラムを新規に作成する。
- ファイルメニュー→新規作成→プロジェクト
- プロジェクトの種類を、Visual C++→Win32→Win32コンソールアプリケーションを選ぶ。
- プロジェクト名(test_inputboxとする)を指定したら、OKボタンを押す。
- アプリケーションウィザードで、次へのボタンを押して、
- アプリケーションの種類で、Windowsアプリケーションを選ぶ。
- 空のプロジェクトにチェックを付けたら、完了ボタンを押して完了。
- ソリューションエクスプローラのプロジェクト名を右クリックして、
- プロジェクトの種類を、Visual C++→Win32→C++ファイル(cpp)を選ぶ。
- ファイル名(main.cppとする)を指定したら、OKボタンを押す。
- まずは試しに、最もシンプルと思われるプログラムを記述する。
#include <windows.h>
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
MessageBox(NULL,"メッセージ","タイトル",0);
return 0;
}
これも、プロジェクトのプロパティで、文字セットをマルチバイト文字セットを使用するに変更しておく。
- F5キー(デバッグモードで実行)して、メッセージボックスが表示されることを確認しておく。
- ソリューションエクスプローラのソリューション名を右クリックして、
- 追加→既存のプロジェクトで、inputbox.vcprojを選択してOK。
- これで、inputboxのプロジェクトが追加される。
- プロジェクトメニューから、プロジェクトの依存関係で、
- inputbox_testの依存先として、inputboxにチェックを入れておく。
- これで、inputboxのコンパイル後に、inputbox_testが実行されることになる。
- 構成プロパティ→C/C++→全般の追加のインクルードディレクトリに、inputboxを設定する。
..\..\inputbox\inputbox
ソリューションエクスプローラのソリューション名を右クリックして、
- 追加→既存の項目で、inputbox.libを追加しておく。
#include <windows.h>
#include <stdio.h>
#include "inputbox.h"
int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInstance,
LPSTR lpCmdLine, int nCmdShow)
{
char line[256];
strcpy(line,InputBox("あなたの名前は?"));
sprintf(line,"%sさん、こんにちは!",line);
MessageBox(NULL,line,"あいさつ",0);
return 0;
}
- 今回は、メインプログラムのプロジェクトに、DLLのプロジェクトを追加して、参照するようにしてみた。
- こうするとDLLを修正した場合、メインプログラムも再コンパイル対象になるし、デバッガも使える。
- 完成したDLLの場合は、DLL本体とヘッダファイル、libファイルを用意して、
- libファイルを、メインプログラムのプロジェクトに追加するだけで良い。
- libファイルを使わずに、明示的にDLLをロード、実行することも出来る。
- これはちょっと面倒なので、別に 纏めたもののDLLの項目を参照。
- このように、作成したdllがあれば、簡単にアプリケーションが作れることが分かる。
- あとはコンパイル時に、DLLの定義を記述したヘッダファイルと、libファイルがあれば良い。
- もちろん普通に関数として記述しても良い訳だけど、部品として扱えるので分かり易い。
- それに、DLLだけ差替えて機能を追加することも出来る(プロトタイプを変更しないようにする必要はあるけど)
- ここで作成したInputBoxは、入力した文字列を返すが、とりあえず押されたボタンの値を返すように(OKなら0を返すとか)して、
- 入力された文字列は、別途読み出す関数を用意した方が使い勝手が良さそうだ。
- でも、これはDLLの作り方を纏めるためのものなので、これでお終い。
- もうちょっと弄れば、実用的なDLLになるかもしれない。
[top]
- 例によって、良く出るエラー、嵌ったことについての記録。
- 実行時のエラー。libファイルの指定がされていなかったり、パスが間違っていたりすると出る。
main.obj : error LNK2019: 未解決の外部シンボル __imp__InputBox@4 が関数 _WinMain@16 で参照されました。
- これも実行時のエラー。リビルドしたら直った。
- パスの変更やDLLの修正をした場合は、リビルドした方が良さそう。
- DLLはlibファイルと同じ場所に置いて、libをプロジェクトに追加すれば、普通はOKのようだ。
- それでも駄目だったら、とりあえずいろんな場所に置いてみよう。
- リソースIDと(NAME)は同じである必要は無いはずだが、正しくIDを指定しているのに値が更新さらないことがあった。
- IDと(NAME)を同じにして、resource.hも手修正してユニークなIDを振り直してリビルドすると直ったりした。
comments powered by