レガシー Windows API(概要)について

ここで紹介しているAPIは、Windows95、Windows NT3.5時代のAPI群であり、最新のAPIは含まれていないことに注しして欲しい。なお、当時の解説書は現在よりもより詳細に解説しているため、参考になるはずである。

  • レイアウトを変換する際、崩れているページがあります。
  • 概要およびSampleコードはありますが、APIの解説はありません。

ご要望があれば旧版のWin32 API(HELP形式)をお譲りします。問い合わせフォームより申し込みください。

Windows API ダイナミック リンク ライブラリの概要

Microsoft(R) Windows(TM) では、 プロセスが自分以外のコードの一部である関数を呼び出すための方法として動的リンクを用意しています。関数の実行可能コードは「ダイナミック リンク ライブラリ」(DLL) 内にあります。DLLにはコンパイルされた関数が含まれており、 その関数を使うプロセスとは別に格納されます。Microsoft(R) Win32(R) アプリケーション プログラミング インターフェイス (API) はダイナミック リンク ライブラリのセットとして実現されているため、 Win32 APIを使用するプロセスは動的リンクを使用することになります。

次に示すトピックでは、 Win32のダイナミック リンク ライブラリについて説明しています。

動的リンクの利点

DLLの作成

DLLエントリポイント関数

モジュール定義ファイル

ロード時動的リンク

実行時動的リンク

DLLのデータ

ダイナミック リンク ライブラリの使用

簡単なダイナミック リンク ライブラリの作成

ロード時動的リンクの使用

実行時動的リンクの使用

DLLでの共有メモリの使用

DLLでのスレッド ローカル記憶域の使用

ダイナミック リンク ライブラリ関数

動的リンクによって、 DLL関数の実行コードを見つけるのに必要な情報だけを実行可能モジュール (.DLLファイルや.EXEファイル) に付加すればよくなります。これは、 これまでの静的リンクとは異なります。静的リンクでは、 ライブラリ関数を使用する各アプリケーションの実行可能モジュールにその関数の実行コードをコピーしなければなりません。

DLLの関数を呼び出すには、 次の2つの方法があります。

アプリケーションのコードがDLL関数を明示的に呼び出したときは、 ロード時動的リンクが発生します。この場合、 アプリケーションの実行可能モジュールは、 DLLのインポート ライブラリとリンクしてビルドしなければなりません。インポート ライブラリは、 アプリケーションが呼び出そうとしているDLL関数を見つけるのに必要な情報を含みます。詳しくは、 ロード時動的リンクを参照してください。

プログラムがLoadLibrary関数やGetProcAddress関数を使ってDLL関数の開始アドレスを取得すると、 実行時動的リンクが発生します。これによって、 インポート ライブラリをリンクする必要がなくなります。詳しくは、 実行時動的リンクを参照してください。

プロセスが起動したり、 プロセスのスレッドがLoadLibrary関数を呼び出すと、 DLLはプロセスに接続されます。オペレーティング システムは、 DLLをプロセスに接続するとき、 DLLモジュールをプロセスのアドレス空間にマップして、 プロセスがDLLの実行コードを利用できるようにします。プロセスが終了したりFreeLibrary関数が呼び出されたためにDLLがプロセスから接続解除されるときは、 DLLモジュールはプロセスのアドレス空間からマップ解除されます。

DLL関数は、 ほかの関数と同様に、 関数を呼び出したスレッド (およびそのスレッドのプロセス) のコンテキストで実行されます。このため、 次に示す条件が適用されます。

DLLを呼び出したプロセスのスレッドは、 DLL関数がオープンしたハンドルを使用できます。同様に、 呼び出し側のプロセスのスレッドがオープンしたハンドルはDLL関数内で使われます。

DLLは、 呼び出し側のスレッドのユーザー スタックと、 呼び出し側のプロセスのアドレス空間を使用します。

DLLが割り当てたメモリは、 呼び出し側のプロセスのアドレス空間内に置かれます。

DLLを呼び出したプロセスのスレッドは、 DLLでグローバルに宣言されている変数を読み書きできます。

動的リンクの利点

動的リンクには、 次に示す利点があります。

複数のプロセスが1つのDLLを同時に共有して使うことができます。これによって、 メモリ空間を節約でき、 スワップの回数が減ります。

関数の引数と戻り値を変更しなければ、 DLLの関数を利用しているアプリケーションをコンパイルしなおしたりリンクしなおさずに関数を変更できます。静的リンク オブジェクト コードの場合は、 関数を変更すると、 アプリケーションをリンクしなおして更新しなければなりません。

DLLを使って出荷後のサービスを行うことができます。たとえば、 アプリケーションの出荷時には利用できなかったディスプレイをサポートするようにディスプレイ ドライバDLLを修正できます。

関数の呼び出し規約に従っていれば、 異なるプログラミング言語で作成されたプログラムが同じDLL関数を呼び出すことができます。プログラムとDLL関数で互換性がなければならないのは、 スタックに引数をプッシュする順番、 スタックの後始末をどちらが行うか、 レジスタで渡される引数についてです。

