zoukankan      html  css  js  c++  java
  • 使用ctypes在Python中调用C++动态库

    使用ctypes在Python中调用C++动态库

    入门操作

    使用ctypes库可以直接调用C语言编写的动态库,而如果是调用C++编写的动态库,需要使用extern关键字对动态库的函数进行声明:

    #include <iostream>
    using namespace std;
    
    extern "C" {
        void greet() {
            cout << "hello python" << endl;
        }
    }
    

    将上述的C++程序编译成动态链接库:

    g++ hello.cpp -fPIC -shared -o hello.so
    

    在Python代码中使用ctypes导入动态库,调用函数:

    # -*- coding: utf-8 -*- #
    from ctypes import CDLL
    
    hello = CDLL('./hello.so')
    
    if __name__ == '__main__':
        hello.greet()
    

    运行上述Python程序:

    xujijun@pc:~/codespace/python$ python3 hello.py
    hello python
    

    参数传递

    编写一个整数加法函数

    #include <iostream>
    using namespace std;
    
    extern "C" {
        int add(int a, int b) {
            return a + b;
        }
    }
    

    编译得到动态库,在Python代码中调用:

    # -*- coding: utf-8 -*- #
    from ctypes import CDLL
    
    hello = CDLL('./hello.so')
    
    if __name__ == '__main__':
        a = input('input num1: ')
        b = input('input num2: ')
        print('output: %d' % hello.add(int(a), int(b)))
    

    运行上述代码,得到输出:

    xujijun@pc:~/codespace/python$ python3 hello.py
    input num1: 12
    input num2: 34
    output: 46
    

    尝试传递字符串参数

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    extern "C" {
        void print_name(const char* name) {
            printf("%s
    ", name);
        }
    }
    

    Python代码调用:

    # -*- coding: utf-8 -*- #
    from ctypes import CDLL
    
    hello = CDLL('./hello.so')
    
    if __name__ == '__main__':
        name = input('input name: ')
        hello.print_name(name.encode('utf-8'))  # 此处需要将Python中的字符串按照utf8编码成bytes
    

    查看输出:

    xujijun@pc:~/codespace/python$ python3 hello.py
    input name: yanhewu
    yanhewu
    

    面向对象

    使用C++编写动态链接库我们肯定会想到如何在Python中调用C++的类,由于ctypes只能调用C语言函数(C++中采用extern "C"声明的函数),我们需要对接口进行一定的处理:

    C++代码示例

    #include <iostream>
    #include <cstdio>
    #include <string>
    using namespace std;
    
    class Student {
    private:
        string name;    
    public:
        Student(const char* n);
        void PrintName();
    };
    
    Student::Student(const char* n) {
        this->name.assign(n);
    }
    
    void Student::PrintName() {
        cout << "My name is " << this->name << endl;
    }
    
    extern "C" {
        Student* new_student(const char* name) {
            return new Student(name);
        }
    
        void print_student_name(Student* stu) {
            stu->PrintName();
        }
    }
    

    Python代码中调用:

    # -*- coding: utf-8 -*- #
    from ctypes import CDLL
    
    hello = CDLL('./hello.so')
    
    class Student(object):
        def __init__(self, name):
            self.stu = hello.new_student(name.encode('utf-8'))
        
        def print_name(self):
            hello.print_student_name(self.stu)
    
    if __name__ == '__main__':
        name = input('input student name: ')
        s = Student(name)
        s.print_name()
    

    输出:

    xujijun@pc:~/codespace/python$ python3 hello.py
    input student name: yanhewu
    My name is yanhewu
    

    内存泄漏?

    上一部分我们我们尝试了如何使用ctypes调用带有类的C++动态库,这里我们不禁会想到一个问题,我们在动态库中使用new动态申请的内存是否会被Python的GC清理呢?这里我们完全可以猜想,C++动态库中的动态内存并不是使用Python中的内存申请机制申请的,Python不应该对这部分内存进行GC,如果真的是这样,C++的动态库就会出现内存泄漏的问题了。那事实是不是这样呢?我们可以使用内存检查工具Valgrind来检查上面的Python代码:

    命令:

    valgrind python3 hello.py
    

    最终结果输出:

    ==17940== HEAP SUMMARY:
    ==17940==     in use at exit: 647,194 bytes in 631 blocks
    ==17940==   total heap usage: 8,914 allocs, 8,283 frees, 5,319,963 bytes allocated
    ==17940== 
    ==17940== LEAK SUMMARY:
    ==17940==    definitely lost: 32 bytes in 1 blocks
    ==17940==    indirectly lost: 0 bytes in 0 blocks
    ==17940==      possibly lost: 4,008 bytes in 7 blocks
    ==17940==    still reachable: 643,154 bytes in 623 blocks
    ==17940==         suppressed: 0 bytes in 0 blocks
    ==17940== Rerun with --leak-check=full to see details of leaked memory
    ==17940== 
    ==17940== For counts of detected and suppressed errors, rerun with: -v
    ==17940== Use --track-origins=yes to see where uninitialised values come from
    ==17940== ERROR SUMMARY: 795 errors from 86 contexts (suppressed: 0 from 0)
    

    可以看到,definitely lost了32字节,确实出现了内存泄漏,但是是不是动态库的问题我们还要进一步验证:

    C++代码加入析构函数定义:

    #include <iostream>
    #include <cstdio>
    #include <string>
    using namespace std;
    
    class Student {
    private:
        string name;    
    public:
        Student(const char* n);
        ~Student();
        void PrintName();
    };
    
    Student::Student(const char* n) {
        this->name.assign(n);
    }
    
    Student::~Student() {
        cout << "Student's destructor called" << endl;
    }
    
    void Student::PrintName() {
        cout << "My name is " << this->name << endl;
    }
    
    extern "C" {
        Student* new_student(const char* name) {
            return new Student(name);
        }
    
        void print_student_name(Student* stu) {
            stu->PrintName();
        }
    }
    

    运行相同的Python代码:

    xujijun@pc:~/codespace/python$ python3 hello.py
    input student name: yanhewu
    My name is yanhewu
    

    从输出可以看到,Student的析构函数并没有被调用。这里可以确定,Python的GC并没有对动态库中申请的内存进行处理,也确实不能进行处理(毕竟不是Python环境下申请的内存,在C++动态库中可能会先释放这部分内存,如果GC再次释放就会出现内存问题)。但是内存泄漏的问题还是需要解决的,可以参照以下做法:

    C++代码中添加内存释放接口:

    #include <iostream>
    #include <cstdio>
    #include <string>
    using namespace std;
    
    class Student {
    private:
        string name;    
    public:
        Student(const char* n);
        ~Student();
        void PrintName();
    };
    
    Student::Student(const char* n) {
        this->name.assign(n);
    }
    
    Student::~Student() {
        cout << "Student's destructor called" << endl;
    }
    
    void Student::PrintName() {
        cout << "My name is " << this->name << endl;
    }
    
    extern "C" {
        Student* new_student(const char* name) {
            return new Student(name);
        }
    
        // 释放对象内存函数
        void del_student(Student* stu) {
            delete stu;
        }
    
        void print_student_name(Student* stu) {
            stu->PrintName();
        }
    }
    

    Python代码中,在Student类中调用内存释放函数:

    # -*- coding: utf-8 -*- #
    from ctypes import CDLL
    
    hello = CDLL('./hello.so')
    
    class Student(object):
        def __init__(self, name):
            self.stu = hello.new_student(name.encode('utf-8'))
    
        def __del__(self):
            # Python的对象在被GC时调用__del__函数
            hello.del_student(self.stu)
        
        def print_name(self):
            hello.print_student_name(self.stu)
    
    if __name__ == '__main__':
        name = input('input student name: ')
        s = Student(name)
        s.print_name()
    

    运行Python代码:

    xujijun@pc:~/codespace/python$ python3 hello.py
    input student name: yanhewu
    My name is yanhewu
    Student's destructor called
    

    可以看到,C++动态库中的Student类的析构函数被调用了。再次使用Valgrind检查内存使用情况:

    ==23780== HEAP SUMMARY:
    ==23780==     in use at exit: 647,162 bytes in 630 blocks
    ==23780==   total heap usage: 8,910 allocs, 8,280 frees, 5,317,023 bytes allocated
    ==23780== 
    ==23780== LEAK SUMMARY:
    ==23780==    definitely lost: 0 bytes in 0 blocks
    ==23780==    indirectly lost: 0 bytes in 0 blocks
    ==23780==      possibly lost: 4,008 bytes in 7 blocks
    ==23780==    still reachable: 643,154 bytes in 623 blocks
    ==23780==         suppressed: 0 bytes in 0 blocks
    ==23780== Rerun with --leak-check=full to see details of leaked memory
    ==23780== 
    ==23780== For counts of detected and suppressed errors, rerun with: -v
    ==23780== Use --track-origins=yes to see where uninitialised values come from
    ==23780== ERROR SUMMARY: 793 errors from 87 contexts (suppressed: 0 from 0)
    

    可以看到,definitely lost已经变为0,可以确定动态库申请的内存被成功释放。

  • 相关阅读:
    java_方法
    Java switch case和数组
    Java流程控制语句
    Java变量和运算符
    Java对象和类
    Java基础语法
    Vmare12(虚拟机)安装Mac OS X Yosemite 10.10
    System Operations on AWS
    System Operations on AWS
    System Operations on AWS
  • 原文地址:https://www.cnblogs.com/yanhewu/p/9296892.html
Copyright © 2011-2022 走看看