BREW interfaces - implementing a new interface

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 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);

Ben Blaukopf
in BREW

Airsource design and develop apps for ourselves and for our clients. We help people like you to turn concepts into reality, taking ideas from initial design, development and ongoing maintenance and support.

Contact us today to find out how we can help you build and maintain your app.