1

gcc/g++ と任意の委員会によって指定された C API を使用していたとします。この仕様は機能を定義します

void foo(void);

現在、この仕様に従っていくつかの実装があります。サンプルとして 2 つを選び、and と呼びましょう(libnfoonfooxfoolibxfoo によってそれぞれ静的ライブラリと動的ライブラリとして提供されます)。

foo次に、 -API用の C++ フレームワークを作成します。したがって、抽象クラスを指定します

class Foo
{
  public:
    virtual void foo(void) = 0;
};

および対応する実装

#include <nfoo.h>
#include "Foo.h"

class NFoo : public Foo
{
  public:
    virtual void foo(void) 
    {
      ::foo(); // calling foo from the nfoo C-API
    }
};

としても

#include <xfoo.h>
#include "Foo.h"

class XFoo : public Foo
{
  public:
    virtual void foo(void) 
    {
      ::foo(); // calling foo from the xfoo C-API
    }
};

今、私たちは問題に直面しています: 1 つのライブラリにすべてを作成 (リンク) するにはどうすればよいでしょうか?

fooC API 実装の関数シンボルとのシンボルの衝突が発生することがわかります。

すでに C++ ラッパーの実装を個別の静的ライブラリに分割しようとしましたが、静的ライブラリはリンクされていないオブジェクト ファイルの単なるコレクションであることに (再び) 気づきました。したがって、C ライブラリをラッパーに完全にリンクし、それらのシンボルを削除/非表示にする方法がない限り、これはまったく機能しません。

提案は大歓迎です。

更新:最適なソリューションは、両方の実装を同時にサポートする必要があります。

注: このコードは、機能することを意図したものではありません。疑似コードとして認識してください。

4

5 に答える 5

2

実行時にdlopen / dlsymを使用して foo 呼び出しを解決できますか。

リンクからのサンプルコードのようなもの(コンパイルできない場合があります):

void    *handle,*handle2;
void (*fnfoo)() = null;
void (*fxfoo)() = null;


/* open the needed object */
handle = dlopen("/usr/home/me/libnfoo.so", RTLD_LOCAL | RTLD_LAZY);
handle2 = dlopen("/usr/home/me/libxfoo.so", RTLD_LOCAL | RTLD_LAZY);

fnfoo = dlsym(handle, "foo");
fxfoo = dlsym(handle, "foo");


/* invoke function */
(*fnfoo)(); 
(*fxfoo)();

// dlclose() を忘れないでください

そうしないと、ライブラリ内のシンボルを変更する必要があります。これは Windows に移植できません。

于 2013-03-28T18:32:25.483 に答える
1

まず最初に、C API を C++ コードでラップする場合は、その依存関係をコンパイル ファイアウォールの背後に隠す必要があります。これは、(1) C API からの名前でグローバル名前空間を汚染することを回避し、(2) サードパーティ ヘッダーへの依存関係からユーザー コードを解放するためです。この例では、C API への依存関係を分離するために、かなり簡単な変更を行うことができます。これを行う必要があります:

// In NFoo.h:

#include "Foo.h"

class NFoo : public Foo
{
  public:
    virtual void foo(void);
};

// In NFoo.cpp:

#include "NFoo.h"

#include <nfoo.h>

void NFoo::foo(void) {
  ::foo();   // calling foo from the nfoo C-API
}

上記のポイントは、C API ヘッダー<nfoo.h>がヘッダー ファイルではなく cpp ファイルにのみ含まれていることです。これは、ライブラリを使用するコードをコンパイルするために、ユーザーコードが C API ヘッダーを提供する必要がないことを意味します。また、C API からのグローバル名前空間名が、コンパイルされている他のものと衝突するリスクもありません。また、C API (またはその他の外部依存関係) で API を使用するときに多数のもの (ハンドル、オブジェクトなど) を作成する必要がある場合は、それらを PImpl (へのポインター) でラップすることもできます。 cpp ファイルでのみ宣言定義されている前方宣言された実装クラス) を使用して、外部依存関係 (つまり、「コンパイル ファイアウォール」) の同じ分離を実現します。

