使用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\n", 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++代碼中添加內存釋放接口:
#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,可以確定動態庫申請的內存被成功釋放。
使用ctypes在Python中調用C++動態庫