挑戦する
私の仕事は、外部の動的ライブラリにいくつかのインターフェイス関数を提供できるライブラリローダーを作成することでした。 ソリューションは、可能な限りクロスプラットフォームにする必要があります(少なくとも、LinuxおよびWindowsで実行する)。 動的ライブラリの作成をサポートするさまざまなプログラミング言語で記述されたライブラリをロードする必要があります。 CおよびPascal言語が例として選択されました。
解決策
メインライブラリローダーはCで記述されています。ロードされたライブラリがメインプログラムの機能を使用できるようにするために、メインプログラムはメインモジュールとロード可能モジュールの2つの部分に分かれています。 メインモジュールは単にプログラムを実行するために必要です。ロード可能なモジュールは、起動時にメインモジュールにリンクされる動的ライブラリでもあります。 Gcc(MinGW for Windows)とfpcがコンパイラとして選択されました。
ここではプログラムの簡単な例を示します。これにより、この問題を理解し、新入生にプログラムのモジュールの作成方法を教えることができます(Pascalは学校でよく教えられます)。
ライブラリローダー
メインライブラリローダーファイルは非常にシンプルに見えます。
main.c
#include "loader.h" #ifdef __cplusplus extern "C" { #endif int main(int argc, char *argv[]) { if (argc > 1) { loadRun(argv[1]); } return 0; } #ifdef __cplusplus } #endif
そして、これは動的ライブラリのロードを担当するモジュールであり、それ自体が動的ライブラリに移動され、ロードされたライブラリが提供する機能を使用できるようになります。
loader.c
#include "loader.h" #include "functions.h" #include <stdio.h> #ifndef WIN32 #include <dlfcn.h> #else #include <windows.h> #endif #ifdef __cplusplus extern "C" { #endif void printString(const char * const s) { printf("String from library: %s\n", s); } void loadRun(const char * const s) { void * lib; void (*fun)(void); #ifndef WIN32 lib = dlopen(s, RTLD_LAZY); #else lib = LoadLibrary(s); #endif if (!lib) { printf("cannot open library '%s'\n", s); return; } #ifndef WIN32 fun = (void (*)(void))dlsym(lib, "run"); #else fun = (void (*)(void))GetProcAddress((HINSTANCE)lib, "run"); #endif if (fun == NULL) { printf("cannot load function run\n"); } else { fun(); } #ifndef WIN32 dlclose(lib); #else FreeLibrary((HINSTANCE)lib); #endif } #ifdef __cplusplus } #endif
ヘッダーファイル
それはすべて実装であり、現在はヘッダーファイルです。
メインモジュールの動的ライブラリをロードするモジュールのインターフェイスは次のとおりです。
loader.h
#ifndef LOADER_H #define LOADER_H #ifdef __cplusplus extern "C" { #endif extern void loadRun(const char * const s); #ifdef __cplusplus } #endif #endif
ロードする動的ライブラリのブートローダーインターフェイス(動的ライブラリがメインプログラムで使用できる関数のリスト)は次のとおりです。
functions.h
#ifndef FUNCTIONS_H #define FUNCTIONS_H #ifdef __cplusplus extern "C" { #endif extern void printString(const char * const s); #ifdef __cplusplus } #endif #endif
ご覧のとおり、ここでは例としてprintString関数を1つだけ示します。
ローダーのコンパイル
非分散コンパイルの例(Windowsの場合、コンパイラオプションに-DWIN32を追加するだけです):
$ gcc -Wall -c main.c $ gcc -Wall -fPIC -c loader.c $ gcc -shared -o libloader.so loader.o -ldl $ gcc main.o -ldl -o run -L. -lloader -Wl,-rpath,.
ディストリビューションコンパイルは、ディストリビューションの場合、動的ライブラリが/ usr / libで検索され、lib $(NAME).so。$(VERSION)という形式である点で異なります。非ディストリビューションコンパイルの場合、lib $(NAME).soと呼ばれます。プログラムの起動ディレクトリで検索されます。
コンパイル後に何が起こったのか見てみましょう:
$ nm run | tail -n 2 U loadRun 08048504 T main $ nm libloader.so| tail -n 4 000005da T loadRun 000005ac T printString U printf@@GLIBC_2.0 U puts@@GLIBC_2.0
ここでは、Uとしてマークされた関数が外部動的ライブラリで検索され、Tとしてマークされた関数がモジュールによって提供されていることがわかります。 これは、バイナリプログラムインターフェイス(ABI)です。
動的ライブラリ
それでは、動的ライブラリ自体の説明に取りかかりましょう。
Cライブラリ
簡単なCライブラリの例を次に示します。
lib.c
#include "functions.h" #ifdef __cplusplus extern "C" { #endif void run(void) { printString("Hello, world!"); } #ifdef __cplusplus } #endif
ここではどこでも、g ++などのC ++コンパイラを使用してプログラムをコンパイルできるように、 外部「C」{}環境が必要です。 単純にC ++では、同じ名前の関数を宣言できますが、それぞれ異なるシグネチャで、この場合、いわゆる関数名の装飾が使用されます。つまり、関数のシグネチャはABIに記録されます。 extern "C" {}環境は、この装飾が使用されないようにするために必要です(特に、この装飾は使用されるコンパイラに依存するため)。
編集
$ gcc -Wall -fPIC -c lib.c $ gcc -shared -o lib.so lib.o
ABI:
$ nm lib.so | tail -n 2 U printString 0000043c T run
打ち上げ:
$ ./run lib.so String from library: Hello, world!
モジュールのextern“ C” {}環境を削除し、gccではなくg ++でコンパイルすると、次のように表示されます。
$ nm lib.so | grep run 0000045c T _Z3runv
つまり、予想どおり、ライブラリのABIが変更されたため、ブートローダーはこのライブラリのrun関数を見ることができなくなります。
$ ./run lib.so cannot load function run
パスカルライブラリ
上記で見たように、ローダーがC ++コンパイラーによって作成された動的ライブラリーの関数を見るためには、C / C ++がコンパイラーおよび関連言語であるという事実にもかかわらず、 extern "C" {}挿入でコードを補完する必要がありました。 完全に異なる言語-PascalのFreePascalコンパイラについて何が言えるでしょうか? 当然、ここでも、追加のジェスチャーなしではできません。
最初に、動的ライブラリ用にCでエクスポートされた関数の使用方法を学習する必要があります。 同様のC / C ++ Pascalヘッダーファイルの例を次に示します。
func.pas
unit func; interface procedure printString(const s:string); stdcall; external name 'printString'; implementation end.
Pascalモジュール自体の例を次に示します。
modul.pas
library modul; uses func; procedure run; stdcall; begin printString('Hello from module!'); end; exports run; begin end.
編集
$ fpc -Cg modul.pas Free Pascal 2.5.1 [2011/02/21] i386 Copyright (c) 1993-2010 by Florian Klaempfl : Linux for i386 modul.pas libmodul.so /usr/bin/ld: warning: link.res contains output sections; did you forget -T? /usr/bin/ld: warning: creating a DT_TEXTREL in a shared object. 13 p, 6.6
結果のライブラリのABIを確認します。
$ nm libmodul.so U printString 000050c0 T run
ご覧のとおり、余計なものはありませんが、コンパイル時のld警告は警告です。 Googleでは、警告の考えられる理由を見つけました、これはPICなしのコンパイル(位置独立コード-コードは物理アドレスに関連付けられていない)によるものですが、man fpcでは、-Cgオプションがそれ自体で奇妙なPICコードを生成する必要があることがわかりますfpcが約束を守らないか、何か間違ったことをしています。
ここで、ヘッダーファイルの「printString」という名前の部分を削除して、コンパイラーが生成するものを確認してみましょう。
$ nm libmodul.so U FUNC_PRINTSTRING$SHORTSTRING 000050d0 T run
ご覧のとおり、FreePascalのシーナリーはg ++のシーナリーとはまったく異なる種類であり、存在しています。
このモジュールから始めると、次のものが得られます。
$ ./run libmodul.so ./run: symbol lookup error: ./libmodul.so: undefined symbol: FUNC_PRINTSTRING$SHORTSTRING
そして、適切なモジュールを使用すると、次のようになります。
$ ./run libmodul.so String from library: Hello from module!
それですべてです。目標を達成しました-さまざまな言語で記述された動的ライブラリの使用と、コードはLinuxとWindowsの両方で動作します(Mac自体は動作しないため、どのように動作するのか見ていません)。 さらに、ロードされたライブラリには、メインプログラムで提供される機能を使用して対話する機能があります(つまり、メインプログラムのプラグインです)。
ローダー自体は別のプロセスで実行できるため、これらのプラグインはメインプログラムで特別な処理を実行できません。 したがって、メインプログラムは、他の便利なプログラミング言語(たとえば、Javaまたは同じC ++)で記述できます。