2 votes

Encapsuler une ancienne bibliothèque C dans une classe C++ (et un rappel sans paramètre utilisateur)

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)

0voto

Captain'Flam Points 90

Pour ceux qui sont intéressés, voici la façon dont j'ai résolu mon propre problème :
J'utilise un pool de fonctions extern C qui remplacent le paramètre utilisateur manquant dans ma librairie à l'ancienne.
La principale limitation de cette approche est que le nombre d'instances de concurrence est limité à un nombre arbitraire statique.

//---------------------------------------------------------------------
//-- l'API du wrapper c++

class Lib
    {
    public:
        struct Functor
            {
            virtual int func ( int x ) const = 0 ;
            };

        Lib ( const Functor & f , int i ) ;
       ~Lib () ;
        int process ( int x ) ;

    protected:
        struct PirvateLib ;
        PirvateLib * m ;
    };

//---------------------------------------------------------------------
//-- utilisation du wrapper

struct MyFunctor : Lib::Functor
    {
    int d ;
    MyFunctor ( int d ) : d(d) {}
    int func ( int x ) const
        {
        return x + d ;
        }
    };

#include 

int main ()
    {
    Lib l1( MyFunctor( 100 ),10 ) ;
    Lib l2( MyFunctor( 200 ),20 ) ;
    Lib l3( MyFunctor( 300 ),30 ) ;
    for ( int i = 0 ; i < 5 ; i++ )
        std::cout << "   " << l1.process( i ) << " // " << l2.process( i ) <<  " // " << l3.process( i ) << std::endl ;
    }

//---------------------------------------------------------------------
//-- code de la librairie ancienne (je ne peux pas le modifier)

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

//---------------------------------------------------------------------
//-- la solution pas très propre : un pool global de fonctions qui remplace le paramètre utilisateur manquant

static const Lib::Functor * get ( int i ) ;

struct funcs_t
    {
    func_t               func ;
    const Lib::Functor * lib_functor ;
    bool                 isfree ;
    } ;

#define FOR_ALL(f) f(0)f(1)f(2)f(3)f(4)f(5)f(6)f(7)f(8)f(9)f(10)f(11)f(12)f(13)f(14)f(15)  // si nécessaire, ajouter f(16)f(17)...

// créer un pool de 16 fonctions...
extern "C" {
#define FUNC_DEF(i)    static int wrapped_func_##i ( int x ) { return get(i)->func(x) ;}
FOR_ALL(FUNC_DEF)
}

// ....et un tableau de structs associé (terminé par un élément "null")
#define FUNC_STRUCT(i) { wrapped_func_##i , 0 , true },
static funcs_t funcs [] = { FOR_ALL(FUNC_STRUCT) {0,0,false} } ;

static int alloc () // renvoie l'index d'un emplacement libre, ou -1
    {
    for ( int i = 0 ; funcs[i].func ; i++ )
        if (funcs[i].isfree)
            return funcs[i].isfree = false || i ;
    return -1 ; // erreur d'allocation non gérée !
    }

static void free ( int i )                
    { 
    funcs[i].isfree = true ;
    }

static const Lib::Functor * get ( int i ) 
    { 
    return funcs[i].lib_functor ;
    }

//---------------------------------------------------------------------
//-- implémentation du wrapper

struct Lib::PirvateLib
    {
    t_lib_struct lib ;
    int          i ;
    };

Lib::Lib ( const Functor & f , int i ) : m ( new Lib::PirvateLib )
    {
    m->i = alloc() ;
    funcs[m->i].lib_functor = &f ;
    lib_init( &m->lib,funcs[m->i].func,i ) ;
    }

Lib::~Lib ()
    {
    lib_close( &m->lib ) ;
    free( m->i ) ;
    delete m ;
    }

int Lib::process ( int x )
    {
    return lib_process( &m->lib,x ) ;
    }

Et la sortie :

110 // 320 // 330
111 // 321 // 331
112 // 322 // 332
113 // 323 // 333
114 // 324 // 334

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X