Xuất các hàm từ DLL với dllexport


105

Tôi muốn có một ví dụ đơn giản về việc xuất một hàm từ C ++ Windows DLL.

Tôi muốn xem tiêu đề, .cpptệp và .deftệp (nếu thực sự cần thiết).

Tôi muốn tên đã xuất không được trang trí . Tôi muốn sử dụng quy ước gọi tiêu chuẩn nhất ( __stdcall?). Tôi muốn sử dụng __declspec(dllexport)và không phải sử dụng .deftệp.

Ví dụ:

  //header
  extern "C"
  {
   __declspec(dllexport) int __stdcall foo(long bar);
  }

  //cpp
  int __stdcall foo(long bar)
  {
    return 0;
  }

Tôi đang cố gắng tránh trình liên kết thêm dấu gạch dưới và / hoặc số (số lượng byte?) Vào tên.

Tôi đồng ý với việc không hỗ trợ dllimportdllexportsử dụng cùng một tiêu đề. Tôi không muốn bất kỳ thông tin nào về việc xuất các phương thức lớp C ++, chỉ là các hàm toàn cầu kiểu c.

CẬP NHẬT

Không bao gồm quy ước gọi (và sử dụng extern "C") cung cấp cho tôi các tên xuất như tôi muốn, nhưng điều đó có nghĩa là gì? Có phải bất kỳ quy ước gọi mặc định nào mà tôi nhận được là pinvoke (.NET), khai báo (vb6) và GetProcAddressmong đợi không? (Tôi đoán choGetProcAddress nó sẽ phụ thuộc vào con trỏ hàm mà người gọi đã tạo).

Tôi muốn DLL này được sử dụng mà không có tệp tiêu đề, vì vậy tôi không thực sự cần quá nhiều thứ lạ mắt #definesđể làm cho tiêu đề có thể sử dụng được bởi người gọi.

Tôi đồng ý với câu trả lời là tôi phải sử dụng một *.deftệp.


Tôi có thể nhớ sai nhưng tôi nghĩ rằng: a) extern Csẽ loại bỏ trang trí mô tả các loại tham số của hàm, nhưng không xóa trang trí mô tả quy ước gọi của hàm; b) để loại bỏ tất cả trang trí, bạn cần chỉ định tên (không trang trí) trong tệp DEF.
ChrisW

Đây là những gì tôi đã thấy. Có lẽ bạn nên thêm điều này như một câu trả lời chính thức?
Aardvark

Câu trả lời:


134

Nếu bạn muốn xuất C thuần túy, hãy sử dụng dự án C không phải C ++. C ++ DLL dựa vào việc phân bổ tên cho tất cả các isms C ++ (không gian tên, v.v ...). Bạn có thể biên dịch mã của mình dưới dạng C bằng cách vào cài đặt dự án của bạn trong C / C ++ -> Nâng cao, có một tùy chọn "Biên dịch dưới dạng" tương ứng với các công tắc trình biên dịch / TP và / TC.

Nếu bạn vẫn muốn sử dụng C ++ để viết nội dung bên trong lib của mình nhưng xuất một số hàm không bị nhầm lẫn để sử dụng bên ngoài C ++, hãy xem phần thứ hai bên dưới.

Xuất / Nhập DLL Libs trong VC ++

Điều bạn thực sự muốn làm là xác định macro có điều kiện trong tiêu đề sẽ được bao gồm trong tất cả các tệp nguồn trong dự án DLL của bạn:

#ifdef LIBRARY_EXPORTS
#    define LIBRARY_API __declspec(dllexport)
#else
#    define LIBRARY_API __declspec(dllimport)
#endif

Sau đó, trên một hàm mà bạn muốn xuất, bạn sử dụng LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Trong dự án xây dựng thư viện của bạn, hãy tạo một xác định LIBRARY_EXPORTS điều này sẽ khiến các chức năng của bạn được xuất cho bản dựng DLL của bạn.

Từ LIBRARY_EXPORTS sẽ không được xác định trong một dự án sử dụng DLL, khi dự án đó bao gồm tệp tiêu đề của thư viện của bạn, tất cả các chức năng sẽ được nhập thay thế.

Nếu thư viện của bạn là đa nền tảng, bạn có thể xác định LIBRARY_API là không có gì khi không có trên Windows:

#ifdef _WIN32
#    ifdef LIBRARY_EXPORTS
#        define LIBRARY_API __declspec(dllexport)
#    else
#        define LIBRARY_API __declspec(dllimport)
#    endif
#elif
#    define LIBRARY_API
#endif

