Free Pascalはプログラム(EXE)のほかにダイナミックリンクライブラリ(DLL)をつくることもできます。以降ライブラリまたしDLLと呼びます。ライブラリを作成する場合も『ひな型を用意する(EXE編)』と同様にライブラリのひな型ファイルを用意しておくと便利です。
ひな形の各構成要素が何を意味しているかを理解すればライブラリ(DLL)の作成も難しくありません。
私の場合は以下のようなひな型ファイルを作成して使用しています。
libraryファイル(その1)はライブラリ(DLL)になるファイルです。コードページ UTF-8 で作成してください。
library DllLib1;
{$MODE OBJFPC}{$H+}{$J-}
{$MINFPCONSTPREC 64}
{$CODEPAGE UTF8}
{$OPTIMIZATION OFF}
uses
SysUtils{, Classes, Unit1, ...};
{ ここに 宣言・定義 }
exports
{ ここに 宣言・定義の中で公開したいシンボル名 };
begin
SetMultiByteConversionCodePage(CP_UTF8);
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
{ ここに 初期化処理の実行文 }
end.
library(その1)は、次のような要素で構成されます。
library DllLib1;
ヘッダ部はDLLソースファイルの先頭でライブラリ名を設定します。
このヘッダ部は省略できません。
ライブラリ名はライブラリのネームスペースになります。プログラム名には「.」を含むこともできます。libraryのネームスペースを使わなければならないケースはないと思います。
生成されるDLLファイルのファイル名はライブラリ名ではなく、ソースファイルのファイル名の拡張子を.dllにしたものが既定のファイル名になります。OS(Windows)から見たライブラリ名は生成されたDLLファイル名になります。
{$MODE OBJFPC}{$H+}{$J-}
{$MINFPCONSTPREC 64}
{$CODEPAGE UTF8}
{$OPTIMIZATION OFF}
コンパイラディレクティブはソースコード内でコンパイル時にコンパイラに対して指示をするものです。
ディレクティブには、グローバルディレクティブとローカルディレクティブがあります。
グローバルディレクティブは、そのソースファイル全体に影響します。他のソースファイルには影響しません。必要であればそれぞれのソースファイルで指定します。同じディレクティブはソースファイル内で1回だけ指定できます。
uses句より前に指定します。ヘッダ部の前でも可能です。
ローカルディレクティブは、ソースファイル全体ではなく記述された時点から有効になります。ソースファイル内で複数指定可能で設定を切り替えることができます。他のソースファイルには影響しません。
{$MODE OBJFPC}は、グローバルディレクティブになります。コンパイルモードの指定でコンパイラにソースコードをObject Pascalとしてコンパイルするよう指示します。
{$H+}は、ローカルディレクティブになります。長い書き方では{$LONGSTRINGS ON}で、String型をAnsiString型(長い文字列)として扱うように指定します。{$H-}または{$LONGSTRINGS OFF}とした場合は、String型はShortString型(255文字までの文字列)となります。ソースコードの任意の場所に指定してString型の扱いを切り替えることも可能です(推奨はしません)。文字列型をAnsiString型やShortString型として宣言すれば本ディレクティブの影響は受けません。{$H-}と{$H+}のどちらが既定になるかはコンパイルモードによって違います。OBJFPCモードでは{$H-}が設定されるため{$H+}を指定して切り替えます。また、{$MODE OBJFPC}の後に{$H+}を指定します。逆にすると{$MODE OBJFPC}が{$H-}を設定するので{$H+}が{$H-}に切り替えられてしまいます。
{$J-}は、ローカルディレクティブになります。既定の動作では型付き定数は代入可能な初期化済み変数のようになります。{$J-}を指定することにより代入不可の定数として扱うようになります。Turbo Pascal時代に変数宣言で初期化ができなかったため、型付き定数宣言でその機能を実装したようです。現在では変数宣言で初期化の設定が可能なため型付き定数は安全のために代入不可能な定数として扱うようにします。
{$MINFPCONSTPREC 64}は、ローカルディレクティブとなります。コンパイラが実数のリテラルや型指定のない定数に型を割り当てるときに精度を落とさず格納可能な最小の型を割り当てます。そのため、0.5はSingle型(32bit実装)となり、0.1はDouble型(64bit実数)になります。計算結果の精度にも影響するため、すべてDouble型(64bit実数)を割り当てるために{$MINFPCONSTPREC 64}を指定します。既定に戻すには{$MINFPCONSTPREC 32}または{$MINFPCONSTPREC DEFAULT}を指定します。
{$CODEPAGE UTF8}は、グローバルディレクティブになります。ソースコードがコードページUTF-8で書かれていることをコンパイラに教えます。Free PascalではソースコードをUTF-8で作成してください。このディレクティブがないとソースコードに書かれた文字列リテラルをプログラム内のデータとしてコンパイルするときに正しく変換できなくなります。
{$OPTIMIZATION OFF}は、コンパイラの最適化を無効にします。ライブラリ(DLL)では、変数を宣言し値を代入してもライブラリ内のコードで参照されるとは限りません。DLLを呼び出すプログラムが参照するだけの場合はライブラリ内では参照されません。コンパイラの最適化処理では参照されない値の設定などは無効な処理として扱われ処理自体がないものとしてコンパイルされてしまいます。そのためライブラリ(DLL)では、すべてのコードを有効とするために最適化を無効にするのが無難です。
ディレクティブとコンパイラのコマンドラインオプションで同じ設定がある場合、ソースコードのディレクティブが優先(上書き)されます。
uses
SysUtils{, Classes, Unit1, ...};
uses句は、ライブラリで使用するユニットを指定します。「,」で区切って複数指定することができます。
何も指定しない場合、uses句は省略可能です。
SysUtilsを使用するとランタイムエラーを例外として扱うことができるようになり、例外の捕捉(try 〜 except 〜 end)もできるようになるため、uses句で指定することを推奨します。その他タイプヘルパー、文字列関数、日付・時間など便利なルーチンも使用できるようになります。
Classesを使用するとFree Componet Libraly(FCL)の基本クラスが使用できるようになります。TStringListやTStreamなど使う場合は指定します。ひな形ではコメントアウトしています。
ちなみに、Classesの中でSysUtilsがusesに指定されているので、Classesだけを指定してもSysUtilsは組み込まれ、ランタイムエラーを例外として扱うことができるようになります。SysUtilsが公開している識別子(定数,型,変数,関数/手続き)などを使用(コードに記述)する場合はusesにSysUtilsを明示的に指定します。
例外のスローは呼び出し元プログラムまでは届かないので、ライブラリ(DLL)内で捕捉し処理する必要があります。呼び出し元に伝えるには、そのための変数や手続き/関数を用意して公開(エクスポート)します。「2.6 exports句」参照。
{ ここに 宣言・定義 }
ラベル、定数、型、変数、手続き/関数などを宣言・定義はヘッダ部とuses句の後で、実行文部の前に記述します。標準Pascalでは、宣言・定義の記述順序は決まっていますが、Free Pascalでは(label, const, type, var, threadvar, resourcestring, procedure, function)が順不同で複数回の記述が可能です。
exports
{ ここに 宣言・定義の中で公開したいシンボル名 };
exports句は、宣言・定義部で記述した変数や手続き/関数をDLLを呼び出すプログラムに公開(エクスポート)したいもののシンボル名(名前)を指定します。「,」で区切って複数指定することができます。
何も指定しない場合、exports句は記述しません。シンボル名を指定しないexportsはエラーとなります。
何も公開しないDLLはあるのか?
DLLはロード時に自動的に初期化部が実行されるため、DLLを使用するプログラムから明示的にシンボルの呼出しが行われなくても処理を行うことができます。プログラム開始時またはDLLの動的ロード時にデータベースを初期化するなど用途はあります。
begin
SetMultiByteConversionCodePage(CP_UTF8);
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
{ ここに 初期化処理の実行文 }
end.
begin〜endの間にライブラリの初期化のための実行文を記述します。ライブラリ(DLL)のロード時にライブラリ初期化部が実行されます。
DLLを呼び出すプログラムがexports句で公開された変数や手続き/関数を使用する前に初期化部の処理は完了しています。
最後の「.」はライブラリ定義の終了です。
初期化部の中にラベル、定数、型、変数、手続き/関数などの宣言・定義を記述することはできません。
SetMultiByteConversionCodePage()とSetMultiByteRTLFileSystemCodePage()については「ひな型を用意する(EXE編)」の『2.6.1 SetMultiByteConversionCodePage()とSetMultiByteRTLFileSystemCodePage()について』参照。
libraryファイル(その2)はlibraryファイル(その1)にDLLアンロード時の処理を追加したものです。初期化処理はbeginではなくinitializationとなります。
library DllLib2;
{$MODE OBJFPC}{$H+}{$J-}
{$MINFPCONSTPREC 64}
{$CODEPAGE UTF8}
{$OPTIMIZATION OFF}
uses
SysUtils{, Classes, Unit1, ...};
{ ここに 宣言・定義 }
exports
{ ここに 宣言・定義の中で公開したいシンボル名 };
initialization
SetMultiByteConversionCodePage(CP_UTF8);
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
{ ここに 初期化処理の実行文 }
finalization
{ ここに 終了処理の実行文 }
end.
library(その2)は、次のような要素で構成されます。
library DllLib2;
「2.2 ヘッダ部」参照。
{$MODE OBJFPC}{$H+}{$J-}
{$MINFPCONSTPREC 64}
{$CODEPAGE UTF8}
{$OPTIMIZATION OFF}
「2.3 コンパイラディレクティブ」参照。
uses
SysUtils{, Classes, Unit1, ...};
「2.4 uses句」参照。
{ ここに 宣言・定義 }
「2.5 宣言・定義部」参照。
exports
{ ここに 宣言・定義の中で公開したいシンボル名 };
「2.6 exports句」参照。
initialization
SetMultiByteConversionCodePage(CP_UTF8);
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
{ ここに 初期化処理の実行文 }
finalization
{ ここに 終了処理の実行文 }
end.
initialization 〜 finalization 〜 end でライブラリの初期化と終了処理を記述します。
initializationの後にライブラリの初期化のための実行文を記述します。ライブラリ(DLL)のロード時にライブラリ初期化部が実行されます。
DLLを呼び出すプログラムがexports句で公開された変数や手続き/関数を使用する前にライブラリ初期化部の処理は完了しています。
ライブラリ初期化部の中にラベル、定数、型、変数、手続き/関数などの宣言・定義を記述することはできません。
SetMultiByteConversionCodePage()とSetMultiByteRTLFileSystemCodePage()については「ひな型を用意する(EXE編)」の『2.6.1 SetMultiByteConversionCodePage()とSetMultiByteRTLFileSystemCodePage()について』参照。
初期化が不要な場合はライブラリ初期化部を省略することができます。initializationキーワードは削除しても残してもどちらでもかまいません。
finalizationの後にライブラリの終了処理のための実行文を記述します。ライブラリ(DLL)のアンロード時にライブラリ終了処理部が実行されます。
ライブラリ(DLL)で動的に確保した記憶域の開放やオープンしたファイルのクローズなどを確実に行うことができます。
終了処理が不要な場合はライブラリ終了処理部を省略することができます。finalizationキーワードは削除しても残してもどちらでもかまいません。
endでライブラリ初期化部とライブラリ終了処理部の定義が終了します。ライブラリ初期化部とライブラリ終了処理部の両方を省略しinitialization/finalizationキーワードも省略した場合でもendは省略できません。
最後の「.」はライブラリ定義の終了です。
initialization 〜 finalization 〜 end の処理で、finalizationを省略した場合は、initialization 〜 end となりますが、begin 〜 end. と記述する「2. libraryファイルのひな型(その1)」と同じです。
libraryファイル(その3)はWindows DLLのエントリポイントの呼出し理由ごとに処理を記述するものです。
Windows DLLでは、呼出し理由は以下の4つで、それぞれのタイミングでDLLのエントリポイントが呼び出されます。理由コードをパラメータで受け取るので、その理由コードに合わせた処理を行います。
理由コード名(値) |
説明 |
|---|---|
| DLL_PROCESS_ATTACH(1) | プロセス起動による静的ロードまたはLoadLibraryによる動的ロードによる呼出し |
| DLL_PROCESS_DETACH(0) | プロセス終了またはFreeLibraryによるアンロードによる呼出し |
| DLL_THREAD_ATTACH(2) | スレッド起動による呼出し |
| DLL_THREAD_DETACH(3) | スレッド終了による呼出し |
C/C++言語では、DLLMainがエントリポイントで、そのなかで理由コード別の処理を実行します。Free Pascalでは、ランタイムシステム内にエントリポイントがあり、理由コード毎に処理を呼び出します。
library DllLib3;
{$MODE OBJFPC}{$H+}{$J-}
{$MINFPCONSTPREC 64}
{$CODEPAGE UTF8}
{$OPTIMIZATION OFF}
uses
SysUtils{, Classes, Unit1, ...};
{ ここに 宣言・定義 }
exports
{ ここに 宣言・定義の中で公開したいシンボル名 };
{ DLL_THREAD_ATTACHの処理 }
procedure DllEntryThreadAttach(DllParam: IntPtr);
{ ここに 宣言・定義 }
begin
{ ここに 実行文 }
end;
{ DLL_THREAD_DETACHの処理 }
procedure DllEntryThreadDetach(DllParam: IntPtr);
{ ここに 宣言・定義 }
begin
{ ここに 実行文 }
end;
initialization
SetMultiByteConversionCodePage(CP_UTF8);
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
{ DLL_THREAD_ATTACH/DLL_THREAD_DETACH処理手続きを設定 }
Dll_Thread_Attach_Hook := @DllEntryThreadAttach;
Dll_Thread_Detach_Hook := @DllEntryThreadDetach;
{ ここに 初期化処理の実行文 : DLL_PROCESS_ATTACHの処理 }
{ グローバル変数としてDllParam参照可。0:LoadLibrary, 非0:静的ロード }
finalization
{ ここに 終了処理の実行文 : DLL_PROCESS_DETACHの処理 }
{ グローバル変数としてDllParam参照可。0:FreeLibrary, 非0:プロセス終了 }
end.
library(その3)は、次のような要素で構成されます。
library DllLib3;
「2.2 ヘッダ部」参照。
{$MODE OBJFPC}{$H+}{$J-}
{$MINFPCONSTPREC 64}
{$CODEPAGE UTF8}
{$OPTIMIZATION OFF}
「2.3 コンパイラディレクティブ」参照。
uses
SysUtils{, Classes, Unit1, ...};
「2.4 uses句」参照。
{ ここに 宣言・定義 }
「2.5 宣言・定義部」参照。
exports
{ ここに 宣言・定義の中で公開したいシンボル名 };
「2.6 exports句」参照。
{ DLL_THREAD_ATTACHの処理 }
procedure DllEntryThreadAttach(DllParam: IntPtr);
{ ここに 宣言・定義 }
begin
{ ここに 実行文 }
end;
DLLエントリポイントの理由コードDLL_THREAD_ATTACHの処理をするTDLL_Entry_Hook型の手続きです。手続き名は任意の名前でかまいません。
スレッド起動時の呼出し時の処理を記述します。
{ DLL_THREAD_DETACHの処理 }
procedure DllEntryThreadDetach(DllParam: IntPtr);
{ ここに 宣言・定義 }
begin
{ ここに 実行文 }
end;
DLLエントリポイントの理由コードDLL_THREAD_DETACHの処理をするTDLL_Entry_Hook型の手続きです。手続き名は任意の名前でかまいません。
スレッド終了時の呼出し時の処理を記述します。
initialization
SetMultiByteConversionCodePage(CP_UTF8);
SetMultiByteRTLFileSystemCodePage(CP_UTF8);
{ DLL_THREAD_ATTACH/DLL_THREAD_DETACH処理手続きを設定 }
Dll_Thread_Attach_Hook := @DllEntryThreadAttach;
Dll_Thread_Detach_Hook := @DllEntryThreadDetach;
{ ここに 初期化処理の実行文 : DLL_PROCESS_ATTACHの処理 }
{ グローバル変数としてDllParam参照可。0:LoadLibrary, 非0:静的ロード }
ライブラリ初期化部はinitializationではじまり、初期化のための実行文を記述します。ライブラリ(DLL)のロード時にライブラリ初期化部が実行されます。
これは、DLLエントリポイントの理由コードDLL_PROCESS_ATTACHの処理になります。
DLLエントリポイントの第3パラメータlpvReservedの値はグローバル変数DllParamで参照することができます。
DLLを呼び出すプログラムがexports句で公開された変数や手続き/関数を使用する前に初期化部の処理は完了しています。
初期化部の中にラベル、定数、型、変数、手続き/関数などの宣言・定義を記述することはできません。
SetMultiByteConversionCodePage()とSetMultiByteRTLFileSystemCodePage()については「Free Pascal(Object Pascal) : ひな型を用意する(EXE編)」の『2.6.1SetMultiByteConversionCodePage()とSetMultiByteRTLFileSystemCodePage()について』参照。
「4.7 DLL_THREAD_ATTACHの処理(手続き)」と「4.8 DLL_THREAD_DETACHの処理(手続き)」で定義した手続きの参照をそれぞれTDLL_Entry_Hook型の手続き型変数Dll_Thread_Attach_HookとDll_Thread_Detach_Hookに代入します。
DLL_THREAD_ATTACHの処理時にはDll_Thread_Attach_Hookの参照手続きが呼び出されます。
DLL_THREAD_DETACHの処理時にはDll_Thread_Detach_Hookの参照手続きが呼び出されます。
DLL_THREAD_ATTACHの処理とDLL_THREAD_DETACHの処理が不要な場合はこれらの設定が不要になります。その場合は「3. libraryファイルのひな型(その2)」となります。
初期化が不要な場合はライブラリ初期化部を省略することができます。
finalization
{ ここに 終了処理の実行文 : DLL_PROCESS_DETACHの処理 }
{ グローバル変数としてDllParam参照可。0:FreeLibrary, 非0:プロセス終了 }
ライブラリ終了処理部はfinalizationではじまり、終了処理(後始末)のための実行文を記述します。ライブラリ(DLL)のアンロード時にライブラリ終了処理部が実行されます。
これは、DLLエントリポイントの理由コードDLL_PROCESS_DETACHの処理になります。
DLLエントリポイントの第3パラメータlpvReservedの値はグローバル変数DllParamで参照することができます。
ライブラリ(DLL)で動的に確保した記憶域の開放やオープンしたファイルのクローズなど確実に行うための最後の砦です。
終了処理が不要な場合はライブラリ終了処理部を省略することができます。
end.
ライブラリ初期化部とライブラリ終了処理はendで終了です。
initialization 〜 finalization 〜 end でライブラリの初期化と終了処理の記述になります。
ライブラリ初期化部とライブラリ終了処理部の両方を省略した場合でもendは省略できません。
最後の「.」はライブラリ定義の終了です。
参考までに、C言語りDLLエントリポイントDllMainは以下のようになります。
#include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
switch(fdwReason) {
case DLL_PROCESS_ATTACH:
/* ここに DLLロード時の処理 */
break;
case DLL_THREAD_ATTACH:
/* ここに スレッド起動時の処理 */
break;
case DLL_THREAD_DETACH:
/* ここに スレッド終了時の処理 */
break;
case DLL_PROCESS_DETACH:
/* ここに DLLアンロード時の処理 */
break;
default:
return FALSE;
}
return TRUE;
}
本記事のプログラムコードなどは以下のページでダウンロードできます。