前言
在构建大型分布式系统时,为了保证数据在系统上下游的自动校验,避免数据结构异常带来的系统稳定性问题,通常会采用 JSON 格式进行数据交互。此时,可以采用 JSON Schema 来定义接口规范,并利用 JSON Schema Validator 来校验接口响应结构的合法性。
然而,系统中不同子系统的实现语言往往并非一致。虽然主流编程语言都提供了 JSON Schema Validator 的具体实现,但不同语言支持的 JSON Schema 标准版本(Draft)并不完全统一,这会给后续的系统集成带来混乱。例如:
- Java: json-schema-validator 支持 Draft 4
- C++: json-schema-validator 支持 Draft 7
- Python: jsonschema 支持 Draft 7
目前大多数语言(如 Java, Golang, PHP, Lua 等)都支持 C/C++ 的扩展机制。因此,最佳实践是用 C/C++ 编写核心逻辑库,再为其他语言提供统一的扩展包。本文详细介绍如何利用 Boost::Python 库将 C++ 的 nlohmann_json_schema_validator 封装为 Python 扩展,并介绍如何使用 distutils 进行编译与分发。
Python 调用 C/C++ 的方式
主要有两种方式可以实现 Python 调用 C/C++ 编写的库:
- ctypes:Python 内置的外部函数库,提供 C 兼容的数据类型,允许在 DLL 或共享库中调用函数。
- Python Extension API:Python 官方提供的扩展机制,通过编写 C/C++ 代码生成
.so(Linux/Mac) 或.pyd(Windows) 文件,被 Python 解释器直接加载。
ctypes 加载 so 文件
ctypes 是 Python 封装的 API 函数库。其中 cdll = <ctypes.LibraryLoader object> 是一个库加载器对象,调用 cdll.LoadLibrary 便可调用 C/C++ 的 so 库。
import ctypes
lib = ctypes.CDLL('./libmylib.so')
lib.my_function.argtypes = [ctypes.c_int]
lib.my_function.restype = ctypes.c_int
result = lib.my_function(10)
此处不再对如何使用 ctypes 模块加载 so 文件做过多的介绍,具体可以参考其官方文档。对于复杂的类封装和内存管理,直接使用 ctypes 较为繁琐且容易出错。
使用 Python 扩展
该方式是 Python 为整合其它语言而提供的一种扩展机制。Python 的可扩展性具有如下优点:
- 方便为语言增加新功能
- 具有高度可定制性
- 可以实现底层代码复用
- 性能更优(相比 ctypes 的边界转换开销)
该方式的具体使用可以参考 Python 官方文档 Building C and C++ Extensions。接下来要讲的例子就是利用了'代码复用'的优点。
Python 提供了 Python/C++ API 用来实现 Python 和 C++ 的交互。然而,直接使用这些 API 来完成交互会存在很多重复工作,例如手动处理引用计数、类型转换等。Boost::Python 则对 Python/C++ API 进行了抽象和包装,使得 Python 和 C++ 的交互更加方便,类似于 pybind11 但基于 Boost 生态。
Boost::Python 封装 C/C++ 扩展
接下来我们介绍如何利用 Boost::Python 为 nlohmann_json_schema_validator 增加 Python 支持。
nlohmann_json_schema_validator 是一个基于 JSON Schema 验证 JSON 文档的 C++ 库,它本身应验证符合 JSON Schema Validation draft-7 标准。
编译 nlohmann json schema validator 库
该库支持产出可执行的 bin 文件以及可供其他项目使用的动态库。为了可以将该扩展包装成 Python 扩展,我们需要先生成该库的动态库。
首先根据 nlohmann_json_schema_validator 的编译文档编译出动态库。通常需要使用 CMake 配置,确保开启 BUILD_SHARED_LIBS 选项。
Boost::Python 封装 C/C++ 代码
在安装 Boost 的时候,虽然 Boost 的头文件中存在 Boost::Python 相关的 hpp 文件,但是默认却没有该动态库。因此,在使用 Boost::Python 之前,首先需要安装该库。
# macOS 示例
brew install boost-python3
# Linux 示例 (Ubuntu/Debian)
sudo apt-get install libboost-python-dev
然后,我们用 C++ 编写 class json_validator_python 来封装 nlohmann_json_schema_validator 库,并利用 Boost::Python 来导出。
// jsv_python.cpp
#include <iomanip>
#include <iostream>
#include <nlohmann/json-schema.hpp>
#include <boost/python.hpp>
using namespace boost::python;
class JSON_SCHEMA_VALIDATOR_API json_validator_python
{
private:
nlohmann::json_schema::json_validator validator;
public:
json_validator_python() {}
void set_root_schema(const std::string &json_string)
{
// 解析 JSON 字符串
auto parsed = nlohmann::json::parse(json_string.begin(), json_string.end());
validator.set_root_schema(parsed);
}
void validate(const std::string &json_string) const
{
// 解析并验证
auto data = nlohmann::json::parse(json_string.begin(), json_string.end());
validator.validate(data);
}
};
// json_schema_validator 为 module 名,必需和生成的 so 库名一样
BOOST_PYTHON_MODULE(json_schema_validator)
{
class_<json_validator_python, boost::noncopyable>("json_validator_python")
.def("set_root_schema", &json_validator_python::set_root_schema)
.def("validate", &json_validator_python::validate)
;
}
如上所示的 BOOST_PYTHON_MODULE 代码,目的是导出类及成员方法。这些导出的类和方法可以在 Python 中直接导入并实例化。关于 Boost::Python 更详细的使用,可以参考其官方文档。
编译并产出 Python 扩展
使用 g++ 进行编译。注意需要指定正确的头文件路径和库路径。
# 1. 编译目标文件
export JSON_SCHEMA_PATH=/path/to/nlohmann-json-schema-validator
export BOOST_PATH=/usr/local/opt/boost
export PYTHON_INCLUDE=$(python3-config --includes)
g++ --std=c++11 -fPIC -c jsv_python.cpp \
-I${JSON_SCHEMA_PATH}/src \
-I/usr/include/nlohmann \
-I${BOOST_PATH}/include \
-I${PYTHON_INCLUDE}
# 2. 链接生成共享库
g++ --std=c++11 -shared \
-L${JSON_SCHEMA_PATH}/build/lib \
-L${BOOST_PATH}/lib \
-L$(python3-config --prefix)/lib \
-lnlohmann_json_schema_validator \
-lboost_python38 -lpython3.8 \
-o json_schema_validator.so jsv_python.o
如上的指令,会生成封装之后的 Python 扩展:json_schema_validator.so。
测试 python 扩展
在当前目录执行如下的 Python 代码,可以发现,我们封装的扩展已经可以当做 Python 扩展来导入并使用了。
import json_schema_validator as jsv
validator = jsv.json_validator_python()
isValidator = True
try:
# 设置根 Schema
validator.set_root_schema('{"type":"object", "properties": {"a":{"type": "string"}}}')
# 验证数据
validator.validate('{"a":"1"}');
except Exception as e:
print(f"Validation Error: {e}")
isValidator = False
print(isValidator)
使用 distutils 编译并分发扩展
为了使用方便,推荐使用 distutils 或 setuptools 模块编译 Python 扩展,这样可以自动化处理依赖路径。
创建 setup.py 文件:
from distutils.core import setup, Extension
import os
os.environ["CC"] = "g++"
os.environ["CXX"] = "g++"
module1 = Extension('json_schema_validator',
include_dirs = ['../../src',
'../../',
'/usr/local/opt/boost/include'],
libraries = ['boost_python38', 'python3.8',
'nlohmann_json_schema_validator'],
library_dirs = ['/usr/local/opt/boost/lib',
'.'],
sources = ['jsv_python.cpp'],
extra_compile_args=['--std=c++11'],
extra_link_args=['--std=c++11'])
setup(
name='json-schema-validator',
version='1.0',
description='json schema validator extension',
ext_modules=[module1]
)
执行以下命令进行构建和安装:
$ python3 setup.py build
$ python3 setup.py install
安装后,可能需要设置环境变量以便运行时能找到动态库:
# macOS / Linux
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/local/lib/python3.8/site-packages/json_schema_validator
常见问题与排查
在实际开发过程中,可能会遇到以下常见问题:
1. 找不到模块错误
如果报错 ImportError: No module named json_schema_validator,请检查以下几点:
- 确认
setup.py中的name参数与生成的.so文件名一致。 - 确认 Python 环境是否正确激活。
- 检查
sys.path是否包含了安装目录。
2. 动态库链接错误
如果运行时报错 undefined symbol 或 library not found,通常是链接顺序或路径问题。
- 确保
-L参数指向了包含.so文件的目录。 - 确保
-l参数对应的库名称正确(去掉前缀lib和后缀.so)。 - 在 macOS 上,可能需要设置
DYLD_LIBRARY_PATH而不是LD_LIBRARY_PATH。
3. 跨平台兼容性
- Windows: 动态库后缀为
.pyd,依赖项需放在同一目录下或通过 PATH 环境变量添加。 - Linux: 注意 glibc 版本兼容性,尽量在较新的系统上编译。
- macOS: 注意架构兼容性(x86_64 vs arm64),建议使用 Universal 二进制或针对当前架构编译。
进阶:打包为 Wheel
为了更方便地分发,建议将扩展打包为 Wheel 格式(.whl)。这需要安装 wheel 包并使用 bdist_wheel 命令。
pip install wheel
python setup.py bdist_wheel
生成的 .whl 文件可以直接通过 pip install 安装,无需用户自行编译 C++ 代码,极大降低了部署成本。
总结
本文详细介绍了利用 Boost::Python 将 C++ 库封装为 Python 扩展的完整流程。从环境准备、C++ 代码封装、编译链接到最终的分发测试,涵盖了关键步骤。通过这种方式,开发者可以利用 C++ 的高性能特性增强 Python 应用的功能,同时保持 Python 开发的便捷性。在处理跨语言数据交互和性能敏感模块时,这种混合编程模式是非常有效的解决方案。


