工作中遇到了Python性能跟不上的情况,就把其间中心核算较多的部分放到C言语中去处理,然后把成果回来到Python傍边。(过后强烈建议:运用Golang来完成一切工作,或许中心部分放到Golang中,而不是C中)

在这个过程傍边,遇到了一些未曾探索的领域。故此记载一下。

怎么把C代码编译成.so同享库文件给Python运用

假定我们有一个sample.proto文件,运用protobuf的东西已经生成了Python和C运用的相对应的pb文件,这里编译so文件。假定我们的C代码文件名为:sourcefile.c

gcc -c -fPIC sample.pb-c.c sample.pb-c.h sourcefile.c
gcc -shared -o a.so sample.pb-c.o sourcefile.o -L/usr/local/lib -lprotobuf-c

榜首行,根据GPT的解说:

这条指令是运用gcc编译一组源文件,并生成一个方位无关的方针文件(.o文件)。让我们逐渐解说每个选项的意义:
-c:这个选项告知gcc只进行编译而不进行链接。它将源文件编译为方针文件,生成.o文件。
-fPIC:这个选项指示gcc生成方位无关代码(Position Independent Code)。这是为了支持动态链接库(同享库)的需求,由于同享库能够在内存中的任何方位加载。
sample.pb-c.c:这是一个源文件的称号,它包含了Protocol Buffers编译器生成的C言语代码。
sample.pb-c.h:这是一个头文件的称号,它包含了Protocol Buffers编译器生成的C言语代码的声明。
sourcefile.c:这是另一个源文件的称号,它或许包含了运用Protocol Buffers库的自定义代码。

第二行,根据GPT的解说:

这条指令是运用gcc将多个方针文件链接为一个同享库(shared library)。让我们逐渐解说每个选项的意义:
-shared:这个选项告知gcc生成一个同享库而不是可执行文件。
-o a.so:这个选项指定生成的同享库的输出文件名为a.so。.so是同享库文件的常见扩展名。
sample.pb-c.o:这是一个方针文件的称号,它包含了Protocol Buffers编译器生成的C言语代码的完成部分。
sourcefile.o:这是另一个方针文件的称号,它或许包含了运用Protocol Buffers库的自定义代码的完成部分。
-L/usr/local/lib:这个选项指定了链接器在搜索库文件时要查找的途径。/usr/local/lib是一个常见的库文件装置途径。
-lprotobuf-c:这个选项告知链接器要链接名为libprotobuf-c的库。-l选项用于指定要链接的库的称号。
综上所述,这条指令的作用是将sample.pb-c.o和sourcefile.o这两个方针文件链接为一个同享库,并命名为a.so。链接器会运用libprotobuf-c库来解析符号引用,以便在运行时正确地链接和加载同享库。

这样就生成了一个a.so文件,接下来在Python中运用它

Python中运用so文件

  1. 需求留意函数名和参数类型的运用,Python代码傍边要指定C的函数入参类型,与C代码的函数入参一一对应,函数名也需求对应上

Python调用C的那些细节问题

  1. 函数假如有杂乱的回来类型,也需求留意类型傍边要一一对应,比方,回来的结构体其间的某个属性是一个数组,那这个数组的长度也需求保持一致(假如不一致,会出现许多意想不到的成果,其实也不多,就是一个成果:空)

Python调用C的那些细节问题

比方这里的a是一个数组,长度是MAX_CNT,假如两头的MAX_CNT不相等,那么Python傍边拿到的成果将满是空。

传递protobuf序列化之后的内容

在C代码中处理由Python传来的proto序列化之后的内容,需求用到protobuf自带的__unpack()函数,这个函数的榜首个入参是一个ProtobufCAllocator,也答应传入NULL,可是不建议这么写,下面是一个例子

typedef struct {
    ProtobufCAllocator base;  // ProtobufCAllocator作为结构体的榜首个成员
    // 自定义的其他成员
} MyProtobufCAllocator;
// 自定义的内存分配函数
static void* my_alloc(void* allocator_data, size_t size) {
    MyProtobufCAllocator* allocator = (MyProtobufCAllocator*)allocator_data;
    return malloc(size);
}
// 自定义的内存释放函数
static void my_free(void* allocator_data, void* data) {
    MyProtobufCAllocator* allocator = (MyProtobufCAllocator*)allocator_data;
    free(data);
}
// 创立自定义的ProtobufCAllocator
static ProtobufCAllocator* create_my_allocator() {
    MyProtobufCAllocator* allocator = (MyProtobufCAllocator*)malloc(sizeof(MyProtobufCAllocator));
    allocator->base.alloc = my_alloc;
    allocator->base.free = my_free;
    // 初始化其他自定义成员
    return (ProtobufCAllocator*)allocator;
}
// 毁掉自定义的ProtobufCAllocator
static void destroy_my_allocator(ProtobufCAllocator* allocator) {
    MyProtobufCAllocator* my_allocator = (MyProtobufCAllocator*)allocator;
    // 清理自定义成员
    free(my_allocator);
}

运用的时分如下:

ProtobufCAllocator* allocator = create_my_allocator();
Example* example = example__unpack(allocator, length, data);
// 增加一堆逻辑之后
example__free_unpacked(example, NULL);
destroy_my_allocator(allocator);

C言语常见问题

  1. malloc和free要成对出现,这是一个老生常谈的问题,可是老生经常会忘记谈到的一个问题是,malloc之后,也要记住初始化,常见的初始化办法有循环遍历赋值,或许memset()赋值,或许爽性不运用malloc,而是运用calloc函数。

  2. 本次调试遇到的另一个问题就是跟榜首个问题有关,malloc了,for循环遍历赋值了,可是依然跑飞,最终发现,循环赋值的时分,只循环了一半,还有一半没有给赋值成0

GDB调试Python代码

  1. 常用指令如下:
gdb python3
run xxx.py a b c args
bt
bt full
py-list
py-bt