2009-10-23 26 views
37

Tôi chỉ mới bắt đầu với ctypes và muốn sử dụng lớp C++ mà tôi đã xuất trong tệp dll từ bên trong python bằng cách sử dụng ctypes. Vì vậy, cho phép nói C++ mã của tôi trông giống như sau:Làm thế nào để sử dụng các lớp C++ với ctypes?

class MyClass { 
    public: 
    int test(); 
... 

tôi sẽ biết tạo ra một file .dll có chứa lớp này và sau đó tải file .dll trong python sử dụng ctypes. Bây giờ làm cách nào để tạo một đối tượng kiểu MyClass và gọi hàm kiểm tra của nó? Điều đó thậm chí có thể với ctypes? Ngoài ra tôi sẽ xem xét sử dụng SWIG hoặc Boost.Python nhưng ctypes có vẻ như là lựa chọn dễ dàng nhất cho các dự án nhỏ.

Trả lời

32

Câu chuyện ngắn là không có giao diện nhị phân chuẩn cho C++ theo cách có C. Các trình biên dịch khác nhau xuất các tệp nhị phân khác nhau cho cùng thư viện động C++, do mangling tên và các cách khác nhau để xử lý ngăn xếp giữa chức năng gọi thư viện.

Rất tiếc, thực sự không phải là cách di động để truy cập thư viện C++ nói chung. Nhưng, đối với một trình biên dịch tại một thời điểm, không có vấn đề gì.

This blog post cũng có tổng quan ngắn gọn về lý do tại sao tính năng này hiện không hoạt động. Có lẽ sau khi C++ 0x xuất hiện, chúng ta sẽ có một ABI chuẩn cho C++? Cho đến lúc đó, có lẽ bạn sẽ không có cách nào để truy cập các lớp C++ thông qua số ctypes của Python.

33

Bên cạnh Boost.Python (có lẽ là giải pháp thân thiện hơn cho các dự án lớn hơn yêu cầu ánh xạ một-một của các lớp C++ tới các lớp python), bạn có thể cung cấp giao diện C bên C++. Đó là một trong những giải pháp của nhiều người vì vậy nó có sự đánh đổi thương mại riêng của mình, nhưng tôi sẽ trình bày nó vì lợi ích của những người không quen thuộc với kỹ thuật này. Để tiết lộ đầy đủ, với cách tiếp cận này, người ta sẽ không giao tiếp C++ với python, nhưng C++ thành C tới Python. Dưới đây tôi bao gồm một ví dụ đáp ứng yêu cầu của bạn để cho bạn thấy ý tưởng chung về cơ sở "c" bên ngoài của trình biên dịch C++.

//YourFile.cpp (compiled into a .dll or .so file) 
#include <new> //For std::nothrow 
//Either include a header defining your class, or define it here. 

extern "C" //Tells the compile to use C-linkage for the next scope. 
{ 
    //Note: The interface this linkage region needs to use C only. 
    void * CreateInstanceOfClass(void) 
    { 
     // Note: Inside the function body, I can use C++. 
     return new(std::nothrow) MyClass; 
    } 

    //Thanks Chris. 
    void DeleteInstanceOfClass (void *ptr) 
    { 
     delete(std::nothrow) ptr; 
    } 

    int CallMemberTest(void *ptr) 
    { 

     // Note: A downside here is the lack of type safety. 
     // You could always internally(in the C++ library) save a reference to all 
     // pointers created of type MyClass and verify it is an element in that 
     //structure. 
     // 
     // Per comments with Andre, we should avoid throwing exceptions. 
     try 
     { 
      MyClass * ref = reinterpret_cast<MyClass *>(ptr); 
      return ref->Test(); 
     } 
     catch(...) 
     { 
      return -1; //assuming -1 is an error condition. 
     } 
    } 

} //End C linkage scope. 

Bạn có thể biên dịch mã này với

gcc -shared -o test.so test.cpp 
#creates test.so in your current working directory. 

Trong mã python của bạn, bạn có thể làm một cái gì đó như thế này (nhắc tương tác từ 2.7 hiển thị):

>>> from ctypes import cdll 
>>> stdc=cdll.LoadLibrary("libc.so.6") # or similar to load c library 
>>> stdcpp=cdll.LoadLibrary("libstdc++.so.6") # or similar to load c++ library 
>>> myLib=cdll.LoadLibrary("/path/to/test.so") 
>>> spam = myLib.CreateInstanceOfClass() 
>>> spam 
[outputs the pointer address of the element] 
>>> value=CallMemberTest(spam) 
[does whatever Test does to the spam reference of the object] 

Tôi chắc chắn Boost .Python làm một cái gì đó tương tự dưới mui xe, nhưng có lẽ sự hiểu biết các khái niệm cấp thấp hơn là hữu ích. Tôi sẽ vui mừng hơn về phương pháp này nếu bạn đang cố truy cập chức năng của thư viện C++ và không cần phải có ánh xạ một-một.

Để biết thêm thông tin về C/C++ tương tác kiểm tra trang này từ Sun: http://dsc.sun.com/solaris/articles/mixing.html#cpp_from_c

+5

Thật vậy, đây là một chút tẻ nhạt, nhưng nó hoạt động. Bạn sẽ đặc biệt muốn xem ra cho ngoại lệ mặc dù. Tôi không nghĩ rằng nó an toàn để giả định các mô-đun 'ctypes' xử lý các chức năng C mà ném ngoại lệ rất tốt. Cụ thể, câu lệnh 'return new MyClass;' rất nguy hiểm vì nó có thể tăng 'std :: bad_alloc'. –

+2

Vui lòng thêm chức năng 'DestroyInstanceOfClass()'. –

+0

Đó là một điểm rất tốt. Tôi sẽ chỉnh sửa ví dụ để sử dụng không có biến thể ném. Một mẹo khác là bắt tất cả các ngoại lệ trong khối thử trong cơ thể C++ của hàm. – AudaAero

0

Đây là một giải thích ngắn gọn về cách sử dụng c và C++ với C_types trong python. How to write a DLL/SO in C++ for Python

+4

-1. Câu hỏi đặt ra là về các lớp C++ * * nhưng liên kết đó chỉ đưa ra một ví dụ về một hàm miễn phí. Ngoài ra, các câu trả lời không chứa bất kỳ thông tin thực tế nào khác ngoài URL phải được đăng dưới dạng nhận xét. – JBentley

3

answer by AudaAero là rất tốt nhưng chưa hoàn thành (ít nhất là đối với tôi).

Trên hệ thống của tôi (Debian Stretch x64 với GCC và G ++ 6.3.0, Python 3.5.3) Tôi có segfaults ngay khi tôi gọi hàm thành viên truy cập giá trị thành viên của lớp học. Tôi được chẩn đoán bằng cách in các giá trị con trỏ tới stdout mà con trỏ void * được mã hóa trên 64 bit trong trình bao bọc đang được biểu diễn trên 32 bit bằng Python.Do đó các vấn đề lớn xảy ra khi nó được truyền lại cho một hàm bao hàm thành viên.

Giải pháp tôi thấy là thay đổi:

spam = myLib.CreateInstanceOfClass() 

Into

Class_ctor_wrapper = myLib.CreateInstanceOfClass 
Class_ctor_wrapper.restype = c_void_p 
spam = c_void_p(Class_ctor_wrapper()) 

Vì vậy, hai điều bị mất tích: thiết lập các kiểu trả về để c_void_p (mặc định là int) sau đó tạo ra đối tượng c_void_p (không chỉ là số nguyên).

Tôi ước tôi có thể viết nhận xét nhưng tôi vẫn thiếu 27 điểm đại diện.

0

Mở rộng AudaAero'sGabriel Devillers câu trả lời tôi sẽ hoàn thành việc tạo trường hợp đối tượng lớp theo: stdc=c_void_p(cdll.LoadLibrary("libc.so.6")) sử dụng ctypesc_void_p kiểu dữ liệu đảm bảo các đại diện thích hợp của con trỏ đối tượng lớp trong python.

Ngoài ra hãy chắc chắn rằng quản lý bộ nhớ của dll được xử lý bởi dll (bộ nhớ phân bổ trong dll nên được deallocated cũng trong dll, và không phải trong python)!

Các vấn đề liên quan