Given a type defined in the C API, how do I associate it with a C ++ class in a namespace?

So, we have in CAPI.h (and without implementation)

struct Message;
typedef struct Message Message;

      

And we have CPP_API.h with

namespace Bla {
  struct Message {};
}

      

How to bind Bla::Message

to the Message

one defined in the C API? In other words, make it the Bla::Message

implementation Message

defined in the C heading?

+3


source to share


2 answers


Inheritance can be used by inheriting from ::Message

to Bla::Message

.

However, this only works efficiently if the C API deals with ::Message*

pointers, not objects ::Message

. This is probably not a problem, as most C libraries handle opaque pointers.

First, display the public C structure to the library users (examples won't use namespaces for simplicity):

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 the inner functions must be implemented as methods of the class Message

that 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 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;
    }
};

      

The methods must then be exported to the outside world using simple C functions that deal with the base CMessage

, the 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));
}

      

Note the use of the std :: nothrow overload <new>

. This is used so that failed allocations return null instead of throwing an exception. This is because C is unaware of exceptions and well, like C ++ characters, exceptions are not guaranteed by the standardized binary level, and hence it is not safe to propagate them to external code.


Another interesting approach, not exactly what the question is asking, but still interesting COM- like interfaces.



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

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

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

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

      

Do not reorder or delete any of the methods if binary compatibility is violated. Likewise, if new methods are added, they should be at the very end of the interface.

Then a derived object outside of the interface must be implemented IMessage

:

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 enable the API user to create an object, you must export a C function that returns an 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);
}

      

Typically this approach forces interface ( IMessage

) to inherit from IUnknown

in order to follow COM , but this is optional if the intent is purely similar to COM interfaces.

+7


source


If your CAPI.h only has a direct message declaration, then you can do whatever you want in your C ++ code, since a direct declaration Message

basically means that the C code knows nothing about the implementation Message

and everything is possible. with C code pass a pointer to a Message that has the same informational content as void*

.

I think it makes more sense, rather, to leave the C struct name different from the C ++ class you intend to use. I see no reason to keep the same. It 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 foo.AsMessage()

to your C API that expects Message*

.

If your C API expects struct Message

as arguments (instead of a pointer to a structure), then what you said is not possible, since you cannot declare struct Message

in the C API, but is not defined in C. This is not C #; C and C ++ require that objects passed as values ​​must be fully exposed in the header.

0


source







All Articles