I'm trying to write a "pluggable factory", where class prototypes register
themselves into a base class in the constructor of a static member.
My problem is that in some cases, the constructor is not called.
A few lines of code should clear this up:
The Object class is defined as follows:
class Object
{
public:
static Object *Create(int id);
static void Register(int id, Object *proto);
virtual void DoSomething() = 0;
private:
static map<int, Object*> _protoMap;
};
The class contains a map of prototypes, identified by IDs, which are used to
generate sub-classes instances.
Here is the code of a typical sub-class, implemented in a separate source
file:
class SubClass: public Object
{
public:
void DoSomething() {...}
SubClass clone() {return new SubClass;}
private:
SubClass(int id)
{
Register(id, this);
}
static SubClass _Proto;
};
SubClass SubClass::_Proto(1);
It has a static member which registers itself in the class Object in its
constructor.
The two classes are part of a separate library, that is linked with my main
project.
To use this factory, the main program has just to do something like that:
Object *obj = Object::Create(1);
obj->DoSomething();
The point of all this is that we can use the class SubClass without any
reference to it. In the upper example, we execute the virtual function
SubClass::DoSomething via an Object pointer, without having any reference to
the real class in the main program.
My problem is that since there is no reference to the static member
SubClass::_Proto in the main project, the linker does not include it in the
executable, and the constructor is not called. So the prototype map is
empty, and I cannot use my factory.
Does somebody know how to have all the constructors of static members called
at startup ?
Thanks,
--
Nicolas
Thue usual idiom is as such :
class BaseFactory()
{
public:
typedef Base* (*ObjectCreator)();
static bool Register (int id, ObjectCreator creator)
{
//fill in the map...
//return true on succesfull registration, false on failure
}
};
class Derived1 : public Base
{
//...
};
//in Derived1.cpp, at file scope
namespace
{
Base* CreateDerived1()
{
return new Derived1();
}
bool derived1_registered=BaseFactory::Register(Derived1Id,
CreateDerived1);
}
derived1_registered being a global, it is initialized at startup. You
can do variations such as using templates instead of ObjectCreator for
more flexibility, using a Clone facory approach, etc...
Arnaud
MVP - VC
The variable derived1_registered you define in in Derived1.cpp isn't
referenced in the main project (and can't be since it's in an anonymous
namespace),
so the linker doesn't include the call to BaseFactory::Register in the
executable.
It is exactly the same problem I'm having with my static member: since it is
not referenced, the linker doesn't bother to include it in the binary, and
it is not added to my map.
<adeb...@club-internet.fr> wrote in message
news:1109926374.1...@f14g2000cwb.googlegroups.com...
The problem is that the static member constructor is only called if the
static member is explicitely referenced in the main project. Otherwise the
linker simply discards it (and thus its registration in the base class
static map).
Note that this works fine with g++.
--
Nicolas
"Murrgon" <mur...@hotmail.com> wrote in message
news:O1zG5uMI...@TK2MSFTNGP15.phx.gbl...
If you want to go with the static approach, check out the attached
article:
Why Pluggable Factories Rock My Multiplayer World GameDev.net
See Also:
Multiplayer and Networking:General
Why Pluggable Factories Rock My Multiplayer World
by Mason McCuskey
Introduction
I've developed a nasty habit over the years. Whenever I come across a
business programming article, I instinctively assume that it won't be
relevant to anything cool. My initial reaction is usually "OK, wow, this is
great for writing middleware, but probably useless in game programming."
Most of the time this turns out to be true (when was the last time you used
a SQL database to store saved games?), however, there are always a few
articles that describe something that can be useful for game programming.
One of those articles recently appeared in the magazine "C++ Report."
(http://www.creport.com). Timothy R. Culp wrote an article entitled
"Industrial Strength Pluggable Factories." In it, he describes a very
valuable trick, not only in the business world, but in game programming as
well.
This article is an attempt to take Mr. Culp's work and bring it down into
the scary mosh pit of game development. Before continuing, head over to the
C++ Report website and read the pluggable factories article. I'm not going
to duplicate what's already been said; I'm going to assume you've read the
article and know the basics, and I'm going to dive straight into showing how
Pluggable Factories can be used to simplify DirectPlay communications.
The Problem
Networked multiplayer apps today must deal with a wide variety of messages.
There's the standard set of DirectPlay messages (Create Player, Delete
Player, Chat, etc.), as well as the army of messages your game needs to
communicate. All of these messages have their own data items, and they all
must be able to send themselves through a DirectPlay connection and
reassemble themselves on the other side. It's your job as a network game
programmer to sort everything out so that your game has an elegant way to
send and receive its information.
The obvious way to do it in C++ is to use classes to represent the different
messages. These classes contain all of the data for a particular message, as
well as methods that serialize and deserialize the data into a byte stream
(suitable for sending over a DirectPlay connection). Also, since all of the
messages have certain data elements in common (like, who the message was
from, and who it's going to), it makes sense to implement an abstract base
class and then derive each different message type from it, like so:
// the net_message base class
class net_message
{
public:
net_message() { }
~net_message() { clear(); }
void clear(void) { }
virtual int serializeto(byte *output) { return(0); }
virtual void serializefrom(byte *fromdata, int datasize) { }
DPID getfrom(void) { return(m_from); }
DPID getto(void) { return(m_to); }
protected:
void setfrom(DPID id) { m_from = id; }
void setto(DPID id) { m_to = id; }
DPID m_from;
DPID m_to;
};
// a specific message derived from the base class - this
// example corresponds to DPSYS_CREATEPLAYERORGROUP.
class net_message_createplayerorgroup : public net_message
{
public:
int serializeto(byte *output);
void serializefrom(byte *fromdata, int datasize);
uti_string getplayername(void) { return(m_playername); }
bool isgroup(void) { return(m_isgroup); }
net_byteblob &getdata(void) { return(m_data); }
private:
net_byteblob m_data;
uti_string m_playername;
bool m_isgroup;
};
// convert a directplay message into our class
void net_message_createplayerorgroup::serializefrom(byte *fromdata, int
datasize)
{
LPDPMSG_CREATEPLAYERORGROUP lp = reinterpret_cast(fromdata);
m_data.setdata(lp->lpData, lp->dwDataSize);
m_isgroup = (lp->dwPlayerType == DPPLAYERTYPE_GROUP);
namestructtoplayer(lp->dpnName, m_playername);
}
// another derivation.
class net_message_destroyplayerorgroup : public net_message
{
public:
int serializeto(byte *output) { output = NULL; return(-1); }
void serializefrom(byte *fromdata, int datasize);
uti_string getplayername(void) { return(m_playername); }
bool isgroup(void) { return(m_isgroup); }
private:
bool m_isgroup;
uti_string m_playername;
};
// convert a directplay message into our class
void net_message_destroyplayerorgroup::serializefrom(byte *fromdata, int
datasize)
{
LPDPMSG_DESTROYPLAYERORGROUP lp = reinterpret_cast(fromdata);
m_isgroup = (lp->dwPlayerType == DPPLAYERTYPE_GROUP);
namestructtoplayer(lp->dpnName, m_playername);
}
Sending these messages isn't a problem - if the client wants to send a
certain message, it instantiates the appropriate class, fills up the class
with the data it wants to send, and then calls the serializeto() method,
which squishes everything into a byte stream, which is then sent using
IDirectPlay->Send(). So far, so good.
The problem is on the receiving end. Developing this class-based approach to
messaging means that when we receive a message, our program will have to
conjure up the appropriate class using nothing but the ID byte contained
within the received message. In other words, our receive code must be able
to look at a message and say, "OK, that's ID ___. that's a ____ message, so
I need to construct a class of type ____." Then, we must deserialize the
data back into the members of the class.
Why Pluggable Factories Rock
Pluggable factories are a solution to that problem. Imagine you write a new
message class that you want to use in your program. Now, imagine that you
can add support for your custom messages by simply adding the source files
to the project. That's right - you don't change any lines in your networking
engine. you simply add your files to the project and recompile.
Sound too good to be true? It's not. Pluggable factories use a few C++
tricks, but it's not rocket science.
Meet Your Maker
"Blessed are the game programmers, for they shalt not have to deal with
legacy file formats."
The pluggable factory relies on two key C++ tricks: polymorphism (derived
classes and virtual functions), and static class members.
Let's look at some code. This code is straight from the networking engine of
my upcoming multiplayer puzzle game, Quaternion (see my homepage for more
information). I've called my base pluggable factory net_message_maker; by
convention, pluggable factories usually have the word "maker" somewhere in
their class name. This not only quickly tells any programmer what they are,
but it also allows us writers to amuse ourselves by creating clever names
for the sections of our articles.
class net_message_maker
{
public:
net_message_maker(int type) {
m_registry.insert(std::make_pair(type, this));
}
static net_message *constructmessage(byte *data, int datasize);
protected:
typedef std::map net_message_maker_map;
static net_message_maker_map m_registry;
virtual net_message *makemessage(byte *data, int datasize) const = 0;
};
For its power, net_message_maker is a fairly simple little class. The
constructmessage() function is the one we're interested in; this function
takes a raw byte stream and creates the appropriate net_message derivative
instance. Note that this function is static, so you don't need to actually
instantiate a net_message_maker to use it (simply say
net_message_maker::constructmessage(.)).
Notice the makemessage() pure virtual function. makemessage() is not the
same thing as constructmessage(); makemessage() is only implemented in the
derivitive classes, and is responsible for newing the message and
deserializing it.
We have one constructor, which takes one argument - the type of message
(i.e. DPSYS_SESSIONLOST, etc.) Notice that this constructor simply hands off
to the base class constructor, which takes the message type, pairs it with a
pointer to itself, and inserts the pair into a map (if you're not familiar
with STL, you might want to learn about maps before continuing). Notice that
the map the constructor inserts into - m_registry -- is static, which means
it's shared by all classes, and by all derivative classes as well.
That's all there is to the base maker class. One static map, one static
function, one pure virtual function.
Now let's look at a maker derivation. You'll need to derive a different
maker for each message you want to support - you can either use templates,
or some old-fashioned #define trickery, or even (horror of horrors) cut and
paste to create them.
class net_message_createplayerorgroup_maker : public net_message_maker
{
public:
net_message_createplayerorgroup_maker() :
net_message_maker(DPSYS_CREATEPLAYERORGROUP) { }
private:
net_message *makemessage(byte *data, int datasize) const
{
net_message_createplayerorgroup *msg = NULL;
try {
// construct the appropriate message type
msg = new net_message_createplayerorgroup;
// tell the message to populate itself using the byte stream
msg->serializefrom(data, datasize);
} catch(...) {
// handle errors!
}
return(msg);
}
static const net_message_createplayerorgroup_maker m_registerthis;
};
Notice the m_registerthis variable. This is one of the tricks Mr. Culp
pointed out, and I hinted at eariler. The C++ language says that static
members of classes are initialized at program startup. So, if this code is
part of the program when it starts up, the constructor for the
m_registerthis variable is going to get called. The m_registerthis
constructor calls the base net_message_maker class constructor, which pairs
the this pointer with the ID given (in this case,
DPSYS_CREATEPLAYERORGROUP). We never explicitly use m_registerthis anywhere
else in the code; it's sole purpose is to trick the compiler into running
the constructor at program startup. (Granted, if we have multiple static
variables, the C++ spec doesn't specify in which order the constructors are
called, but that doesn't matter to us).
What this means is that before the first line of our WinMain() is executed,
the m_registry member is going to contain a valid map, linking all
registered message_makers to their message IDs. This is how it's possible to
add support for a new message without changing one line of the networking
code.
How It Works
Now let's take a look at the heart of the whole system: the function that
takes a message ID and returns the appropriate class.
net_message *net_message_maker::constructmessage(byte *data, int datasize)
{
// cast the raw memory to a generic message to determine its type
LPDPMSG_GENERIC lpMsg = (LPDPMSG_GENERIC)data;
try {
// find the appropriate factory in the map of factories...
net_message_maker *maker =
(*m_registry.find(lpMsg->dwType)).second;
// use that factory to construct the net_message derivative
return maker->makemessage(data, datasize);
} catch(...) {
err_printf("net_message_maker::constructmessage: logic error, I don't
know
how to (or can't) construct message ID %d!", lpMsg->dwType);
}
return(NULL);
}
Let's say I've just received a big blob of data from DirectPlay's receive
function, and now I want to convert that blob of data into the appropriate
net_message derivative. I call net_message_maker::constructmessage(), giving
it the blob of data, and the size of the blob of data.
The first thing constructmessage() does is cast the raw data to a generic
message. This is the sort of "blind casting" that should make any good C++
programmer freeze in terror, but it's a necessary evil. The DirectX docs
even tell us to do it this way.
Once we've cast the blob, we know the type of the message: lpMsg->dwType. We
look in our m_registry variable, and pull out the correct pair. Then we get
the second member of that pair, which is really the this pointer that the
constructor registered at program start. (If we can't find the type,
m_registry.find() is going to return NULL (or, in debug, 0xcdcdcdcd), which
will generate an exception on the next line, and will land us in the
exception handler for the function. Not the cleanest way to do things, but
it gets the job done).
Assuming nothing goes wrong, the local variable "maker" now points to the
appropriate factory we should use to construct the message. We then call the
makemessage() function of that factory (we can do so, because we have access
to the private methods of other instances of ourselves). makemessage() is a
pure virtual function, so we'll end up inside of the appropriate maker.
makemessage() news up the appropriate net_message derivative, and then tells
that instance to deserialize itself from the provided byte blob. Now we have
a perfect net_message, all ready to go.
From here, you can do whatever you want. Maybe your networking system is
like mine, and stores all of the incoming messages in a vector. or maybe
you've got some thread action happening, and have a secondary thread
processing the messages. That really doesn't matter - what matters is that
with one simple function call, constructmessage(), you've transformed a byte
blob into a C++ class.
Conclusion
Congratulations, you now know about pluggable factories. Keep in mind that
this technique, as Mr. Culp explains, isn't just for networking messages.
Basically any place in your code where you need to turn an ID byte into a
class is a great place for pluggable factories. There's a lot more power
contained in this pattern than I'm illustrating; the purpose of this article
was to show you how to apply a theoretical concept directly to your code.
And, just maybe, to make you think twice before you cast off that "business
programming journal" as useless. :)
Mason McCuskey is the leader of Spin Studios, an indie development group
looking to break into the industry by creating a great game, Quaternion, and
getting it published. He can be reached via the Spin Studios website
(http://www.spin-studios.com), and doesn't mind answering your questions by
email at ma...@spin-studios.com.
Discuss this article in the forums
Date this article was posted to GameDev.net: 11/10/1999
(Note that this date does not necessarily correspond to the date the article
was written)
© 1999-2002 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Send us an e-mail!
>
> The point of all this is that we can use the class SubClass without
> any
> reference to it. In the upper example, we execute the virtual function
> SubClass::DoSomething via an Object pointer, without having any
> reference to
> the real class in the main program.
>
>
> My problem is that since there is no reference to the static member
> SubClass::_Proto in the main project, the linker does not include it
> in the
> executable, and the constructor is not called. So the prototype map is
> empty, and I cannot use my factory.
>
> Does somebody know how to have all the constructors of static members
> called
> at startup ?
>
By referencing them. :-)
Or at least something in the same object file. Or don't put it in a
library.
The linker *is* supposed to skip unreferenced modules while linking to a
library. That's the general idea of using libraries...
Bo Persson
A very common question indeed.
You've already figured out the problem - the linker (correctly, IMO) simply
ignores your .OBJ file if it's placed in a library, so your plug-in is
simply excluded from the program.
The solution is to force the OBJ to be included in the link. There are a
number of ways to accomplish this.
1. Don't put your OBJ files into a LIB, but link them directly.
2. Explicitly call something in the plug-in from the main (thus somewhat
compromising your elegant pluggable factory).
3. Use linker options to explicitly force your plug-in modules to be linked.
4. Don't list the library containing your plug-ins as a library. Rather,
add it to the linker input as an additional OBJ (in which case everything in
the LIB will be included).
Option 4 is the easiest - assuming you really do want everything in the LIB,
and assuming you're doing a command-line build. There's no obvious way to
do this from the IDE (it cleverly recognizes the LIB file as a library and
passes it to the linker as such).
Option 3 is usually a reasonable approach. Typically what's done is you
have a header file that's included in your factory and that header file
lists all of the plug-ins that you want to have included in the link. You
force a plug-in into the link by using
#pragma comment(linker,'/include:your_symbol_name_here')
Of course, the symbol names that you need to reference are "mangled", so
you'll typically use some macro voodoo to hide all the messiness.
ATL provides an example of such a mechanism in it's OBJECT_ENTRY_AUTO macro.
Assuming you have Visual Studio (not the toolkit or the Express edition
beta), take a look at atlcom.h, around lines 2100 - 2116.
HTH
-cd
Sorry, I missed the part about the plug-in being in a library.
Unfortunately, both idoms don't work well with static libs nor with DLLs.
See Carl's post for a very complete ans detailed answer (as usual)
Arnaud
MVP - VC
Since I don't want the users of my lib to do anything particular about it,
the only option I have is number 3.
The problem is that when I add a new derived class, I cannot just add the
cpp file in my lib, I'll have also to modify the factory header file. I was
hoping there was a magic pragma I could have used in the cpp file that said
"include me in any executable using my lib". Apparently I was just dreaming
:-).
Anyway your solution is still indeed very reasonable, since no work has to
be done by the library user. I'll just have now to look into this ATL voodoo
macros...
Btw, what I said about g++ was false; the exact same problem arises when
using a separate library file.
Bye,
--
Nicolas