DLLの欠点は、 アプリケーションが単体で動作しなくなることです。DLLを使用するアプリケーションは、 DLLモジュールが存在しなければ動作できません。プロセスの起動時にDLLが存在しなければ、 ロード時動的リンクを使用するプロセスはオペレーティング システムによって終了させられます。実行時動的リンクを使用するアプリケーションはこのような場合でも終了しませんが、 DLLが提供する機能をプログラムが利用することはできません。

ダイナミック リンク ライブラリの作成

DLLを作成するには、 まず、 ソース コード ファイル (.C) とモジュール定義ファイル (.DEF) を作成します。ソース コード ファイルには、 DLLの関数を記述します。.DEFファイルでは、 関数をリストしたり、 DLLの属性を定義します。

コンパイラは、 .Cファイルからオブジェクト コード ファイル (.OBJ) を作成します。ライブラリ マネージャは、 .DEFファイルからエクスポート ファイル (.EXP) とインポート ライブラリ ファイル (.LIB) を作成します。

最後に、 リンカが.OBJ .EXP .LIBの各ファイルを使ってDLLの実行可能ファイルをビルドします。.OBJファイルと.EXPファイルには、 リンカが.DLLファイルをビルドするのに必要な情報が含まれます。.LIBファイルには、 リンカがDLL関数への外部参照を解決して、 プログラムが指定したDLLを実行時に見つけるために必要な情報が含まれます。リンカは、 .LIBファイルによって、 DLLの関数を呼び出す実行可能モジュール (.EXE.DLL) を作成できます。.DEFについて詳しくは、 モジュール定義ファイルを参照してください。

このトピックは、 Win32のソフトウェア開発ツールに基づいています。DLLの作成について詳しくは、 使用している開発ツールのマニュアルを参照してください。

DLLエントリポイント関数

DllEntryPoint関数はオプションのDLL関数で、 オペレーティング システムがユーザー定義の処理を実行するために呼び出されます。DllEntryPointは通常の関数ではなく、 リンカのコマンド ラインで指定する関数への参照です。DLLにはエントリポイント関数が必ずしも必要ではありませんが、 エントリポイント関数はプロセスやスレッドの初期化と後始末に役立ちます。DLLのエントリポイント関数の名前は、 リンカの-entryスイッチで指定します。たとえば、 次に示すコマンド ラインは、 MYDLL.DLLのエントリポイント関数としてLibMainを指定します。

link -entry:LibMain -out:mydll.dll mydll.exp mydll.obj $(mylibs)

エントリポイント関数を指定すると、 オペレーティング システムは、 次に示すイベントが発生したときにそのエントリポイント関数を呼び出します。

DLLをプロセスに接続するとき。ロード時動的リンクを使用するプロセスの場合、 これは初期化時に発生します。実行時動的リンクを使用するプロセスの場合、 LoadLibraryが戻る前にDLLが接続され、 エントリポイント関数が呼び出されます。

DLLをプロセスから接続解除するとき。これは、 プロセスが終了するときや、 FreeLibrary関数を呼び出してDLLをアンロードするときに発生します。TerminateProcess関数やTerminateThread関数によってプロセスが終了するときは、 システムはエントリポイント関数を呼び出しません。

DLLをスレッドに接続するとき。これは、 DLLが接続されているプロセスで新しいスレッドを作成すると発生します。

DLLをスレッドから接続解除するとき。これは、 DLLが接続されているプロセスのスレッドが終了したときに発生します。ただし、 TerminateThread関数やTerminateProcess関数によって終了したときは除きます。DLLをスレッドから接続解除するとき、 エントリポイント関数は、 プロセスの終了する各スレッドではなく、 プロセス全体に対して一度だけ呼び出されます。

システムはエントリポイント関数が呼び出されるようにしますが、 関数が呼び出されるのは、 DLLを接続または接続解除するプロセスやスレッドのコンテキストです。このため、 DLLは、 エントリポイント関数を使って、 プロセスのアドレス空間にメモリを割り当てたり、 プロセスがアクセス可能なハンドルをオープンできます。新しいスレッドに接続するために呼び出された場合、 エントリポイント関数は、 そのスレッドに専用のスレッド ローカル記憶域 (TLS) のメモリを割り当てることができます。スレッド ローカル記憶域について詳しくは、 プロセスとスレッドの概要を参照してください。

DLLのエントリポイント関数は、 プロセスへの接続で呼び出された場合、 TRUEを返して正常終了を示します。ロード時リンクを使用するプロセスの場合は、 FALSEを返すとプロセスの初期化は失敗し、 プロセスは終了します。実行時リンクを使用するプロセスの場合は、 FALSEを返すと、 LoadLibrary関数は失敗を示すNULLを返します。プロセスの接続やスレッドの接続や接続解除などのために呼び出されたときは、 エントリポイント関数の戻り値は無視されます。

