動的ロードとは、コンピュータプログラムが実行時にライブラリ(またはその他のバイナリ)をメモリにロードし、ライブラリに含まれる関数や変数のアドレスを取得し、それらの関数を実行したり、それらの変数にアクセスしたり、メモリからライブラリをアンロードしたりできるメカニズムです。これは、コンピュータプログラムがプログラム内で他のソフトウェアを使用できる3つのメカニズムの1つです。他の2つは、静的リンクと動的リンクです。静的リンクや動的リンクとは異なり、動的ロードでは、コンピュータプログラムはこれらのライブラリが存在しない状態でも起動でき、利用可能なライブラリを検出し、追加機能を取得できる可能性があります。[ 1 ] [ 2 ]
動的ロードは、IBMのSystem/360向けオペレーティングシステム( OS/360など)で、特にI/Oサブルーチン、 COBOLおよびPL/Iランタイムライブラリにおいて一般的な手法であり、 z/Architecture向けIBMオペレーティングシステム( z/OSなど)でも引き続き使用されています。アプリケーションプログラマーにとって、ロードはオペレーティングシステム(またはそのI/Oサブシステム)によって大部分が処理されるため、ほとんど透過的です。主な利点は次のとおりです。
IBMの戦略的なトランザクション処理システムであるCICS(1970年代以降)は、カーネルと通常のアプリケーション プログラムのロードの両方で動的ロードを多用しています。アプリケーション プログラムの修正はオフラインで行え、変更されたプログラムの新しいコピーはCICSを再起動することなく動的にロードできます[ 3 ] [ 4 ] (CICSは24時間365日稼働することができ、実際によく稼働しています)。
共有ライブラリは1980年代にUnixに追加されましたが、当初は起動後にプログラムが追加のライブラリをロードする機能はありませんでした。[ 5 ]
動的ロードは、ソフトウェアプラグインの実装で最も頻繁に使用されます。[ 1 ]例えば、Apache Web Server の*.dso「動的共有オブジェクト」プラグインファイルは、実行時に動的ロードされるライブラリです。 [ 6 ]動的ロードは、複数の異なるライブラリが必要な機能を提供する可能性があり、ユーザーがどのライブラリを提供するかを選択できるコンピュータ プログラムの実装にも使用されます。
すべてのシステムが動的ロードをサポートしているわけではありません。macOS 、Linux、SolarisなどのUnix系オペレーティングシステムは、C言語の「dl」ライブラリを使用して動的ロードを提供します。Windowsオペレーティングシステムは、Windows APIを介して動的ロードを提供します。
ライブラリの読み込みは、WindowsではLoadLibraryまたはを使用し、Unix 系オペレーティングシステムでは を使用します。以下に例を示します。LoadLibraryExdlopen
void * sdl_library = dlopen ( "libSDL.so" , RTLD_LAZY ); if ( ! sdl_library ) { // エラーを報告... } else { // 結果を dlsym の呼び出しに使用}Unixライブラリとして:
void * sdl_library = dlopen ( "libSDL.dylib" , RTLD_LAZY ); if ( ! sdl_library ) { // エラーを報告... } else { // 結果を dlsym の呼び出しに使用}macOSフレームワークとして:
void * sdl_library = dlopen ( "/Library/Frameworks/SDL.framework/SDL" , RTLD_LAZY ); if ( ! sdl_library ) { // エラーを報告... } else { // 結果を dlsym の呼び出しに使用}または、フレームワークまたはバンドルにObjective-Cコードが含まれている場合:
NSBundle * bundle = [ NSBundle bundleWithPath : @"/Library/Plugins/Plugin.bundle" ]; NSError * err = nil ; if ([ bundle loadAndReturnError :& err ]) { // バンドル内のクラスと関数を使用する。} else { // エラーを処理する。}HMODULE sdl_library = LoadLibrary ( TEXT ( "SDL.dll" )); if ( ! sdl_library ) { // エラーを報告... } else { // 結果を GetProcAddress の呼び出しに使用}動的にロードされたライブラリの内容を抽出するには、WindowsGetProcAddressではを使用し、Unix系オペレーティングシステムでは を使用します。dlsym
void * initializer = dlsym ( sdl_library , "SDL_Init" ); if ( ! initializer ) { // エラーを報告... } else { // initializer を適切な型にキャストして使用}macOSでは、Objective-Cバンドルを使用する場合、以下のことも可能です。
Class rootClass = [ bundle principalClass ]; // または、NSClassFromString() を使用して名前でクラスを取得することもできます。if ( rootClass ) { id object = [[ rootClass alloc ] init ]; // オブジェクトを使用します。} else { // エラーを報告します。}FARPROC initializer = GetProcAddress ( sdl_library , "SDL_Init" ); if ( ! initializer ) { // エラーを報告... } else { // initializer を適切な型にキャストして使用}dlsym()またはの結果はGetProcAddress()、使用する前に適切な型のポインタに変換する必要があります。
Windowsでは、FARPROCは基本的に既に関数ポインタであるため、変換は簡単です。
typedef INT_PTR ( * FARPROC )( void );オブジェクトのアドレスを取得する場合、関数を取得する場合とは異なり、これは問題となる可能性があります。しかし、通常は関数を抽出することが目的であるため、これは通常問題になりません。
typedef void ( * SDLInitFunctionType )( void ); SDLInitFunctionType init_func = ( SDLInitFunctionType ) initializer ;POSIX仕様によれば、 の結果はポインタdlsym()ですvoid。しかし、関数ポインタはデータオブジェクトポインタと同じサイズである必要はなく、そのため、型void*と関数ポインタ間の有効な変換をすべてのプラットフォームで実装するのは容易ではない可能性があります。
現在使用されているほとんどのシステムでは、関数ポインタとオブジェクトポインタは事実上相互変換可能です。以下のコードスニペットは、多くのシステムで変換を実行できる回避策の一例を示しています。
typedef void ( * SDLInitFunctionType )( void ); SDLInitFunctionType init_func = ( SDLInitFunctionType ) initializer ;上記のコードスニペットは、一部のコンパイラで警告を表示しますwarning: dereferencing type-punned pointer will break strict-aliasing rules。別の回避策は次のとおりです。
typedef void ( * SDLInitFunctionType )( void );union { SDLInitFunctionType func ; void * obj ; } alias ;alias.obj = initializer ; SDLInitFunctionType init_func = alias.func ;これは、厳密なエイリアシングが有効になっている場合でも警告を無効にします。これは、最後に書き込まれた共用体メンバーとは異なる共用体メンバーから読み取る(「型パンニング」と呼ばれる)ことが一般的であり、共用体型を介してメモリに直接アクセスする場合、厳密なエイリアシングが有効になっている場合でも明示的に許可されるという事実を利用します。[ 7 ]ただし、関数ポインタが共用体の外で使用するためにコピーされるため、ここでは厳密にはそうではありません。このトリックは、データポインタのサイズと関数ポインタのサイズが同じでないプラットフォームでは機能しない可能性があることに注意してください。
関数ポインタとデータオブジェクトポインタ間の変換は、(本質的に移植性のない)実装拡張とみなさざるを得ず、直接変換のための「正しい」方法は存在しないという事実は変わらない。なぜなら、この点に関してPOSIX規格とISO規格は互いに矛盾しているからである。
この問題のため、古いイシュー6のPOSIXドキュメントにdlsym()は、「将来のバージョンでは、関数ポインタを返す新しい関数を追加するか、現在のインターフェースを廃止して、データポインタを返す関数と関数ポインタを返す関数の2つの新しい関数を導入する可能性がある」と記載されている。[ 8 ]
標準の次のバージョン(第 7 号、2008 年)では、この問題が議論され、関数ポインタはvoid*POSIX 準拠のために変換可能である必要があるという結論に至りました。[ 8 ]これにより、コンパイラメーカーはこの場合の動作キャストを実装する必要があります。
ライブラリの内容を変更できる場合(つまり、カスタムライブラリの場合)、関数自体に加えて、その関数へのポインタをエクスポートできます。関数ポインタへのポインタはそれ自体がオブジェクトポインタであるため、このポインタは呼び出しdlsym()とそれに続く変換によって常に正当に取得できます。ただし、この方法では、外部で使用するすべての関数へのポインタを個別に管理する必要があり、そのメリットは通常小さいです。
ライブラリをロードするとメモリが割り当てられます。メモリリークを防ぐには、ライブラリを解放する必要があります。また、ライブラリのアンロードに失敗すると、ライブラリを含むファイルに対するファイルシステム操作ができなくなる可能性があります。ライブラリのアンロードは、 Windowsでは、Unix 系オペレーティングシステムではで行います。ただし、メインアプリケーションのオブジェクトが DLL 内に割り当てられたメモリを参照している場合、DLL のアンロードによってプログラムがクラッシュする可能性があります。たとえば、DLL が新しいクラスを導入し、DLL が閉じられた場合、メインアプリケーションからそのクラスのインスタンスに対してさらに操作を行うと、メモリアクセス違反が発生する可能性があります。同様に、DLL が動的にロードされたクラスをインスタンス化するためのファクトリ関数を導入した場合、DLL が閉じられた後にその関数を呼び出したり参照解除したりすると、未定義の動作が発生します。FreeLibrarydlclose
dlclose ( sdl_library );FreeLibrary ( sdl_library );Unix系オペレーティングシステムやWindowsにおける動的ロードの実装により、プログラマは現在実行中のプロセスからシンボルを抽出することができる。
Unix系オペレーティングシステムでは、プログラマはグローバルシンボルテーブルにアクセスできます。このテーブルには、メインの実行ファイルと、その後ロードされる動的ライブラリの両方が含まれています。
Windowsでは、プログラマーはメイン実行ファイルによってエクスポートされたシンボルにアクセスできます。Windowsはグローバルシンボルテーブルを使用しておらず、複数のモジュールを横断して名前でシンボルを検索するためのAPIもありません。
void * this_process = dlopen ( NULL , 0 );HMODULE this_process = GetModuleHandle ( NULL );HMODULE this_process_again ; GetModuleHandleEx ( 0 , 0 , & this_process_again );Javaプログラミング言語では、オブジェクトを使用してクラスを動的にロードできますClassLoader。例:
Class type = ClassLoader.getSystemClassLoader ( ) . loadClass ( name ) ; Object obj = type.newInstance ( ) ;リフレクション機構は、クラスがまだロードされていない場合に、そのクラスをロードする手段も提供します。これは、現在のクラスのクラスローダーを使用します。
Class type = Class.forName ( name ) ; Object obj = type.newInstance ( ) ;しかし、クラスを制御された方法でアンロードする簡単な方法はありません。ロードされたクラスは、プログラマーがアンロードを希望する場合、クラスのロードに使用されたクラスローダーがシステムクラスローダーではなく、かつそのクラスローダー自体がアンロードされている場合に限り、制御された方法でアンロードできます。その際、クラスが確実にアンロードされるように、さまざまな詳細に注意を払う必要があります。そのため、クラスのアンロードは面倒な作業となります。
Java では、クラスの暗黙的なアンロード、つまりガベージ コレクタによる制御されないアンロードは何度か変更されています。Java 1.2 までは、ガベージ コレクタは、どのクラス ローダーがクラスのロードに使用されたかに関係なく、スペースが必要だと感じたらいつでもクラスをアンロードできました。Java 1.2 以降、システム クラス ローダーを介してロードされたクラスはアンロードされず、他のクラス ローダーを介してロードされたクラスは、その他のクラス ローダーがアンロードされたときにのみアンロードされるようになりました。Java 6 以降、クラスには、ガベージ コレクタがクラスのロードに使用されたクラス ローダーに関係なく、必要に応じてアンロードできることを示す内部マーカーを含めることができます。ガベージ コレクタはこのヒントを無視することもできます。
同様に、ネイティブメソッドを実装するライブラリは、System.loadLibraryメソッドを使用して動的にロードされます。メソッドはありませんSystem.unloadLibrary。
1980年代にUnixやWindowsを通じて普及したにもかかわらず、一部のシステムは依然として動的ローディングを追加しない、あるいは削除することさえ選択しました。たとえば、ベル研究所のPlan 9とその後継である9frontは、動的リンクを「有害」とみなして意図的に回避しています。[ 9 ] Plan 9と同じ開発者の一部によって開発されたGoプログラミング言語も、動的リンクをサポートしていませんでしたが、 Go 1.8 (2017年2月)以降はプラグインローディングが利用可能になりました。Goランタイムとライブラリ関数は、コンパイルされたバイナリに静的にリンクされます。[ 10 ]
dlopen()(問題 6 および 7)。