5

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

  • 制作C++ 包装器:这里的问题是包装器类应该包含 C 库具有的所有全局/静态变量。由于 C 库中的函数使用这些变量,我将不得不为这些函数创建非常大的参数列表。
  • 复制粘贴C 库:这里我必须修改 C 库中每个函数和每个变量的名称。

哪一个是最快的解决方案?还有其他可能运行相同 C 源的 2 个实例吗?

谢谢,

最大限度

4

5 回答 5

3

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

如果幸运的话,优化器/链接器会成功折叠相同的代码。但是,类型 inA::B::是不相关的。

于 2010-04-22T15:33:32.550 回答
2

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

于 2010-04-22T12:27:53.580 回答
2

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

};

根据你所拥有的而不是aand b,这可能不是非常有效。

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

于 2010-04-22T12:32:11.047 回答
0

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

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

对象.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;
}
于 2010-04-22T13:58:56.357 回答
0

也许有什么东西让我无法理解,但是......

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

这意味着在您的情况下,您可以让同一个 C 程序的两个进程工作,并且它们不会干扰另一个进程,除非它们以某种方式与进程共享内存一起工作。

...如果您需要在同一进程中运行的 C 代码的两个实例...

那你就完蛋了。

TLS,也许?

您可以在单独的线程中启动它们并将全局变量声明为 Thread-Local-Storage 变量。例如,在 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 ;
}

在上面的示例中,两者一个接一个地被调用,但您可以启动线程。

保存全局状态?

如果您的代码是单线程的,您可以通过保存/重置全局状态来交错对第一个实例的调用和对第二个实例的调用。让我们使用上面相同的结构:

/* 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) 是快速的'n'dirty 解决方案,而第二个是重构代码以正确编写它(有很好的理由不赞成全局变量,显然,你偶然发现了其中一个),并且第三个是“hack”,使您可以将两个呼叫交错。

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

于 2010-04-22T15:14:00.250 回答