Khi sử dụng dllexport / dllimport bạn không cần sử dụng tệp DEF, nếu sử dụng tệp DEF bạn không cần sử dụng dllexport / dllimport. Hai phương pháp hoàn thành cùng một nhiệm vụ theo những cách khác nhau, tôi tin rằng dllexport / dllimport là phương pháp được đề xuất trong số hai phương pháp.

Xuất các hàm không bị nhầm lẫn từ C ++ DLL cho LoadLibrary / PInvoke

Nếu bạn cần điều này để sử dụng LoadLibrary và GetProcAddress hoặc có thể nhập từ một ngôn ngữ khác (tức là PInvoke từ .NET hoặc FFI trong Python / R, v.v.), bạn có thể sử dụng extern "C" nội tuyến với dllexport của mình để yêu cầu trình biên dịch C ++ không làm sai tên. Và vì chúng tôi đang sử dụng GetProcAddress thay vì dllimport, chúng tôi không cần thực hiện điệu nhảy ifdef từ trên xuống, chỉ cần một dllexport đơn giản:

Mật mã:

#define EXTERN_DLL_EXPORT extern "C" __declspec(dllexport)

EXTERN_DLL_EXPORT int getEngineVersion() {
  return 1;
}

EXTERN_DLL_EXPORT void registerPlugin(Kernel &K) {
  K.getGraphicsServer().addGraphicsDriver(
    auto_ptr<GraphicsServer::GraphicsDriver>(new OpenGLGraphicsDriver())
  );
}

Và đây là những gì xuất khẩu trông như thế nào với Dumpbin / export:

  Dump of file opengl_plugin.dll

  File Type: DLL

  Section contains the following exports for opengl_plugin.dll

    00000000 characteristics
    49866068 time date stamp Sun Feb 01 19:54:32 2009
        0.00 version
           1 ordinal base
           2 number of functions
           2 number of names

    ordinal hint RVA      name

          1    0 0001110E getEngineVersion = @ILT+265(_getEngineVersion)
          2    1 00011028 registerPlugin = @ILT+35(_registerPlugin)

Vì vậy, mã này hoạt động tốt:

m_hDLL = ::LoadLibrary(T"opengl_plugin.dll");

m_pfnGetEngineVersion = reinterpret_cast<fnGetEngineVersion *>(
  ::GetProcAddress(m_hDLL, "getEngineVersion")
);
m_pfnRegisterPlugin = reinterpret_cast<fnRegisterPlugin *>(
  ::GetProcAddress(m_hDLL, "registerPlugin")
);

1
extern "C" dường như đã xóa tên mangling kiểu c ++. Toàn bộ điều nhập và xuất (mà tôi đã cố gắng đề xuất không đưa vào câu hỏi) không thực sự là điều tôi đang hỏi (nhưng là thông tin tốt của nó). Tôi đã nghĩ rằng nó sẽ làm mờ vấn đề.
Aardvark

Lý do duy nhất tôi có thể nghĩ rằng bạn cần điều đó là cho LoadLibrary và GetProcAddress ... Điều này đã được giải quyết, tôi sẽ giải thích trong phần trả lời của tôi ...
joshperry 11/02/09

Có phải EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport) không? Đó có phải là trong SDK không?
Aardvark

3
Đừng quên thêm tệp định nghĩa mô-đun vào cài đặt trình liên kết của dự án - chỉ "thêm một mục hiện có vào dự án" là không đủ!
Jimmy

1
Tôi đã sử dụng điều này để biên dịch một DLL với VS và sau đó gọi nó từ R bằng cách sử dụng .C. Tuyệt quá!
Juancentro

33

Đối với C ++:

Tôi vừa phải đối mặt với vấn đề tương tự và tôi nghĩ rằng điều đáng nói là một vấn đề xuất hiện khi một người sử dụng cả __stdcall(hoặc WINAPI) extern "C" :

Như bạn đã biết, extern "C"loại bỏ phần trang trí để thay vào đó:

__declspec(dllexport) int Test(void)                        --> dumpbin : ?Test@@YaHXZ

bạn nhận được một tên biểu tượng không được trang trí:

extern "C" __declspec(dllexport) int Test(void)             --> dumpbin : Test

Tuy nhiên, _stdcall(= macro WINAPI, thay đổi quy ước gọi) cũng trang trí tên để nếu chúng ta sử dụng cả hai, chúng ta sẽ thu được:

   extern "C" __declspec(dllexport) int WINAPI Test(void)   --> dumpbin : _Test@0

và lợi ích của extern "C"bị mất vì biểu tượng được trang trí (với _ @byte)

