Delphi Programming / Object Pascal
[掲載 2009年09月14日] [更新 2015年10月05日] Delphi 一般・その他
VCL で未定義の Windows API 関数の使用 - DLL の使用方法
動作確認等
各サンプルの記事参照
2010年12月02日
[非公開 API の使用方法] を追加
2015年10月05日
.NET Framework 関係の文章を追加
DLL (ダイナミックリンクライブラリ) は、各種のプログラム言語から共通に使用することを目的にした、一種のライブラリです。
この DLL 内に定義されている関数類を、外部から利用することになります。したがって、DLL 内に格納されている関数類の仕様がわかれば利用できるということになります。DLL が、どの開発言語で作成されたのか、ということは直接には関係ありません。
外部から使用可能な DLL 内の関数を、エクスポート関数と言います。
本ページの記事は、主に Windows が提供している DLL 内の関数の使用方法です。外部から使用可能な、これらの関数類を一般的には Windows API の関数と言っています。
DLL そのものの使用方法としては、参考リンクのたたみやさんのサイトのサンプルで十分です。本ページの記事は、補足的な意味で書いています。
[備考 1]
同じ DLL でも .NET Framework 用は全く別です。.NET Framework 用の開発言語で作成した DLL は、本ページで説明している方法では Delphi, C, C++ 等から利用できません。
その逆に、Delphi, C, C++ 等で作成した DLL は、.NET Framework 用の開発言語では、直接利用することはできません。
.NET Framework は、既存の OS に別の実行環境をインストールして利用するようなイメージで考えると理解しやすいかも知れません。
.NET Framework には、Windows API にはない機能が追加実装されていて、Windows が提供している機能をより多く利用できるようになっています。.NET Framework 用の主な開発言語は C# (シーシャープ) ですが、C# では Windows API も利用可能な仕組みを持っています。
[備考 2]
システムに登録しないと使用できない DLL があります。それは OCX (ActiveX) と同じように、主に COM オブジェクト操作用としての DLL です。
01_ VCL で未定義の Windows API 関数の使用
Windows API の関数を使用するということは、Windows が提供している DLL 内の関数を使用するということです。これらの DLL は主にシステムフォルダにあります。
Delphi には、Windows の API を使用するための定義が多く用意されています。
例えば、ShellExecute 関数は shell32.dll 内にあり、CreateProcess 関数は Kernel32.dll 内にある関数です。これらの関数類は、「DLL を使用する」ということを意識しなくても使用できるようになっています。しかし、Windows で使用可能な関数全てが Delphi で使用できるわけではありません。
Delphi に用意されていない Windows API の関数を利用するには、自分で定義して使用します。関数の仕様などは [MSDN のオンラインヘルプ] の項を参考にして調べることになります。
コミュニティの場で、Delphi 以外の他の言語で作成された DLL を使用するにはどうしたらいいか、という質問を見かけることがあります。Windows が提供している DLL は、Delphi 以外の言語で作成されていることは間違いありません。したがって、
Windows API の関数を使用する => 他の言語で作成された DLL (内の関数) を使用する
ということになります。この逆は成立しません。サードパーティ等が提供している DLL も他の言語で作成しているかも知れませんが、それらは Windows API の関数とは言いません。
Windows API の関数は、マイクロソフトのサイトに資料がありますが、それ以外の DLL については、DLL を提供している側の資料、特に関数の引数の型等の情報がなければ利用できません。
これは「 DLL の使用方法」というより、関数の使用方法の問題です。
本項目で述べたことは、Delphi に限りません。どの開発言語でも同じことが言えます。
[備考 1]
他の言語からは利用できないようにしたり、特定のアプリケーション以外からは利用できないように作成された DLL もあります。
DLL を作成する際、言語特有の機能を使用して作成すると、他の言語からは使用できないこともあります。例えば、関数の引数に Delphi の String 型の文字列を使用すると、C, C++ 等では利用できない DLL となります。C, C++ には Delphi の String 型に相当する文字列型がありません。
Windows API の文字列は、文字列の最後を意味する文字が null (#0) となっている null 終端字列です。Delphi で null 終端文字列を扱う時は主にそのポインタ型である PChar を使用します。
Delphi 専用の文字列である String 型のポインタ型は PString です。Delphi ではポインタの型名として、慣習的に先頭を P とする型名が使われています。
[備考 2]
API は、Application Programming Interface の略ですが、現在は API という用語がその意味で使われているようです。クイズ番組で「何の略でしょう ?」という質問に答えなければならないとか、情報関係の受験をする場合等は別ですが、特に意識する必要はないでしょう。
用語というのは、意味さえ理解していれば、業務の上で問題になることはありません。試験で正しく答えられることと、意味を理解している、ということは別の問題です。
02_ DLL 内の関数の使用方法 - DLL と目的の関数が存在する場合
サンプルプログラム集では、次項以降に述べるいろいろな方法を使用していますが、DLL が (ライブラリのパスではなく) システム的にパスの通った場所に確実に存在して、しかも該当関数が必ずその DLL 内にあるのであれば、おそらく、この方法が一番シンプルで利用しやすいかも知れません。
以下のように使用します。
(1) 使用する関数の定義文を書く (interface 部に書くことが多い)
(2) その関数を使用するコードを書く
これだけです。この方法を「DLL の静的リンク」と言っています。
最大の難関は、DLL 内の関数の引数の型と構造体 (Delphi ではレコード型) の変数の型です。Windows API に限らす、サードパーティが提供している DLL の多くは C あるいは C++ で作成されています。それらの変数の型が Delphi のどの型に相当するのかを知る必要があります。
例えば、Kernel32.dll 内に QueryFullProcessImageNameW という関数があります。この関数は MSDN の記事では以下のように定義されています。この定義を Delphi で使用可能な定義文にしたのが、その下のコードです。
リスト1
MSDN の記事での関数の定義文
in は関数に渡す引数
out は関数で処理した値を受け取る変数で、Delphi では var またはポインタ型の変数にする
copy code
BOOL WINAPI QueryFullProcessImageNameW(
_In_ HANDLE hProcess,
_In_ DWORD dwFlags,
_Out_ LPTSTR lpExeName,
_Inout_ PDWORD lpdwSize
);
リスト2
上の関数の定義を、Delphi で使用可能にした関数の定義文
変数の型を Delphi の型にしているが、LPTSTR 等はそのままの定義でも使用可能
copy code
function QueryFullProcessImageNameW(
Process: THandle;
Flags: DWORD;
Buffer: PChar;
Size: PDWORD): Boolean; stdcall ; external 'kernel32.dll' ;
stdcall は Windows API で利用されているデファクトスタンダードの呼出規約で、Pascal の呼出規約と同じです。呼び出し規約の説明は少し面倒です。DLL 内の関数を使用する際の一種の「おまじない」ぐらいに考えても差支えありません。
external 'kernel32.dll' は、この関数が kernel32.dll 内に存在することを意味しています。対象の関数がどの DLL にあるかは MSDN の記事に書いてあります。
この関数は Unicode 版と Ansi 版があります。Uncode 版の関数名は最後に W があり、Ansi 版のそれには A があります。LPTSTR 等の Windows の型が、Delphi ではどの型に相当するかは、例えば、以下のリンクの記事を参考にしてください。
MSDN の Windows Data Types (Windows) に掲載されているデータ型の多くは、Delphi でもそのまま使用可能です。
例えば、上の関数で Buffer を LPTSTR で定義しても、下図のように、その関数を使用する際に翻訳して、Delphi のどの型に相当するのかを教えてくれます。この図は Delphi XE(UP1) Pro の場合です、非ユニコード版の Delphi 2007 では PAnsiChar と表示されます。
実際には、Delphi 2007 以前では、末尾が A の関数を使用します。
図1
引数の型に LPTSTR を指定すると、Delphi の何の型に相当するかを教えてくれる
03_ DLL 内の関数の使用方法 - DLL を動的にロードして使用
前項の「DLL の静的リンク」では、アプリの起動時に自動的に DLL がメモリにロードされます。
Windows API を使用するということは、Windows に実装されている DLL 内の関数を使用するわけですから、前項の方法で基本的には十分です。
問題となるのは、異なる Windows のバージョンで動作させるアプリケーションとか、Windows の DLL ではなく、サードパーティが提供している DLL や他の方が作成した DLL を利用する場合です。そのような場合、もし、対象の DLL が存在しないとエラーになります。また、たとえ DLL が存在しても、目的の関数がその DLL 内にないとエラーになります。
そこで、まず、DLL が存在するか確認します。そして、存在すればメモリにロードして、更にその DLL 内に目的の関数が存在するかチェックし、その DLL 内の関数のアドレスを取得して利用する方法があります。これを「DLL の動的リンク」とも言っています。
以下のリンクの最初の 3 つの記事は、ntdll.dll にある RtGetVersion という関数を利用するためのコードです。このサンプルでは、DLL の利用方法の参考となるように、DLL の静的リンクと動的リンクの両方のサンプルコードを掲載しています。
DLL の動的リンクでは、DLL の存在を確認して、存在すれば LoadLibrary で DLL をロードしています。その DLL 内の目的の関数のアドレスを GetProcAddress で取得して使用します。DLL の使用が終了したら FreeLibrary で DLL を解放しています。これが DLL の一般的な利用方法です。
DLL は、LoadLibrary で読み込んで (メモリにロードして)、必ず FreeLibrary で解放します。
DLL 内の関数のアドレスは、GetProcAddress で取得します。
04_ DLL 内の関数の使用方法 - タイプライブラリの取り込み
[472_ システム関係ダイアログ (Shell)] では、shell32.dll をタイプライブラリの取り込みでコンポーネントにして利用しています。
全ての DLL がタイプライブラリの取り込みで、Delphi で使用可能なコードが生成できるわけではありません。DLL 作成時の仕様によります。
タイプライブラリの取り込みで作成したユニットには、COM オブジェクトとしての操作用コードが書かれています。文字列には WideString や PWideString が使用されていて、非ユニコード版の Delphi でも、ユニコード版の Delphi でも使用可能です。
COM インターフェイスで使用する文字列は、WideString 型や PWideString 型です。String 型を使用すると例外が発生することがあります。
05_ DLL 内の関数の使用方法 - JEDI API ライブラリ
Project JEDI のサイトで、Delphi が標準では実装していない Windows API の関数類のライブラリが提供されています。これらは Sorce-forge でダウンロードできます。2010年07月27日現在の最新版は [JEDI API 2.3 and JEDI WSCL 0.9.3.zip] です。
サンプルプログラム集の [475_パフォーマンスカウンタで CPU 使用率] でも使用しています。
JEDI Project では、この他にも、大量のコンポーネント類が提供されています。下記の参考リンクの記事で紹介しているリンクからダウンロードできます。
2013年10月07日現在の対応バージョンは、Delphi 6 ~ Delphi XE5 となっています。
06_ DLL 内の関数の使用方法 - 非公開 API の使用方法
非公開 API の関数は、上で説明している方法では利用できません。[815_フォルダの監視コンポーネント] に非公開APIを使用したコードがあります。参考にしてください。非公開APIでは、序数が必要な関数がありますが、この値は、Dependency Walker という、DLL 内の関数をリストするソフトを使用すると調べることができます。日本語パッチも提供されている方がいます。
この序数を調べる際、非公開となっている Windows のバージョンではなく、該当する関数が通常使用可能なWindowsのバージョンの DLL で調べることになります。下図は、フォルダ監視コンポーネントで使用している Shell32.dll 内の非公開 API 関数、SHChangeNotifyRegister の序数を調べています。
序数を使用する方法は、将来もこの機能をサポートする予定が無いことを、マイクロソフトは表明しています。
図2
非公開 API 関数の序数
07_ C++ ヘッダを Delphi のコードに変換
Delphi では、多くの Windows API が使用可能ですが、全てではありません。
[IDL コンパイラを使用したタイプライブラリの作成と取り込み] の記事内の Microsoft Windows SDK をインストールすると。Delphi には実装されていない Windows API 関係の C++ 用ヘッダファイルがあります。これらの拡張子は .h です。
Dr.Bob's Delphi Programming Clinic のサイトでは、C++ のヘッダファイルを Delphi のコードに変換するユーティリティを提供しています。検討する価値があるかも知れません。
08_ 32 ビット版の DLL と 64 ビット版の DLL
基本的には、32 ビット版の DLL は 64 ビットアプリケーションからは利用できません。また、64 ピット版の DLL は 32 ビットのアプリケーションでは利用できません。
いずれも LoadLibrary で失敗します。
システム関係の DLL は、64 ビット Windows では、32 ビット版の DLL は SysWOW64 フォルダ内にあります。また、64 ビット版の DLL は System32 フォルダ内にあります。
アプリケーションでシステム関係の DLL を使用する場合、これらのフォルダを意識する必要はありません。32 ビットのアプリでは、SysWOW64 フォルダ内のの DLL が、 64 ビットアプリでは System32 フォルダ内の DLL が自動的に使用される仕組みになっています。
これをリダイレクト機能と言っています。