Question

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

Was it helpful?

Solution

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.

OTHER TIPS

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.

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.

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

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top