C ライブラリの C++ ラッパー
-
01-10-2019 - |
質問
最近、C++ プロジェクトで使用したい C ライブラリを見つけました。このコードは次のように構成されています グローバル変数 そしてその出力を が指すメモリに書き込みます 静的ポインタ。プロジェクトを実行するときに、C プログラムの 2 つのインスタンスを実行したいと考えています。1 つは構成 A で、もう 1 つは構成 B です。プログラムを 2 回実行するわけにはいかないので、2 つの選択肢があると思います。
- 作る C++ ラッパー:ここでの問題は、ラッパークラスには C ライブラリが持つすべてのグローバル/静的変数を含める必要があることです。C ライブラリの関数はこれらの変数を使用するため、それらの関数用に非常に大きな引数リストを作成する必要があります。
- コピーペースト C ライブラリ:ここでは、C ライブラリ内のすべての関数とすべての変数の名前を調整する必要があります。
最も早い解決策はどれですか?同じ C ソースの 2 つのインスタンスを実行する他の可能性はありますか?
ありがとう、
マックス
解決
c ++ - wrapper
「ライブラリ全体」(わずかに改造されているだけで、クラスに貼り付けることで、簡単に逃げます。
// C
static char resultBuffer[42];
void ToResult(int x) { ... }
char const * GetResult() { return resultBuffer; }
なります
// C++
class CMyImportantCLib
{
private:
char resultBuffer[42];
void ToResult(int x) { ... } // likely, no code changes at all
char const * GetResult() { return resultBuffer; }
} ;
ほとんどの場合、宣言的な変更があります(静的宣言や外部宣言を「殺す」など)。ただし、メソッド内の静的変数を追い詰めて、それらをメンバーに変える必要があります
個別の名前空間
それは醜い解決策ですが、あなたにとって十分かもしれません:
// impMyLib.h
namespace A
{
#include "c-lib.h"
}
namespace B
{
#include "c-lib.h"
}
// impMyLib.cpp
namespace A
{
#include "c-lib.c"
}
namespace B
{
#include "c-lib.c"
}
運が良ければ、オプティマイザー/リンカーは同一のコードの折りたたみに成功します。ただし、タイプ A::
と B::
無関係です。
他のヒント
2回実行する余裕がない場合、3回はどうですか? Cプログラムの2つの個別のインスタンスを起動する小さなフロントエンドプロセスを作成することができます。使用法の観点からは、1回だけ走っているが、舞台裏で2人の子供を持つ親プロセスがあると、単一の.exeのように見えます。そのアプローチが実際のニーズに合うかどうかはわかりませんが、他の2つのオプションのいずれよりもほぼ確実に速くなります。
iiuc、あなたが持っているのは、基本的に、これ:
extern int a;
extern int b;
void f();
void g();
どこ a
と b
の動作を変更します f()
と g()
. 。あれは正しいですか?
これを持っていて、これをC ++で包みたい場合は、できることは次のとおりです。
class the_library {
public:
the_library(int a, int b) : a_(a), b_(b) {}
void f() {a=a_; b=b_; ::f();}
void g() {a=a_; b=b_; ::g();}
private:
int a_;
int b_;
};
代わりに持っているものによって異なります a
と b
, 、これはひどく効率的ではないかもしれません。
もちろん、Rakiがコメントで言ったように、これはグローバル変数を使用しているため、まったく安全ではありません。
私はここでアイデアが好きです。しかし、変更する必要があるすべての変数のポインターを作成する必要があります。これが例です:
lib.H:
void f();
int g();
lib.C:
#include "lib.h"
extern int a;
extern int * output;
void f(){
*output=(*output+6)*a;
}
int g(){
return *output;
}
object.cc:
#include "lib.h"
#include <iostream>
using namespace std;
int a;
int * output;
class the_library {
public:
the_library(int a, int * output) : a_(a), output_(output) {}
void f() {a=a_; output=output_; ::f();}
int g() {a=a_; output=output_; ::g();}
private:
int a_;
int * output_;
};
int main(){
int out1=2;
the_library icache(3,&out1);
icache.f();
cout<<"icache.f() -> icache is "<<icache.g()<<endl;
icache.f();
cout<<"icache.f() -> icache is "<<icache.g()<<endl;
int out2;
out2=8;
the_library dcache(7,&out2);
dcache.f();
cout<<"dcache.f()\t-> icache is "<<icache.g()<<endl;
cout<<"\t\t-> dcache is "<<dcache.g()<<endl;
return 0;
}
もしかしたら、私には何か抜け落ちているものがあるのかもしれませんが…。
...グローバル変数はプロセスではなくスレッド間で共有されます...
これは、あなたの場合、同じ C プログラムの 2 つのプロセスを動作させることができ、プロセス共有メモリを使用して動作しない限り、一方が他方に干渉しないことを意味します。
...同じプロセスで C コードの 2 つのインスタンスを実行する必要がある場合...
それならあなたはめちゃくちゃです。
TLS、もしかして?
これらを別のスレッドで起動し、グローバル変数をスレッド ローカル ストレージ変数として宣言することもできます。たとえば、Visual C++ では次のコードになります。
int myGlobalVariable = 42 ; // Global variable
__declspec(thread) int myTLSVariable = 42 ; // Thread local variable
各スレッドには独自のバージョンの変数があります。こうすることで、スレッドの最後にコンテンツを別の場所にコピーできます。
コードを書き換えています...
それに C++ レイヤーを追加する必要はありません。C コードを保持し、すべてのグローバル変数を struct で宣言できます。
/* C global variable */
int iMyGlobalVariable = 42 ;
const char * strMyGlobalString = NULL ;
short iMyShortData = 7 ;
/* C struct */
typedef struct MyStruct
{
int iMyGlobalVariable ;
const char * strMyGlobalString ;
short iMyShortData ;
}
MyStruct ;
次に、この構造体へのポインターを最初のパラメーターとして受け入れるように関数のプロトタイプを変更し、グローバル変数を変更する代わりに、構造体のメンバーを変更します。
/* old function */
int foo(char *p)
{
/* fudge with the global variables */
iMyShortData = 55 ;
/* etc. */
fooAgain("Hello World", 42) ;
}
それは次のようになります:
/* new function */
int foo(MyStruct * s, char *p)
{
/* fudge with the struct variables */
s->iMyShortData = 55 ;
/* etc. */
fooAgain(s, "Hello World", 42) ;
}
次に、メインでは、最初の関数を呼び出す代わりに、右の構造体へのポインターを指定して関数を呼び出します。の代わりに :
int main(int argc, char * argv[])
{
bar(42, 55) ;
}
あなたが書く :
int main(int argc, char * argv[])
{
MyStruct A = { /* initialize A's members if needed */ } ;
MyStruct B = { /* initialize B's members if needed */ } ;
bar(&A, 42, 55) ;
bar(&B, 42, 55) ;
return 0 ;
}
上の例では、2 つが順番に呼び出されますが、代わりにスレッドを起動することもできます。
グローバル状態を保存しますか?
コードがシングルスレッドの場合、グローバル状態を保存/リセットすることで、最初のインスタンスの呼び出しと 2 番目のインスタンスの呼び出しをインターリーブできます。上記と同じ構造体を使用してみましょう。
/* C global variable */
int iMyGlobalVariable = 42 ;
short iMyShortData = 7 ;
void saveState(MyStruct * s)
{
s->iMyGlobalVariable = iMyGlobalVariable ;
s->iMyShortData = iMyShortData ;
}
void resetState(const MyStruct * s)
{
iMyGlobalVariable = s->iMyGlobalVariable ;
iMyShortData = s->iMyShortData ;
}
次に、必要に応じて保存関数とリセット関数を呼び出します。
int main(int argc, char * argv[])
{
MyStruct A = { /* initialize A's members if needed */ } ;
MyStruct B = { /* initialize B's members if needed */ } ;
resetState(&A) ; /* now, we work on A */
bar(42, 55) ;
saveState(&A) ; /* we save the progress on A */
resetState(&B) ; /* now, we work on B */
bar(42, 55) ;
saveState(&B) ; /* we save the progress on B */
resetState(&A) ; /* now, we work on A */
foo("Hello World", 3.14159) ;
saveState(&A) ; /* we save the progress on A */
resetState(&B) ; /* now, we work on B */
foo("Hello World", 3.14159) ;
saveState(&B) ; /* we save the progress on B */
/* etc. */
return 0 ;
}
これを C++ コードでラップして、resetState/saveState 関数を自動的にラップすることができます。例えば :
struct MyWrapper
{
void foo(const char * p, double d)
{
resetState(&m_s) ;
foo(p, d) ;
saveState(&m_s) ;
}
void bar(int i, short i2)
{
resetState(&m_s) ;
bar(i, i2) ;
saveState(&m_s) ;
}
MyStruct m_s ;
} ;
これにより、メインを次のように書き直すことができます。
int main(int argc, char * argv[])
{
MyWrapper A ;
MyWrapper B ;
A.bar(42, 55) ;
B.bar(42, 55) ;
A.foo("Hello World", 3.14159) ;
B.foo("Hello World", 3.14159) ;
// etc.
return 0 ;
}
C バージョンよりもはるかに優れているようです。それでも、MyWrapper はスレッドセーフではありません...
結論
最初の解決策 (TLS) は簡単で汚い解決策ですが、2 番目の解決策はコードをリファクタリングして正しく記述することです (グローバル変数が嫌われるのには十分な理由があり、どうやら、そのうちの 1 つに遭遇したようです)。 3 つ目は、2 つの呼び出しをインターリーブできるようにする「ハック」です。
3 つのソリューションのうち、必要な場合にこのコードを堅牢でスレッドセーフな C++ クラス内にラップすることを容易にするのは 2 つ目のソリューションだけです。