モジュール定義ファイル

モジュール定義 (.DEF) ファイルは、 ダイナミック リンク ライブラリのさまざまな属性を記述するモジュール定義文を含むテキスト ファイルです。ここでの説明と例は、 Win32ソフトウェア開発ツールで使われているキーワードと構文に基づいています。モジュール定義ファイルについて詳しくは、 使用している開発ツールのマニュアルを参照してください。

DLLには、 インポート ライブラリ (.LIB) ファイルとエクスポート (.EXP) ファイルを作成するためのモジュール定義ファイルが必要です。リンカは、 インポート ファイルを使って、 DLLを使用する実行可能ファイルを構築します。また、 エクスポートファイルを使って.DLLファイルを作成します。

DLLのモジュール定義ファイルには、 LIBRARY文とEXPORTS文を指定しなければなりません。LIBRARY文は、 モジュール定義ファイルがDLLに属するものであることを示し、 ファイルの最初の文でなければなりません。LIBRARY文には、 DLLのインポート ライブラリの名前を指定します。EXPORTS文には、 DLLがエクスポートする関数の名前とオプションの序数値をリストします。序数値は1からDLL関数がエクスポートする関数の個数までの値でなければなりません。

3つの関数をエクスポートし、 各関数に序数値を関連付けるDLLモジュール定義ファイルを次に示します。

LIBRARY mydll

EXPORTS
Func_A @1
Func_B @2
Func_C @3

モジュール定義ファイルには、 DLLのそのほかの属性を指定する文を追加することもできます。たとえば、 SECTIONS文を使って、 グローバル変数や静的変数のデフォルトの適用範囲を変更できます。SECTIONS文は、 DLLの初期化データを共有にしますが、 初期化されない変数は共有されません。

SECTIONS
.data READ WRITE SHARED; share initialized variables

ロード時動的リンク

アプリケーションのコードがDLL関数を明示的に呼び出すと、 ロード時動的リンクが発生します。ソース コードをコンパイルまたはアセンブルすると、 DLL関数呼び出しは、 外部関数参照に変換されます。この外部参照を解決するため、 アプリケーションにDLLのインポート ライブラリをリンクしなければなりません。

外部関数がインポート ライブラリ内にあれば、 リンカは、 その関数のコードがダイナミック リンク ライブラリにあるものと考えます。リンカは、 ダイナミック リンク ライブラリへの外部参照を解決するとき、 プロセスの起動時にシステムがDLLのコードを見つけるための情報を実行可能ファイルに付加するだけです。

システムは、 動的リンクを参照するプログラムを起動するとき、 プログラムの実行可能ファイルの情報を使って必要なDLLを見つけます。システムは、 まず、 メイン実行ライブラリ (KERNEL32.DLL) やセキュリティ ライブラリ (USER32.DLL) などのインストール済みDLLのセットを検索します。それから、 システムは、 次に示す検索パスを使ってDLLを検索します。

1. 現在のプロセスの実行可能モジュールが存在するディレクトリ

2. 現在のディレクトリ

3.32ビットWindowsのシステム ディレクトリ。GetSystemDirectory関数は、 システム ディレクトリのパスを取得します。このパスの名前は、 SYSTEM32です。

4.16ビットWindowsのシステム ディレクトリ。このディレクトリのパスを取得するWin32関数はありませんが、 パスは検索されます。このパスの名前は、 SYSTEMです。

5.Windowsディレクトリ。GetWindowsDirectory関数は、 Windowsディレクトリのパスを取得します。

6.PATH環境変数にリストされているディレクトリ

LIBPATH環境変数は使われません。

指定されたDLLが見つからなければ、 システムは、 プロセスを終了し、 エラーを報告するダイアログ ボックスを表示します。そうでないときは、 システムは、 そのDLLモジュールをプロセスのアドレス空間にマップします。

DLLDllEntryPoint関数があれば、 オペレーティング システムはエントリポイント関数を呼び出します。DllEntryPointを呼び出すとき、 システムは、 DLLがプロセスに接続されようとしていることを示すコードを指定します。エントリポイント関数がTRUEを返さなければ、 システムはプロセスを終了し、 エラーを報告します。エントリポイント関数について詳しくは、 DLLエントリポイント関数を参照してください。

最後に、 システムは、 プロセスの実行コードを修正して、 DLL関数の開始アドレスを設定します。

DLLのコードは、 プログラム コードのほかの部分と同様に、 プロセスの起動時にプロセスのアドレス空間にマップされ、 必要なときだけロードされます。このため、 Windowsの以前のバージョンでロード方法を制御するためにモジュール定義ファイルで使われていたPRELOADコード属性やLOADONCALLコード属性は無意味です。

実行時動的リンク

