Mengekspor fungsi dari DLL dengan dllexport

105

Saya ingin contoh sederhana mengekspor fungsi dari C ++ Windows DLL.

Saya ingin melihat header, .cppfile, dan .deffile (jika benar-benar diperlukan).

Saya ingin nama yang diekspor tidak didekorasi . Saya ingin menggunakan konvensi pemanggilan paling standar ( __stdcall?). Saya ingin menggunakannya __declspec(dllexport)dan tidak harus menggunakan .deffile.

Sebagai contoh:

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

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

Saya mencoba untuk menghindari linker menambahkan garis bawah dan / atau angka (jumlah byte?) Ke nama.

Saya setuju dengan tidak mendukung dllimportdan dllexportmenggunakan tajuk yang sama. Saya tidak ingin informasi apa pun tentang mengekspor metode kelas C ++, hanya fungsi global gaya-c.

MEMPERBARUI

Tidak termasuk konvensi pemanggilan (dan penggunaan extern "C") memberi saya nama ekspor sesuka saya, tapi apa artinya? Apakah konvensi pemanggilan default apa pun yang saya dapatkan adalah pinvoke (.NET), menyatakan (vb6), dan GetProcAddressharapkan? (Saya kira untuk GetProcAddressitu akan tergantung pada penunjuk fungsi yang dibuat pemanggil).

Saya ingin DLL ini digunakan tanpa file header, jadi saya tidak terlalu membutuhkan banyak fasilitas #definesuntuk membuat header dapat digunakan oleh pemanggil.

Saya baik-baik saja dengan jawaban bahwa saya harus menggunakan *.deffile.

tapir
sumber
Saya mungkin salah mengingat tetapi saya berpikir bahwa: a) extern Cakan menghapus dekorasi yang menjelaskan jenis parameter fungsi, tetapi bukan dekorasi yang menjelaskan konvensi pemanggilan fungsi; b) untuk menghapus semua dekorasi Anda perlu menentukan nama (tanpa dekorasi) dalam file DEF.
ChrisW
Ini juga yang saya lihat. Mungkin Anda harus menambahkan ini sebagai jawaban lengkap?
Aardvark

Jawaban:

134

Jika Anda menginginkan ekspor C biasa, gunakan proyek C, bukan C ++. DLL C ++ mengandalkan pengaturan nama untuk semua isme C ++ (ruang nama, dll ...). Anda dapat mengkompilasi kode Anda sebagai C dengan masuk ke pengaturan proyek Anda di bawah C / C ++ -> Advanced, ada opsi "Compile As" yang sesuai dengan switch compiler / TP dan / TC.

Jika Anda masih ingin menggunakan C ++ untuk menulis internal lib tetapi mengekspor beberapa fungsi yang tidak diubah ukurannya untuk digunakan di luar C ++, lihat bagian kedua di bawah.

Mengekspor / Mengimpor Libs DLL di VC ++

Apa yang benar-benar ingin Anda lakukan adalah menentukan makro bersyarat di header yang akan disertakan di semua file sumber di proyek DLL Anda:

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

Kemudian pada fungsi yang ingin Anda ekspor Anda gunakan LIBRARY_API:

LIBRARY_API int GetCoolInteger();

Dalam proyek pembangunan perpustakaan Anda membuat mendefinisikan LIBRARY_EXPORTSini akan menyebabkan fungsi Anda akan diekspor untuk membangun DLL Anda.

Karena LIBRARY_EXPORTStidak akan ditentukan dalam proyek yang menggunakan DLL, ketika proyek itu menyertakan file header perpustakaan Anda, semua fungsi akan diimpor sebagai gantinya.

Jika perpustakaan Anda akan lintas platform, Anda dapat mendefinisikan LIBRARY_API sebagai tidak ada ketika tidak di Windows:

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

Saat menggunakan dllexport / dllimport anda tidak perlu menggunakan file DEF, jika menggunakan file DEF anda tidak perlu menggunakan file dllexport / dllimport. Kedua metode menyelesaikan tugas yang sama dengan cara yang berbeda, saya percaya bahwa dllexport / dllimport adalah metode yang disarankan dari keduanya.

Mengekspor fungsi yang tidak diubah dari C ++ DLL untuk LoadLibrary / PInvoke

Jika Anda memerlukan ini untuk menggunakan LoadLibrary dan GetProcAddress, atau mungkin mengimpor dari bahasa lain (misalnya PInvoke dari .NET, atau FFI dengan Python / R dll), Anda dapat menggunakan extern "C"inline dengan dllexport Anda untuk memberi tahu compiler C ++ agar tidak merusak nama. Dan karena kita menggunakan GetProcAddress daripada dllimport, kita tidak perlu melakukan tarian ifdef dari atas, cukup dllexport sederhana:

Kode:

#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())
  );
}

Dan inilah tampilan ekspor dengan Dumpbin / ekspor:

  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)

Jadi kode ini berfungsi dengan baik:

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")
);
joshperry
sumber
1
extern "C" sepertinya menghapus nama gaya c ++ mangling. Seluruh hal impor vs. ekspor (yang saya coba sarankan untuk tidak termasuk dalam pertanyaan) tidak benar-benar apa yang saya tanyakan (tetapi info bagusnya). Saya pikir itu akan menutupi masalah.
Aardvark
Satu-satunya alasan saya dapat berpikir bahwa Anda memerlukannya adalah untuk LoadLibrary dan GetProcAddress ... Ini sudah diurus, saya akan menjelaskan di badan jawaban saya ...
joshperry
Apakah EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport)? Apakah itu di SDK?
Aardvark
3
Jangan lupa untuk menambahkan file definisi modul ke dalam pengaturan linker proyek - hanya "menambahkan item yang ada ke proyek" saja tidak cukup!
Jimmy
1
Saya menggunakan ini untuk mengkompilasi DLL dengan VS dan kemudian memanggilnya dari R menggunakan .C. Bagus!
Juancentro
33

