web-dev-qa-db-ja.com

Cでのモジュールシステムの作成(動的ロード)

実行時にコンパイルされたCコードをロードし、その中の関数を呼び出すにはどうすればよいでしょうか。単にexec()を呼び出すのとは異なります。

編集:モジュールをロードするプログラムはCです。

38
Nikron

Linux/UNIXでは、POSIX dlopen/dlsym/dlerror/dlclose関数を使用して、共有ライブラリを動的に開き、シンボル(関数を含む)にアクセスできます。詳細については、 manページ を参照してください。

34
Robert Gamble

dlopenは行く方法です。次にいくつかの例を示します。

Dlopenでプラグインをロードする:

#include <dlfcn.h>
...
int
main (const int argc, const char *argv[])
{

  char *plugin_name;
  char file_name[80];
  void *plugin;
  ...
  plugin = dlopen(file_name, RTLD_NOW);
  if (!plugin)
  {
     fatal("Cannot load %s: %s", plugin_name, dlerror ());
  }

上記のコンパイル:

% cc  -ldl -o program program.o 

次に、プラグイン用にこのAPIを想定します。

/* The functions we will find in the plugin */
typedef void (*init_f) ();
init_f init;
typedef int (*query_f) ();
query_f query;

プラグインでinit()のアドレスを見つける:

init = dlsym(plugin, "init");
result = dlerror();
if (result)
{
   fatal("Cannot find init in %s: %s", plugin_name, result);
}
init();

他の関数であるquery()は、値を返します。

query = dlsym (plugin, "query");
result = dlerror();
if (result)
{
    fatal("Cannot find query in %s: %s", plugin_name, result);
}
printf("Result of plugin %s is %d\n", plugin_name, query ());

完全な例を取得できます オンライン

43
bortzmeyer

この質問への回答を参照してくださいが、このトピックに関心のある他の人は、古いプラグインベースのアプリケーションからのクロスプラットフォームの例を高く評価するかもしれません。この例はwin32またはlinuxで機能し、ファイル引数で指定された動的にロードされた.soまたは.dllで「コンストラクター」と呼ばれる関数を検索して呼び出します。例はc ++ですが、手順はcでも同じである必要があります。

//firstly the includes
#if !defined WIN32
   #include <dlfcn.h>
   #include <sys/types.h>
#else
   #include <windows.h>
#endif

//define the plugin's constructor function type named PConst
typedef tcnplugin* (*PConst)(tcnplugin*,tcnplugin*,HANDLE);

//loads a single specified tcnplugin,allmychildren[0] = null plugin
int tcnplugin::loadplugin(char *file) {
    tcnplugin *hpi;
#if defined WIN32               //Load library windows style
    HINSTANCE hplugin=LoadLibrary(file);
    if (hplugin != NULL) {
            PConst pinconstruct = (PConst)GetProcAddress(hplugin,"construct");
#else                                   //Load it nix style
    void * hplugin=dlopen(file,RTLD_NOW);
    if (hplugin != NULL) {
            PConst pinconstruct = (PConst)dlsym(hplugin,"construct");
#endif   
            if (pinconstruct != NULL) { //Try to call constructor function in dynamically loaded file, which returns a pointer to an instance of the plugin's class
                    hpi = pinconstruct(this, this, hstdout);
            } else {
                    piprintf("Cannot find constructor export in plugin!\n");
                    return 0;
            }
    } else {
            piprintf("Cannot open plugin!\n");
#if !defined WIN32
            perror(dlerror());
#endif
            return 0;
    }
    return addchild(hpi); //add pointer to plugin's class to our list of plugins
}

また、呼び出したい関数のモジュールがc ++で記述されている場合は、次のようなextern "C"を使用して関数を宣言する必要があることにも言及しておく必要があります。

extern "C" pcparport * construct(tcnplugin *tcnptr,tcnplugin *parent) {
    return new pcparport(tcnptr,parent,"PCPARPORT",0,1);
}
8
erisu

gNU/Linuxユーザー向け

動的ロードライブラリは、それを利用してプログラムを実行し、実行時に使用する関数を決定できるメカニズムです。場合によっては、static変数も可能だと思います。

まず、_man 3 dlopen_または オンラインで表示 を確認することから始めます。

必要なヘッダーファイルは次のとおりです。dlfcnこれは標準の一部ではないため、はオブジェクトファイルにそれを好きにする必要がありますこのライブラリ:libdl.(so/a)したがって、次のようなものが必要です。

_gcc yours.c -ldl
_

次に、ファイル名_a.out_があり、それを実行できます[〜#〜]しかし[〜#〜]正しく機能しませんその理由を説明します。


完全な例:

最初のクレート2ファイルはそれぞれ_func1.c_と_func2.c_です。これらの関数を実行時に呼び出したいと思います。

func.c

_int func1(){
    return 1;
}
_

func2.c

_const char* func2(){
    return "upgrading to version 2";
}
_

これで2つの関数ができました。モジュールを作成しましょう。

_ALP ❱ gcc -c -fPIC func1.c
ALP ❱ gcc -c -fPIC func2.c
ALP ❱ gcc -o libfunc.so -shared -fPIC func1.o func2.o 
_

_-fPIC_ => [〜#〜] pic [〜#〜] について心に問い合わせてください

これで、_dynamic library_の名前ができました:_libfunc.so_

これらの関数を使用したいメインプログラム(= _temp.c_)を作成しましょう。

ヘッダーファイル

_#include <stdio.h>
#include <stdlib.h>
#include <dlfcn.h> 
_

およびメインプログラム

_int main()
{
    // pointer function to func1 and func2
    int         ( *f1ptr )();
    const char* ( *f2ptr )();

    // for pointing to the library
    void* handle = NULL;

    // for saving the error messages
    const char* error_message = NULL;

    // on error dlopen returns NULL
    handle = dlopen( "libfunc.so", RTLD_LAZY );

    // check for error, if it is NULL
    if( !handle )
    {
        fprintf( stderr, "dlopen() %s\n", dlerror() );
        exit( 1 );
    }

    /*
        according to the header file:

        When any of the above functions fails, call this function
        to return a string describing the error.  Each call resets
        the error string so that a following call returns null.

        extern char *dlerror (void) __THROW;
    */

    // So, reset the error string, of course we no need to do it just for sure
    dlerror();

    // point to func1
    f1ptr = (int (*)()) dlsym( handle, "func1" );

    // store the error message to error_message
    // because it is reseted if we use it directly
    error_message = dlerror();
    if( error_message ) //   it means if it is not null
    {
        fprintf( stderr, "dlsym() for func1 %s\n", error_message );
        dlclose( handle );
        exit( 1 );
    }

    // point the func2
    f2ptr = (const char* (*)()) dlsym( handle, "func2" );

    // store the error message to error_message
    // because it is reseted if we use it directly
    error_message = dlerror();
    if( error_message ) //   it means if it is not null
    {
        fprintf( stderr, "dlsym() for func2 %s\n", error_message );
        dlclose( handle );
        exit( 1 );
    }

    printf( "func1: %d\n", ( *f1ptr )() );
    printf( "func2: %s\n", ( *f2ptr )() );

    // unload the library
    dlclose( handle );

    // the main return value
    return 0;
}
_

ここで、このコード(= _temp.c_)をコンパイルする必要があるため、次のことを試してください。

_ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or directory
_

それは動作しません! [〜#〜]なぜ[〜#〜]簡単; _a.out_プログラムは、関連するライブラリ_libfunc.so_の場所を認識していないため、_cannot not open ..._を通知します。

プログラム(= _a.out_)にライブラリを見つけるように指示する方法は?

  1. ldリンカーを使用
  2. 環境変数の使用_LD_LIBRARY_PATH_
  3. 標準パスを使用

最初の方法、ldの助けを借りて

_-Wl,-rpath,_とpwdを使用し、パスを引数として指定します

_ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or directory
ALP ❱ pwd
/home/shu/codeblock/ALP
ALP ❱ gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
_

2番目の方法

_ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or direc
ALP ❱ export LD_LIBRARY_PATH=$PWD
ALP ❱ echo $LD_LIBRARY_PATH
/home/shu/codeblock/ALP
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
ALP ❱ export LD_LIBRARY_PATH=
ALP ❱ ./a.out
libfunc.so: cannot open shared object file: No such file or 
_

そして第三の道

現在のパスに_libfunc.so_があるので、ライブラリの標準パスにコピーできます。

_ALP $ Sudo cp libfunc.so /usr/lib
ALP ❱ gcc temp.c -ldl
ALP ❱ ./a.out
func1: 1
func2: upgrading to version 2
_

_/usr/lib_から削除して使用できます。それはあなた次第です。

[〜#〜]注[〜#〜]

私たちの_a.out_がそのパスを知っていることを知る方法は?
簡単:

_ALP ❱ gcc temp.c -ldl -Wl,-rpath,/home/shu/codeblock/ALP
ALP ❱ strings a.out  | grep \/
/lib/ld-linux.so.2
/home/shu/codeblock/ALP
_

c ++ でどのように使用できますか?
_g++_は関数名をマングルしますが、gccはマングルしないため、私が知っている限り、次のように使用する必要があります:extern "C" int func1();

詳細については、manページとLinuxプログラミングブックを参照してください。

4
Shakiba Moshiri

また、 cpluff を見ることができます。これは、純粋なcのプラグイン管理ライブラリです。

3
lazyden

フレームワークを検討する場合、QtはQPluginLoaderを提供します: Qt 5ドキュメント (または古いQt 4.8ドキュメントについては ここ を参照)

よりきめ細かい制御が必要/必要な場合、QtはQLibraryを使用してライブラリをオンザフライでロードする手段も提供します: Qt 5ドキュメント (または古いQt 4.8ドキュメントについては ここ を参照) )

さらに良いことに、これらはプラットフォーム間で移植可能です。

2
Andrew Ross

Perlのような動的言語は常にこれを行います。 PerlインタープリターはCで記述されており、多くのPerlモジュールは部分的にCで記述されています。これらのモジュールが必要な場合、コンパイルされたCコンポーネントはその場で動的にロードされます。別の回答で述べたように、これらのモジュールを格納するメカニズムは、WindowsではDLLであり、UNIXでは共有ライブラリ(.soファイル)です。 UNIXで共有ライブラリをロードするための呼び出しはdlopen()だと思います。 UNIXでこれを実現する方法については、その呼び出しのドキュメントから始めることで、おそらくポインターを見つけることができます。 Windowsの場合、DLLを調べて、実行時に動的にロードする方法を学ぶ必要があります。 [または、Cygwin UNIXエミュレーションレイヤーを通過することもできます。これにより、WindowsでもUNIXと同じ呼び出しを使用できるようになりますが、Cygwinを既に使用してコンパイルしている場合を除いて、お勧めしません。]

これは、共有ライブラリに対して単にリンクすることとは異なることに注意してください。呼び出すコードが事前に正確にわかっている場合は、共有ライブラリに対してビルドでき、ビルドはそのライブラリに「動的にリンク」されます。特別な処理をしなくても、ライブラリのルーチンは、プログラムが実際に呼び出した場合にのみメモリにロードされます。しかし、任意の任意オブジェクトコード、ビルド時に現在識別できないコードをロードできるものを作成することを計画している場合は、それを行うことはできませんが、代わりに選択されるのを待っていますどういうわけか実行時に。そのためには、dlopen()とそのWindowsのいとこを使用する必要があります。

Perlまたは他の動的言語がこれを行う方法を見て、いくつかの実際の例を見ることができます。この種の動的ロードを担当するPerlライブラリはDynaLoaderです。 PerlとCの両方のコンポーネントがあると思います。 Pythonのような他の動的言語には、あなたがむしろ見るかもしれない似たようなものがあると確信しています;そしてリリースされていないPerl 6の仮想マシンであるParrotは、確かにこれを行うためのメカニズムを持っています(または将来的に)。

さらに言えば、JavaはJNI(Java Native Interface)インターフェースを介してこれを実現するため、OpenJDKのソースコードを見てJava UNIXとWindowsの両方でこれを実現します。

1
skiphoppy

DIYのアプローチがあります。これを行う方法(および可能性)はシステムによって異なりますが、一般的な考え方は、ファイルを開き、ファイルの内容をメモリに読み込み、そのメモリを実行可能にし、このメモリ内の有効な位置への関数ポインタを初期化することです。 、そしてあなたはそこにいます。

もちろん、これは実行可能コードであると想定しています-ほとんどありません。コードはおそらくデータをRAMにもロードする必要があり、グローバル/静的変数用のスペースが必要になる場合があります。これをすべて自分でロードすることもできますが、実行可能コードに移動してその中のすべてのメモリ参照を調整します。

ほとんどのオペレーティングシステムでは、動的リンクが可能です。これにより、これらすべてが実行されます。

0
Artelius

Windowsでは、これが私のやり方です。

  • コードを生成します(コンパイラーを見つけるのが簡単で、ライブラリーの要件が最小限であるため、Cで)
  • ジョブを生成して、DLLにコンパイル/リンクします
  • loadLibraryでロードします
  • getProcAddressで関数ポインタを取得する

生成/コンパイル/リンクの手順は、通常1秒未満で完了します。

0
Mike Dunlavey