プロセスがDLL名を指定してLoadLibrary関数を呼び出したり、 GetProcAddress関数を呼び出してDLL内の関数の開始アドレスを取得しようとすると、 実行時動的リンクが発生します。実行時動的リンクによってプロセスとインポート ライブラリをリンクする必要がなくなります。プロセスがDLL関数を明示的に呼び出さないため、 外部参照は生成されません。

すでにプロセスのアドレス空間にマップされているDLLモジュールを指定してLoadLibraryを呼び出すと、 LoadLibraryは、 モジュールの参照カウントを1つ増やしてDLLのハンドルを返すだけです。モジュールがまだマップされていないときは、 LoadLibraryは、 ロード時動的リンク (ロード時動的リンクを参照してください) と同じ検索シーケンスを使ってDLLを検索します。DLLが見つかれば、 システムは、 プロセスのアドレス空間にDLLモジュールをマップします。DLLDllEntryPoint関数があれば、 システムは、 LoadLibraryを呼び出したスレッドのコンテキストでその関数を呼び出します。以前にLoadLibraryでプロセスにDLLがすでに接続されているときは、 エントリポイント関数は呼び出されません。これは、 LoadLibraryに対応するFreeLibrary関数が呼び出されていないときに起こります。

システムがDLLを見つけられなかったときや、 エントリ関数がFALSEを返したときは、 LoadLibraryNULLを返します。LoadLibraryは、 正常に終了すると、 DLLモジュールのハンドルを返します。プロセスは、 GetProcAddress関数やFreeLibrary関数を呼び出すときに、 このハンドルを使ってDLLを識別します。

また、 GetModuleHandle関数も、 GetProcAddress関数やFreeLibraryで使われるハンドルを返します。GetModuleHandleは、 ロード時リンクやLoadLibraryによってDLLモジュールがすでにプロセスのアドレス空間にマップされているときだけ正常に終了します。GetModuleHandleは、 LoadLibraryとは異なり、 モジュールの参照カウントを増やしません。GetModuleFileName関数は、 GetModuleHandleLoadLibraryが返したハンドルに関連するモジュールのフル パスを取得します。

DLL関数の開始アドレスを取得するには、 LoadLibraryGetModuleHandleが返したDLLモジュール ハンドルを指定して、 GetProcAddressを呼び出します。関数を指定するには、 DLLのモジュール定義ファイルのEXPORTS文にリストされている関数名か序数値を使います。

DLLモジュールが不要になったら、 プロセスは、 FreeLibrary関数を呼び出してください。これによって、 モジュールの参照カウントが1つ減らされ、 参照カウントが0になると、 モジュールはプロセスのアドレス空間からマップ解除されます。

実行時動的リンクによって、 プロセスは、 DLLが利用可能でなくても、 別の方法を使って目的を達成し、 実行を続けることができます。たとえば、 プロセスは、 DLLを見つけることができなければ、 ほかのDLLを使うか、 ユーザーにエラーを報告することができます。プロセスが見つけられなかったDLLのフル パスをユーザーが指定できれば、 DLLが通常の検索パスになくても、 プロセスはDLLをロードできます。これに対して、 ロード時リンクでは、 DLLが存在しなければ、 システムがプロセスを終了させるだけです。

DLLの名前やDLL関数の名前がわかっている必要はありません。プロセスは、 この情報を構成ファイルやユーザーから取得できます。

DLLDllEntryPoint関数を使ってプロセスの各スレッドの初期化を行っている場合、 LoadLibraryが呼び出されたときに存在するスレッドは初期化されないため、 実行時動的リンクでは問題が発生する場合があります。この問題を処理する方法の例については、 ダイナミック リンク ライブラリでのスレッド ローカル記憶域の使用を参照してください。

ダイナミック リンク ライブラリのデータ

Windowsは、 次に示すデータをサポートしています。

DLLを使用する各モジュールにプライベートなグローバル変数と静的変数

DLLを同時に使用するプロセスすべてが共有するグローバル変数と静的変数

DLLを使用する各プロセスにプライベートな、 動的に割り当てられたメモリ

複数のプロセスが共有する、 動的に割り当てられたメモリ

マルチスレッド プロセスの各スレッドにプライベートな、 静的記憶域

DLLの変数のデフォルトの適用範囲は、 静的にリンクされるコードと同じです。DLLのソース コードのグローバル変数は、 DLLを使用する各プロセスについてグローバルです。静的変数に対しては、 各プロセスごとに恒久的な記憶域が作成され、 適用範囲は変数が宣言されているブロックに限定されます。このため、 デフォルトでは、 各プロセスは、 DLLのグローバル変数と静的変数のプロセス固有のインスタンスを持ちます。

Win32開発ツールによって、 DLLのモジュール定義ファイルを使ってグローバル変数と静的変数のデフォルトの適用範囲を変更できます。詳しくは、 モジュール定義ファイルや、 使用しているツールのマニュアルを参照してください。