Lưu ý rằng điều này chỉ xảy ra đối với kiến ​​trúc x86 vì __stdcallquy ước bị bỏ qua trên x64 ( msdn : trên kiến ​​trúc x64, theo quy ước, các đối số được chuyển vào thanh ghi khi có thể và các đối số tiếp theo được chuyển vào ngăn xếp .).

Điều này đặc biệt phức tạp nếu bạn đang nhắm mục tiêu cả hai nền tảng x86 và x64.


Hai giải pháp

  1. Sử dụng tệp định nghĩa. Nhưng điều này buộc bạn phải duy trì trạng thái của tệp def.

  2. cách đơn giản nhất: xác định macro (xem msdn ):

#define EXPORT nhận xét (trình liên kết, "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

và sau đó đưa pragma sau vào thân hàm:

#pragma EXPORT

Ví dụ đầy đủ:

 int WINAPI Test(void)
{
    #pragma EXPORT
    return 1;
}

Thao tác này sẽ xuất hàm chưa được trang trí cho cả mục tiêu x86 và x64 trong khi vẫn giữ nguyên __stdcallquy ước cho x86. Các __declspec(dllexport) không cần thiết trong trường hợp này.


5
Cảm ơn bạn vì gợi ý quan trọng này. Tôi đã tự hỏi tại sao 64Bit DLL của tôi khác với 32 bit. Tôi thấy câu trả lời của bạn hữu ích hơn nhiều so với câu trả lời được chấp nhận.
Elmue

1
Tôi thực sự thích cách tiếp cận này. Đề xuất duy nhất của tôi là đổi tên macro thành EXPORT_FUNCTION vì __FUNCTION__macro chỉ hoạt động trong các hàm.
Luis

3

Tôi gặp chính xác vấn đề tương tự, giải pháp của tôi là sử dụng tệp định nghĩa mô-đun (.def) thay vì __declspec(dllexport)để xác định xuất ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Tôi không biết tại sao nó hoạt động, nhưng nó có


Lưu ý cho bất kỳ ai khác đang gặp phải vấn đề này: sử dụng .deftệp xuất mô-đun hiệu quả, nhưng với chi phí là có thể cung cấp externđịnh nghĩa trong tệp tiêu đề, ví dụ: dữ liệu toàn cầu — trong trường hợp đó, bạn phải cung cấp externđịnh nghĩa theo cách thủ công trong sử dụng nội bộ của dữ liệu đó. (Có, đôi khi bạn cần điều đó.) Tốt hơn cả là nói chung và đặc biệt là mã đa nền tảng chỉ đơn giản là sử dụng __declspec()với macro để bạn có thể xử lý dữ liệu một cách bình thường.
Chris Krycho

2
Lý do có lẽ là vì nếu bạn đang sử dụng __stdcall, sau đó __declspec(dllexport)sẽ không loại bỏ các đồ trang trí. .defTuy nhiên, việc thêm chức năng vào một di chúc.
Björn Lindqvist

1
@ BjörnLindqvist +1, lưu ý rằng nó chỉ dành cho trường hợp x86. Hãy xem câu trả lời của tôi.
Malick

-1

Tôi nghĩ _naked có thể nhận được những gì bạn muốn, nhưng nó cũng ngăn trình biên dịch tạo mã quản lý ngăn xếp cho hàm. extern "C" gây ra C kiểu trang trí tên. Loại bỏ điều đó và điều đó sẽ loại bỏ _ của bạn. Trình liên kết không thêm dấu gạch dưới, trình biên dịch thì có. stdcall khiến kích thước ngăn xếp đối số được thêm vào.

Để biết thêm, hãy xem: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

Câu hỏi lớn hơn là tại sao bạn muốn làm điều đó? Có gì sai với những cái tên bị xáo trộn?


Các tên bị xáo trộn rất xấu khi được gọi bằng cách sử dụng LoadLibrary / GetProcAddress hoặc các phương thức khác không dựa vào việc có tiêu đề ac / c ++.
Aardvark

4
Điều này sẽ không hữu ích - bạn chỉ muốn xóa mã quản lý ngăn xếp do trình biên dịch tạo ra trong những trường hợp rất chuyên biệt. (Chỉ cần sử dụng __cdecl sẽ là một cách ít gây hại để mất các đồ trang trí - bởi __declspec mặc định (dllexport) dường như không bao gồm các thông thường _ prefix với __cdecl phương pháp.)
Ian Griffiths

Tôi không thực sự nói rằng nó sẽ hữu ích, do đó tôi báo trước về những tác dụng khác và đặt câu hỏi tại sao anh ấy thậm chí muốn làm điều đó.
Rob K
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.