2010-06-09 36 views
9

Vì tôi quan sát thấy một số hành vi kỳ lạ của các biến toàn cầu trong các thư viện được nạp động, tôi đã viết bài kiểm tra sau đây.Thư viện được tải động và biểu tượng chung được chia sẻ

Lúc đầu chúng ta cần một thư viện liên kết tĩnh: Tiêu đề test.hpp

#ifndef __BASE_HPP 
#define __BASE_HPP 

#include <iostream> 

class test { 
private: 
    int value; 
public: 
    test(int value) : value(value) { 
    std::cout << "test::test(int) : value = " << value << std::endl; 
    } 

    ~test() { 
    std::cout << "test::~test() : value = " << value << std::endl; 
    } 

    int get_value() const { return value; } 
    void set_value(int new_value) { value = new_value; } 
}; 

extern test global_test; 

#endif // __BASE_HPP 

và nguồn test.cpp

#include "base.hpp" 

test global_test = test(1); 

Sau đó, tôi đã viết một thư viện tự động nạp: library.cpp

#include "base.hpp" 

extern "C" { 
    test* get_global_test() { return &global_test; } 
} 

và tải chương trình khách hàng g thư viện này: client.cpp

#include <iostream> 
#include <dlfcn.h> 
#include "base.hpp" 

typedef test* get_global_test_t(); 

int main() { 
    global_test.set_value(2); // global_test from libbase.a 
    std::cout << "client:  " << global_test.get_value() << std::endl; 

    void* handle = dlopen("./liblibrary.so", RTLD_LAZY); 
    if (handle == NULL) { 
    std::cout << dlerror() << std::endl; 
    return 1; 
    } 

    get_global_test_t* get_global_test = NULL; 
    void* func = dlsym(handle, "get_global_test"); 
    if (func == NULL) { 
    std::cout << dlerror() << std::endl; 
    return 1; 
    } else get_global_test = reinterpret_cast<get_global_test_t*>(func); 

    test* t = get_global_test(); // global_test from liblibrary.so 
    std::cout << "liblibrary.so: " << t->get_value() << std::endl; 
    std::cout << "client:  " << global_test.get_value() << std::endl; 

    dlclose(handle); 
    return 0; 
} 

Bây giờ tôi biên dịch thư viện nạp tĩnh với

g++ -Wall -g -c base.cpp 
ar rcs libbase.a base.o 

thư viện tự động nạp

g++ -Wall -g -fPIC -shared library.cpp libbase.a -o liblibrary.so 

và khách hàng

g++ -Wall -g -ldl client.cpp libbase.a -o client 

Bây giờ tôi quan sát: Máy khách và thư viện được nạp động có một phiên bản khác nhau của biến số global_test. Nhưng trong dự án của tôi, tôi đang sử dụng cmake. Xây dựng kịch bản trông như thế này:

CMAKE_MINIMUM_REQUIRED(VERSION 2.6) 
PROJECT(globaltest) 

ADD_LIBRARY(base STATIC base.cpp) 

ADD_LIBRARY(library MODULE library.cpp) 
TARGET_LINK_LIBRARIES(library base) 

ADD_EXECUTABLE(client client.cpp) 
TARGET_LINK_LIBRARIES(client base dl) 

phân tích tạo makefile s Tôi thấy rằng cmake xây dựng cho khách hàng

g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client 

này kết thúc trong một hành vi hơi khác nhau nhưng gây tử vong: Các global_test của khách hàng và thư viện được nạp động là như nhau nhưng sẽ bị hủy hai lần vào cuối chương trình.

Tôi có sử dụng cmake sai không? Có thể khách hàng và thư viện được tải động sử dụng cùng một global_test nhưng không có vấn đề hủy diệt kép này không?

+0

Phản ứng đầu tiên của tôi là đặt câu hỏi về sự cần thiết cho biến toàn cầu này. –

+0

Ok, trong chương trình gốc của tôi, biến toàn cầu này là một hằng số được cung cấp bởi một thư viện liên kết tĩnh. Tuy nhiên, nó sẽ bị hủy hai lần trong phiên bản cmake – phlipsy

+0

Vấn đề tương tự sẽ áp dụng cho bất kỳ mẫu đơn nào vì vậy tôi không thấy vấn đề với toàn cầu – Elemental

Trả lời

1

Câu hỏi đầu tiên của tôi là nếu có bất kỳ lý do cụ thể nào mà bạn cả tĩnh lẫn động (thông qua dlopen) liên kết cùng một mã?

Đối với vấn đề của bạn: -rdynamic sẽ xuất các ký hiệu từ chương trình của bạn và những gì có thể xảy ra là liên kết động sẽ giải quyết tất cả các tham chiếu đến biến toàn cầu của bạn đến ký hiệu đầu tiên nó gặp trong bảng biểu tượng. Cái nào tôi không biết.

EDIT: cho mục đích của bạn tôi sẽ liên kết chương trình của bạn theo cách đó:

g++ -Wall -g -ldl client.cpp -llibrary -L. -o client 

Bạn có thể cần phải sửa chữa đơn đặt hàng.

+0

