C++ wrapper for C library
Asked Answered
D

5

5

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

Dribble answered 22/4, 2010 at 12:12 Comment(0)
C
3

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.

Crigger answered 22/4, 2010 at 15:33 Comment(1)
@paercebal, @peterchen: Eventually I did it by pasting the entire library and hunting the static/extern/global variables. Another difficulty were writing destructors. ThanksDribble
A
2

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.

Aerodonetics answered 22/4, 2010 at 12:27 Comment(2)
So the two configurations also need to communicate with each other?Annaleeannaliese
Say I have a project Bar and I create two processes foo1 and foo2 that each have a separate configuration. During the run of Bar, it needs to transfer information to foo1 and foo2. At the very end of the run, Bar collects the information gathered by foo1 and foo2.Dribble
C
2

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.

Clemmie answered 22/4, 2010 at 12:32 Comment(4)
It's probably worth mentioning that this approach is safe only for single-threaded applications.Aerodonetics
@Rakis: Since the library is configured using globals, I'd think this goes without mentioning.Clemmie
The comment was intended for C++ newcommers that might happen this thread in the future. I can see this topic being found fairly easily by a search engine. "It goes without saying" is relative to your level of experience. Remember college your college physics professor dropping that comment? Yeah.Aerodonetics
@Rakis: You got a point there. I'll add a sentence to the answer. Thanks.Clemmie
I
1

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.

Incontrollable answered 22/4, 2010 at 15:14 Comment(0)
D
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;
}
Dribble answered 22/4, 2010 at 13:58 Comment(5)
I'm not sure what "make a pointer of every variable I need to modify" refers to. instead of int a; int b; you have int a; int* output;, but otherwise no difference. Am I missing something?Clemmie
What I mean is the following: suppose you have 2 global variables in your C code: int a; int output; The output of your C library is written to "output". When you want to write a C++ wrapper like this you'll need to create a pointer to memory instead of just a variable.Dribble
Could it be that you (mistakingly) believe that a pointer is not a variable?Clemmie
BTW, you should start your answering comments to me with @sbi, so that SO has a chance to notify me. I found the above one by pure coincidence.Clemmie
@sbi: thanks for the hint ;-). Yeah, it would've been more clear if I replaced "variable" with "primitive non-pointer datatypes".Dribble

© 2022 - 2024 — McMap. All rights reserved.