C++ C 库的包装器

发布于 2024-08-30 01:10:50 字数 410 浏览 14 评论 0原文

最近我发现了一个我想在我的 C++ 项目中使用的 C 库。 该代码配置了全局变量,并将其输出写入静态指针指向的内存。 我希望运行 C 程序的 2 个实例:一个具有配置 A,一个具有配置 B。我不能运行我的程序两次,所以我认为有 2 个选项:

  • 当我执行我的项目时, >C++ 包装器:这里的问题是包装器类应该包含 C 库拥有的所有全局/静态变量。由于 C 库中的函数使用这些变量,我必须为这些函数创建非常大的参数列表。
  • 复制粘贴 C 库:这里我必须调整 C 库中每个函数和每个变量的名称。

哪一种是最快的解决方案? 是否有其他可能性来运行同一 C 源代码的 2 个实例?

谢谢,

麦克斯

Recently I found a C library that I want to use in my C++ project.
This code is configured with global variables and writes it's output to memory pointed by static pointers.
When I execute my project I would like 2 instances of the C program to run: one with configuration A and one with configuration B. I can't afford to run my program twice, so I think there are 2 options:

  • Make a C++ wrapper: The problem here is that the wrapper-class should contain all global/static variables the C library has. Since the functions in the C library use those variables I will have to create very big argument-lists for those functions.
  • Copy-paste the C library: Here I'll have to adapt the name of every function and every variable inside the C library.

Which one is the fastest solution?
Are there other possibilities to run 2 instances of the same C source?

Thanks,

Max

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

冬天旳寂寞 2024-09-06 01:10:50

C++ - 包装器
通过将“整个库”(仅稍作修改)粘贴到类中,您可以更轻松地完成任务。

// 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:: 中的类型不相关。

C++ -Wrapper
You get away easier by pasting "the entire library" - only slightly modfied - into a class.

// C
static char resultBuffer[42];
void ToResult(int x) { ... }
char const * GetResult() { return resultBuffer; }

becomes

// C++
class CMyImportantCLib
{
  private:
    char resultBuffer[42];
    void ToResult(int x) { ... } // likely, no code changes at all
    char const * GetResult() { return resultBuffer; }
} ;

There are mostly declarative changes (such as "killing" static and extern declarations). You would need to hunt down static variables inside the methods, though, and turn them into members as well

Separate Namespaces
That is an ugly solution, but might be enough for you:

// 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"
}

If you are lucky, the optimizer/linker succeeds in folding the identical code. However, types in A:: and B:: are unrelated.

一身骄傲 2024-09-06 01:10:50

如果你无法承担运行两次的费用,那么运行 3 次怎么样?您可以想象编写一个小型前端进程来启动 C 程序的两个单独实例。从使用的角度来看,它仍然看起来像一个仅运行一次的 .exe,但在幕后您将有一个带有两个子进程的父进程。我不知道这种方法是否适合您的实际需求,但它几乎肯定比其他两种选择更快。

If you can't afford to run it twice, how about 3 times? You could conceivably write a tiny front-end process that launches two separate instances of your C program. From the usage perspective it would still look like a single .exe that you run only one time but behind the scenes you'd have a parent process with two children. I have no idea if that approach would suit your actual needs but it'd almost certainly be faster than either of your other two options.

青巷忧颜 2024-09-06 01:10:50

IIUC,基本上,您所拥有的是:

extern int a;
extern int b;

void f();
void g(); 

其中 ab 修改 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_;

};

根据您所拥有的而不是 ab,这可能不是非常有效。

当然,正如 Raki 在评论中所说,由于这是使用全局变量,所以它根本不是线程安全的。

IIUC, what you have is, basically, this:

extern int a;
extern int b;

void f();
void g(); 

where a and b modify the behavior of f() and g(). Is that correct?

If you have this and you want to wrap this in C++, then what you could do is this:

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_;

};

Depending on what you have instead of a and b, this might not be terribly efficient.

Of course, as Raki said in the comments, since this is using global variables, it's not at all thread safe.

千里故人稀 2024-09-06 01:10:50

也许有一些事情让我困惑,但是

......全局变量在线程之间共享,而不是进程之间......

这意味着在你的情况下,你可以让同一个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 ;

然后修改函数的原型以接受指向该结构的指针作为第一个参数,然后修改该结构而不是修改全局变量member :

/* 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) ;
}

然后,在 main 中,您不调用第一个函数,而是通过给它指向正确结构的指针来调用它。而不是 :

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 ;
}

然后,在需要时调用 save 和 reset 函数:

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 ;
} ;

您可以将 main 重写为:

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)是快速但肮脏的解决方案,而第二个解决方案是重构代码以正确编写它(有充分的理由不赞成全局变量,显然,您偶然发现了其中一个),第三个是“黑客”,使您能够交错这两个调用。

在所有三个解决方案中,只有第二个解决方案可以轻松地将这些代码包装在健壮的、线程安全的 C++ 类中(如果仍然需要)。

Perhaps there is something that eluded me but...

...Global variables are shared between threads, not processes...

This means that in your case, you can have two processes of the same C program working, and they won't interfere one with the other UNLESS they work somehow with process-shared memory.

...If you need two instances of the C code running in the same process...

Then you're screwed.

TLS, perhaps ?

Either you can launch them in separate threads and declare the global variables as Thread-Local-Storage variables. For example, on Visual C++, the following code :

int myGlobalVariable = 42 ;                 // Global variable
__declspec(thread) int myTLSVariable = 42 ; // Thread local variable

Each thread will have its own version of the variable. This way, at the end of the thread, you can copy the content elsewhere.

Rewriting the code...

You don't need to add a C++ layer to that. You can keep your C code, and declare all your global variables in a 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 ;

And then you modify the prototypes of the functions to accept a pointer to this struct as first parameter, and then instead of modifying the global variable, you modify the struct member :

/* old function */
int foo(char *p)
{
   /* fudge with the global variables */
   iMyShortData = 55 ;

   /* etc. */
   fooAgain("Hello World", 42) ;
}

which become:

/* new function */
int foo(MyStruct * s, char *p)
{
   /* fudge with the struct variables */
   s->iMyShortData = 55 ;

   /* etc. */
   fooAgain(s, "Hello World", 42) ;
}

Then, in the main, instead of calling the first function, you call it by giving it the pointer to the right struct. Instead of :

int main(int argc, char * argv[])
{
   bar(42, 55) ;
}

You write :

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 ;
}

In the example above, the two are called one after the other, but your can launch threads instead.

Saving the global state?

If your code is single-threaded, you can interleave calls for the first instance and calls for the second by saving/resetting the global state. Let's use the same struct above :

/* 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 ;
}

And then, you call the save and reset functions when needed :

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 ;
}

This could be wrapped by C++ code to automatically wrap the resetState/saveState functions. For example :

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 ;
} ;

Which you enable you the re-write the main 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 ;
}

Which looks like a lot better than the C version. Still, MyWrapper is not thread-safe...

Conclusion

The first solution (TLS) is the quick'n'dirty solution, while the second is refactoring the code to write it correctly (there are very good reasons global variables are frowned upon, and apparently, you stumbled upon one of them), and the third is a "hack" enabling to you interleave the two calls.

Of all the three solutions, only the second will make it easy to wrap this code inside robust, thread-safe C++ classes if still needed.

昇り龍 2024-09-06 01:10:50

我喜欢这里的想法。但我应该为我需要修改的每个变量创建一个指针。
这是一个示例:

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;
}

I like the idea here. But I should make a pointer of every variable I need to modify.
Here's an example:

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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文