DLLが割り当て関数 (GlobalAlloc LocalAllocHeapAlloc VirtualAlloc、 またはmallocなどのC言語ランタイム割り当て関数) を使ってメモリを割り当てると、 メモリは呼び出し側のプロセスのアドレス空間に割り当てられ、 そのプロセスのスレッドだけからアクセスできるようになります。動的に割り当てられるメモリの適用範囲は、 DLLのモジュール定義ファイルを使って変更することはできません。

DLLは、 ファイル マッピングを使って、 プロセス間で共有されるメモリを割り当てることができます。この方法の概要については、 ファイル マッピングの概要を参照してください。DllEntryPoint関数を使ってファイル マッピングによる共有メモリを設定する例については、 「ダイナミック リンク ライブラリでの共有メモリの使用」を参照してください。

DLLは、 スレッド ローカル記憶域関数によって、 マルチスレッド プロセスの各プロセスごとに異なる値を格納または取得するためのインデックスを割り当てることができます。たとえば、 表計算アプリケーションは、 ユーザーが表をオープンするたびに、 同じスレッドから新しいインスタンスを作成します。さまざまな表計算操作を実現するDLLは、 スレッド ローカル記憶域を使って、 各表の現在の状態 (行や列など) に関する情報を保存できます。スレッド ローカル記憶域の概要については、 プロセスとスレッドの概要を参照してください。DllEntryPoint関数を使ってスレッド ローカル記憶域をセットアップする例は、 「ダイナミック リンク ライブラリでのスレッド ローカル記憶域の使用」を参照してください。

ダイナミック リンク ライブラリの使用

ここの例では、 次に示す処理を示しています。

簡単なダイナミック リンク ライブラリの作成

ロード時リンクによるDLL関数の呼び出し

実行時リンクによるDLL関数の呼び出し

DLLのエントリポイント関数による、 DLLを使用するすべてのプロセスからアクセス可能な名前付き共有メモリの作成

DLLでのスレッド ローカル記憶域の使用

簡単なダイナミック リンク ライブラリの作成

ダイナミック リンク ライブラリによって、 よく使われる関数を複数のプロセスやスレッドが同時に利用できます。次に示すMYPUTS.Cは、 簡単な文字列印刷関数です。このDLLでは初期化や後始末を行わないため、 DllEntryPoint関数は使用しません。

#include <windows.h>

/*
* A function that writes a null terminated string to
* console output.
*/

VOID myPuts(LPTSTR lpszMsg) {
DWORD cchWritten;
HANDLE hStdout;

/* Get a handle to the console output. */

hStdout = GetStdHandle(STD_OUTPUT_HANDLE);

/* Write a null-terminated string to the console output. */

while (*lpszMsg)
WriteFile(hStdout, lpszMsg++, 1, &cchWritten, NULL);
}

このDLLのモジュール定義ファイルはMYPUTS.DEFで、 次に示す情報が含まれています。

LIBRARY myputs

EXPORTS
myPuts

LIBRARY文は、 DLLのインポート ライブラリのDLL名を識別します。ロード時動的リンクでは、 システムはこの名前を使ってDLLを検索します。実行時ダイナミック リンク ライブラリでは、 LoadLibrary関数はこの関数を使ってDLLを検索しません。EXPORTS文は、 ほかの.EXEモジュールや.DLLモジュールがmyPuts関数を呼び出せることを示します。

MYPUTS.Cのソースコードをコンパイルすると、 オブジェクト モジュールMYPUTS.OBJが生成されます。ライブラリ マネージャは、 モジュール定義ファイル (MYPUTS.DEF) を使って、 インポート ライブラリ (MYPUTS.LIB) とエクスポート ファイル (MYPUTS.EXP) を作成します。リンカは、 MYPUTS.EXPMYPUTS.OBJを使って実行可能DLLモジュール (MYPUTS.DLL) を作成します。MYPUTS.DLLを作成するときにはMYPUTS.LIBインポート ライブラリは使われませんが、 リンカは、 myPuts関数を呼び出す実行可能モジュール (.EXE.DLL) を作成するときにインポート ライブラリを使います。

DLLをコンパイルしてリンクするための典型的なコマンド ラインを次に示します。DLLDLLを使用するプログラムのコンパイルとリンクについて詳しくは、 使用している開発ツールのマニュアルを参照してください。

cvars=-DNT -DWIN -DWIN32 -D_MT -DSTRICT

# Compile myputs.c to create myputs.obj.
myputs.obj: myputs.c
$(cc)$(cflags) $(cvars) myputs.c
cvtomf myputs.obj

# Create the DLL's import library.
# Also create the myputs.exp export file.
myputs.lib: myputs.def
coff -lib -machine:i386 \
-def:myputs.def\
-out:myputs.lib

# Link myputs.obj and myputs.exp to create myputs.dll.
# myputs.lib is not used here but is included as a
# dependency to force creation of myputs.exp.
myputs.dll: myputs.obj myputs.lib
link\
-out:myputs.dll\
myputs.exp myputs.obj $(conlibs)

