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."