Domanda

Recentemente ho trovato una libreria C che voglio usare nel mio progetto C ++. Questo codice è configurata con le variabili globali e lo scrive di uscita alla memoria puntato da statica puntatori . Quando eseguo il mio progetto vorrei 2 istanze del programma C per funzionare: una con configurazione A e uno con la configurazione B. Non posso permettermi di eseguire il mio programma di due volte, quindi penso che ci sono 2 opzioni:

  • Fare un C ++ involucro : Il problema qui è che il wrapper di classe dovrebbe contenere tutte le variabili globali / static la libreria C ha. Dal momento che le funzioni della libreria C usano tali variabili dovrò creare molto grande argomento-liste per quelle funzioni.
  • Copia-incolla la libreria C: Qui dovrò adattare il nome di ogni funzione ed ogni variabile all'interno della libreria C
  • .

Qual è la soluzione più veloce? ci sono altre possibilità per eseguire 2 istanze della stessa sorgente C?

Grazie,

Max

È stato utile?

Soluzione

C ++ -Wrapper
È possibile ottenere via più facile incollando "l'intera libreria" - solo un po 'modfied - in una classe

.
// C
static char resultBuffer[42];
void ToResult(int x) { ... }
char const * GetResult() { return resultBuffer; }

diventa

// C++
class CMyImportantCLib
{
  private:
    char resultBuffer[42];
    void ToResult(int x) { ... } // likely, no code changes at all
    char const * GetResult() { return resultBuffer; }
} ;

Ci sono per lo più variazioni dichiarative (come statico "uccidere" e dichiarazioni extern). Si avrebbe bisogno di dare la caccia a variabili statiche all'interno dei metodi, però, e li trasformano in componenti e

spazi dei nomi separati
Questa è una soluzione brutto, ma potrebbe essere abbastanza per voi:

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

Se si è fortunati, l'ottimizzatore / linker riesce a piegare il codice identico. Tuttavia, i tipi di A:: e B:: sono indipendenti.

Altri suggerimenti

Se non può permettersi di correre due volte, come circa 3 volte? Si potrebbe plausibilmente scrivere un piccolo processo di front-end che lanci due istanze separate del vostro programma C. Dal punto di vista utilizzo sarebbe ancora l'aspetto di un singolo exe che si esegue una sola volta, ma dietro le quinte si avrebbe un processo padre con due figli. Non ho idea se questo approccio sarebbe adatto alle vostre esigenze reali, ma che sarebbe quasi certamente essere più veloce di uno dei tuoi altre due opzioni.

IIUC, quello che hai è, in fondo, in questo modo:

extern int a;
extern int b;

void f();
void g(); 

dove a e b modificare il comportamento di f() e g(). È corretto?

Se si dispone di questo e si vuole avvolgere questo in C ++, allora che cosa si potrebbe fare è questa:

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

};

A seconda di quello che hai, invece di a e b, questo potrebbe non essere terribilmente efficace.

Naturalmente, come ha detto Raki nei commenti, dal momento che questo sta usando le variabili globali, non è affatto sicuro thread.

Mi piace l'idea qui. Ma dovrei fare un puntatore di ogni Ho bisogno variabile da modificare. Ecco un esempio:

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

Forse c'è qualcosa che mi sfuggiva, ma ...

... Le variabili globali sono condivise tra i thread, non elabora ...

Ciò significa che, nel suo caso, si possono avere due processi dello stesso funzionamento del programma C, e non interferiranno uno con l'altro MENO lavorano in qualche modo con la memoria di processo-condivisa.

... Se avete bisogno di due istanze del codice C in esecuzione nello stesso processo ...

Quindi sei fregato.

TLS, forse?

In entrambi si possono lanciare in thread separati e dichiarare le variabili globali come variabili-immagazzinaggio del filetto-locale. Ad esempio, in Visual C ++, il seguente codice:

int myGlobalVariable = 42 ;                 // Global variable
__declspec(thread) int myTLSVariable = 42 ; // Thread local variable

Ogni thread avrà una propria versione della variabile. In questo modo, alla fine del filo, è possibile copiare il contenuto altrove.

Riscrivere il codice ...

Non c'è bisogno di aggiungere uno strato C ++ a questo. È possibile mantenere il codice C, e dichiarare tutte le variabili globali in una struttura:

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

E poi si modificano i prototipi delle funzioni ad accettare un puntatore a questo struct come primo parametro, e poi invece di modificare la variabile globale, è possibile modificare il membro struct:

/* old function */
int foo(char *p)
{
   /* fudge with the global variables */
   iMyShortData = 55 ;

   /* etc. */
   fooAgain("Hello World", 42) ;
}

che diventano:

/* new function */
int foo(MyStruct * s, char *p)
{
   /* fudge with the struct variables */
   s->iMyShortData = 55 ;

   /* etc. */
   fooAgain(s, "Hello World", 42) ;
}

Poi, per lo più, invece di chiamare la prima funzione, si chiama dandogli il puntatore alla struct destra. Invece di:

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

Si scrive:

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

Nell'esempio precedente, i due sono chiamati uno dopo l'altro, ma le discussioni che può lanciare, invece.

Salvataggio dello stato globale?

Se il codice è a thread singolo, è possibile interleave chiede che il primo grado e le chiamate per il secondo salvando / reset lo stato globale. Usiamo la stessa struct sopra:

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

E poi, si chiama Salva e ripristino funzioni quando necessario:

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

Questo potrebbe essere avvolto dal codice C ++ per avvolgere automaticamente le funzioni resetState / SaveState. Ad esempio:

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

Che si si abilita la ri-scrittura il principale:

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

che sembra molto migliore rispetto alla versione C. Ancora, MyWrapper non è thread-safe ...

Conclusione

La prima soluzione (TLS) è la soluzione frettoloso e approssimativo, mentre la seconda è refactoring del codice per scrivere correttamente (ci sono ottime ragioni variabili globali sono disapprovate, ea quanto pare, è inciampato su uno di loro ), e il terzo è un "hack" che permette a voi interleave le due chiamate.

Di tutte le tre soluzioni, solo la seconda renderà facile racchiudere il codice all'interno robusto, thread-safe C ++ classi se ancora necessario.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top