Untuk C ++:

Saya baru saja menghadapi masalah yang sama dan saya pikir ada baiknya menyebutkan masalah muncul ketika seseorang menggunakan keduanya __stdcall(atau WINAPI) dan extern "C" :

Seperti yang Anda ketahui extern "C"menghapus dekorasi sehingga bukan:

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

Anda mendapatkan nama simbol tanpa dekorasi:

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

Namun _stdcall(= makro WINAPI, yang mengubah konvensi pemanggilan) juga menghiasi nama sehingga jika kita menggunakan keduanya kita mendapatkan:

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

dan keuntungan extern "C"hilang karena simbolnya dihias (dengan _ @bytes)

Perhatikan bahwa ini hanya terjadi untuk arsitektur x86 karena __stdcallkonvensi diabaikan pada x64 ( msdn : pada arsitektur x64, menurut konvensi, argumen diteruskan dalam register jika memungkinkan, dan argumen selanjutnya diteruskan pada stack .).

Ini sangat rumit jika Anda menargetkan platform x86 dan x64.


Dua solusi

  1. Gunakan file definisi. Tetapi ini memaksa Anda untuk mempertahankan status file def.

  2. cara paling sederhana: tentukan makro (lihat msdn ):

#define EKSPOR komentar (linker, "/ EKSPOR:" __FUNCTION__ "=" __FUNCDNAME__)

dan kemudian masukkan pragma berikut ke dalam fungsi tubuh:

#pragma EXPORT

Contoh Lengkap:

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

Ini akan mengekspor fungsi tanpa dekorasi untuk target x86 dan x64 sambil mempertahankan __stdcallkonvensi untuk x86. The __declspec(dllexport) tidak diperlukan dalam hal ini.

Malick
sumber
5
Terima kasih atas petunjuk penting ini. Saya sudah bertanya-tanya mengapa 64Bit DLL saya berbeda dari yang 32 bit. Saya menemukan jawaban Anda jauh lebih berguna daripada yang diterima sebagai jawaban.
Elmue
1
Saya sangat suka pendekatan ini. Satu-satunya rekomendasi saya adalah mengganti nama makro menjadi EXPORT_FUNCTION karena __FUNCTION__makro hanya berfungsi dalam fungsi.
Luis
3

Saya memiliki masalah yang persis sama, solusi saya adalah menggunakan file definisi modul (.def) daripada __declspec(dllexport)menentukan ekspor ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ). Saya tidak tahu mengapa ini berhasil, tetapi berhasil

Rytis I
sumber
Catatan untuk siapa pun yang mengalami ini: menggunakan .deffile ekspor modul memang berfungsi, tetapi dengan mengorbankan kemampuan untuk memberikan externdefinisi di file header misalnya untuk data global — dalam hal ini, Anda harus memberikan externdefinisi secara manual dalam penggunaan internal data itu. (Ya, ada kalanya Anda membutuhkannya.) Lebih baik secara umum dan terutama untuk kode lintas platform cukup digunakan __declspec()dengan makro sehingga Anda dapat menyerahkan data secara normal.
Chris Krycho
2
Alasannya mungkin karena jika Anda menggunakan __stdcall, maka tidak__declspec(dllexport) akan menghapus dekorasi. Namun menambahkan fungsi ke wasiat. .def
Björn Lindqvist
1
@ BjörnLindqvist +1, perhatikan bahwa ini hanya kasus untuk x86. Lihat jawaban saya.
Malick
-1

Saya pikir _naked mungkin mendapatkan apa yang Anda inginkan, tetapi itu juga mencegah kompilator menghasilkan kode manajemen tumpukan untuk fungsi tersebut. extern "C" menyebabkan dekorasi nama gaya C. Hapus itu dan itu akan menghilangkan _'s Anda. Linker tidak menambahkan garis bawah, kompilator tidak. stdcall menyebabkan ukuran tumpukan argumen ditambahkan.

Untuk lebih lanjut, lihat: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

Pertanyaan yang lebih besar adalah mengapa Anda ingin melakukan itu? Apa yang salah dengan nama yang hancur?

Rob K
sumber
Nama yang rusak akan jelek saat dipanggil use LoadLibrary / GetProcAddress atau metode lain yang tidak mengandalkan header ac / c ++.
Aardvark
4
Ini tidak akan membantu - Anda hanya ingin menghapus kode manajemen tumpukan yang dibuat kompilator dalam keadaan yang sangat khusus. (Hanya menggunakan __cdecl akan menjadi cara yang tidak terlalu berbahaya untuk menghilangkan dekorasi - secara default __declspec (dllexport) sepertinya tidak menyertakan _ prefiks biasa dengan metode __cdecl.)
Ian Griffiths
Saya tidak benar-benar mengatakan bahwa itu akan membantu, oleh karena itu peringatan saya tentang efek lain dan mempertanyakan mengapa dia bahkan ingin melakukannya.
Rob K