Trong một số hàm tôi đã xác định một số hằng số cồng kềnh trong thư viện liên kết tĩnh. Thư viện được tải động là một plugin cho máy khách sử dụng các tính năng này được cung cấp bởi thư viện liên kết tĩnh. – phlipsy

+0

@phlipsy: cách bạn có thể nên sử dụng các hằng số đó là liên kết thư viện tĩnh với tệp thực thi chính, liên kết tệp thực thi chính với -rdynamic (như cmake) và không liên kết plugin với thư viện tĩnh. Tuy nhiên, tôi không biết cách liên kết plugin của bạn với chương trình. Bạn có thể thử đưa ra mã chung (thư viện tĩnh) vào thư viện động và liên kết cả chương trình lẫn plugin với điều đó. – Tomek

+0

Sau đó, khi chạy, plugin sẽ sử dụng định nghĩa của các biểu tượng được sử dụng nằm trong ứng dụng khách thực thi? Điều đó có hợp pháp không? – phlipsy

2

Nếu sử dụng thư viện được chia sẻ, bạn phải xác định nội dung bạn muốn xuất với macro như here. Xem định nghĩa macro DLL_PUBLIC trong đó.

+1

Chỉ trên Windows! – bmargulies

+0

Đây là một macro tổng quát. Tôi làm việc cả trên GNU/Linux và Windows. Xem khai báo #ifdef. – INS

2

Theo mặc định, trình liên kết sẽ không kết hợp biến toàn cục ('D') trong cơ sở thực thi với một trong thư viện được chia sẻ. Cơ sở thực thi là đặc biệt. Có thể có một cách mơ hồ để làm điều này với một trong những tập tin kiểm soát tối nghĩa mà ld đọc, nhưng tôi loại nghi ngờ nó.

--nhập động sẽ gây ra các biểu tượng 'D' có sẵn cho libs dùng chung.

Tuy nhiên, hãy xem xét quy trình. Bước 1: bạn tạo một DSO từ một .o với một 'U' và a .a với một 'D'. Vì vậy, trình liên kết kết hợp biểu tượng trong DSO. Bước 2, bạn tạo tập tin thực thi có chữ 'U' trong một trong các tệp .o và 'D' trong cả tệp .a và DSO. Nó sẽ cố gắng giải quyết bằng cách sử dụng quy tắc từ trái sang phải.

Các biến, trái với các chức năng, gây ra những khó khăn nhất định đối với mối liên kết giữa các mô-đun trong mọi trường hợp. Cách thực hành tốt hơn là tránh tham chiếu var toàn cục qua các ranh giới mô-đun và sử dụng các cuộc gọi hàm. Tuy nhiên, điều đó sẽ vẫn không thành công cho bạn nếu bạn đặt cùng một hàm trong cả tệp thực thi cơ sở lẫn lib được chia sẻ.

+0

Bạn có nghĩa là liên kết các khách hàng * và * thư viện chống lại 'libbase.a' là một ý tưởng tồi? Ok, vì vậy bạn đề nghị không liên kết thư viện với 'libbase.a' bởi vì khi chạy các ký hiệu từ máy khách sẽ nhận các cuộc gọi trong thư viện? Nó hoạt động ... nhưng nó có hợp pháp không? – phlipsy

+0

Tôi đã chỉnh sửa để đơn giản vì tôi không hoàn toàn chắc chắn từ câu hỏi của bạn mà các tệp đang kết thúc ở đâu. – bmargulies

+0

Có một thư viện thường được liên kết tĩnh (được gọi là 'libbase.a'), một thư viện được nạp động (một plugin) sử dụng các phần của' libbase.a' và một trình khách tải plugin và sử dụng các phần của 'libbase.a'. – phlipsy

3
g++ -Wall -g -ldl -rdynamic client.cpp libbase.a -o client 

CMake thêm -rdynamic tùy chọn cho phép thư viện tải để giải quyết các biểu tượng trong việc tải thực thi ... Vì vậy, bạn có thể thấy rằng đây là những gì bạn không muốn. Nếu không có tùy chọn này, nó chỉ bỏ lỡ biểu tượng này một cách tình cờ.

Nhưng ... Bạn không nên làm bất kỳ nội dung nào như vậy ở đó. Thư viện và tệp thi hành của bạn nên không chia sẻ biểu tượng trừ khi chúng thực sự cần được chia sẻ.

Luôn nghĩ liên kết động là liên kết tĩnh.

0

Tôi khuyên bạn nên sử dụng dlopen(... RTLD_LAZY|RTLD_GLOBAL); để hợp nhất các bảng biểu tượng toàn cầu.

0

tôi sẽ kiến ​​nghị để biên dịch bất kỳ thư viện tĩnh .a mà bạn có kế hoạch liên kết đến một thư viện dinamic, với -fvisibility = tham số ẩn, vì vậy:

g ++ Wall -fvisibility = ẩn -g cơ sở -c. cpp

+0

Bạn có thể giải thích tại sao không? – Charles

+0

Tôi xin lỗi, đã lâu rồi tôi mới tham gia vào vấn đề này và tôi không thấy bối cảnh nữa. Tuy nhiên, tôi đánh giá cao đề xuất của tôi là hữu ích. Phải không? ;-) – smrt28

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