2009-11-21 33 views
30

Tôi đang thiết kế API cho thư viện C++ sẽ được phân phối trong một đối tượng dll/shared. Thư viện chứa các lớp polymorhic với các chức năng ảo. Tôi lo ngại rằng nếu tôi phơi bày các hàm ảo này trên API DLL, tôi tự cắt bỏ khả năng mở rộng cùng một lớp với nhiều chức năng ảo hơn mà không làm hỏng khả năng tương thích nhị phân với các ứng dụng được xây dựng cho phiên bản trước của thư viện.Cách thiết kế API C++ cho khả năng mở rộng tương thích nhị phân

Một tùy chọn sẽ là sử dụng thành ngữ PImpl để ẩn tất cả các lớp có chức năng ảo, nhưng điều đó dường như có những hạn chế: ứng dụng này mất khả năng phân lớp các lớp thư viện và ghi đè các phương thức ảo.

Làm thế nào bạn thiết kế một lớp API có thể được phân lớp trong ứng dụng mà không mất khả năng mở rộng API với các phương thức ảo (không trừu tượng) trong phiên bản mới của dll trong khi vẫn tương thích nhị phân ngược?

Cập nhật: nền tảng đích cho thư viện là windows/msvc và linux/gcc.

Trả lời

29

Một vài tháng trước, tôi đã viết một bài viết có tên "Khả năng tương thích nhị phân của các thư viện được chia sẻ được thực hiện trong C++ trên hệ thống GNU/Linux" [pdf]. Mặc dù các khái niệm tương tự nhau trên hệ thống Windows, tôi chắc chắn chúng không giống nhau. Nhưng khi đọc bài viết, bạn có thể nhận được một khái niệm về những gì đang xảy ra ở mức độ nhị phân C++ có liên quan đến khả năng tương thích.

Nhân tiện, giao diện nhị phân ứng dụng GCC được tóm tắt trong bản nháp tài liệu chuẩn "Itanium ABI", vì vậy bạn sẽ có nền tảng chính thức cho tiêu chuẩn mã hóa bạn chọn.

Chỉ cần cho một ví dụ nhanh: trong GCC, bạn có thể mở rộng một lớp với nhiều chức năng ảo hơn, nếu không có lớp nào khác thừa hưởng nó. Đọc bài viết để có các quy tắc tốt hơn.

Tuy nhiên, các quy tắc đôi khi quá phức tạp để hiểu. Vì vậy, bạn có thể quan tâm đến một công cụ xác minh khả năng tương thích của hai phiên bản nhất định: abi-compliance-checker cho Linux.

+0

Máy chủ lưu trữ tệp PDF bạn đã đăng dường như đã hoàn tất. Bạn có thể đăng lại không? –

+0

