ダイナミックリンクライブラリ( DLL ) は、Microsoft WindowsおよびOS/2オペレーティングシステムにおける共有ライブラリの概念をMicrosoftが実装したものです。これらのライブラリは通常、ファイル拡張子が、 ( ActiveXコントロールを含むライブラリの場合)、または(レガシーシステムドライバの場合) となります。DLL のファイル形式は、Windows EXEファイルと同じです。つまり、 32 ビットおよび64 ビットWindows ではポータブル実行可能ファイル(PE) 、16 ビットWindows では新規実行可能ファイル(NE) となります。EXE と同様に、DLL にはコード、データ、リソースを任意の組み合わせで含めることができます。DLLOCXDRV
DLL と同じファイル形式だが、ファイル拡張子が異なり、リソースセクションのみを含む可能性のあるデータファイルは、リソース DLL と呼ばれることがあります。このような DLL の例としては、拡張子を持つこともあるアイコンライブラリや、拡張子とを持つフォントファイルなどがあります。[ 1 ]ICLFONFOT
Microsoft Windowsの初期バージョンでは、プログラムは単一のアドレス空間で一緒に実行されていました。すべてのプログラムは、CPU を他のプログラムに譲ることで連携し、グラフィカル ユーザー インターフェイス(GUI) がマルチタスクを実行し、最大限の応答性を発揮できるように設計されていました。オペレーティングシステム レベルの操作はすべて、基盤となるオペレーティングシステムであるMS-DOSによって提供されていました。より高レベルのサービスはすべて、Windows ライブラリの「ダイナミック リンク ライブラリ」によって提供されていました。描画API、グラフィックス デバイス インターフェイス(GDI) は、という DLL に実装されGDI.EXE、ユーザー インターフェイスは に実装されてUSER.EXEいました。DOS の上に構築されたこれらの追加レイヤーは、実行中のすべての Windows プログラムで共有する必要がありました。これは、Windows が 1 メガバイト未満の RAM を搭載したマシンで動作できるようにするためだけでなく、プログラム同士が連携できるようにするためでもありました。GDI のコードは、描画コマンドを特定のデバイス上の操作に変換する必要がありました。ディスプレイ上では、フレーム バッファ内のピクセルを操作する必要がありました。プリンタに描画する場合、API 呼び出しはプリンタへの要求に変換される必要がありました。カラーグラフィックスアダプタディスプレイやHP LaserJetプリンタコマンド言語など、限られたデバイスセットに対してハードコードによるサポートを提供することも可能だったが、マイクロソフトは別のアプローチを選択した。GDIは、「デバイスドライバ」と呼ばれる異なるコード片をロードすることで、さまざまな出力デバイスに対応するように動作する。
GDIが様々なデバイスドライバをロードすることを可能にしたのと同じアーキテクチャ概念によって、Windowsシェルは様々なWindowsプログラムをロードし、これらのプログラムが共有のUSERライブラリとGDIライブラリからAPI呼び出しを行うことが可能になった。その概念こそが「動的リンク」である。
従来の非共有静的ライブラリでは、コードの一部は、実行可能ファイルが「リンク」フェーズでビルドされる際に、呼び出し元のプログラムに単純に追加されます。2つのプログラムが同じルーチンを呼び出す場合、そのルーチンは両方のプログラムのリンク段階で含まれます。動的リンクでは、共有コードは単一の独立したファイルに配置されます。このファイルを呼び出すプログラムは、実行時にオペレーティングシステム(または、初期のバージョンのWindowsの場合はOS拡張機能)によってこのファイルに接続されます。
Windowsの初期バージョン(1.0~3.11)では、DLLがGUI全体の基盤となっていました。そのため、ディスプレイドライバは単に.DRV拡張子を持つDLLであり、統一デバイスドライバインターフェイス(DDI)を介して同じ描画APIのカスタム実装を提供していました。また、描画(GDI)APIとGUI(USER)APIは、それぞれ.EXE拡張子を持つシステムDLLであるGDIとUSERによってエクスポートされる関数呼び出しに過ぎませんでした。
動的にロードされるライブラリの集合体からオペレーティングシステムを構築するというこの概念は、2015年現在も存続するWindowsの中核的なコンセプトである。DLLは、モジュール性など、共有ライブラリの標準的な利点を提供します。モジュール性により、複数のアプリケーションで共有される単一の自己完結型DLL内のコードとデータに変更を加えることができ、アプリケーション自体に変更を加える必要がありません。
モジュール化のもう一つの利点は、プラグインに汎用インターフェースを使用できることです。単一のインターフェースを開発することで、既存のアプリケーションにアプリケーション自体を変更することなく、古いモジュールも新しいモジュールも実行時にシームレスに統合できます。この動的な拡張性の概念は、 ActiveXの基盤となっているコンポーネント オブジェクト モデルで極限まで追求されています。
Windows 1.x、2.x、3.x では、すべての Windows アプリケーションが同じアドレス空間とメモリを共有していました。DLL はこのアドレス空間に一度だけロードされ、それ以降はライブラリを使用するすべてのプログラムがそれにアクセスしました。ライブラリのデータはすべてのプログラムで共有されていました。これはプロセス間通信の間接的な形式として使用できたものの、意図せず異なるプログラムを破損させる可能性もありました。Windows 95で32 ビットライブラリが導入されると、すべてのプロセスが独自のアドレス空間で実行されるようになりました。DLL コードは共有される可能性がありますが、共有データがライブラリによって明示的に要求されない限り、データはプライベートです。とはいえ、Windows 95、Windows 98、Windows Meの大部分は16 ビット ライブラリから構築されており、起動時にPentium Proマイクロプロセッサのパフォーマンスを制限し、最終的には DOS ベースの Windows バージョンの安定性と拡張性を制限しました。
DLLはWindowsアーキテクチャの中核を成すものですが、総称して「DLL地獄」と呼ばれるいくつかの欠点があります。[ 2 ] 2015年現在マイクロソフトは、DLL地獄の問題に対する解決策の一つとして.NET Frameworkを推進していますが、現在では、アプリケーション間の分離性が向上していることから、 Microsoft Virtual PCやMicrosoft Application Virtualizationといった仮想化ベースのソリューションを推奨しています。DLL地獄を緩和する代替策としては、サイドバイサイドアセンブリを実装する方法があります。
DLLは基本的にEXEと同じであるため、リンク処理の一部としてどちらを生成するかを選択するのは、どちらからでも関数やデータをエクスポートできるため、明確化のためです。
DLLを直接実行することはできません。オペレーティングシステムがエントリポイントを介してDLLをロードするにはEXEファイルが必要となるためです。そのため、RUNDLL.EXEやRUNDLL32.EXEのようなユーティリティが存在し、十分な機能を備え、特別なサポートなしで実行できるDLLに対してエントリポイントと最小限のフレームワークを提供します。
DLLは、共有コードとデータのためのメカニズムを提供し、共有コード/データの開発者がアプリケーションの再リンクや再コンパイルを必要とせずに機能をアップグレードできるようにします。アプリケーション開発の観点から見ると、WindowsとOS/2はアップグレード可能なDLLの集合体と考えることができ、OSベンダーがインターフェースと機能の互換性を保証していれば、あるバージョンのOS用のアプリケーションを後のバージョンでも動作させることができます。
DLLは呼び出し元プロセスのメモリ空間内で、同じアクセス権限で実行されるため、使用時のオーバーヘッドは少ないものの、DLLに何らかのバグがあった場合、呼び出し元プログラムを保護する仕組みがないという欠点があります。
Windows APIでは、DLL ファイルはセクションに分けられています。各セクションには、書き込み可能か読み取り専用か、実行可能(コード用)か実行不可能(データ用)かなど、独自の属性セットがあります。
DLL のコードは通常、その DLL を使用するすべてのプロセスで共有されます。つまり、物理メモリ上の単一の場所を占有し、ページ ファイルの領域を占有しません。Windows は DLL に位置独立コードを使用しません。代わりに、コードはロード時に再配置され、DLL を最初にロードするプロセスのメモリ空間で空いている場所にすべてのエントリ ポイントのアドレスが固定されます。すべての実行中のプロセスが単一の共通アドレス空間を占有していた古いバージョンの Windows では、DLL のコードの単一のコピーですべてのプロセスに対応できました。しかし、プログラムごとに別々のアドレス空間を使用する新しいバージョンの Windows では、各プログラムが DLL のコードを格納するために同じ仮想アドレスを空けている場合にのみ、同じ再配置された DLL のコピーを複数のプログラムで使用できます。一部のプログラム (または既にロードされている DLL の組み合わせ) にこれらのアドレスが空いていない場合は、再配置されたエントリ ポイントの異なるセットを使用して、DLL のコードの追加の物理コピーを作成する必要があります。コードセクションが占有している物理メモリを解放する必要がある場合、その内容は破棄され、必要に応じて後でDLLファイルから直接再ロードされます。
コードセクションとは対照的に、DLLのデータセクションは通常プライベートです。つまり、DLLを使用する各プロセスは、DLLのすべてのデータの独自のコピーを保持します。オプションとして、データセクションを共有することで、この共有メモリ領域を介したプロセス間通信が可能になります。しかし、共有DLLメモリの使用にはユーザー制限が適用されないため、セキュリティホールが発生します。つまり、あるプロセスが共有データを破損させると、他のすべての共有プロセスが望ましくない動作をする可能性があります。たとえば、ゲストアカウントで実行されているプロセスが、特権アカウントで実行されている別のプロセスをこのように破損させる可能性があります。これが、DLLで共有セクションを使用しないようにすべき重要な理由です。
DLLが特定の実行可能パッカー(UPXなど)によって圧縮されると、そのコードセクションはすべて読み書き可能としてマークされ、共有されなくなります。読み書き可能なコードセクションは、プライベートデータセクションと同様に、各プロセスに固有のものです。したがって、共有データセクションを持つDLLは、複数のプログラムで同時に使用されることを想定している場合は圧縮すべきではありません。各プログラムインスタンスがDLLの独自のコピーを保持する必要があり、結果としてメモリ消費量が増加するためです。
静的ライブラリと同様に、DLL のインポートライブラリもファイル拡張子で識別されます.lib。たとえば、ファイル作成やメモリ管理などの Windows の基本機能の主要な動的ライブラリであるkernel32.dll はkernel32.lib、. を介してリンクされます。インポートライブラリと適切な静的ライブラリを区別する一般的な方法はサイズです。インポートライブラリは、リンク時に処理される実際の DLL を参照するシンボルのみを含むため、サイズがはるかに小さくなります。ただし、どちらも Unix のarフォーマットファイルです。
動的ライブラリへのリンクは通常、実行可能ファイルを作成するためのビルドまたはリンク時にインポートライブラリにリンクすることによって処理されます。作成された実行可能ファイルには、すべてのDLL関数呼び出しが参照されるインポートアドレステーブル(IAT)が含まれます(参照される各DLL関数は、IATに独自のエントリを持ちます)。実行時には、IATは、個別にロードされたDLL内の関数を直接指す適切なアドレスで埋められます。[ 3 ]
.dll.aCygwin/MSYS および MinGW では、インポート ライブラリには慣例として、Windows DLL サフィックスと Unix ar サフィックスを組み合わせたサフィックスが付けられます。ファイル形式は似ていますが、インポートをマークするために使用されるシンボルが異なります (_head_foo_dllと__IMPORT_DESCRIPTOR_foo)。[ 4 ] GNU Binutilsツールチェーンはインポート ライブラリを生成してそれらにリンクできますが、DLL に直接リンクする方が高速です。[ 5 ] MinGW の genlib と呼ばれる実験的なツールを使用すると、MSVC スタイルのシンボルを使用してインポート ライブラリを生成できます。
DLL によってエクスポートされる各関数は、数値の序数とオプションで名前によって識別されます。同様に、関数は、序数または名前のいずれかを使用して DLL からインポートできます。序数は、DLL エクスポート アドレス テーブルにおける関数のアドレス ポインタの位置を表します。内部関数は、序数のみでエクスポートされるのが一般的です。ほとんどの Windows API 関数では、名前のみがさまざまな Windows リリース間で保持され、序数は変更される可能性があります。したがって、Windows API 関数を序数で確実にインポートすることはできません。
関数を順序番号でインポートする方法は、名前でインポートする方法と比べてパフォーマンスがわずかに向上するだけです。DLLのエクスポートテーブルは名前順に並べられているため、バイナリサーチを使用して関数を見つけることができます。見つかった名前のインデックスを使用して、エクスポート順序番号テーブルで順序番号を検索します。16ビット版Windowsでは、名前テーブルがソートされていなかったため、名前検索のオーバーヘッドがはるかに顕著でした。
実行可能ファイルを特定のバージョンのDLLにバインドすることも可能です。つまり、コンパイル時にインポートされた関数のアドレスを解決できます。バインドされたインポートの場合、リンカーはインポートがバインドされているDLLのタイムスタンプとチェックサムを保存します。実行時に、Windowsは同じバージョンのライブラリが使用されているかどうかを確認し、同じであればインポートの処理をスキップします。そうでない場合、バインドされたライブラリと異なるライブラリが使用されている場合は、Windowsは通常どおりインポートを処理します。
バインドされた実行ファイルは、コンパイルされた環境と同じ環境で実行されると若干速くロードされ、異なる環境で実行されるとまったく同じ時間でロードされるため、インポートをバインドすることにデメリットはありません。たとえば、すべての標準 Windows アプリケーションは、それぞれの Windows リリースのシステム DLL にバインドされています。アプリケーションのインポートをターゲット環境にバインドする良い機会は、アプリケーションのインストール時です。これにより、次の OS アップデートまでライブラリが「バインド」された状態が維持されます。ただし、実行ファイルのチェックサムが変更されるため、署名付きプログラムや、ファイル バージョンを管理するためにチェックサム ( MD5チェックサムなど) を使用する構成管理ツールで管理されているプログラムでは実行できません。最近の Windows バージョンでは、セキュリティ上の理由から、ロードされたすべてのライブラリに固定アドレスを持たせる方式が廃止されているため、実行ファイルをバインドする機会と価値は低下しています。
DLL ファイルは、実行時に明示的にロードできます。このプロセスは、Microsoft では単に実行時動的リンクLoadLibraryと呼ばれ、API 関数(またはLoadLibraryEx) を使用します。APIGetProcAddress関数は、エクスポートされたシンボルを名前で検索するために使用され、 は DLL をアンロードするために使用されます。これらの関数は、POSIX標準 API の 、 、 およびにFreeLibrary相当します。dlopendlsymdlclose
明示的な実行時リンクの手順は、関数へのポインタをサポートするどの言語でも同じです。これは、言語構造ではなくWindows APIに依存しているためです。
通常、DLL のインポート ライブラリにリンクされたアプリケーションは、DLL が見つからない場合、起動に失敗します。これは、アプリケーションが必要とするすべての DLL が見つからない限り、Windows がアプリケーションを実行しないためです。ただし、アプリケーションは、動的ライブラリの遅延ロードを可能にするためにインポート ライブラリにリンクされることがあります。[ 6 ] この場合、オペレーティングシステムは、アプリケーションの起動時に DLL を検索またはロードしようとはしません。代わりに、リンカによってスタブがアプリケーションに含まれ、その関数の 1 つが呼び出されたときLoadLibraryにDLL を検索してロードしようとしますGetProcAddress。DLL が見つからないかロードできない場合、または呼び出された関数が存在しない場合、アプリケーションは例外を生成し、それをキャッチして適切に処理することができます。アプリケーションが例外を処理しない場合、オペレーティングシステムが例外をキャッチし、エラー メッセージとともにプログラムを終了します。
遅延ロード機構は通知フックも提供しており、 DLLがロードされたとき、および/またはDLL関数が呼び出されたときに、アプリケーションが追加の処理やエラー処理を実行できるようにします。
ソースファイルでは、キーワードlibraryの代わりに が使用されますprogram。ファイルの末尾には、エクスポートする関数がexports句にリストされます。
Delphi では、DLL から関数をインポートするためにファイルは必要ありませんLIB。DLL にリンクするには、external関数宣言でキーワードを使用して DLL 名を指定し、その後に を続けてnameシンボル名 (異なる場合) またはindexインデックスを識別します。
Visual Basic (VB)では、実行時リンクのみがサポートされていますが、 API 関数LoadLibraryの使用に加えてGetProcAddress、インポートされた関数の宣言も可能です。
DLLDLL関数を宣言によってインポートする場合、ファイルが見つからないとVBは実行時エラーを生成します。開発者はこのエラーを捕捉し、適切に処理することができます。
VBでDLLを作成する場合、IDEはActiveX DLLの作成のみを許可しますが、各エクスポート関数の順序位置と名前を定義する.DEFファイルを含めるようにリンカーに明示的に指示できるメソッドが作成されています[ 7 ]。これにより、ユーザーはVisual Basic(バージョン6以下)を使用して標準のWindows DLLを作成し、「Declare」ステートメントで参照できるようになります。
Microsoft Visual C++ (MSVC) は、標準C++に対していくつかの拡張機能を提供しており、関数を C++ コード内で直接インポートまたはエクスポートとして指定できます。これらは、GCCの Windows 版を含む他の Windows Cおよび C++ コンパイラにも採用されています。これらの拡張機能は、関数宣言の前に属性を使用します。C++ から C 関数にアクセスする場合、C リンケージを使用する必要があることをコンパイラに伝えるために、C++ コード内でも宣言する必要があることに注意してください。[ 8 ]__declspecextern "C"
属性を使用してインポートまたはエクスポートする関数を指定する以外にも、プロジェクトで使用されるファイル__declspecの IMPORT または EXPORTS セクションにそれらをリストすることができます。このファイルはコンパイラではなくリンカによって処理されるため、C++ に固有のものではありません。DEFDEF
DLL のコンパイルでは、 と の両方DLLのLIBファイルが生成されます。LIBファイル (インポート ライブラリ) はコンパイル時に DLL にリンクするために使用され、実行時リンクには必要ありません。 DLL がコンポーネント オブジェクト モデル(COM) サーバーでない限り、DLLファイルは PATH 環境変数にリストされているディレクトリのいずれか、デフォルトのシステム ディレクトリ、またはそれを使用するプログラムと同じディレクトリに配置する必要があります。 COM サーバー DLL は regsvr32.exe を使用して登録され、DLL の場所とグローバル一意 ID ( GUID ) がレジストリに登録されます。 プログラムは、レジストリで GUID を検索してその場所を見つけるか、クラス識別子とインターフェイス識別子を使用して COM オブジェクトのインスタンスを間接的に作成することにより、DLL を使用できます。
以下の例は、コンパイル時にDLLにリンクするためのシンボルをインポートするために、言語固有のバインディングを使用する方法を示しています。
デルフィ
{$APPTYPE CONSOLE}プログラム例;// 2 つの数値を加算する関数をインポートしますfunction AddNumbers ( a , b : Double ) : Double ; StdCall ; external 'Example.dll' ;// メインプログラムvar R : Double ;begin R := AddNumbers ( 1 , 2 ) ; Writeln ( '結果は: ' , R ) ; end .C
静的リンクを行う前に、プロジェクトに「Example.lib」ファイルを含める必要があります(Example.dll が生成されている場合)。「Example.lib」ファイルは、DLL のコンパイル時にコンパイラによって自動的に生成されます。上記のステートメントを実行しないと、リンカーが の定義を見つける場所がわからないため、リンクエラーが発生しますAddNumbers。DLL ファイル「Example.dll」は、次のコードによって .exe ファイルが生成される場所にコピーする必要がある場合もあります。
#include <windows.h> #include <stdio.h>// 2つの数値を加算する関数をインポートしますextern "C" __declspec ( dllimport ) double AddNumbers ( double a , double b );int main ( int argc , char * argv []) { double result = AddNumbers ( 1 , 2 ); printf ( "結果は: %f \n " , result ); return 0 ; }以下の例は、言語固有のWindows APIバインディングを使用して、実行時ロードおよびリンク機能を利用する方法を示しています。
4 つのサンプルすべてがDLL プリロード攻撃に対して脆弱であることに注意してください。example.dll は作成者が意図しない場所に解決される可能性があるため (現在の作業ディレクトリがシステムライブラリの場所よりも前に来る)、悪意のあるバージョンのライブラリに解決される可能性があります。安全なライブラリのロードに関する Microsoft のガイダンスについては、リファレンスを参照してください。ライブラリがロードされる前に、現在のディレクトリの検索を削除するためにSetDllDirectoryWin を使用する必要があります。 [ 9 ]kernel32
Option Explicit Declare Function AddNumbers Lib "Example.dll" _ ( ByVal a As Double , ByVal b As Double ) As DoubleSub Main ( ) Dim Result As Double Result = AddNumbers ( 1 , 2 ) Debug.Print "結果は: " & Result End Subprogram Example ; {$APPTYPE CONSOLE} uses Windows ; var AddNumbers : function ( a , b : integer ) : Double ; StdCall ; LibHandle : HMODULE ; begin LibHandle := LoadLibrary ( 'example.dll' ) ; if LibHandle <> 0 then AddNumbers := GetProcAddress ( LibHandle , 'AddNumbers' ) ; if Assigned ( AddNumbers ) then Writeln ( '1 + 2 = ' , AddNumbers ( 1 , 2 ) ) ; Readln ; end .#include <windows.h> #include <stdio.h>// DLL関数のシグネチャtypedef double ( * importFunction )( double , double );int main ( int argc , char ** argv ) { importFunction addNumbers ; double result ; HINSTANCE hinstLib ;// DLL ファイルをロードするhinstLib = LoadLibrary ( TEXT ( "Example.dll" )); if ( hinstLib == NULL ) { printf ( "エラー: DLL をロードできません\n " ); return 1 ; }// 関数ポインタを取得addNumbers = ( importFunction ) GetProcAddress ( hinstLib , "AddNumbers" ); if ( addNumbers == NULL ) { printf ( "エラー: DLL 関数が見つかりません\n " ); FreeLibrary ( hinstLib ); return 1 ; }// 関数を呼び出す。result = addNumbers ( 1 , 3 );// DLL ファイルをアンロードするFreeLibrary ( hinstLib );// 結果を表示printf ( "結果は: %f \n " , result );return 0 ; }Pythonのctypesバインディングは、POSIXシステム上ではPOSIX APIを使用します。
ctypesをインポートmy_dll = ctypes 。cdll 。LoadLibrary ( "Example.dll" )# Python が関数によって返される型を理解できるようにするには、次の「restype」メソッドの指定が必要です。 my_dll . AddNumbers . restype = ctypes . c_doublep = my_dll.AddNumbers ( ctypes.c_double ( 1.0 ) , ctypes.c_double ( 2.0 ) )print ( "結果は:" , p )コンポーネントオブジェクトモデル(COM)は、DLLおよびEXEファイルでオブジェクトの実装をホストするためのバイナリ標準を定義します。COMは、これらのファイルを検索およびバージョン管理するメカニズムと、言語に依存しない機械可読なインターフェースの説明を提供します。COMオブジェクトをDLLでホストすると軽量化され、クライアントプロセスとリソースを共有できます。これにより、COMオブジェクトはVisual BasicやASPなどのシンプルなGUIフロントエンドに対して強力なバックエンドを実装できます。また、スクリプト言語からプログラミングすることもできます。[ 10 ]
DLL ハイジャック、DLL スプーフィング、DLL プリロード、バイナリ プラントなどとして知られる脆弱性により、多くのプログラムが、これらのプログラムによって開かれたデータ ファイルと同じフォルダに含まれる悪意のある DLL をロードして実行します。 [ 11 ] [ 12 ] [ 13 ] [ 14 ]この脆弱性は、2000 年に Georgi Guninski によって発見されました。 [ 15 ] 2010 年 8 月に、ACROS Security がこれを再発見し、数百ものプログラムが脆弱であることが判明したため、世界的に注目を集めました。[ 16 ]ダウンロードや一時ディレクトリ などのユーザーが書き込み可能なフォルダなど、安全でない場所から実行されるプログラムは、ほぼ常にこの脆弱性の影響を受けます。[ 17 ] [ 18 ] [ 19 ] [ 20 ] [ 21 ] [ 22 ] [ 23 ]
インポートライブラリは通常の UNIX ライクな .a ライブラリですが、プログラムが DLL とどのようにやり取りするか (「インポート」するか) を OS に伝えるために必要なごくわずかな情報のみが含まれています。この情報は .exe にリンクされます。