BREW makes heavy use of an interface framework derived from COM. It's very useful to understand the internals of how it works, especially if you want to extend any of the built in classes. In this series of articles, I will discuss:
- Part 1. The internals of a BREW interface.
- Part 2. How to implement a simple new interface.
- Part 3 (not published yet). How to publish your new interface as an extension.
Part 2. How to implement a simple interface.
As with any task, the simplest way to do something is to copy an existing solution and adapt it to your own needs. If you want to get up and running quickly, then a very good approach is to download the uiOneSDK, which includes Widgets, and checkout widgets/src/BitmapWidget.c, which includes a simple class exposing the IImage interface.
However, in this article, we'll go through the steps of implementing an interface all the way from scratch. We're going to implement a very simple interface, that extends IAStream, treats the stream data as ASCII, and capitalizes it. Obviously this is pretty useless as it stands, but it provides a good basis for more useful streams, such as encryption classes. You should not that we take some shortcuts which mean that the implementation as it stands is not suitable for using in an extension.
First, decide on the interface
We'll call it IUCStream, for Upper Class Stream. It looks exactly like an IAStream, with one additional interface - IUCSTREAM_EnableTranslation which enables or disables the upper type translation.
Write the public header (AEEUCStream.h)
The public header needs to declare the layout of the interface, so that other pieces of code can use it. It does not have to - and should not - say anything about the private implementation of the class.
// Include our base interface definition. AStream is defined in AEE.h
#include <AEE.h>
// Provide a cast method
#define IUCSTREAM_TO_IASTREAM(po) (IAStream*)(po)
// Define a struct that lets people inherit from the new interface
#define INHERIT_VTBL(IUCStream) \
INHERIT_VTBL(IAStream) \
void (*EnableTranslation)(iname * pIUCStream, boolean enable)
// Define the new interface
DEFINE_VTBL(IUCStream)
{
INHERIT_VTBL(IUCStream);
};_
// Set up standard macros
// We only define one extra function. We do not define the base
// functions. Since this class is expected to be used like an
// IAStream, we will override a couple of functions by hooking into
// the IAStream implementations. See later
#define IUCSTREAM_EnableTranslation \
AEEGETPVTBL(IUCStream, po)->EnableTranslation()
Write the private header (UCStream.hpp)
The private header declares the general implementation of the class, storage of the vtables, and declares functions that will implement interface methods,
#include <AEEUCStream.h>
// This class MUST NOT use any C++ virtual functions, as we are reliant
// on the first 4 bytes being the pointer to the interface vtable. If
// we use virtuals, then the first four bytes will instead be a pointer
// to a C++ vtable. This is useful if you want to model BREW interfaces
// in C++ - but only if you are not using the ADS or RVCT 1.2
// compilers, as these have a non-compliant ABI
class UCStream
{
public:
// Factory method to create an IUCStream wrapped around an IAStream
static IUCStream* create(IAStream*);
private:
// The VTable
const AEEVTBL(IUCStream)* pvt;
// Store a reference to the stream we are wrapping
IAStream* stream;
// Is translation on?
boolean translationEnabled;
// Static member implemnetations of interface functions
static uint32 Release(IAStream* pStream);
static int32 Read(IAStream* pStream,
void* pBuffer,
uint32 dwCount);
static void EnableTranslation(IAStream* pStream,
boolean enable);
// Other interface implementations hook onto the IAStream
// implementation
UCStream(IAStream* pStream);
~UCStream(void) {}
};
Write the factory method implementation
The first thing we need to do is construct one of these new classes. Since we are not writing an extension, but merely implementing a new interface to be used within our own code, we do not need to hook into CreateInstance, and we can set up the IAStream private member in construction, which is normally impossible. We use a single heap allocation for both the class memory space and the vtable. In order to do this in a slightly safer manner, and save ourselves from arithmetic, we use MALLOCREC_EX from AEEStdLib.h.
#include "AEEStdLib.h" // For MALLOCREC_EX_
#include <new> // For placement new
IUCStream* UCStream::create(IAStream* pStream)
{
// Allocate space for both the vtable and the class itself
UCStream* ucs = MALLOCREC_EX(UCStream, sizeof(AEEVTBL(IUCStream));
// Placement new
new (ucs) UCStream(pStream);
return (IUCStream*)ucs;
}
UCStream::UCStream(IAStream* pStream)
{
// Initialise the vtable
AEEVTBL(IUCStream*) vt = (AEEVTBL(IUCStream)*)(this + 1);
pvt = vt;
Now we come to initialising the vtable. As we mentioned, we'll be using IASTREAM default methods in several cases. We could write a UCStream::AddRef method that simply called through to IASTREAM_AddRef - but it's much easier to use our knowledge of the vtable layout, and point directly at the correct method. We would need to be a bit more rigorous in our implementation if writing an extension with its own class id - this will be discussed in Part 3.
// Use the base AStream implemnetations for some functions
AEEVTBL(IAStream)* astreamVT = AEEGETPVTBL(pStream, IAStream);
vt->AddRef = astreamVT->AddRef;
vt->Readable = astreamVT->Readable;
vt->Cancel = astreamVT->Cancel;
// Point the rest at our own implementations
vt->Release = UCStream::Release;
vt->Read = UCStream::Read;
vt->EnableTranslation = UCStream::EnableTranslation;
}
Implement the interface
This is the easiest bit. We want to take data read from the IAStream, and do some post-processing on it.
#define ME_FROM_IUCSTREAM(pStream) UCStream* me = (UCStream*)pStream;
int32 UCStream::Read(IUCStream* pStream, void* pBuffer, uint32 dwCount)
{
ME_FROM_IUCSTREAM(pStream);
int32 bytes_read = IASTREAM_Read(me->stream, pBuffer, dwCount);
if(bytes_read > 0 && me->translationEnabled)
{
char* buf = (char*)pBuffer;
char* end = buf + bytes_read;
while(buf < end)
{
if(*buf >= 'a' && *buf <= 'z')
{
*buf += 'A' - 'a';
}
buf++;
}
}
return bytes_read;
}
Our Release method uses the reference counting of IAStream to avoid the need to store a reference count ourselves. When the ref count hits 0, we'll cleanup this object as well.
uint32 UCStream::Release(IUCStream* pStream)
{
ME_FROM_IUCSTREAM(pStream);
int32 nRefs;
nRefs = IASTREAM_Release(me->stream);
if(0 == nRefs)
{
delete me;
}
return nRefs;
}
Finally, and simplest of all, we implement the new method
void UCStream::EnableTranslation(IUCStream* pStream, boolean enable)
{
ME_FROM_IUCSTREAM(pStream);
me->translationEnabled = enable;
}
Use the interface in your code
You can use an IUCStream anywhere that you'd use an IAStream. For example:
IAStream* pAStream;
// Get an ASTREAM from somewhere - simplest way is to construct a
// MEMSTREAM and use that as an ASTREAM, or build an IPEEK from
// an ISOURCEUTIL
...
IUCSStream pUCStream = UCSStream::create(pAStream);
IUCSTREAM_EnableTranslation(pUCStream, TRUE);
// Now use it like an IAStream in any method that requires an
// IAStream*. You can directly cast - or better use the defined
// cast macro
int32 bytes_read = IASTREAM_Read(
IUCSTREAM_TO_IASTREAM(pUCStream),
pBuffer,
dwCount);