65 Stub

A stub acts as a proxy for a remote object. Local calls made to the stub are converted into corresponding messages sent "over the wire" using IPC.

Most generally a function call can involve "in" parameters, "out" parameters and "in-out" parameters. We only support "in" parameters.

In general we assume the parameters are sent by serialising them to an Archive. This allows for marshalling of user defined types.

Example

Consider the following rpc interface Ix:


$interface rpc Ix
{
    void f1(int32 x, uint16 y);
    void f2(const X& x);
    void f3(const xstring& s);
};

Xcpp generates the following code to register a stub


struct StubIx : ceda::Stub
{
    StubIx() : Stub(ceda::ptr<Ix>(this),3) {}
    void f1(ceda::int32 x,ceda::uint16 y)
    {
        StubBegin(0,NULL) << x << y;
        StubEnd();
    }
    void f2(X const& x)
    {
        StubBegin(1,NULL) << x;
        StubEnd();
    }
    void f3(ceda::string8 const& s)
    {
        StubBegin(2,NULL) << s;
        StubEnd();
    }
};
struct StubIxFactory : public ceda::IStubFactory
{
    StubIxFactory()
    {
        ceda::RegisterStub(ceda::GetIpcInterfaceName<Ix>(),this);
    }
    ~StubIxFactory()
    {
        ceda::UnregisterStub(ceda::GetIpcInterfaceName<Ix>());
    }
    virtual ceda::Stub* CreateStub()
    {
        return new StubIx;
    }
};
void _Register_StubIx()
{
    static StubIxFactory s;
}

Stub.h

// Stub.h
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2020

@import "cxRpc.h"
@import "Ceda/cxObject/Object.h"
#include "Ceda/cxUtils/Archive.h"

namespace ceda
{
class Stub;

///////////////////////////////////////////////////////////////////////////////////////////////////
// IStubFactory

struct IStubFactory
{
    virtual Stub* CreateStub() = 0;
};

///////////////////////////////////////////////////////////////////////////////////////////////////
// Stub factory registry

// The StubFactoryRegistry is a transient registry indexed by fully qualified name of the
// interface.
// This allows the system to automatically generate a suitable stub object for a given interface

// The following three functions can be called by different threads - they are fully threadsafe.

// Returns false if stub factory has already been registered
@api bool RegisterStub(ConstStringZ qualifiedInterfaceName, IStubFactory* factory);

@api void UnregisterStub(ConstStringZ qualifiedInterfaceName);

// Create a stub for the given interface for which a stub factory has previously been
// registered.
// Returns nullptr if no stub factory for the given interface has been registered.
@api Stub* CreateStub(ConstStringZ qualifiedInterfaceName);

///////////////////////////////////////////////////////////////////////////////////////////////////
// Stub

/*
Base class for a stub.  More specifically, for a stub for a single implemented interface on a
single object.
*/

class @api Stub : public BaseMixin<Stub,EmptyBase>
{
    cxNotCloneable(Stub)
public:
    Stub(AnyInterface stub, ssize_t numMethods) :
        m_ar(nullptr),
        m_stub(stub),
        numMethods(numMethods)
    {
        // We assume there are no more than 256 methods, allowing the method index to be
        // serialised with a single octet.
        // todo:  Xcpp should allow for an alternative Stub to be used which uses two octets for the
        // index when the number of methods exceeds 256
        cxAssert(numMethods <= 256);
    }

    inline Archive& StubBegin(ssize_t index) const
    {
        cxAssert(0 <= index && index <= numMethods);
        *m_ar << (octet_t) index;
        return *m_ar;    // Return the archive used to push the arguments
    }

public:
    Archive* m_ar;
    AnyInterface m_stub;      // It is assumed this can be reinterpret cast to the interface provided by the stub
    ssize_t numMethods;
};

} // namespace ceda

Stub.cpp

