C ++ - a shell for the C library

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

  • Create a C ++ wrapper . The problem is that the wrapper class must contain all the global / static variables available in the C library. Since the functions in the C library use these variables, I will have to create very large argument lists for these functions.
  • Copy-paste library C: Here I will need to adapt the name of each function and each variable inside the C library.

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

Thanks,

Max

+4
source share
5 answers

C ++ -Wrapper
You will slip away by inserting "the entire library" - only slightly modedied - into the 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; } } ; 

Basically declarative changes (for example, the "killing" of static and external declarations). However, you will need to track down the static variables inside the methods, and also turn them into members.

Separate namespaces
This is an ugly solution, but it 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're lucky, the optimizer / linker manages to collapse the identical code. However, the types A:: and B:: not related.

+2
source

If you can not afford to run it twice, about 3 times? You could write a tiny interface process that runs two separate instances of your program in C. From a usage point of view, it would still look like one .exe that you only run once, but behind the scenes you will have a parent process with two children . I have no idea if this approach is suitable for your real needs, but it will almost certainly be faster than either of your other two options.

+2
source

IIUC, what you have is basically:

 extern int a; extern int b; void f(); void g(); 

where a and b change the behavior of f() and g() . It's right?

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

 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 may not be very effective.

Of course, as Raki said in the comments, since he uses global variables, he is not safe.

+2
source

I like the idea here. But I have to make a pointer to every variable that I need to change. Here is 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; } 
0
source

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, and they will not interfere with one another if they do not work with shared technology memory.

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

Then you are screwed.

TLS, maybe?

Either you can run them in separate threads, or declare global variables as Thread-Local-Storage variables. For example, in 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. Thus, at the end of the stream, you can copy the contents to another location.

Code rewriting ...

You do not need to add a C ++ layer to it. You can save your C code and declare all your global variables in the structure:

 /* 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 function prototypes to take a pointer to this structure as the first parameter, and then instead of changing the global variable, you change the struct element:

 /* 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, basically, instead of calling the first function, you call it by pointing it to a pointer to the correct structure. Instead:

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

You write:

 int main(int argc, char * argv[]) { MyStruct A = { /* initialize A members if needed */ } ; MyStruct B = { /* initialize B members if needed */ } ; bar(&A, 42, 55) ; bar(&B, 42, 55) ; return 0 ; } 

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

Saving global status?

If your code is single-threaded, you can alternate calls for the first instance and call the second, saving / reloading the global state. Let the same structure be used:

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

Then you call the save and reset functions if necessary:

 int main(int argc, char * argv[]) { MyStruct A = { /* initialize A members if needed */ } ; MyStruct B = { /* initialize B 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 can be wrapped with C ++ code to automatically wrap the resetState / saveState functions. For instance:

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

What do you allow to overwrite 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 ; } 

It looks much better than version C. However, MyWrapper is not thread safe ...

Conclusion

The first solution (TLS) is a quick solution, while the second is a refactoring of the code to write it correctly (there are very good reasons that the global variables frowned on, and apparently you came across one of them), and the third is a “hack,” which allows you to alternate between two calls.

Of all three solutions, only the second will allow you to easily wrap this code inside reliable, thread-safe C ++ classes, if necessary.

0
source

Source: https://habr.com/ru/post/1307666/


All Articles