# If dll has a DllEntryPoint function, substitute a line
# like this to specify the name of the entrypoint function.
#link -entry:LibMain\

ロード時動的リンクの使用

ダイナミック リンク ライブラリを作成したら、 アプリケーションで使うことができます。次に示すLOADTIME.Cは、 MYPUTS.DLLmyPuts関数を使用する簡単なコンソール プロセスのソース コードです。

/*
*File: loadtime.c
*A simple program that uses myPuts() from myputs.dll
*/

#include <windows.h>

VOID myPuts(LPTSTR);/* a function from a DLL */

VOID main(VOID) {

myPuts("message printed using the DLL function\n");

}

LOADTIME.CDLL関数を明示的に呼び出しているため、 アプリケーションの実行可能モジュールには、 インポート ライブラリ (MYPUTS.LIB) をリンクしなければなりません。DLLを使用するプログラムをコンパイルしてリンクするための典型的なコマンド ラインを次に示します。

# Compile loadtime.c to create loadtime.obj.
loadtime.obj: loadtime.c
$(cc)$(cflags) $(cvars) loadtime.c
cvtomf loadtime.obj

# Link loadtime.obj and myputs.lib to create loadtime.exe.
loadtime.exe: loadtime.obj myputs.lib
link $(conflags) \
-out:loadtime.exe loadtime.obj myputs.lib $(conlibs)

実行時動的リンクの使用

ロード時動的リンクと実行時動的リンクでは、 同じDLLを使うことができます。次に示すソース コードは、 指定されたDLLモジュールをシステムが見つけることができなければ、 ロード時リンクの例と同様の出力を生成します。プログラムは、 LoadLibrary関数を使ってMYPUTS.DLLのハンドルを取得します。LoadLibraryが正常に終了すれば、 プログラムは、 GetProcAddress関数が返したハンドルを使って、 DLLmyPuts関数の開始アドレスを取得します。DLL関数を呼び出した後、 プログラムは、 FreeLibrary関数を呼び出してプロセスからDLLを接続解除します。

次の例は、 実行時動的リンクとロード時動的リンクの重要な違いを示しています。MYPUTS.DLLファイルがなければ、 ロード時動的リンクを使用しているアプリケーションは単に終了するだけです。しかし、 この例のように実行時動的リンクを使うことによって、 エラーに対応できます。

/*
*File: runtime.c
*A simple program that uses LoadLibrary and
*GetProcAddress to access myPuts() from the myputs DLL
*/

#include <stdio.h>
#include <windows.h>

typedef VOID (*MYPROC)(LPTSTR);

VOID main(VOID) {
HINSTANCE hinstLib;
MYPROC ProcAdd;
BOOL fFreeResult, fRunTimeLinkSuccess = FALSE;

/* Get a handle to the DLL module. */

hinstLib = LoadLibrary("myputs");

/* If a handle is valid, try to get the function address. */

if (hinstLib != NULL) {
ProcAdd = (MYPROC) GetProcAddress(hinstLib, "myPuts");

/* If the function address is valid, call the function. */

if (fRunTimeLinkSuccess = (ProcAdd != NULL))
(ProcAdd) ("message via DLL function\n");

/* Free the DLL module. */

fFreeResult = FreeLibrary(hinstLib);

}

/* If unable to call the DLL function, use the alternative. */

if (! fRunTimeLinkSuccess)
printf("message via alternative method\n");

}

上記のプログラムは実行時動的リンクを使っているため、 実行可能モジュールを作成するときにDLLのインポート ライブラリとリンクする必要はありません。実行時リンクを使用するプログラムのコンパイルとリンクに使われる典型的なコマンド ラインを次に示します。

# Compile runtime.c to create runtime.obj.
runtime.obj: runtime.c
$(cc)$(cflags) $(cvars) runtime.c
cvtomf runtime.obj

# Link runtime.obj to create loadtime.exe.
runtime.exe: runtime.obj
link $(conflags) \
-out:runtime.exe runtime.obj $(conlibs)

ダイナミック リンク ライブラリでの共有メモリの使用

ここでは、 DLLのエントリポイント関数を使って複数のプロセスが共有するメモリを設定する方法を示します。このDLLのエントリポイント関数の名前はLibMainで、 モジュール定義ファイルはMYPUTS.DEFです。モジュール定義ファイルの内容を次に示します。

LIBRARY myputs

EXPORTS
myPuts

この例では、 ファイル マッピングを使って、 名前付き共有メモリのブロックを、 DLLが接続されている各プロセスのアドレス空間にマップします。DLLが新しいプロセスに接続されると、 DLLのエントリポイント関数は、 CreateFileMapping関数を呼び出してファイル マッピング オブジェクトを作成します。DLLが最初に接続されたプロセスは、 ファイル マッピング オブジェクトを作成します。2番目以降のプロセスは、 そのオブジェクトのハンドルをオープンします。GetLastError関数は最初のプロセスに対してはNO_ERRORを、 2番目以降のプロセスに対してはERROR_ALREADY_EXISTSを返すため、 この関数を使って、 共有メモリの初期化を行うかどうかを判断できます。

