Files
oak-editor/docs/zh/modularization-plan/09-c-api-design.md
T

20 KiB
Raw Blame History

C API 设计规范总纲

必读:本文件定义了所有 Olive/Oak 动态库的 C API 设计约定。01-07- 各模块的 API 均遵循此规范。


1. 设计原则

1.1 不透明指针(Opaque Pointer

所有 C++ 对象在 C 接口中均隐藏实现,仅暴露为 struct 的前向声明:

// 公共头文件(.h)中
typedef struct OliveNodeGraph OliveNodeGraph;  // 只有声明,无定义

// 实现文件(.cpp)中
struct OliveNodeGraph {
    olive::NodeGraph* impl;  // 实际的 C++ 对象
};

外部代码只能操作指针,无法解引用或 sizeof。

1.2 纯 C 接口

  • 函数名使用 snake_case,前缀为 olive_<module>_
  • 参数和返回值仅使用 C 基础类型、结构体、不透明指针。
  • 禁止使用 C++ 特性:类、引用、重载、模板、异常、std::stringQString
  • 字符串使用 const char*UTF-8 编码)。
  • 布尔值使用 int0 = false,非 0 = true)。

1.3 动态库自身可以用 C++

动态库的实现文件(.cpp)内部可以继续使用:

  • QtQObject, QString, QList, 信号槽等)
  • C++ STL
  • 虚函数、模板、Lambda
  • 异常(但不得穿透 C 接口边界)

C 接口层只是薄薄的封装胶合层。


2. 命名规范

元素 规范 示例
类型名 Olive + PascalCase OliveNodeGraph, OliveFrame
函数名 olive_<module>_<snake_case> olive_node_graph_create, olive_codec_decoder_open
枚举名 Olive<Module><PascalCase> OliveCodecResultOk, OliveRenderModeOffline
常量宏 OLIVE_<MODULE>_UPPER_SNAKE OLIVE_NODE_OK, OLIVE_CODEC_ERROR_NOT_FOUND
版本宏 OLIVE_<MODULE>_API_VERSION OLIVE_NODE_API_VERSION 1

3. 内存管理约定

3.1 谁创建,谁释放

  • 库创建的对象,必须由库的对应 destroy/free 函数释放。
  • 主进程分配并传入的缓冲区(如 char* 参数),由主进程管理,库内部只读或复制。
  • 库返回的字符串/缓冲区,必须使用库提供的 free 函数释放,不能用 C 标准 free()(因为库的堆和主进程的堆可能是分离的,尤其是在 Windows 上)。
// 正确:库分配,库释放
char* xml = olive_node_graph_save_xml(graph, &len);
// ... 使用 xml ...
olive_core_free(xml);  // 使用库提供的释放函数

// 错误:
free(xml);  // 危险!堆可能不一致

3.2 通用释放函数

每个模块提供一个通用释放函数:

void olive_core_free(void* ptr);   // 释放字符串/二进制缓冲区
void olive_core_mem_free(void* ptr, size_t size);  // 带大小的释放(用于安全擦除)

3.3 对象生命周期模式

// 模式 ACreate/Destroy(堆分配)
OliveNodeGraph* olive_node_graph_create(void);
void olive_node_graph_destroy(OliveNodeGraph* obj);

// 模式 BInit/Cleanup(栈分配或外部缓冲区)
int olive_frame_init(OliveFrame* frame, int w, int h, OlivePixelFormat fmt);
void olive_frame_cleanup(OliveFrame* frame);

// 模式 CRef/Unref(引用计数)
void olive_frame_ref(OliveFrame* frame);
void olive_frame_unref(OliveFrame* frame);

优先使用 模式 ACreate/Destroy,因为不透明指针天然适合堆分配。


4. 错误处理

4.1 返回码约定

所有可能失败的函数返回 int

#define OLIVE_OK                0   // 成功
#define OLIVE_ERROR_GENERIC    -1   // 通用错误
#define OLIVE_ERROR_INVALID    -2   // 无效参数
#define OLIVE_ERROR_NOMEM      -3   // 内存不足
#define OLIVE_ERROR_NOT_FOUND  -4   // 找不到对象/文件
#define OLIVE_ERROR_IO         -5   // IO 错误
#define OLIVE_ERROR_CANCELLED  -6   // 操作被取消
#define OLIVE_ERROR_UNSUPPORTED -7  // 不支持的操作

