【问题标题】:C++ wrapper for C libraryC 库的 C++ 包装器
【发布时间】:2010-04-22 12:12:06
【问题描述】:

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

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

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

谢谢,

最大

【问题讨论】:

    标签: c++ c global-variables wrapper static-variables


    【解决方案1】:

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

    【讨论】:

    • @paercebal, @peterchen:最终我通过粘贴整个库并搜索静态/外部/全局变量来做到这一点。另一个困难是编写析构函数。谢谢
    【解决方案2】:

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

    【讨论】:

    • 那么两个配置也需要相互通信?
    • 假设我有一个项目 Bar,我创建了两个进程 foo1 和 foo2,每个进程都有一个单独的配置。 Bar 在运行过程中,需要向 foo1 和 foo2 传递信息。在运行的最后,Bar 收集 foo1 和 foo2 收集的信息。
    【解决方案3】:

    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 在 cmets 中所说,因为这是使用全局变量,所以它根本不是线程安全的。

    【讨论】:

    • 可能值得一提的是,这种方法仅对单线程应用程序是安全的。
    • @Rakis:由于库是使用全局配置的,我认为这不用提了。
    • 该评论是为将来可能会出现在这个线程中的 C++ 新手准备的。我可以看到这个主题很容易被搜索引擎找到。 “不言而喻”是相对于你的经验水平而言的。记得大学你的大学物理教授放弃了那个评论吗?是的。
    • @Rakis:你说得对。我将在答案中添加一句话。谢谢。
    【解决方案4】:

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

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

    【讨论】:

    • 我不确定“为我需要修改的每个变量创建一个指针”指的是什么。而不是int a; int b; 你有int a; int* output;,但除此之外没有区别。我错过了什么吗?
    • 我的意思是:假设你的 C 代码中有 2 个全局变量: int a;整数输出; C 库的输出被写入“输出”。当您想编写这样的 C++ 包装器时,您需要创建一个指向内存的指针,而不仅仅是一个变量。
    • 您是否(错误地)认为指针不是变量?
    • 顺便说一句,您应该使用@sbi 开始向我回答 cmets,以便 SO 有机会通知我。我偶然发现了上面的一个。
    • @sbi:感谢您的提示 ;-)。是的,如果我用“原始非指针数据类型”替换“变量”会更清楚。
    【解决方案5】:

    也许有什么让我无法理解的事情,但是......

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

    这意味着在您的情况下,您可以让同一个 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) 是 quick'n'dirty 解决方案,而第二个解决方案是重构代码以正确编写它(有很好的理由不赞成全局变量,显然,您偶然发现了其中一个),第三个是“hack”,使您可以将两个调用交错。

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

    【讨论】:

      猜你喜欢
      • 2016-09-10
      • 1970-01-01
      • 2010-11-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-05
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多