さて、基本的なことは終わったので、当面の問題に移ることができます。名前が衝突するシンボルを使用して 2 つの C API に同時にリンクすることです。これは問題であり、簡単な方法はありません。上記のコンパイル ファイアウォール手法は、コンパイル中に依存関係を分離して最小化することに関するものであり、それによって、競合する名前を持つ 2 つの API に依存するコードを簡単にコンパイルできます (これは、お使いのバージョンでは当てはまりません)。リンク フェーズに到達すると、ODR (One Definition Rule) エラーが発生します。

このスレッドには、C API 名の競合を解決するための便利なトリックがいくつかあります。要約すると、次の選択肢があります。

  • 2 つの C API の少なくとも 1 つの静的ライブラリ (またはオブジェクト ファイル) にアクセスできる場合は、objcopy(Unix/Linux で) のようなユーティリティを使用して、その静的ライブラリ (オブジェクト ファイル) 内のすべてのシンボルにプレフィックスを追加できます。 )、たとえば、objcopy --prefix-symbols=libn_ libn.olibn.o 内のすべてのシンボルの前にlibn_. もちろん、これは、API のヘッダー ファイルの宣言に同じプレフィックスを追加する必要があることを意味します (または、必要なものだけを含む縮小バージョンを作成する必要があります)。その外部依存関係に対して適切なコンパイルファイアウォールが配置されているためです。

  • 静的ライブラリ (またはオブジェクト ファイル) にアクセスできない場合、または上記の (やや面倒な) 方法を実行したくない場合は、動的ライブラリを使用する必要があります。ただし、これは思ったほど些細なことではありません ( DLL Hellの話題には立ち入りません)。)。通常の静的ロードとは対照的に、ダイナミック リンク ライブラリ (または共有オブジェクト ファイル) の動的ロードを使用する必要があります。つまり、LoadLibrary / GetProcAddress / FreeLibrary (Windows 用) および dlopen / dlsym / dlclose (すべての Unix 系 OS) を使用する必要があります。つまり、使用する関数ごとに関数ポインタ アドレスを個別にロードして設定する必要があります。繰り返しますが、依存関係がコード内で適切に分離されている場合、これは、この反復コードをすべて記述するだけの問題になりますが、ここではそれほど危険ではありません。

  • C API の使用が C API 自体よりもはるかに単純である場合 (つまり、数百の関数のうち数個の関数のみを使用する場合)、C API ごとに 1 つずつ、2 つの動的ライブラリを作成する方がはるかに簡単な場合があります。 、関数の限定されたサブセットのみをエクスポートし、それらに一意の名前を付けて、C API への呼び出しをラップします。次に、メイン アプリケーションまたはライブラリを、これら 2 つの動的ライブラリに直接 (静的にロードして) リンクすることができます。もちろん、その C API のすべての関数に対してそれを行う必要がある場合は、このすべての問題を経験しても意味がありません。

したがって、より合理的または実現可能と思われるものを選択できますが、これを修正するにはかなりの手作業が必要になることは間違いありません。

于 2013-03-28T17:12:24.690 に答える
0

リンカーは、同じシンボル名の 2 つの異なる定義を区別できないため、同じ名前の 2 つの関数を使用しようとしている場合は、何らかの方法でそれらを分離する必要があります。

それらを分離する方法は、それらを動的ライブラリーに入れることです。動的ライブラリからエクスポートするものを選択できるため、基礎となる API 関数を隠したままラッパーをエクスポートできます。実行時に動的ライブラリをロードし、一度に 1 つずつシンボルにバインドすることもできるため、同じ名前が複数で定義されていても、互いに干渉しません。

于 2013-03-28T15:30:07.520 に答える
0

一度に 1 つのライブラリ実装にのみアクセスしたい場合は、動的ライブラリを使用するのが自然な方法です。

Windows では、一度に 2 つ以上のライブラリ実装にアクセスする場合にも機能します。これは、Windows 動的ライブラリが内部のものを完全にカプセル化するためです。

于 2013-03-28T14:37:53.320 に答える
0

IIUCifdefはあなたが必要とするものです

#define _NFOO nfoo lib と xfoo lib に入れ#define XFOOます。

また、nfoo lib と xfoo lib の両方に Foo という関数がある場合、コンパイル中にエラーが発生することも覚えておいてください。これを回避するために、GCC/G++ は名前マングリングによる関数のオーバーロードを使用します。

その後、ifdefs を使用して xfoo がリンクされているかどうかを確認できます

#ifdef XFOO
//call xfoo's foo()
#endif
于 2013-03-28T14:38:35.767 に答える