4.2 详细错误信息

提供线程局部的错误信息获取函数:

int olive_core_last_error_code(void);
const char* olive_core_last_error_string(void);  // 线程安全,返回静态缓冲区或 TLS

实现方式:

// .cpp 中
thread_local int g_last_error_code = OLIVE_OK;
thread_local char g_last_error_string[1024];

static void SetError(int code, const char* fmt, ...) {
    g_last_error_code = code;
    va_list args;
    va_start(args, fmt);
    vsnprintf(g_last_error_string, sizeof(g_last_error_string), fmt, args);
    va_end(args);
}

5. 字符串处理

5.1 输入字符串

  • 所有 const char* 参数均视为 UTF-8 编码
  • 库内部在边界处转换为 QString
// 封装层内部
QString qstr = QString::fromUtf8(cstr);

5.2 输出字符串

  • 返回 char* 的函数,使用 olive_core_free() 释放。
  • 如果只需读取而不持有,提供 const char* 返回版本:
const char* olive_node_get_type_name(OliveNode* node);  // 生命周期与 node 绑定
char* olive_node_graph_save_xml(OliveNodeGraph* graph, size_t* out_len);  // 需释放

6. 显式加载器(ModuleLoader

6.1 设计目标

主进程通过一个统一的 ModuleLoader 类显式加载所有动态库,将 dlopen/dlsym 的细节隐藏。

6.2 C++ 封装类

// app/moduleloader.h
#pragma once

#include <QString>
#include <QHash>
#include <functional>

namespace olive {

class ModuleLoader {
public:
    ModuleLoader();
    ~ModuleLoader();

    // 加载指定路径的动态库
    bool Load(const QString& module_name, const QString& library_path);

    // 卸载
    void Unload(const QString& module_name);

    // 获取函数指针(模板封装,内部调用 dlsym)
    template<typename FuncPtr>
    FuncPtr GetFunction(const QString& module_name, const char* func_name) {
        return reinterpret_cast<FuncPtr>(GetFunctionRaw(module_name, func_name));
    }

    // 检查是否已加载
    bool IsLoaded(const QString& module_name) const;

    // 获取加载错误信息
    QString LastError() const;

private:
    void* GetFunctionRaw(const QString& module_name, const char* func_name);

    struct ModuleHandle {
        void* handle;  // dlopen handle
        QString path;
    };
    QHash<QString, ModuleHandle> modules_;
    QString last_error_;
};

// 便捷宏:从指定模块获取函数并调用
#define OLIVE_LOAD_FUNC(loader, module, name, type) \
    auto name = (loader).GetFunction<type>(module, #name); \
    if (!name) { qFatal("Failed to load function: " #name " from module: " #module); }

}  // namespace olive

6.3 实现(POSIX

// app/moduleloader.cpp
#include "moduleloader.h"
#include <dlfcn.h>
#include <QDebug>

namespace olive {

bool ModuleLoader::Load(const QString& module_name, const QString& library_path) {
    if (modules_.contains(module_name)) return true;

    void* handle = dlopen(library_path.toUtf8().constData(), RTLD_NOW | RTLD_LOCAL);
    if (!handle) {
        last_error_ = QString::fromUtf8(dlerror());
        qWarning() << "Failed to load" << library_path << ":" << last_error_;
        return false;
    }

    modules_.insert(module_name, {handle, library_path});
    qInfo() << "Loaded module:" << module_name << "from" << library_path;
    return true;
}

void ModuleLoader::Unload(const QString& module_name) {
    auto it = modules_.find(module_name);
    if (it != modules_.end()) {
        dlclose(it->handle);
        modules_.erase(it);
    }
}

void* ModuleLoader::GetFunctionRaw(const QString& module_name, const char* func_name) {
    auto it = modules_.find(module_name);
    if (it == modules_.end()) return nullptr;
    dlerror();  // 清除之前的错误
    void* func = dlsym(it->handle, func_name);
    return func;
}

}  // namespace olive

6.4 实现(Windows

#include <windows.h>

bool ModuleLoader::Load(const QString& module_name, const QString& library_path) {
    HMODULE handle = LoadLibraryW(library_path.toStdWString().c_str());
    if (!handle) {
        last_error_ = QString::number(GetLastError());
        return false;
    }
    modules_.insert(module_name, {handle, library_path});
    return true;
}

void* ModuleLoader::GetFunctionRaw(const QString& module_name, const char* func_name) {
    auto it = modules_.find(module_name);
    if (it == modules_.end()) return nullptr;
    return GetProcAddress(static_cast<HMODULE>(it->handle), func_name);
}

6.5 使用示例

// core.cpp 中初始化
module_loader_ = new ModuleLoader();
module_loader_->Load("core", FindLibraryPath("libolivecore.so"));
module_loader_->Load("codec", FindLibraryPath("libolivecodec.so"));
module_loader_->Load("node", FindLibraryPath("libolivenode.so"));

// 获取函数
OLIVE_LOAD_FUNC(*module_loader_, "core", olive_rational_make, OliveRational(*)(int64_t, int64_t));

OliveRational r = olive_rational_make(1001, 30000);

7. 类型映射表

C++ 类型(内部) C 接口类型(公共) 说明
olive::Rational OliveRational struct { int64_t num, den; }
olive::Color OliveColor struct { double r, g, b, a; }
olive::TimeRange OliveTimeRange* 不透明指针
olive::Frame OliveFrame* 不透明指针
olive::SampleBuffer OliveSampleBuffer* 不透明指针
olive::PixelFormat OlivePixelFormat enum
olive::VideoParams OliveVideoParams 公开结构体(POD
olive::AudioParams OliveAudioParams 公开结构体(POD
olive::Node* OliveNode* 不透明指针
olive::NodeGraph* OliveNodeGraph* 不透明指针
olive::RenderTicketPtr OliveRenderTicket* 不透明指针(引用计数内部管理)
QString const char* UTF-8 编码
QSize struct { int width; int height; } OliveSize
QMatrix4x4 float[16] 列优先

7.1 POD 结构体定义示例

// olivecore_api.h

typedef struct {
    int64_t num;
    int64_t den;
} OliveRational;

typedef struct {
    double r;
    double g;
    double b;
    double a;
} OliveColor;

typedef struct {
    int width;
    int height;
    int depth;
    int channel_count;
    OlivePixelFormat format;
    double pixel_aspect_num;
    double pixel_aspect_den;
} OliveVideoParams;

typedef struct {
    int sample_rate;
    int64_t channel_layout;  // FFmpeg AV_CH_LAYOUT_* 值
    OliveSampleFormat format;
} OliveAudioParams;

typedef struct {
    int width;
    int height;
} OliveSize;

8. 线程安全

8.1 API 层面

  • 默认不保证线程安全。除非文档明确标注 thread-safe,否则每个 OliveXxx* 对象只能在创建它的线程中使用。
  • 这是刻意的设计:由于渲染进程是"用完即弃"的,不存在多线程共享渲染状态的问题。

8.2 主进程中的线程使用

  • ModuleLoader 本身是线程安全的(只读查找,加载/卸载在初始化/退出时串行执行)。
  • UI 对象在主线程操作。
  • IO/解码可以在工作线程中通过 C API 操作独立的 OliveDecoder* 实例。

9. 版本与 ABI 兼容性

9.1 API 版本号

每个模块的 C API 有一个主版本号:

#define OLIVE_NODE_API_VERSION 1

int olive_node_api_version(void);  // 返回 OLIVE_NODE_API_VERSION

9.2 加载时版本检查

bool LoadNodeModule(ModuleLoader* loader, const QString& path) {
    if (!loader->Load("node", path)) return false;
    auto version_fn = loader->GetFunction<int(*)()>("node", "olive_node_api_version");
    if (!version_fn || version_fn() != EXPECTED_NODE_API_VERSION) {
        qFatal("Incompatible libolivenode.so version");
        return false;
    }
    return true;
}

9.3 ABI 兼容性规则

  • 允许:新增函数、新增枚举值(在末尾)、新增结构体字段(在末尾,且文档标注"v2 起可用")。
  • 不允许:删除函数、修改函数签名、修改已有字段含义、改变枚举值顺序。
  • 结构体扩展:POD 结构体新增字段时,提供初始化宏确保旧代码不会未初始化新字段:
#define OLIVE_VIDEO_PARAMS_DEFAULT { \
    .width = 1920, .height = 1080, .depth = 1, \
    .channel_count = 4, .format = OLIVE_PIXEL_FMT_RGBA32F, \
    .pixel_aspect_num = 1.0, .pixel_aspect_den = 1.0 \
}

10. 头文件组织

10.1 目录结构

c_api/
├── include/
│   ├── olive/               # 公共 C API 头文件(安装时发布)
│   │   ├── core_api.h
│   │   ├── codec_api.h
│   │   ├── node_api.h
│   │   ├── render_api.h
│   │   ├── audio_api.h
│   │   ├── plugin_api.h
│   │   ├── ui_api.h
│   │   └── olive_api.h      # 总入口,包含所有模块
│   └── olivecpp/            # 主进程内部使用的 C++ 辅助封装
│       ├── module_loader.h
│       ├── core_wrapper.h   // RAII 包装类
│       ├── node_wrapper.h
│       └── ...
└── src/
    ├── core_api.cpp         // 对应各模块的 C 封装实现
    ├── codec_api.cpp
    ├── node_api.cpp
    ├── render_api.cpp
    ├── audio_api.cpp
    ├── plugin_api.cpp
    └── ui_api.cpp

10.2 C API 头文件示例

// c_api/include/olive/node_api.h
#ifndef OLIVE_NODE_API_H
#define OLIVE_NODE_API_H

#ifdef __cplusplus
extern "C" {
#endif

#include "core_api.h"

#define OLIVE_NODE_API_VERSION 1

/* ========== 类型前向声明 ========== */
typedef struct OliveNodeGraph OliveNodeGraph;
typedef struct OliveNode      OliveNode;
typedef struct OliveParam     OliveParam;

/* ========== 函数导出宏 ========== */
#ifdef OLIVE_BUILDING_NODE
#  define OLIVE_NODE_API __attribute__((visibility("default")))
#else
#  define OLIVE_NODE_API
#endif

/* ========== API 版本 ========== */
OLIVE_NODE_API int olive_node_api_version(void);

/* ========== NodeGraph ========== */
OLIVE_NODE_API OliveNodeGraph* olive_node_graph_create(void);
OLIVE_NODE_API void olive_node_graph_destroy(OliveNodeGraph* g);

OLIVE_NODE_API int olive_node_graph_load_xml(OliveNodeGraph* g,
                                              const char* xml_data,
                                              size_t xml_len);
OLIVE_NODE_API char* olive_node_graph_save_xml(OliveNodeGraph* g,
                                                size_t* out_len);

OLIVE_NODE_API OliveNode* olive_node_graph_find_node(OliveNodeGraph* g,
                                                      const char* node_id);
OLIVE_NODE_API int olive_node_graph_add_node(OliveNodeGraph* g,
                                              const char* node_type,
                                              const char* node_id);

/* ========== Node ========== */
OLIVE_NODE_API const char* olive_node_get_id(OliveNode* node);
OLIVE_NODE_API const char* olive_node_get_type_name(OliveNode* node);

OLIVE_NODE_API int olive_node_connect(OliveNode* from_node,
                                       int from_output_index,
                                       OliveNode* to_node,
                                       int to_input_index);

/* ========== Param ========== */
OLIVE_NODE_API int olive_node_set_param_int(OliveNode* node,
                                             const char* param_name,
                                             int64_t value);
OLIVE_NODE_API int olive_node_set_param_double(OliveNode* node,
                                                const char* param_name,
                                                double value);
OLIVE_NODE_API int olive_node_set_param_rational(OliveNode* node,
                                                  const char* param_name,
                                                  OliveRational value);
OLIVE_NODE_API int olive_node_set_param_string(OliveNode* node,
                                                const char* param_name,
                                                const char* value);

/* ========== Project ========== */
OLIVE_NODE_API OliveNodeGraph* olive_project_create(const char* name);
OLIVE_NODE_API int olive_project_load_file(OliveNodeGraph* project,
                                            const char* filename);
OLIVE_NODE_API int olive_project_save_file(OliveNodeGraph* project,
                                            const char* filename);

#ifdef __cplusplus
}
#endif

#endif  // OLIVE_NODE_API_H

11. CMake 中的 C API 编译

11.1 为模块添加 C API 目标

# app/node/CMakeLists.txt

# 原有 C++ 源码(内部实现,不暴露头文件)
set(NODE_INTERNAL_SOURCES
  node.cpp node.h
  traverser.cpp traverser.h
  project/project.cpp project/project.h
  # ... 其他内部文件
)

# C API 封装层源码
set(NODE_API_SOURCES
  ${CMAKE_SOURCE_DIR}/c_api/src/node_api.cpp
)

# 模块对外头文件(安装时发布)
set(NODE_API_HEADERS
  ${CMAKE_SOURCE_DIR}/c_api/include/olive/node_api.h
)

# 创建动态库
add_library(olivenode SHARED
  ${NODE_INTERNAL_SOURCES}
  ${NODE_API_SOURCES}
)

target_compile_definitions(olivenode PRIVATE OLIVE_BUILDING_NODE)
target_include_directories(olivenode
  PRIVATE
    ${CMAKE_SOURCE_DIR}/app
    ${CMAKE_SOURCE_DIR}/c_api/include
  PUBLIC
    $<INSTALL_INTERFACE:include>
)

target_link_libraries(olivenode
  PRIVATE
    olivecore
    olivecodec
    Qt${QT_VERSION_MAJOR}::Core
)

# 设置符号可见性:默认隐藏,只有标记 OLIVE_NODE_API 的才导出
set_target_properties(olivenode PROPERTIES
  CXX_VISIBILITY_PRESET hidden
  VISIBILITY_INLINES_HIDDEN YES
)

# 安装 C API 头文件
install(FILES ${NODE_API_HEADERS} DESTINATION include/olive)
install(TARGETS olivenode DESTINATION lib)

11.2 主可执行文件不链接业务库

# app/CMakeLists.txt(改造后)

add_executable(olive-editor
  main.cpp
  core.cpp
  core.h
  ${CMAKE_SOURCE_DIR}/c_api/src/module_loader.cpp  # 显式加载器实现
)

# 主程序只链接 Qt 和系统库,不链接 olivecore/olivecodec 等业务库!
target_link_libraries(olive-editor PRIVATE
  Qt${QT_VERSION_MAJOR}::Core
  Qt${QT_VERSION_MAJOR}::Gui
  Qt${QT_VERSION_MAJOR}::Widgets
  # ... 其他 UI 依赖
)

target_include_directories(olive-editor PRIVATE
  ${CMAKE_SOURCE_DIR}/c_api/include
)

12. 测试策略

12.1 C API 单元测试

为每个 C API 函数编写独立测试:

// tests/c_api/test_node_api.cpp
#include <gtest/gtest.h>
#include "olive/node_api.h"

TEST(NodeAPITest, CreateDestroy) {
    OliveNodeGraph* g = olive_node_graph_create();
    ASSERT_NE(g, nullptr);
    olive_node_graph_destroy(g);
}

TEST(NodeAPITest, AddNodeAndParam) {
    OliveNodeGraph* g = olive_node_graph_create();
    ASSERT_EQ(OLIVE_OK, olive_node_graph_add_node(g, "Transform", "T1"));
    OliveNode* n = olive_node_graph_find_node(g, "T1");
    ASSERT_NE(n, nullptr);
    ASSERT_EQ(OLIVE_OK, olive_node_set_param_double(n, "position_x", 100.0));
    olive_node_graph_destroy(g);
}

12.2 ABI 稳定性测试

在 CI 中:

  1. 编译当前版本的动态库。
  2. 用上一个发布版本的测试可执行文件加载当前动态库运行。
  3. 验证所有测试通过(确保未破坏 ABI)。

13. 常见陷阱

陷阱 说明 对策
异常穿透 C 边界 C++ 异常抛出到 C 调用方是 UB。 所有 C API 函数用 try/catch(...) 包裹,捕获所有异常并转换为错误码。
RTTI 跨边界 dynamic_cast 在不同动态库间可能失败。 C 接口不使用 RTTI,内部若必须 dynamic_cast,确保类型定义在同一个库内。
Qt 元对象跨库 qobject_cast 依赖 moc 生成的静态元对象数据,跨库时可能失效。 不在 C API 中暴露 Qt 对象,所有 Qt 对象封装在库内部。
全局静态变量 多个动态库各有一份全局静态变量。 避免在 C API 头文件中定义全局静态变量,使用函数内 static + 首次调用初始化。
堆不一致(Windows A 库 mallocB 库 free 导致崩溃。 严格遵循"谁分配谁释放",使用库提供的 olive_core_free()