Je reçois un vieux librairie C d'école qui nécessite un rappel, et ce rappel n'a aucun paramètre utilisateur.
Comme je dois l'utiliser dans un beau projet c++, j'ai écrit une classe enveloppante.
Le rappel nécessaire par la librairie interne est vu comme une classe avec seulement une méthode abstraite.
Dans l'implémentation, j'ai écrit un rappel C qui appelle la méthode abstraite de l'utilisateur.
Mais comme ce rappel n'a aucun paramètre utilisateur, il utilise un affreux pointeur global!
Et comme ce pointeur est partagé par toutes les instances, l'utilisation parallèle est incorrecte!
Mais je ne peux pas comprendre comment le faire correctement...
J'ai écrit un extrait minimaliste qui abstrait mon problème.
Dans la sortie de l'utilisation parallèle de la version c++, vous pouvez voir que les résultats sont incorrects.
Y a-t-il une façon correcte de faire cela, ou puisque le rappel n'a pas de paramètre utilisateur, suis-je condamné?
Le voici:
#include
using namespace std ;
//---------------------------------------------------------------------
//-- code de la vieille librairie (je ne peux pas la changer)-------------------
extern "C" {
typedef int (*func_t)(int) ; // rappel sans "void * user_param"
typedef struct
{
func_t f ;
int i ;
} t_lib_struct ;
void lib_init ( t_lib_struct * self , func_t f , int i )
{
self->f = f ;
self->i = i ;
}
void lib_close ( t_lib_struct * self )
{
self->f = 0 ;
self->i = 0 ;
}
int lib_process ( t_lib_struct * self , int x )
{
return self->f( x + self->i ) ;
}
}
//---------------------------------------------------------------------
//-- utilisation de l'ancienne école -------------------------------------------------
extern "C" int old_school_func_1 ( int x )
{
return x + 100 ;
}
extern "C" int old_school_func_2 ( int x )
{
return x + 200 ;
}
void old_school_lib_sequential_usage ()
{
t_lib_struct l1 ;
lib_init( &l1,old_school_func_1,10 ) ;
for ( int i = 0 ; i < 5 ; i++ )
cout << " " << lib_process( &l1,i ) ;
cout << endl ;
lib_close( &l1 ) ;
t_lib_struct l2 ;
lib_init( &l2,old_school_func_2,20 ) ;
for ( int i = 0 ; i < 5 ; i++ )
cout << " " << lib_process( &l2,i ) ;
cout << endl ;
lib_close( &l2 ) ;
}
void old_school_lib_parallel_usage ()
{
t_lib_struct l1,l2 ;
lib_init( &l1,old_school_func_1,10 ) ;
lib_init( &l2,old_school_func_2,20 ) ;
for ( int i = 0 ; i < 5 ; i++ )
cout << " " << lib_process( &l1,i ) << " // " << lib_process( &l2,i ) << endl ;
lib_close( &l1 ) ;
lib_close( &l2 ) ;
}
void old_school_lib_usage ()
{
cout << "séquentiel:" << endl ;
old_school_lib_sequential_usage() ;
cout << "parallèle:" << endl ;
old_school_lib_parallel_usage() ;
}
//---------------------------------------------------------------------
//-- enveloppe c++ ------------------------------------------------------
struct Lib
{
struct LibFunc
{
virtual int func ( int x ) const = 0 ;
};
Lib ( const LibFunc & f , int i ) ;
~Lib () ;
int process ( int x ) ;
//protected:
t_lib_struct lib ;
const LibFunc & f ;
};
//---------------------------------------------------------------------
Lib * global_lib = 0 ;
extern "C" int wrapped_func ( int x )
{
if (!global_lib) return -1 ;
return global_lib->f.func( x ) ;
}
Lib::Lib ( const LibFunc & f , int i ) : f(f)
{
global_lib = this ;
lib_init( &lib,wrapped_func,i ) ;
}
Lib::~Lib ()
{
lib_close( &lib ) ;
global_lib = 0 ;
}
int Lib::process ( int x )
{
return lib_process( &lib,x ) ;
}
//---------------------------------------------------------------------
//-- utilisation de style c++ --------------------------------------------------
struct MyFunc : Lib::LibFunc
{
int d ;
MyFunc ( int d ) : d(d) {}
int func ( int x ) const
{
return x + d ;
}
};
void cpp_lib_sequential_usage ()
{
Lib l1( MyFunc( 100 ),10 ) ;
for ( int i = 0 ; i < 5 ; i++ )
cout << " " << l1.process( i ) ;
cout << endl ;
Lib l2( MyFunc( 200 ),20 ) ;
for ( int i = 0 ; i < 5 ; i++ )
cout << " " << l2.process( i ) ;
cout << endl ;
}
void cpp_lib_parallel_usage ()
{
Lib l1( MyFunc( 100 ),10 ),l2( MyFunc( 200 ),20 ) ;
for ( int i = 0 ; i < 5 ; i++ )
cout << " " << l1.process( i ) << " // " << l2.process( i ) << endl ;
}
void cpp_lib_usage ()
{
cout << "séquentiel:" << endl ;
cpp_lib_sequential_usage() ;
cout << "parallèle:" << endl ;
cpp_lib_parallel_usage() ;
}
//---------------------------------------------------------------------
int main ()
{
cout << "==== vieille école ===================" << endl ;
old_school_lib_usage() ;
cout << "==== style c++ ====================" << endl ;
cpp_lib_usage() ;
}
Et la sortie:
==== vieille école ===================
séquentiel:
110 111 112 113 114
220 221 222 223 224
parallèle:
110 // 220
111 // 221
112 // 222
113 // 223
114 // 224
==== style c++ ====================
séquentiel:
110 111 112 113 114
220 221 222 223 224
parallèle:
210 // 220
211 // 221
212 // 222
213 // 223
214 // 224
Les utilisations séquentielles sont correctes, mais vous pouvez voir que l'utilisation parallèle de la classe c++ imprime des valeurs >= 200.
Cela signifie que le deuxième rappel est utilisé par les deux instances...
(J'ai déclaré toutes mes classes en tant que struct pour éviter les problèmes public/private dans cet extrait)