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

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

所以我们在 CAPI.h(并且没有实施)

struct Message;
typedef struct Message Message;

我们有 CPP_API.h 和

namespace Bla {
  struct Message {};
}

如何将Bla::Message与C API中定义的Message联系起来?换句话说,使 Bla::Message 成为 C 头文件中定义的 Message 的实现?

可以使用继承,从::Message继承到Bla::Message

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

首先,向库用户公开 public 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;

那么内部功能应该作为 Message class 的方法实现,它继承自 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;
    }
};

然后,应使用处理基础 CMessage 对象作为指针的普通 C 函数将方法导出到外部世界。

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

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


另一种有趣的方法,并不是问题真正要问的,但仍然有趣的是 COM 类接口。

这是特定于 C++ 的 (因为它使用 classes) 但与导出 C++ classes 的通常方式不同,它破坏了不同编译器供应商之间的兼容性可能对方法名称使用不同的符号表示,一个虚方法 table 被导出,它可能对相关平台中的所有编译器具有相同的布局。这是因为 vtable 非常微不足道,所以平台 ABI 可能会定义虚拟方法 table 应该如何在内存中分层,或者就如何做到这一点达成共识。

COM 方法在 Windows 世界中非常普遍,例如 Direct3D 使用类似的方式与外部世界通信。

首先,class/struct 的布局应该使用抽象方法暴露给 API 用户:

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

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

然后应实现 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;
    }
};

要赋予 API 用户创建对象的能力,必须导出一个 C 函数,其中 returns 接口指针。

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

通常这种方法使接口 (IMessage) 从 IUnknown 继承以遵循 COM 模式,但如果意图纯粹类似于 COM 接口。

如果你的 CAPI.h 只有 Message 的前向声明,那么你可以在 C++ 代码中自由地做任何你想做的事情,因为 Message 的前向声明基本上意味着 C 代码对 Message 的实现一无所知,所有可能用 C 代码做的就是传递一个指向 Message 的指针,它具有与 void*.

相同的信息内容

我认为让 C 结构名称与您将要使用的 C++ class 不同是有道理的。我认为没有任何理由保持完全相同。那只会引起混乱。我愿意

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

然后您可以将 foo.AsMessage() 传递给期望 Message*.

的 C API

如果你的 C API 期望 struct Message 作为参数(而不是指向结构的指针),那么你所说的是不可能的,因为你不能 struct Message在 C API 中声明但未在 C header 中定义。这不是 C#; C 和 C++ 要求作为值传​​递的 objects 必须在 header.

中完全公开