2

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)

Captain'Flam
  • 479
  • 4
  • 12
  • 1
    what compiler are you using? gcc? – tstanisl Apr 08 '21 at 10:47
  • How about something like `template struct Lib;`, so `Lib<1> l1;` and `Lib<2> l2;` can co-exist (as they each have their own global)? – Jarod42 Apr 08 '21 at 10:49
  • @Jarod42 I was thinking something similar, maybe CRTP the `LibFunc` to get a new callback for each instance? – IWonderWhatThisAPIDoes Apr 08 '21 at 10:51
  • 1
    I suggest reading https://stackoverflow.com/a/4393782/4989451 – tstanisl Apr 08 '21 at 11:40
  • Did you try to use a class method as callback? – the busybee Apr 08 '21 at 13:38
  • How many lines of code do you have: thousands, or millions of lines of C code? With a few thousands lines, you can modify the code manually. With millions of lines, you need some automated tool (perhaps some enhanced [Bismon](https://github.com/bstarynk/bismon) or [RefPerSys](http://refpersys.org/)....) contact me by email to `basile@starynkevitch.net` or `basile.starynkevitch@cea.fr` (near Paris in France) – Basile Starynkevitch May 05 '21 at 17:15

1 Answers1

0

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
Captain'Flam
  • 479
  • 4
  • 12