passing c++ methods as c-callbacks

Z

zholthran

Hi folks,

after reading several threads on this issue (-> subject) I fear that I
got a problem that cannot easily be solved by the offered workarounds
in an acceptable way, at least not with my limited c & c++ experience.
Maybe some of you can help.

the problem:
I need several instances of a class whose (non-static!) methods should
serve as callbacks for a dll (which can' be manipulated/adapted in any
way). Static use is inacceptable and won't work for my setup because
I'm in desperate need for several independent working instances of the
same class.
The actual problem is the absence of any possibility to pass user data
to the callers of the callbacks (which are some device driver-dlls) so
that I can't use a wrapper to delegate the calls to the correct class
instance.

Is there any chance for a workaround that doesn't touch the dlls in any
way so the same class can be used for each dll? Any help would be
appreciated.

Thanks,
Dirk
 
M

Michiel.Salters

Hi folks,

after reading several threads on this issue (-> subject) I fear that I
got a problem that cannot easily be solved by the offered workarounds
in an acceptable way, at least not with my limited c & c++ experience.
Maybe some of you can help.

the problem:
I need several instances of a class whose (non-static!) methods should
serve as callbacks for a dll (which can' be manipulated/adapted in any
way). Static use is inacceptable and won't work for my setup because
I'm in desperate need for several independent working instances of the
same class.

So, in summary, the DLL has only one function pointer (probably 4
bytes)
yet you require it to distinguish not only functions but also instances

(probably 4+4 bytes). That simply won't fit.
Is there any chance for a workaround that doesn't touch the dlls in any
way so the same class can be used for each dll? Any help would be
appreciated.

If you accept a fixed (but configurable) number of callbacks, define a
template<int N> wrapper, and store the instance pointers and member
pointers in two arrays. wrapper<i> will then retrieve the i'th instance
pointer
and member. While you can decide at runtime what wrapper<i> means,
you can't set more callbecks than the number of instantiated
wrapper<i>s.
(Of course, if you put those in your own DLLs too, you can just open
100.DLL, 200.DLL, 300.DLL to get extra wrapper functions - non
standard)

HTH
Michiel Salters
 
L

Lyell Haynes

The actual problem is the absence of any possibility to pass user data
If I understand you correctly, you have multiple device driver dlls
that have callback routines that you must implement and you want to
associate a specific instance of one callback class with each dll?
Based on your description, I'm assuming the callbacks from the device
driver dlls are c-style functions. Also, you are unable pass a void* or
some other type of user data to the device driver that would get
returned in the callback, correct?

In this case, since you cannot pass user data to the dlls that will get
returned in the callback, you will need another way of delegating the
callbacks to C++ classes. Depending on your situation, you may want to
set up a singleton or global callback class that all the c-style
functions will know about and can use to delegate their calls to. Once
the calls are delegated to that singleton object, it would then decide
what specific instance of your final callback class to call based on
some information either known at compile time or at runtime. (You could
also set up your final callback classes as singletons themselves and
have the device driver callbacks call them directly, if there is a
different set of c-style callback for each dll.)

The singleton pattern is often overused, but in a situation like this,
it is definitely a correct choice.

Hope that helps.

--Lyell
 
Z

zholthran

First of all thanks a lot for your feedback Michiel and Lyell.

@Lyell:
Unfortunately your approach won't work here because only one of four
callbacks (each device driver controls the same device type and thus
has the same interface and the same four callbacks) contains
information that could help to determine the caller at run or compile
time. No chance to tell where the other callbacks originate from.

@Michiel:
It seems to be that your template approach with a fixed amount of
instances is the only thing that could serve as a solution at the
moment. It's quite frustrating though that one has to sacrifice the
advantage of dynamic scaling.

So again, thanks a lot for your help!

Bye,
Dirk
 
L

Lyell Haynes

Unfortunately your approach won't work here because only one of four
Maybe you could write an adapter dll for each of your device drivers
and then load your adapter dll instead of loading the device driver dll
directly? There could be a generic adapter dll for any device drivers
you don't handle specifically. You'd then have control over the
callback handling and still have the ability to dynamically scale by
just writing a new adapter dll anytime you want to do something
specific for a particular device driver.

Lyell
 
P

Puppet_Sock

The actual problem is the absence of any possibility to pass user data
to the callers of the callbacks (which are some device driver-dlls) so
that I can't use a wrapper to delegate the calls to the correct class
instance.

So, you write a configurable thunk table. Like so: You create several
functions that can be passed to the dlls as callbacks. Each of these
then looks up in a global table where to call to get the real
functionality.
Each function looks for a particular line in the table. And you fill in
that line dynamically according to what you want things to be doing.

So, the thunk table might include a pointer to an instance of a class
that does the job you want. And that instance might be derived from
a base class that has a member fcn to call. And you just derive a
class for each type of functionality you want.

So it only adds one layer of indirection. You create an instance of the
handling class and put the pointer in the table. Then you pass the
appropriate function pointer to the dll. With a little work you can
even
have the table collect the function pointers, and keep track of what
dlls they have been assigned to, and what type of class they have
had attached for doing the work. Or whatever else book keeping is
required to assist. You can make this exactly as fancy as required.

