__cdecl hoặc __stdcall trên Windows?


84

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 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 chức năng 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 MSVC ++ và MinGW mà không cần phải biên dịch lại DLL. Tuy nhiên, tôi bối rối không biết quy ước gọi nào là tốt nhất, cdeclhoặc stdcall.

Đôi khi tôi nghe thấy các tuyên bố như "quy ước gọi C là quy ước duy nhất được đảm bảo là giống nhau giữa các trình biên dịch", điều này trái ngược với các tuyên bố như " Có một số biến thể trong cách diễn giải cdecl, đặc biệt là trong cách trả về giá trị ". Đ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 C trong các tệp DLL mà họ phân phối mà không gặp bất kỳ vấn đề nào.

Mặt khác, stdcallquy ước gọi dường như đã được xác định rõ ràng. Từ những gì tôi đã được nói, tất cả các trình biên dịch Windows về cơ bản bắt buộc phải tuân theo nó vì đó là quy ước được sử dụng cho Win32 và COM. Điều này dựa trên giả định rằng trình biên dịch Windows 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 các diễn đàn khai báo các chức năng stdcallnhưng tôi dường như không thể tìm thấy một bài đăng nào giải thích rõ ràng tại sao .

Có quá nhiều thông tin mâu thuẫn ngoài đó và mỗi tìm kiếm tôi chạy đều cho tôi những câu trả lời khác nhau, điều này không thực sự giúp tôi quyết định giữa hai điều đó. Tôi đang tìm kiếm một lời giải thích rõ ràng, chi tiết, được tranh luận về lý do tại sao tôi nên chọn cái này hơn cái kia (hoặc tại sao cả hai tương đương nhau).

Lưu ý rằng câu hỏi này không chỉ áp dụng cho các hàm "cổ điển", mà còn cho các lệnh gọi hàm thành viên ảo, vì hầu hết mã máy khách sẽ giao tiếp với DLL của tôi thông qua "giao diện", các lớp ảo thuần túy (theo các mẫu được mô tả, ví dụ như ở đâyở đó ).


4
"Có một số biến thể trong cách diễn giải cdecl, đặc biệt là trong cách trả về giá trị" - điều này xấp xỉ 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 như bạn nói với stdcall, nó vô dụng đối với lập trình Windows và có một số C tiêu chuẩn nó sẽ khó thực hiện.
Steve Jessop

1
Quy ước gọi __stdcall được sử dụng để gọi các hàm API của Win32. docs.microsoft.com/en-us/cpp/cpp/stdcall?view=vs-2017
Zanna

Câu trả lời:


79

Tôi vừa thực hiện một số thử nghiệm trong thế giới thực (biên dịch DLL và ứng dụng với 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 cdeclquy ước gọi điện.

Cụ thể hơn: vấn đề stdcalllà MSVC ++ mang tên trong bảng xuất DLL, ngay cả khi sử dụng extern "C". Ví dụ footrở thành _foo@4. Điều này chỉ xảy ra khi sử dụng __declspec(dllexport), không xảy ra khi sử dụng tệp DEF; tuy nhiên, theo quan điểm của tôi, các tệp DEF là một vấn đề phức tạp khi bảo trì và tôi không muốn sử dụng chúng.

Việc đặt tên MSVC ++ đặt ra hai vấn đề:

  • Việc sử dụng GetProcAddresstrên DLL trở nên phức tạp hơn một chút;
  • MinGW theo mặc định không thêm dấu gạch dưới vào các tên được trang trí (ví dụ: MinGW sẽ sử dụng foo@4thay vì _foo@4), điều này làm phức tạp liên kết. Ngoài ra, nó dẫn đến nguy cơ nhìn thấy "phiên bản không gạch dưới" của các tệp DLL và các ứng dụng bật lên tự nhiên không tương thích với "phiên bản gạch dưới".

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

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


Đáng chú ý, điều này không áp dụng cho các DLL 64-bit, bởi vì __stdcall và __cdecl nói chung đều bị bỏ qua- vì vậy không có sự xáo trộn tên nào xảy ra trên các hàm C đã xuất.
jtbr 19/07/18

@jtbr Làm thế nào về các hàm C ++ đã xuất (tức là không extern "C")?
Ela782

@ Ela782 C ++ sẽ luôn có một số tên mangling, để hỗ trợ quá tải hàm. Nhưng điều này không được chuẩn hóa và do đó có thể khác nhau giữa các trình biên dịch.
jtbr

@jtbr Xin lỗi, ý tôi không phải là cái tên mangling. Ý tôi là liệu __stdcall và __cdecl đều bị bỏ qua trong các DLL 64-bit với các hàm C ++ được xuất hay không.
Ela782

1
@ Ela782 Có; Tôi tin rằng __stdcall và __cdecl bị bỏ qua trong tất cả các biên dịch x86-64. Xem wikipedia .
jtbr

20

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 ngăn xếp sau một lệnh gọi hàm trên trình gọi, điều này cho phép các hàm có số lượng đối số thay đổi. Quy ước "__stdcall" về bản chất "đơn giản" hơn, tuy nhiên về mặt này kém linh hoạt hơn.

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 nếu bạn sử dụng cdecl.

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


11

Về mặt bảo mật, __cdeclquy ước là "an toàn hơn" vì nó là người gọi cần phân bổ ngăn xếp. Điều có thể xảy ra trong __stdcallthư viện là nhà phát triển có thể đã quên phân bổ ngăn xếp đúng cách hoặc kẻ tấn công có thể chèn một số mã bằng cách làm hỏng ngăn xếp của DLL (ví dụ: bằng cách nối API) mà sau đó người gọi không 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à đúng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.