// Stub.cpp
//
// Author David Barrett-Lennard
// (C)opyright Cedanet Pty Ltd 2020

@import "Stub.h"
#include "Ceda/cxUtils/xstring.h"
#include "Ceda/cxUtils/Tracer.h"
#include <map>
#include <mutex>

@def tf_StubFactory = false

namespace ceda
{

/*
@api const ceda::ReflectedClass& _GetReflected(Stub*)
{
    static const ceda::ReflectedClass rc =
    {
        0,
        "ceda::Stub",
        sizeof(Stub),
    };
    return rc;
}
*/

///////////////////////////////////////////////////////////////////////////////////////////////////
// StubFactoryRegistry

class StubFactoryRegistry
{
public:
    static StubFactoryRegistry& GetInstance()
    {
        static StubFactoryRegistry s;
        return s;
    }

    StubFactoryRegistry() { m_magic = MAGIC; }
    ~StubFactoryRegistry() { m_magic = 0; }

    bool Register(ConstStringZ qualifiedInterfaceName, IStubFactory* factory);
    void Unregister(ConstStringZ qualifiedInterfaceName);
    Stub* CreateStub(ConstStringZ qualifiedInterfaceName);

private:
    enum
    {
        MAGIC = 0xceda2009
    };
    ssize_t m_magic;

    // Map from fully qualified interface name to factory
    typedef std::map<xstring, IStubFactory*> MAP;
    MAP m_map;

    // Mutex used to control access to the map
    std::mutex mapMutex_;
};

bool StubFactoryRegistry::Register(ConstStringZ qualifiedInterfaceName, IStubFactory* factory)
{
    @if (tf_StubFactory)
    {
        Tracer() << "STUB: Registering stub for interface " << qualifiedInterfaceName << '\n';
    }

    std::lock_guard<std::mutex> lock(mapMutex_);

    MAP::iterator i = m_map.find(qualifiedInterfaceName);
    if (i == m_map.end())
    {
        m_map[qualifiedInterfaceName] = factory;
        return true;
    }
    else
    {
        return false;
    }
}

void StubFactoryRegistry::Unregister(ConstStringZ qualifiedInterfaceName)
{
    // When the application is closed ungracefully, Unregister can be called after
    // the StubFactoryRegistry has already destructed, so we check for this using a magic
    // value.
    if (m_magic == MAGIC)
    {
        std::lock_guard<std::mutex> lock(mapMutex_);

        MAP::iterator i = m_map.find(qualifiedInterfaceName);
        cxAssert(i != m_map.end());
        m_map.erase(i);
    }
}

Stub* StubFactoryRegistry::CreateStub(ConstStringZ qualifiedInterfaceName)
{
    @if (tf_StubFactory)
    {
        Tracer() << "STUB: Creating stub for interface " << qualifiedInterfaceName << '\n';
    }

    std::lock_guard<std::mutex> lock(mapMutex_);

    MAP::iterator i = m_map.find(qualifiedInterfaceName);
    if (i == m_map.end())
    {
        // Not found
        @if (tf_StubFactory)
        {
            Tracer() << "STUB: *** ERROR : Stub interface " << qualifiedInterfaceName << " not registered\n";
        }
        return NULL;
    }
    else
    {
        return i->second->CreateStub();
    }
}

///////////////////////////////////////////////////////////////////////////////////////////////////

@api bool RegisterStub(ConstStringZ qualifiedInterfaceName, IStubFactory* factory)
{
    return StubFactoryRegistry::GetInstance().Register(qualifiedInterfaceName,factory);
}

@api void UnregisterStub(ConstStringZ qualifiedInterfaceName)
{
    StubFactoryRegistry::GetInstance().Unregister(qualifiedInterfaceName);
}

@api Stub* CreateStub(ConstStringZ qualifiedInterfaceName)
{
    return StubFactoryRegistry::GetInstance().CreateStub(qualifiedInterfaceName);
}

} // namespace ceda