c ++包装器的C库
-
01-10-2019 - |
题
最近,我发现了一个C ++项目中要使用的C库。此代码配置 全局变量 并将其输出输出到指向的内存 静态指针。当我执行项目时,我希望C程序的两个实例运行:一个带有配置A的配置A和配置B。我负担不起两次运行程序,因此我认为有2个选项:
- 做一个 C ++包装器: :这里的问题是包装级应包含C库具有的所有全局/静态变量。由于C库中的功能使用这些变量,因此我将不得不为这些函数创建非常大的参数列表。
- 复制粘贴 C库:在这里,我必须调整C库中每个功能和每个变量的名称。
哪一个是最快的解决方案?是否还有其他可能运行同一C源的实例的可能性?
谢谢,
最大限度
解决方案
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::
是无关的。
其他提示
如果您负担不起两次,大约3次?您可以想象,您可以编写一个微小的前端过程,该过程启动了C程序的两个单独实例。从用法的角度来看,它仍然看起来像一个单一的.exe,您只运行一次,但在幕后您将与两个孩子一起父母流程。我不知道这种方法是否适合您的实际需求,但几乎可以肯定比其他两个选择都快。
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程序的过程,除非它们以某种方式使用流程共享的内存,否则它们不会干扰另一个程序。
...如果您需要在同一过程中运行的C代码的两个实例...
然后你被搞砸了。
TLS,也许?
要么您可以在单独的线程中启动它们,然后将全局变量声明为线程 - 本地存储变量。例如,在Visual C ++上,以下代码:
int myGlobalVariable = 42 ; // Global variable
__declspec(thread) int myTLSVariable = 42 ; // Thread local variable
每个线程将具有自己的变量版本。这样,在线程结束时,您可以在其他地方复制内容。
重写代码...
您无需在其中添加C ++层。您可以保留C代码,并在结构中声明所有全局变量:
/* 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 ;
}
在上面的示例中,两个被称为一个接一个地称为,但是您可以启动线程。
保存全球状态?
如果您的代码是单线程,则可以交流第一个实例的调用,并通过保存/重置全局状态来调用第二个实例。让我们使用上面的相同结构:
/* 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 ++代码包裹,以自动包装RESETESTATE/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 ;
} ;
您可以使重写主要内容AS:
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)是Quick'n'dirty解决方案,而第二个解决方案是重构代码正确编写(有很好的原因,全局变量都皱眉了,显然您偶然发现了其中一个),并且第三个是启用您的“黑客”,交错了两个呼叫。
在所有三个解决方案中,只有第二个可以使该代码在鲁棒,线程安全的C ++类中的包装中很容易,如果仍然需要。