Search code examples
c++cfunction-pointerswrapper

Wrapping old school C lib within C++ class (and a callback without user param)


I inherit an old school C lib that needs a call back, and this call back doesn't have any user param.

Since I have to use it within a nice c++ project, I wrote a wrapping class.
The call back needed by the inner lib is seen as a class with only one abstract method.
Within the implementation, I wrote a C callback that calls the user's abstract method.
But since this callback don't have any user param, it uses an awful global pointer!
And since this pointer is shared by all the instances, parallel usage is wrong!
But I can't figure out how to do it properly...

I wrote a minimalist snippet that abstracts my problem.
In the output of the parallel usage of the c++ version, you can see that the results are wrong.

Is there is proper way to do this, or since the callback has no user param, I am doomed ?

Here it is:

#include <iostream>
using namespace std ;

//---------------------------------------------------------------------
//-- code of the old school lib (I can't change it )-------------------

extern "C" {

typedef int (*func_t)(int) ; // callback without any "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 ) ;
    }
}

//---------------------------------------------------------------------
//-- old school usage -------------------------------------------------

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 << "sequential:" << endl ;
    old_school_lib_sequential_usage() ;
    cout << "parallel:" << endl ;
    old_school_lib_parallel_usage() ;
    }

//---------------------------------------------------------------------
//-- c++ wrapper ------------------------------------------------------

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

//---------------------------------------------------------------------
//-- c++ style usage --------------------------------------------------

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 << "sequential:" << endl ;
    cpp_lib_sequential_usage() ;
    cout << "parallel:" << endl ;
    cpp_lib_parallel_usage() ;
    }

//---------------------------------------------------------------------

int main ()
    {
    cout << "==== old school ===================" << endl ;
    old_school_lib_usage() ;
    cout << "==== c++ style ====================" << endl ;
    cpp_lib_usage() ;
    }

And the output:

==== old school ===================
sequential:
   110   111   112   113   114
   220   221   222   223   224
parallel:
   110 // 220
   111 // 221
   112 // 222
   113 // 223
   114 // 224
==== c++ style ====================
sequential:
   110   111   112   113   114
   220   221   222   223   224
parallel:
   210 // 220
   211 // 221
   212 // 222
   213 // 223
   214 // 224

Sequential usages are ok, but you can see that parallel usage of the c++ class prints values >= 200.
It means that the second call back is used by both instances...

(I declared all my classes as struct to avoid public/private issues is this snippet)


Solution

  • For those who are interested, here is the way I solved my own problem:
    I use a pool of extern C functions that take the place of the missing user param in my old school lib.
    The main limitation of this approach is that the number of concurrency instances is limited to a static arbitrary number.

    //---------------------------------------------------------------------
    //-- the c++ wrapper API
    
    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 ;
        };
    
    //---------------------------------------------------------------------
    //-- wrapper usage
    
    struct MyFunctor : Lib::Functor
        {
        int d ;
        MyFunctor ( int d ) : d(d) {}
        int func ( int x ) const
            {
            return x + d ;
            }
        };
    
    #include <iostream>
    
    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 of the old school lib (I can't change it)
    
    extern "C" {
    
        typedef int (*func_t)(int) ; // callback without any "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 ) ;
            }
        }
    
    //---------------------------------------------------------------------
    //-- the not-very-clean-solution: a global pool of functions that takes the place of the missing user-param
    
    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)  // if necessary, add f(16)f(17)...
    
    // create a pool of 16 functions...
    extern "C" {
    #define FUNC_DEF(i)    static int wrapped_func_##i ( int x ) { return get(i)->func(x) ;}
    FOR_ALL(FUNC_DEF)
    }
    
    // ....and an associated array of structs (terminated by a "null" element)
    #define FUNC_STRUCT(i) { wrapped_func_##i , 0 , true },
    static funcs_t funcs [] = { FOR_ALL(FUNC_STRUCT) {0,0,false} } ;
    
    static int alloc () // return the index of a free slot, or -1
        {
        for ( int i = 0 ; funcs[i].func ; i++ )
            if (funcs[i].isfree)
                return funcs[i].isfree = false || i ;
        return -1 ; // allocation error not managed!
        }
    
    static void free ( int i )                
        { 
        funcs[i].isfree = true ;
        }
    
    static const Lib::Functor * get ( int i ) 
        { 
        return funcs[i].lib_functor ;
        }
    
    //---------------------------------------------------------------------
    //-- wrapper implementation
    
    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 ) ;
        }
    

    And the output:

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