2011-06-28 31 views
62

Tôi hiện đang phát triển thư viện C++ cho Windows sẽ được phân phối dưới dạng tệp DLL. Mục tiêu của tôi là tối đa hóa khả năng tương tác nhị phân; chính xác hơn, các hàm trong DLL của tôi phải có thể sử dụng được từ mã được biên dịch với nhiều phiên bản của MSVC++ và MinGW mà không cần biên dịch lại DLL. Tuy nhiên, tôi nhầm lẫn về quy ước gọi điện nào là tốt nhất, cdecl hoặc stdcall.__cdecl hoặc __stdcall trên Windows?

Thỉnh thoảng tôi nghe các câu như "quy ước gọi C là người duy nhất được đảm bảo là cùng một trình biên dịch", tương phản với các câu như "There are some variations in the interpretation of cdecl, particularly in how to return values". Điều này dường như không ngăn các nhà phát triển thư viện nhất định (như libsndfile) sử dụng quy ước gọi điện C trong các tệp DLL mà họ phân phối, mà không có bất kỳ sự cố rõ ràng nào.

Mặt khác, quy ước gọi stdcall dường như được xác định rõ. Từ những gì tôi đã nói, tất cả các trình biên dịch Windows về cơ bản là cần thiết để làm theo nó bởi vì nó là quy ước được sử dụng cho Win32 và COM. Điều này dựa trên giả định rằng một trình biên dịch Windows mà không hỗ trợ Win32/COM sẽ không hữu ích lắm. Rất nhiều đoạn mã được đăng trên diễn đàn khai báo chức năng là stdcall nhưng tôi không thể tìm thấy một bài đăng nào rõ ràng giải thích tại sao lý do tại sao.

Có quá nhiều thông tin xung đột ở đó và mọi tìm kiếm tôi chạy đều mang lại cho tôi các câu trả lời khác nhau, điều này thực sự không giúp tôi quyết định giữa hai câu hỏi. Tôi đang tìm kiếm một lời giải thích rõ ràng, chi tiết, có lý luận về lý do tại sao tôi nên chọn giải thích khác (hoặc tại sao hai cái này tương đương). Lưu ý rằng câu hỏi này không chỉ áp dụng cho các chức năng "cổ điển", mà còn cho các cuộc gọi hàm thành viên ảo, vì hầu hết mã khách hàng sẽ giao tiếp với DLL của tôi thông qua "giao diện", lớp ảo thuần túy (các mẫu sau được mô tả ví dụ: herethere).

+3

"Có một số biến thể trong cách giải thích cdecl, đặc biệt là cách trả về giá trị" - điều này có nghĩa là các hệ điều hành khác nhau sử dụng cdecl trên x86 có thể sử dụng các biến thể khác nhau của cdecl, đó là các ABI khác nhau mà cả hai đều gọi là "cdecl" . Windows ghim các biến thể, bất kỳ triển khai nào chạy trên Windows và không tôn trọng các lựa chọn của Windows sẽ không thể gọi các hàm cdecl của Windows, vì vậy khi bạn nói với stdcall, nó vô dụng đối với lập trình Windows, và có một số tiêu chuẩn C các hàm sẽ khó triển khai. –

Trả lời

63

Tôi đã thực hiện một số thử nghiệm trong thế giới thực (biên dịch các tệp DLL và các ứng dụng bằng MSVC++ và MinGW, sau đó trộn chúng). Khi nó xuất hiện, tôi đã có kết quả tốt hơn với quy ước gọi là cdecl.

Cụ thể hơn: vấn đề với stdcall là MSVC++ mangles tên trong bảng xuất DLL, ngay cả khi sử dụng extern "C". Ví dụ: foo trở thành [email protected]. Điều này chỉ xảy ra khi sử dụng __declspec(dllexport), không phải khi sử dụng tệp DEF; tuy nhiên, các tập tin DEF là một vấn đề bảo trì trong quan điểm của tôi, và tôi không muốn sử dụng chúng.

Các MSVC++ tên mangling đặt ra hai vấn đề:

  • Sử dụng GetProcAddress trên DLL trở nên hơi phức tạp hơn;
  • MinGW theo mặc định không thêm một dấu gạch dưới vào tên trang trí (ví dụ: MinGW sẽ sử dụng [email protected] thay vì [email protected]), làm phức tạp liên kết. Ngoài ra, nó giới thiệu các nguy cơ nhìn thấy "phiên bản không gạch dưới" của DLL và các ứng dụng bật lên trong tự nhiên mà không tương thích với "phiên bản gạch dưới".

Tôi đã thử quy ước cdecl: khả năng tương tác giữa MSVC++ và MinGW hoạt động hoàn hảo, ngoài hộp và tên vẫn chưa được đặt trong bảng xuất DLL. Nó thậm chí hoạt động cho các phương thức ảo.

Vì những lý do này, cdecl là người chiến thắng rõ ràng đối với tôi.

14

Sự khác biệt lớn nhất trong hai quy ước gọi là "_ _cdecl" đặt gánh nặng cân bằng chồng sau khi gọi hàm trên người gọi, cho phép các hàm có số lượng đối số thay đổi. Quy ước "__stdcall" là "đơn giản" trong tự nhiên, tuy nhiên ít linh hoạt hơn trong vấn đề này. Ngoài ra, tôi tin rằng các ngôn ngữ được quản lý sử dụng quy ước stdcall theo mặc định, vì vậy bất kỳ ai sử dụng P/Invoke trên nó sẽ phải nêu rõ quy ước gọi điện nếu bạn sử dụng cdecl.

Vì vậy, nếu tất cả các chữ ký chức năng của bạn sẽ được định nghĩa tĩnh, tôi có thể nghiêng về phía stdcall, nếu không phải là cdecl.

6

Về mặt bảo mật, quy ước __cdecl là "an toàn hơn" vì đó là người gọi cần phải phân phối lại ngăn xếp. Điều gì có thể xảy ra trong thư viện __stdcall là nhà phát triển có thể đã quên phân phối chồng đúng cách hoặc kẻ tấn công có thể tiêm một số mã bằng cách hỏng ngăn xếp của DLL (ví dụ: bằng API hooking) mà sau đó không được người gọi kiểm tra. Tôi không có bất kỳ ví dụ bảo mật CVS nào cho thấy trực giác của tôi là chính xác.

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