[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ] [ Search: ]

4.3.1.1 How it Works

This section contains technical details about SCF. If you're not interested in details, you can skip directly to the next section which explains how SCF should be used from the end-user's point of view.

SCF is somewhat similar to COM. This is because historically Crystal Space initially used COM for inter-module communication. Thanks to Dan Ogles for the initial implementation of a cross-platform COM library; his implementation was a good test-bed for this SCF implementation.

The differences between COM and SCF are due to the increased need for an easier-to-use and more lightweight mechanism than COM. The COM implementation caused some problems (because of historic bugs in EGCS 1.1.x and incompatibilities with platforms to which Crystal Space has ported). Also it has some problems due to the native Windows types used in COM; this experience was taken into account while developing SCF.

The main paradigm of SCF, as in COM/CORBA/Java/etc. is the interface. You define an interface, that is, a set of abstract methods that you want to access within some object. SCF interfaces are plain C++ structs (or classes, it doesn't matter much except that if you're using classes to which you will have to add the ‘public:’ keyword). Theoretically, interfaces can contain member variables and inline methods (though not non-virtual methods or constructors), but this practice is not encouraged because later someone may want to add a COM, CORBA, etc. layer between the client and the actual object, and this will not be possible if the interfaces contains variables, inline methods, or non-virtual methods. If you don't plan to use COM or CORBA later, then you can use variables and inline functions as much as you like in your own code. In Crystal Space code, the use of inline methods is acceptable if the methods have the nature of small helper or convenience methods and would work for any implementation. The use of member variables is highly discouraged.

Here is an example of an interface in a file named ‘idog.h’:

 
struct iDog
{
  virtual bool IsAlive() = 0;
  virtual char const* GetName() = 0;
  virtual void SetName (char const*) = 0;
  virtual void Shout(int Volume) = 0;
  virtual void Run(int Speed, float Direction) = 0;
  virtual bool GetChildren(iObjVector* oBrood) = 0;
};

Note the last method that gets a pointer of ‘iObjVector’ type. ‘iObjVector’ is yet another interface. We could pass a pointer to a ‘csObjVector’ (the implementation of that interface) as well, but this will mean both modules (caller and callee) should have same idea about what a ‘csObjVector’ is, and if it happened that you compiled the shared library with a slightly different version of ‘csObjVector’ (that, say, had one member variable fewer) you will end up with a SIGSEGV crash. By instead specifying the abstract ‘iObjVector’, there is no need to link the ‘csObjVector’ class into both the caller and the callee.

Now let's write a particular implementation of the above interface.

 
#include "idog.h"

class MyDog : public iDog
{
private:
  // private member functions & variables ...
  csString Name;
public:
  virtual bool IsAlive();
  virtual char const* GetName();
  virtual void SetName(char const*);
  virtual void Shout(int Volume);
  virtual void Run(int Speed, float Direction);
  virtual bool GetChildren(iObjVector* oBrood);
  ... public member functions & variables ...
};

bool MyDog::IsAlive()
{
  return true;
};

char const* MyDog::GetName()
{
  return Name;
}

void MyDog::SetName(char const* NewName)
{
  Name = NewName;
}

// And so on ...

Now, we put the actual implementation into a separate module (i.e. into a shared library), and include within the client just the interface file ‘idog.h’. Since the client does not have any idea how we should build an object of the ‘MyDog’ class, we also provide a function that will return a newly-allocated object of that class. This is called the class factory (in fact, a class factory is a bit more than just this, but more about this later). Here is how to do it:

 
static iDog* MyDog_Create()
{
  return new MyDog();
}

Okay, we did it. Now back to the client. To work with an object that implements the ‘iDog’ interface we need to load the shared library, get a pointer to the MyDog_Create() function, call it and get a new ‘MyDog’ object. Further we work with this pointer as if it were pointing to an ‘iDog’ object:

 
csLibraryHandle handle = csLoadLibrary("./libdog.so");
iDog (*iDog_Create)() = csGetLibrarySymbol(handle, "MyDog_Create");
iDog* dog = iDog_Create();
printf("Doggy's name is %s\n", dog->GetName());
dog->Shout(100);
...

Of course, you don't have to do all this stuff manually with the SCF implementation. The SCF library provides a number of useful templates and functions which hide these ugly implementation details from end-user.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

This document was generated using texi2html 1.76.