在C API中定义的类型如何将它与命名空间中的C++类相关联?

Duc*_*een 3 c c++ implementation

所以我们在CAPI.h中(没有实现)

struct Message;
typedef struct Message Message;
Run Code Online (Sandbox Code Playgroud)

我们有一个CPP_API.h

namespace Bla {
  struct Message {};
}
Run Code Online (Sandbox Code Playgroud)

如何Bla::MessageMessageC API中定义的关联?换句话说,要Bla::Message成为MessageC头中定义的实现?

Den*_*rim 7

继承可以使用,继承::MessageBla::Message.

但是,只有在C API处理::Message*指针而不是::Message对象本身时,这才有效.这可能不是问题,因为大多数C库处理不透明指针.

首先,向库用户公开公共C结构(为简单起见,示例不会使用名称空间):

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;
Run Code Online (Sandbox Code Playgroud)

然后内部功能应该作为Message继承自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;
    }
};
Run Code Online (Sandbox Code Playgroud)

然后,应该使用处理base的纯C函数将CMessage对象作为指针导出到外部世界.

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));
}
Run Code Online (Sandbox Code Playgroud)

注意使用std :: nothrow重载<new>.这是使用失败的分配返回null而不是抛出异常.这是因为C不知道异常,就像C++符号一样,异常不能保证是二进制级标准化的,因此将它们传播到外部代码是不安全的.


另一个有趣的方法,不是真正的问题,但仍然有趣的是类似COM的接口.

这是C++特定的(因为它使用类)但不像通常的方式导出C++类,打破可能对方法名称使用不同符号表示的不同编译器供应商之间的兼容性,导出的虚拟方法表可能具有相同的布局有问题的平台中的所有编译器.这是因为vtable非常简单,因此平台ABI可以定义如何在内存中分层虚拟方法表,或者对如何这样做有共识.

COM的做法是很常见的Windows世界,Direct3D的,例如使用类似于这种与外部世界沟通.

首先,应使用抽象方法向API用户公开类/结构的布局:

struct IMessage
{
    virtual ~IMessage() {}
    virtual void Release() = 0;
    virtual void DoStuff(int i) = 0;
};
Run Code Online (Sandbox Code Playgroud)

如果不打算破坏二进制兼容性,则不应更改顺序或删除任何方法.同样,如果添加新方法,那些方法应该在界面的最后.

然后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;
    }
};
Run Code Online (Sandbox Code Playgroud)

为了使API用户能够创建对象,必须导出一个返回接口指针的C函数.

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

通常这种方法使得interface(IMessage)继承IUnknown遵循COM模式,但如果intent完全类似于COM接口则没有必要.

  • 你不应该在C代码中抛出异常,这禁止使用常规的`new`.使用`new std :: nothrow Whatever`代替.否则竖起大拇指! (2认同)