ライブラリとパッケージ(Delphi)
ライブラリとパッケージ:インデックス への移動
動的読み込み可能ライブラリは、Windows では動的リンク ライブラリ(DLL)、Mac では DYLIB、Linux では共有オブジェクト(SO)です。 これは、アプリケーションや他の DLL や共有オブジェクトから呼び出すことのできるルーチンの集合です。 ユニットと同様、動的読み込み可能ライブラリには、共有可能なコードやリソースが含まれています。 ただしこのライブラリは、別々にコンパイルして、ライブラリを使用するプログラムとのリンクは実行時に行うことができる実行可能ファイルです。
Delphi のプログラムは、他の言語で書かれた DLL や共有オブジェクトを呼び出すことができます。また、他の言語で書かれたアプリケーションも、Delphi で書かれた DLL や共有オブジェクトを呼び出すことができます。
動的読み込み可能ライブラリの呼び出し
アプリケーションにリンクされていないオペレーティング システム ルーチンを呼び出すことはできます。これらのルーチンは通常、DLL または共有オブジェクトにあります。Windows または macOS では、ルーチンをインポートできるかがコンパイル時に確認されることもありません。 つまり、プログラムをコンパイルするときにはライブラリが存在しなくてもいいことになります。
Linux など、その他のプラットフォームでは、外部参照を解決するため、共有オブジェクトにリンクしなければなりません。検証を避けたい場合には、「動的読み込み」セクションにあるように、LoadLibrary
や GetProcAddress
を使用します。
DLL や共有オブジェクトで定義されたルーチンを呼び出すには、まずそのルーチンをインポートしなければなりません。 方法は 2 つあります。 external の手続きまたは関数として宣言するか、オペレーティング システムを直接呼び出すかです。 どちらの方法を取る場合でも、実行時に初めてルーチンがアプリケーションにリンクされます。
Delphi では、DLL や共有オブジェクトの変数のインポートはサポートしていません。
静的読み込み
手続きや関数をインポートするには、external 指令を付けて宣言する方法が最も簡単です。 例:
procedure DoSomething; external 'MYLIB.DLL';
プログラムでこのように宣言すると、プログラムの起動時に MYLIB.DLL が一度読み込まれます。 そのプログラムが実行している間、DoSomething という識別子は常に同じ共有ライブラリの同じエントリ ポイントを参照します。
インポートするルーチンの宣言は、ルーチンを呼び出すプログラムまたはユニットの中に直接記述することができます。 ただし、保守のことを考えると、external の付いた宣言を集めて独立した "インポート用ユニット" を作成し、その中にそのライブラリと相互作用するために必要な定数や型も含めるようにした方が便利です。 他のモジュールでも、そのインポート用ユニットを使用して、そこで宣言された任意のルーチンを呼び出すことができます。
遅延読み込み(Windows のみ)
external ルーチンに delayed 指令を付けると、そのルーチンが含まれているライブラリの読み込みを遅らせることができます。 実際の読み込みは、ルーチンが初めて呼び出されたときに行われます。 次の例は、delayed 指令の使い方を示しています。
function GetSomething: Integer; external 'somelibrary.dll' delayed;
この例では、somelibrary.dll ライブラリから GetSomething ルーチンがインポートされています。 delayed 指令を付けることで、somelibrary.dll をアプリケーションに静的にリンクするのではなく、動的にリンクすることができます。
delayed 指令が有益なのは、インポート対象のルーチンが、アプリケーションが実行されるオペレーティング システム上に存在しない場合です。 静的にインポートされるルーチンの場合、アプリケーションの起動時に、オペレーティング システムがそのライブラリを見つけて読み込まなければなりません。 読み込んだライブラリ内でルーチンが見つからない場合や、ライブラリが存在しない場合には、オペレーティング システムはアプリケーションの実行を停止します。 delayed 指令を指定すると、要求された API をオペレーティング システムがサポートしているかどうかの確認を実行時に行うことができます。インポートされたルーチンを呼び出せるようになるのは確認が済んだ後です。
delayed 指令の使い方としてもう 1 つ考えられるのは、アプリケーションのメモリ使用量を考慮して、使われない可能性が高いルーチンに指定することです。ライブラリは必要が生じなければ読み込まれないため、delayed を指定することでアプリケーションのメモリ使用量を減らすことができる可能性があります。 delayed を乱用すると、プログラムの速度性能(ユーザーが認識するもの)が低下する可能性があります。
メモ: 遅延ルーチンを呼び出そうとしてそのルーチンが解決できなかった場合には、実行時エラー(SysUtils ユニットが読み込まれている場合には例外)が発生します。
Delphi ランタイム ライブラリが使用する遅延読み込み処理を微調整したい場合には、フック手続きを登録して、動作を監視したり変更することができます。 そのためには、SysInit ユニットで宣言されている SetDliNotifyHook2 と SetDliFailureHook2 を使用します。 DelayedLoading (Delphi) のコード例も参照してください。
動的読み込み
LoadLibrary や FreeLibrary や GetProcAddress といった Windows API を直接呼び出して、ライブラリ内のルーチンにアクセスすることができます。 また、macOS、Linux、Android でも利用可能です。これらの関数は、Windows では Winapi.Windows.pas ユニットで、その他のプラットフォームでは System.SysUtils.pas で宣言されています。 この場合は、手続き型変数を使ってインポートされたルーチンを参照します。
例:
uses System.SysUtils {$IFDEF MSWINDOWS},Winapi.Windows{$ENDIF};
type
TTimeRec = record
Second: Integer;
Minute: Integer;
Hour: Integer;
end;
TGetTime = procedure(var Time: TTimeRec);
var
Time: TTimeRec;
Handle: HMODULE;
GetTime: TGetTime;
begin
Handle := LoadLibrary('libraryname');
if Handle <> 0 then
begin
@GetTime := GetProcAddress(Handle, 'GetTime');
if @GetTime <> nil then
begin
GetTime(Time);
with Time do
Writeln('The time is ', Hour, ':', Minute, ':', Second);
end;
FreeLibrary(Handle);
end;
end.
このようにしてルーチンをインポートすると、LoadLibrary の呼び出しを含むコードが実行されるまでライブラリは読み込まれません。 読み込んだライブラリは、FreeLibrary を呼び出してアンロードすることができます。 こうすることで、メモリを節約したり、使用する一部のライブラリが存在しない場合にもプログラムを実行したりすることができます。