@ MichałGórny có vẻ như nó đã hoạt động trở lại, nhưng tôi đã lưu trữ lại nó [ở đây] (http://static.coldattic.info/restricted/science/syrcose09/cppbincomp.pdf) chỉ trong trường hợp. –

1

Soạn thảo nhị phân C++ thường khó, ngay cả khi không thừa kế. Hãy xem GCC chẳng hạn. Trong 10 năm qua, tôi không chắc có bao nhiêu lần phá vỡ những thay đổi của ABI mà họ đã có. Sau đó, MSVC có một tập hợp các quy ước khác nhau, do đó liên kết với GCC và ngược lại không thể thực hiện được ... Nếu bạn so sánh với thế giới C, trình biên dịch inter-op có vẻ tốt hơn ở đó.

Nếu bạn đang sử dụng Windows, bạn nên xem COM. Khi bạn giới thiệu chức năng mới, bạn có thể thêm giao diện. Sau đó, người gọi có thể QueryInterface() để người mới phơi bày chức năng mới đó và thậm chí nếu bạn kết thúc việc thay đổi nhiều thứ, bạn có thể rời khỏi cài đặt cũ ở đó hoặc bạn có thể viết shims cho giao diện cũ.

+4

"Trong 10 năm qua, tôi không chắc chắn có bao nhiêu thay đổi ABI mà họ đã có". Hãy để tôi nói cho bạn biết có bao nhiêu. ** ONE. ** ABI hiện tại được chính thức hóa và được mô tả trong tài liệu chuẩn. –

+1

Tôi biết có một sự ngắt quãng lớn giữa 2.95 và 3.0 (đây là vấn đề nghiêm trọng đối với BeOS và Haiku), nhưng tôi có vẻ nhớ lại một khoảng thời gian khá lớn giữa 3.2 và 3.3 hoặc ở xa (gây ra một chút rắc rối cho Gentoo) . Điều này có đúng không? – greyfade

+1

Ồ, tôi nghĩ rằng 3.0 đã lớn hơn 10 năm. Yeah, hai. Vào tháng 6 năm 2001, với bản phát hành 3.0. Kể từ đó, họ đã làm việc để tạo ra một thiết kế ABI sống lâu và chấp nhận nó với phiên bản 3.2 vào tháng 8 năm 2002. Bảy năm trước là lần cuối cùng. –

11

Có một bài viết thú vị trên cơ sở tri thức KDE mô tả của việc phải làm và không nên làm khi nhằm tương thích nhị phân khi viết một thư viện: Policies/Binary Compatibility Issues With C++

1

Tôi nghĩ rằng bạn hiểu sai vấn đề của subclassing.

Đây là Pimpl của bạn:

// .h 
class Derived 
{ 
public: 
    virtual void test1(); 
    virtual void test2(); 
private; 
    Impl* m_impl; 
}; 

// .cpp 
struct Impl: public Base 
{ 
    virtual void test1(); // override Base::test1() 
    virtual void test2(); // override Base::test2() 

    // data members 
}; 

void Derived::test1() { m_impl->test1(); } 
void Derived::test2() { m_impl->test2(); } 

Thấy không?Không có vấn đề gì với việc ghi đè các phương thức ảo của Base, bạn chỉ cần đảm bảo redeclare chúng virtual trong Derived để chúng phát sinh từ Derived biết chúng có thể viết lại chúng (chỉ khi bạn muốn như vậy, mà theo cách này là một cách tuyệt vời cung cấp số final cho những người thiếu thông tin này) và bạn vẫn có thể xác định lại nó cho chính mình trong Impl thậm chí có thể gọi phiên bản Base.

Không có vấn đề với Pimpl ở đó.

Mặt khác, bạn mất đa hình, điều này có thể gây phiền toái. Đó là vào bạn để quyết định xem bạn muốn đa hình hay chỉ là thành phần.

+0

Lớp trình bao bọc của Pimpl nên có các phương thức không ảo, như trong trường hợp này nó được sử dụng chính xác để ẩn các phương thức ảo của các lớp thư viện. Nếu các phương thức ảo sẽ có mặt trên giao diện thư viện, nó sẽ làm cho nó không thể mở rộng giao diện thư viện trong các phiên bản mới với nhiều phương thức ảo hơn trong khi vẫn giữ được phần mềm nhị phân. Nhưng nếu inteface được công bố là không ảo, làm thế nào khách hàng sẽ phân lớp nó? Do đó bài viết. – shojtsy

+1

Được rồi, vậy thì tôi hiểu ý của bạn. Nhưng nó không thực sự là một vấn đề của Pimpl vào thời điểm này. Thêm một vấn đề về việc sử dụng các phương thức 'virtual' trong giao diện. –

+0

"bạn chỉ cần đảm bảo redeclare chúng ảo trong Derived để những người xuất phát từ Derived có thể viết lại chúng quá". Không, các phương thức ảo bị ghi đè cũng ảo hoàn toàn. –

0

Nếu bạn vạch trần lớp PImpl trong tệp tiêu đề, bạn có thể kế thừa từ đó. Bạn vẫn có thể duy trì tính di động ngược vì các lớp bên ngoài chứa một con trỏ tới đối tượng PImpl. Tất nhiên nếu mã khách hàng của thư viện không phải là rất khôn ngoan, nó có thể lạm dụng đối tượng PImpl tiếp xúc này, và hủy hoại khả năng tương thích ngược nhị phân. Bạn có thể thêm một số ghi chú để cảnh báo người dùng trong tệp tiêu đề của PImpl.

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