DuckQueen
DuckQueen

Reputation: 810

Having a type defined in C API how to associate it with C++ class in a namespace?

So we have in CAPI.h (and no implementation)

struct Message;
typedef struct Message Message;

And we have a CPP_API.h with

namespace Bla {
  struct Message {};
}

How to associate Bla::Message with Message defined in C API? In other words to make Bla::Message be an implementation of Message defined in C header?

Upvotes: 3

Views: 282

Answers (2)

Denilson Amorim
Denilson Amorim

Reputation: 10302

Inheritance could be used, inheriting from ::Message into Bla::Message.

However, this only works effectively if the C API deals with ::Message* pointers, not ::Message objects itself. That's probably a non-issue as most C libraries deals with opaque pointers.

First, expose to the library users the public C struct (Examples are not going to use namespaces for simplicity reasons):

typedef struct CMessage
{
    // Public data fields for the C API users.
    // Having such is **not** recommended when using this inheritance approach,
    // a completly opaque structure is recommended instead.
} CMessage;

Then internal functionalities should be implemented as methods of the Message class which inherits from CMessage:

struct Message : CMessage
{
    // Fields can be safely added here, assuming one do not ever remove fields off
    // CMessage causing the reduction of it's size.
    // All the fields defined here should be private to the implementation.

    int stuff;

    // Construct the Message and perhaps initialize CMessage fields.
    Message(int param)
    {
        this->stuff = param;
    }

    void DoStuff(int i)
    {
        // Perform heavy work on i
        this->stuff = i * 10;
    }
};

Then, the methods should be exported to the outer world by using plain C functions that deals with the base, CMessage, object as a pointer.

CMessage* msg_new(int k)
{
    return new(std::nothrow) Message(k);
}

void msg_do_stuff(CMessage* message, int i)
{
    return (static_cast<Message*>(message))->DoStuff(i);
}

void msg_free(CMessage* message)
{
    delete  (static_cast<Message*>(message));
}

Notice the use of the std::nothrow overload of <new>. This is used so that failed allocations returns null instead of throwing a exception. That's because C doesn't know about exceptions and well, just like C++ symbols, exceptions are not guaranteed to be binary level standardized and thus it's unsafe to propagate them to foreign code.


Another interesting approach, not really what the question asks, but still interesting are COM-like interfaces.

This is C++ specific (since it uses classes) but unlike exporting C++ classes the usual way, breaking compatibility between different compiler vendors that might use different symbolic representations for the method names, a virtual method table is exported which perhaps will have the same layout for all of the compilers in the platform in question. This because the vtables are very trivial so the platform ABI may define how a virtual method table should be layered in memory or there's a consensus of how to do so.

The COM approach is very common in the Windows world, Direct3D for instance uses something like this to communicate with the outer world.

First, the layout of the class/struct should be exposed to the API user, using abstract methods:

struct IMessage
{
    virtual ~IMessage() {}
    virtual void Release() = 0;
    virtual void DoStuff(int i) = 0;
};

One should not change the order or remove any of the methods when breaking binary compatibility is not intended. The same way, if new methods are added, those should be at the very end of the interface.

Then a derived object off the IMessage interface should be implemented:

struct Message : IMessage
{
    int stuff;

    Message(int param)
    {
        this->stuff = param;
    }

    ~Message()
    {
        // Perform cleanup
    }

    void Release()
    {
        // Release this Message object
        delete this;
    }

    void DoStuff(int i)
    {
        // Perform heavy work on i
        this->stuff = i * 10;
    }
};

To give the ability of the API user to create the object, one must export a C function which returns the interface pointer.

// The user is responsible for releasing the returned object pointer by calling obj->Release();
IMessage* MessageCreate(int param)
{
    return new(std::nothrow) Message(param);
}

Usually this approach makes the interface (IMessage) inherit from IUnknown to follow the COM patterns, but it's not necessary if the intent is purely resemble COM interfaces.

Upvotes: 7

Mark Lakata
Mark Lakata

Reputation: 20903

If your CAPI.h only has a forward declaration of Message, then you are free to do whatever you want in your C++ code, because a forward declaration of Message basically means that the C code knowns nothing about the implementation of Message and all could possibly do with the C code is pass around a pointer to Message, which has the same information content as void*.

I think it makes sense, rather, to keep the C struct name different from the C++ class you are going to use. I don't see any reason to keep the exactly the same. That just causes confusion. I would do

#include "CAPI.h"

class Foo
{
public:
   const Message* AsMessage() const { return reinterpret_cast<const Message*>(this);}
   Message* AsMessage()  { return reinterpret_cast<Message*>(this);}
   //
   + the rest of Foo
};

And then you can just pass the foo.AsMessage() to your C API that is expecting Message*.

If your C API is expecting struct Message as arguments (instead of a pointer to the struct), then what you have said is impossible, as you can not have struct Message declared in a C API but not defined in a C header. This is not C#; C and C++ requires that objects passed as values must be fully exposed in the header.

Upvotes: 0

Related Questions