MapViewOfFile関数は、 ファイル マッピング オブジェクトを各プロセスのアドレス空間にマップし、 そのメモリを指すポインタを返します。共有メモリは、 各プロセスの異なるアドレスにマップされます。このため、 各プロセスは、 自分自身のlpvMemのインスタンスをグローバル変数として宣言し、 DLL関数からアクセスできるようにします。この例では、 DLLのグローバル データが共有されないことを仮定しているため、 DLLに接続する各プロセスは、 それぞれ専用のlpvMemのインスタンスを持ちます。DLLのデータは共有されませんが、 共有するかどうかはDLLのモジュール定義ファイルで変更できます (モジュール定義ファイルを参照してください)

共有DLLメモリは、 DLLがプロセスに接続されている間だけ存在します。この例では、 ファイル マッピング オブジェクトの最後のハンドルがクローズされると、 共有メモリは解放されます。恒久的な共有メモリを作成するには、 DLLは、 最初のプロセスに接続されるときに、 別のプロセスを作成します (プロセスとスレッドの概要)。このプロセスがDLLを使用して終了しなければ、 ファイルマッピング プロセスのハンドルは保持され、 共有メモリは解放されません。

/*
*File: dllshmem.c
*
*DLL entrypoint function sets up shared memory using
*named file mapping.
*/

#include <windows.h>
#include <memory.h>

#define SHMEMSIZE 4096

static LPVOID lpvMem = NULL; /* address of shared memory */

/*
* LibMain() is the DllEntryPoint function for this DLL.
*/

BOOL LibMain(HINSTANCE hinstDLL,/* DLL module handle*/
DWORD fdwReason,/* reason called*/
LPVOID lpvReserved) { /* reserved*/

HANDLE hMapObject = NULL;/* handle to file mapping */
BOOL fInit, fIgnore;

switch (fdwReason) {

/*
* DLL is attaching to a process, due to process
* initialization or a call to LoadLibrary.
*/

case DLL_PROCESS_ATTACH:

/* create named file mapping object */

hMapObject = CreateFileMapping(
(HANDLE) 0xFFFFFFFF, /* use paging file*/
NULL,/* no security attr. */
PAGE_READWRITE,/* read/write access*/
0,/* size: high 32-bits */
SHMEMSIZE,/* size: low 32-bits */
"dllmemfilemap");/* name of map object */
if (hMapObject == NULL)
return FALSE;

/* The first process to attach initializes memory. */

fInit = (GetLastError() != ERROR_ALREADY_EXISTS);

/* Get a pointer to file mapped shared memory. */

lpvMem = MapViewOfFile(
hMapObject,/* object to map view of*/
FILE_MAP_WRITE, /* read/write access*/
0,/* high offset: map from*/
0,/* low offset: beginning */
0);/* default: map entire file */
if (lpvMem == NULL)
return FALSE;

/* Initialize memory if this is the first process. */

if (fInit)
memset(lpvMem, '\0', SHMEMSIZE);

break;

/* The attached process creates a new thread. */

case DLL_THREAD_ATTACH:
break;

/* The thread of the attached process terminates. */

case DLL_THREAD_DETACH:
break;

/*
* The DLL is detaching from a process, due to
* process termination or a call to FreeLibrary.
*/

case DLL_PROCESS_DETACH:

/* Unmap shared memory from process' address space. */

fIgnore = UnmapViewOfFile(lpvMem);

/* Close the process' handle to the file-mapping object. */

fIgnore = CloseHandle(hMapObject);

break;

default:
break;

}

return TRUE;
UNREFERENCED_PARAMETER(hinstDLL);
UNREFERENCED_PARAMETER(lpvReserved);

}


/*
* SetSharedMem() sets the contents of shared memory.
*/

VOID SetSharedMem(LPTSTR lpszBuf) {
LPTSTR lpszTmp;

/* Get the address of the shared memory block. */

lpszTmp = (LPTSTR) lpvMem;

/* Copy the null-terminated string into shared memory. */

while (*lpszBuf)
*lpszTmp++ = *lpszBuf++;
*lpszTmp = '\0';
}


/*
* GetSharedMem() gets contents of shared memory.
*/

VOID GetSharedMem(LPTSTR lpszBuf, DWORD cchSize) {
LPTSTR lpszTmp;

/* Get the address of the shared memory block. */

lpszTmp = (LPTSTR) lpvMem;

/* Copy from shared memory into the caller's buffer. */

while (*lpszTmp && --cchSize)
*lpszBuf++ = *lpszTmp++;
*lpszBuf = '\0';
}

ダイナミック リンク ライブラリでのスレッド ローカル記憶域

ここでは、 DLLのエントリポイント関数を使ってスレッド ローカル記憶域インデックスを設定し、 マルチスレッド プロセスの各スレッドに対してプライベートな記憶域を用意する方法を示します。