Wrap the entire thing in a singleton and you are cooking.
Socks
 
K

Kaz Kylheku

The actual problem is the absence of any possibility to pass user data
to the callers of the callbacks (which are some device driver-dlls) so
that I can't use a wrapper to delegate the calls to the correct class
instance.

Is there any chance for a workaround that doesn't touch the dlls in any
way so the same class can be used for each dll? Any help would be
appreciated.

Yes. In the Lisp programming language, there is something known as a
dynamically-scoped variable. A dynamically scoped variable has a
global top-level binding, but it can be overriden locally. For instance
if X is dynamically scoped then (LET ((X 3)) ...) binds X to a new
storage location over the scope of the let block. Any function you call
from there will see X as being bound to that storage location which has
that value. X can be changed of course. When the LET block terminates,
the binding is restored to the previous binding (no matter how that
block terminates: normal exit or non-local exit via throw or whatever).

Under multithreaded implementations, these re-bindings affect only the
calling thread, so dynamic variables effectively become an abstraction
for thread-local storage.

They are useful for passing down context information through functions,
or layers of functions, which don't have the arguments for doing so.

Because of the saving-restoring discipline of the local rebinding,
dynamic variables are more disciplined than simple global variables.
Sure you rebind the variable to create an effect in some other module,
but then the old binding is restored when you are done.

You can give yourself the same thing in C++. In single-threaded code,
it can be done portably. Simply use a global variable for the context,
and use RAII to save and restore its value.

I wrote a pair of templates to do this. The template DynamicVar<T> is
used to define a dynamic variable. It's actually a class object which
contains a T, but also contains a T * pointer to the latest binding. If
that pointer is null, then the embedded T is the binding of that
variable. If that pointer is not null, then it points to the current
location of T.

The other template, RebindVar<T>, is used to temporarily bind a given
variable to a new location. Like DynamicVar<T>, it contains an embedded
T. It takes an initial value through the constructor, and a reference
to the DynamicVar<T>. It saves the original T * pointer within the
DynamicVar<T> and then chages it to point to its own embedded instance
of T. In the destructor, the pointer in the global is restored to its
previous value.

Both DynamicVar and RebindVar have the assignment and conversion
operators to transparently operate on the embedded T. RebindVar
bypasses the pointer, of course! If you have the RebindVar object in
lexical scope, then the dynamic lookup thorugh the pointer isn't
required.

So your code would look something like this:

// file scope or static class variable
DynamicVar<MyClass *> d_contextPointer;

void CallLibrary(MyClass *obj)
{
// Set up local re-binding of d_contextPointer

RebindVar<MyClass *> contextPointer(obj);
LibraryFunction(&MyClass::StaticCallback);

// scope ends, old binding is now restored
}

void MyClass::StaticCallback()
{
// retrieve context from temporary binding
d_contextPointer->NonStaticCallback();
}

If you need threading support, the DynamicVar and RebindVar templates
can hide the calls to the thread-specific storage API functions on your
platform or whatever, so that the constructor and destructor function
of RebindVar<> is visible only in the context of the calling thread.

As you can see, the scheme supports recursion nicely. The callback
itself can override the dynamic variable in a nested fashion and call
into library, resulting in another callback which can use a different
object.

Greenspun's Tenth Rule of Programming:

"Any sufficiently complicated C or Fortran program contains an ad-hoc,
informally-specified bug-ridden slow implementation of half of Common
Lisp."
 
P

pillbug

The solution isn't pretty, but I believe you want what is referred to as
a 'thunk'.

The short explanation of it is to allocate and initialize a structure
with machine language opcodes and use the address of the structure as
your function pointer.

For example if you have the following definition:

class MyObject {
public:
int myMethod (int value);
};

and have an allocated instance of this object named myObject, and need
to create a callback that accomplishes the following:

int Callback1 (int value)
{
return myObject->myMethod (value);
}

Then you need to create a thunk structure like so:

struct Thunk1
{
unsigned u1; // set to opcodes for __asm mov ecx, pThis
unsigned u2; // set to opcodes for __asm push value
unsigned u3; // set to opcodes for __asm call myMethod
};

And then use the address of Thunk1 as the callback.

Obviously this sort of thing is highly unportable, requiring you to
compile a similar function for the target machine then look at the
opcodes emitted to determine how to initialize your structure, but since
we are talking about device drivers I am guessing you have some freedom
in this area.

I may have mistated a few things here, but hopefully the concept is
clear enough. I apologize for the gross hackery :)
 
P

pillbug

So, you write a configurable thunk table. Like so: You create several
functions that can be passed to the dlls as callbacks. Each of these
then looks up in a global table where to call to get the real
functionality.
Each function looks for a particular line in the table. And you fill in
that line dynamically according to what you want things to be doing.

I want to add that I like this method a lot better than my suggestion,
but it does have the difference of requiring you to know an upper limit
to the number of objects you may need; which is very likely since we are
talking about a device driver.
 
Z

zholthran

Things get quite interesting and fascinating here. I think I need some
time to find my way through all your detailed concepts and
descriptions. Thanks a lot for your help and participation.

Bye,
Dirk
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,770
Messages
2,569,583
Members
45,074
Latest member
StanleyFra

Latest Threads

Top