目录

前言

C/C++动态共享库编译

ctype模块

ctype数据类型

使用案例

float数据

指针

输出数组

 结构体及结构体指针

numpy图像当作指针传入

参考资料

前言

        在项目开发中,有时会使用到多种编程语言,比如部分功能是C/C++代码实现的,而另一部分是Python代码实现的,这样就可能需要使用多种编程语言。当然,也可以把C/C++代码转成Python,但这样可能费时;也有可能某个模块用Python来实现,速度很慢,但用C/C++速度却很快,这就会使用到用Python来调用C/C++代码。

        本文主要说明如何使用Python来调用C/C++代码,且是基于Linux平台,Linux上编译生成动态共享库so文件,然后Python使用ctype模块来调用so文件里的函数。如果不同平台,比如Windows或者mac,编译的动态共享库文件是不一样的。

C/C++动态共享库编译

        首先要把C/C++代码编译成动态共享库,一般是通过extern C来实现对外接口。下面给出一个例子。test.h文件进行函数声明,test.cpp是函数实现。

/******c动态库接口文件test.h*************/
#ifndef TEST_H
#define TEST_H

#ifdef __cplusplus
extern "C" {
#endif

    int hello();

#ifdef __cplusplus
}
#endif

#endif /* TEST_H */
/*****c动态库函数实现test.cpp*****************/
#include <stdio.h>
#include <iostream>
#include <string.h>
#include <stdlib.h>
#include "test.h"

int hello()
{
    printf("hello world\n");
    return 0;
}

编译命令为:g++ -std=c++11 -o libtest.so -shared -fPIC test.cpp。编译完成后,就会在当前路径下生成libtest.so文件。

ctype模块

        通过ctype模块,可以加载动态库到进程中并返回其实例对象,该方法每次都会返回一个新实例,使用方法如下:

import ctypes

mylib = ctypes.cdll.LoadLibrary("libtest.so") #此次可能要加入绝对路径

ctype数据类型

        因为ctypes只能调用C编译成的库,因此不支持重载,需要在程序中显示定义函数类型和返回值类型。ctypes基本数据类型如下:

python转成int python转成c++代码_python转成int

        在定义函数的参数和返回值时,必须使用ctypes的数据类型,若没有显示定义参数类型和返回类型,python默认为int型。参数类型用关键字argtypes定义,返回类型用restype定义,其中argtypes必须是一个序列,如tuple或者list,否则会报错,好像不使用参数类型关键字和返回类型也行。

使用案例

float数据

C/C++代码

float addfloat(float a, float b)
{
    printf("a = %f, b = %f \n", a, b);
    return a + b;
}

Python端代码

def c_basic_test():
    mylib.addfloat.argtypes = [ctypes.c_float, ctypes.c_float]
    mylib.addfloat.restype = ctypes.c_float # 必须否则c会是整型
    a = ctypes.c_float(2.1)
    b = ctypes.c_float(3.5)
    c = mylib.addfloat(a, b) # ctypes.c_float(mylib.addfloat(a, b))没设置restype时
    print(c)

指针

        从上面的表格中,我们可以看到char*和void*已经有专用类型了,直接使用即可,对于其他类型的指针,ctypes提供了两种定义方式pointer和POINTER。POINTER必须传入ctypes类型,创建出新的ctypes指针类型(pointer type),而pointer传入一个对象,创建出一个新的指针实例。(POINTER创建出了pointer)。

        传输地址,ctypes提供了byref函数,ctypes.byref(obj[, offset]),该函数返回一个指向ctypes实例对象的轻量级指针,函数中还可以通过参数(必须是int)来设置偏移地址,这个返回的对象只能用于外部函数调用的参数。

 C/C++代码

void pointerTest(int* pInt, float* pFloat)
{
    *pInt = 10;
    *pFloat = 12.34;
}

Python代码

def c_pointer_test():
    # 好像可有可无
    #library.pointerTest.argtypes = [POINTER(c_int), POINTER(c_float)]
    #library.pointerTest.restype = c_void_p
    a = 0
    b = 0
    int_a = ctypes.c_int(a)
    float_b = ctypes.c_float(b)

    # byref()函数用于传输地址
    mylib.pointerTest(ctypes.byref(int_a), ctypes.byref(float_b))
    print("out_a:", int_a.value) # a的值还是0,没变
    print("out_b", float_b.value)

 外部传输字符空间,在函数内部进行赋值

C/C++代码

void mallocTest(char* pszStr)
{
    strcpy(pszStr, "Happay Children's day");
}

Python代码

def c_malloc_test():
    mylib.mallocTest.argtypes = [ctypes.c_char_p]
    mylib.mallocTest.restype = ctypes.c_void_p

    word = (ctypes.c_char * 32)()
    mylib.mallocTest(word)
    print("out_word:", word.value)

输出数组

        有时会返回一个数组。

   C/C++代码