DLLがプロセスに接続されると、 エントリポイント関数は、 TlsAlloc関数を使ってTLSインデックスを割り当てます。プロセスの各スレッドは、 このインデックスを使って、 自分自身のメモリ ブロックを指すポインタを確認できます。

エントリポイント関数は、 DLL_PROCESS_ATTACH値を指定して呼び出されると、 次に示す処理を行います。

1.TlsAlloc関数を使ってTLSインデックスを割り当てます。

2. プロセスの初期スレッドだけが使用するメモリ ブロックを割り当てます。

3.TLSインデックスを指定してTlsSetValue関数を呼び出して、 割り当てたメモリを指すポインタを格納します。

プロセスが新しいスレッドを作成するたびに、 エントリポイント関数はDLL_THREAD_ATTACHを指定して呼び出されます。呼び出されたエントリポイント関数は、 新しいスレッドのためのメモリ ブロックを割り当てて、 そのメモリ ブロックを指すポインタをTLSインデックスを使って格納します。各スレッドは、 このTLSインデックスを指定してTlsGetValueを呼び出すことによって、 自分自身のメモリ ブロックを指すポインタを取得できます。

スレッドが終了するとき、 エントリポイント関数はDLL_THREAD_DETACHを指定して呼び出され、 そのスレッドのメモリは解放されます。プロセスが終了するとき、 エントリポイント関数はDLL_PROCESS_DETACHコードを指定して呼び出され、 TLSインデックスは解放されます。

TLSインデックスは、 すべてのDLL関数からアクセスできるように、 グローバル変数に格納されます。TLSインデックスはDLLが接続されている各プロセスで同じとは限らないため、 次の例では、 DLLのグローバル データが共有されないと仮定しています。

static DWORD dwTlsIndex; /* address of shared memory */

/*
* LibMain() is the DllEntryPoint function for this DLL.
*/

BOOL LibMain(HINSTANCE hinstDLL,/* DLL module handle*/
DWORD fdwReason,/* reason called*/
LPVOID lpvReserved) {/* reserved*/

LPVOID lpvData;
BOOL fIgnore;

switch (fdwReason) {

/*
* The DLL is attaching to a process, due to process
* initialization or a call to LoadLibrary.
*/

case DLL_PROCESS_ATTACH:

/* Allocate a TLS index. */

if ((dwTlsIndex = TlsAlloc()) == 0xFFFFFFFF)
return FALSE;

/* No break: Initialize the index for first thread. */

/* The attached process creates a new thread. */

case DLL_THREAD_ATTACH:

/* Initialize the TLS index for this thread. */

lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (lpvData != NULL)
fIgnore = TlsSetValue(dwTlsIndex, lpvData);

break;

/* The thread of the attached process terminates. */

case DLL_THREAD_DETACH:

/* Release the allocated memory for this thread. */

lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData);

break;

/*
* The DLL is detaching from a process, due to
* process termination or call to FreeLibrary.
*/

case DLL_PROCESS_DETACH:

/* Release the allocated memory for this thread. */

lpvData = TlsGetValue(dwTlsIndex);
if (lpvData != NULL)
LocalFree((HLOCAL) lpvData);

/* Release the TLS index. */

TlsFree(dwTlsIndex);
break;

default:
break;

}

return TRUE;
UNREFERENCED_PARAMETER(hinstDLL);
UNREFERENCED_PARAMETER(lpvReserved);

}

このDLLに対してプロセスがロード時リンクを使用するときは、 エントリポイント関数でスレッド ローカル記憶域を管理できます。しかし、 プロセスが実行時リンクを使用する場合、 LoadLibrary関数を呼び出してプロセスに接続する前に存在していたスレッドにはTLSメモリは割り当てられないため、 問題が発生します。次の例では、 TlsGetValue関数が返した値を調べて、 そのスレッドのTLSスロットがまだ設定されていないことをその値が示していれば、 メモリを割り当てます。

LPVOID lpvData;

/* Retrieve a data pointer for the current thread. */

lpvData = TlsGetValue(dwTlsIndex);

/* If NULL, allocate memory for this thread. */

if (lpvData == NULL) {
lpvData = (LPVOID) LocalAlloc(LPTR, 256);
if (lpvData != NULL)
TlsSetValue(dwTlsIndex, lpvData);
}

ダイナミック リンク ライブラリ関数

動的リンクで使われる関数を次に示します。

DisableThreadLibraryCalls

DllEntryPoint

FreeLibrary

FreeLibraryAndExitThread

GetModuleFileName

GetModuleHandle

GetProcAddress

LoadLibrary

LoadLibraryEx

LoadModule

使われなくなったか、 または削除された関数

FreeModule

FreeProcInstance

GetCodeHandle

GetInstanceData

GetModuleUsage

MakeProcInstance

▲ページトップに戻る

【本を読んでもよくらからない】 … 個別指導でわかりやすくお教えします