I have a library which was written in C, you call a function, it
provides the result by a callback to specific function names. I am
trying to wrap the calls to this inside a class, but this causes a
problem with the callbacks, for example:
[snip]
First of all, I hope you have a good reason to wrap this C library with C++
objects. Sometimes the C libraries have a good object-oriented design (at
least at the interface level), just this design is expressed in a wrong
language (C). If the design is apparent from the library itself, then it
should be straightforward to perform the wrapping. You will recognize such a
design, for example, if there are groups of functions, which are grouped
around particular tasks, where majority of functions from a single group has
something common in the parameters - some opaque pointer to a structure, a
"handle" or something similar. Here is a couple of declarations from zlib,
for example (comments omitted):
ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level));
ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush));
ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm));
....
ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode));
ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len));
ZEXTERN int ZEXPORT gzwrite OF((gzFile file,
voidpc buf, unsigned len));
....
As a first attempt, one could wrap all inflate/deflate functions into one
class (which will encapsulate z_streamp), while wrapping all gz... functions
into another class (which will encapsulate gzFile). This may not produce a
terribly good C++ code, but at least the overall class design of this code
won't be worse that the object-oriented design of the original class
library.
Regarding the callbacks: as Greg have noticed, sometimes C libraries provide
a "context" that you specify when registering a callback with the library
(and which you receive in your callback). Similarly, the C library may
return you a "cookie" while registering a callback. This is an additional
sign that the C library creator had an OO design in mind. If it is missing,
then this C library is probably not designed in an OO way.
My point is, actually: look at the C library and try to understand its
design. If it is object-oriented, you can try to wrap it into classes. If it
isn't, you better leave the C library as it is, and directly use it in your
C++ programs. IMHO it is easier for C++ programmers (at least for me!) to
comprehend no object-oriented design from a C library than to comprehend a
bad design from a C++ library.
Finally, to avoid comments that I am talkative without saying anything... if
you lack "context" supplied when registering callbacks, you still can
develop a "connection point", i.e. a Singletone class that can distribute
notifications (a degenerate case of Observer pattern). Something like:
//==================================
// library.h
//==================================
#ifndef LIBRARY_H
#define LIBRARY_H
extern "C"
{
void library_register_add(void (*callback)(int));
void library_unregister_add();
void library_add(int x, int y);
}
#endif /* LIBRARY_H */
//==================================
// AddResultCP.h
//==================================
#ifndef ADD_RESULT_CP_H
#define ADD_RESULT_CP_H
#include <set>
class AddResultReceiver;
class AddResultCP
{
public:
static AddResultCP &Instance();
void Register(AddResultReceiver *receiver) {
receivers.insert(receiver); }
void Unregister(AddResultReceiver *receiver) {
receivers.erase(receiver); }
private:
AddResultCP() { library_register_add(AddResultCallback); }
~AddResultCP() { library_unregister_add(); }
static void AddResultCallback(int result) {
Instance().NotifyAddResult(result); }
void NotifyAddResult(int result);
typedef std::set<AddResultReceiver *> ReceiverSet;
ReceiverSet receivers;
};
class AddResultReceiver
{
protected:
AddResultReceiver() { Register(); }
virtual ~AddResultReceiver() { Unregister(); }
void Add(int x, int y) { library_add(x, y); }
void Register() { AddResultCP::Instance().Register(this); }
void Unregister() { AddResultCP::Instance().Unregister(this); }
private:
friend class AddResultCP;
virtual void AddResult(int result) = 0;
};
#endif // ADD_RESULT_CP_H
//==================================
// AddResultCP.cpp
//==================================
AddResultCP &AddResultCP::Instance()
{
static AddResultCP cp;
return cp;
}
void AddResultCP::NotifyAddResult(int result)
{
for (ReceiverSet::iterator it = receivers.begin(); it !=
receivers.end(); ++it)
{
(*it)->AddResult(result);
}
}
To use this approach, one should derive from AddResultReceiver and implement
AddResult method. Then, she can call Add from the derived class and expect
AddResult to be called when the library prepares the result. Of course, this
is not a very polished C++ code, but it illustrates a possible approach.
Beware of:
(1) although Add is invoked on a single AddResultReceiver object, ALL
registered AddResultReceiver objects will receive the result (this is
actually a biggest problem in this approach, but sometimes you can't do
better),
(2) reentrancy (your AddResult method may be called in the middle of your
Add method),
(3) threading issues if you work on a multithreaded platform,
(4) temptation to add much functionality to the above classes. They should
serve just as an interface between C library and its C++ users - the real
job is performed by the users.
I don't know whether this suits your needs - I hope it may help.
Regards,
Rade