void OutputArr(float *arr, int len)
{
    for (int i = 0; i < len; i++) {
        arr[i] = i + 0.5;
    }
}

Python代码

def TestOutputArr():
    arrf = (ctypes.c_float*10)()
    mylib.OutputArr(arrf, ctypes.c_int(10))
    for i in range(10):
        print(arrf[i])

 

 结构体及结构体指针

        结构体是C/C++中常用类型,使用前要先定义其成员类型,在python中也是同样处理,python中的结构体必须继承Structure类,定义其成员必须使用_field_属性。该属性是一个list,其成员都是2个值的tuple,分别是每个结构体成员的类型和长度,而且定义类型必须使用ctypes类型或者由ctype组合而成的新类型。而且结构体还存在嵌套的情况,这里也嵌套的结构体为例。

 C/C++代码

struct sub_struct{
    char* test_char;
    int test_int;
};

struct struct_def {
    char* stru_string;
    int stru_int;
    char stru_arr_num[4];
    sub_struct son_struct;
};


int test(struct_def struct_mystruct, struct_def* struct_test_p) 
{
    //输出结构体指针的数据
    cout<<"ouput struct char*:";
    cout << struct_mystruct.stru_string << endl;
    cout<<"output struct int:";
    cout << struct_mystruct.stru_int <<endl;
    cout <<"output struct char arr:";
    for(int x = 0;x< 4;x++) {
        cout << struct_mystruct.stru_arr_num[x]<<"   ";
    }
    cout<< endl;
    cout<<"output struct char*:";
    cout << struct_mystruct.son_struct.test_char<<endl;
    cout<<"ouput struct int:";
    cout<<struct_mystruct.son_struct.test_int<<endl;
    
    cout<<endl;
    cout<<endl;
    cout<<"pointer char*:";
    cout << struct_test_p->stru_string << endl;
    cout<<"pointer int:";
    cout << struct_test_p->stru_int <<endl;
    cout <<"pointer char arr:";
    for(int x = 0;x< 4;x++) {
        cout << struct_test_p->stru_arr_num[x]<<"   ";
    }
    cout<< endl;
    cout<<"sub struct string:";
    cout<<struct_test_p->son_struct.test_char;
    cout << endl;
    cout<<"sub struct pointer int";
    cout<<struct_test_p->son_struct.test_int<<endl;
}

Python代码

class sub_struct(ctypes.Structure):
    _fields_ = [
        ("test_char_p",ctypes.c_char_p),
        ("test_int",ctypes.c_int)
    ]

class struct_def(ctypes.Structure):
    _fields_ = [
        ("stru_string",ctypes.c_char_p),
        ("stru_int", ctypes.c_int),
        ("stru_arr_num", ctypes.c_char*4),
        ("son_struct", sub_struct)
    ]

struct_mystruct = struct_def()
struct_mystruct.stru_string = b"string in the struct"
struct_mystruct.stru_int = 99
struct_mystruct.stru_arr_num = b"ABCD"
struct_mystruct.son_struct.test_char_p =b"sub struct of the string"
struct_mystruct.son_struct.test_int = 10
mylib.test(struct_mystruct,ctypes.byref(struct_mystruct))

numpy图像当作指针传入

        在处理图像时,经常是传入一张图像,输出另外一张图像,图像处理部分是C/C++代码处理,这里也把彩色图像转出灰度图像为例。

C/C++代码

void GetGrayImage(unsigned char *rgb, unsigned char *gray, int width, int height)
{
    for(int j =0; j < height; j++) {
        for(int i = 0; i < width; i++) {
            int r = rgb[j * width + i];
            int g = rgb[j * width + i + width * height];
            int b = rgb[j * width + i + 2 * width * height];
            int val = 0.299 * r + 0.587 * g + 0.114 * b;
            gray[j * width + i] = val;
        }
    }
}

 Python代码

img = cv2.imread('1.jpg', 1)
    h, w, c = img.shape
    imgrgb = img[:, :, ::-1] # BGR2RGB
    imgrgb = np.transpose(imgrgb, (2, 0, 1)) # RGBRGB格式转成RRR  GGG  BBB
    imgrgb = imgrgb.reshape(1, -1)
    grayimg = np.zeros((h,w), np.uint8)
    grayimg = grayimg.reshape(1, -1)
    rgbdata = imgrgb.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))
    graydata = grayimg.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte))
    width = ctypes.c_int(w)
    height = ctypes.c_int(h)
    mylib.GetGrayImage(rgbdata, graydata, width, height)
    grayimg = grayimg.reshape(h, w)
    cv2.imwrite('gray.jpg', grayimg)

         暂且列了这些应用,一些没涉及到的,可能根据这些用例可以类似处理,之后如果遇到新的使用案例再新增。 

参考资料:

python调用c++模块.so库, 互相回传数据(ctypes、pybind11) - 知乎

python3调用c++动态库(linux) - 知乎