การส่งออกฟังก์ชันจาก DLL ด้วย dllexport


106

ฉันต้องการตัวอย่างง่ายๆในการส่งออกฟังก์ชันจาก C ++ Windows DLL

ฉันต้องการดูส่วนหัว.cppไฟล์และ.defไฟล์ (ถ้าจำเป็นจริงๆ)

ฉันต้องการชื่อที่ส่งออกจะได้รับการประดับประดา ฉันต้องการใช้รูปแบบการโทรที่เป็นมาตรฐานที่สุด ( __stdcall?) ฉันต้องการใช้__declspec(dllexport)และไม่ต้องใช้.defไฟล์

ตัวอย่างเช่น:

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

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

ฉันพยายามหลีกเลี่ยงตัวเชื่อมโยงที่เพิ่มขีดล่างและ / หรือตัวเลข (จำนวนไบต์?) ให้กับชื่อ

ฉันไม่เป็นไรที่ไม่สนับสนุนdllimportและdllexportใช้ส่วนหัวเดียวกัน ฉันไม่ต้องการข้อมูลใด ๆ เกี่ยวกับการเอ็กซ์พอร์ตเมธอดคลาส C ++ เพียงแค่ฟังก์ชันโกลบอลสไตล์ c

อัปเดต

ไม่รวมหลักการเรียก (และการใช้งานextern "C") ให้ชื่อส่งออกตามที่ฉันต้องการ แต่หมายความว่าอย่างไร หลักการเรียกเริ่มต้นใด ๆ ก็ตามที่ฉันได้รับ pinvoke (.NET) ประกาศ (vb6) และGetProcAddressคาดหวังหรือไม่ (ฉันเดาว่าGetProcAddressมันจะขึ้นอยู่กับตัวชี้ฟังก์ชันที่ผู้โทรสร้างขึ้น)

ฉันต้องการให้ใช้ DLL นี้โดยไม่มีไฟล์ส่วนหัวดังนั้นฉันจึงไม่จำเป็นต้องมีอะไรมากมายใน#definesการทำให้ส่วนหัวใช้งานได้โดยผู้โทร

ฉันโอเคกับคำตอบว่าฉันต้องใช้*.defไฟล์


ฉันอาจจะจำผิด แต่ฉันคิดว่า: a) extern Cจะลบการตกแต่งที่อธิบายประเภทพารามิเตอร์ของฟังก์ชันออก แต่ไม่ใช่การตกแต่งที่อธิบายหลักการเรียกของฟังก์ชัน b) ในการลบการตกแต่งทั้งหมดคุณต้องระบุชื่อ (ไม่ได้ตกแต่ง) ในไฟล์ DEF
ChrisW

นี่คือสิ่งที่ฉันเห็นเช่นกัน บางทีคุณควรเพิ่มสิ่งนี้เป็นคำตอบที่สมบูรณ์?
Aardvark

คำตอบ:


135

หากคุณต้องการเอ็กซ์พอร์ต C ธรรมดาให้ใช้โปรเจ็กต์ C ไม่ใช่ C ++ C ++ DLL ขึ้นอยู่กับ name-mangling สำหรับ C ++ isms ทั้งหมด (เนมสเปซและอื่น ๆ ... ) คุณสามารถคอมไพล์โค้ดของคุณเป็น C ได้โดยเข้าไปที่การตั้งค่าโปรเจ็กต์ของคุณภายใต้ C / C ++ -> ขั้นสูงมีตัวเลือก "คอมไพล์เป็น" ซึ่งสอดคล้องกับสวิตช์คอมไพเลอร์ / TP และ / TC

หากคุณยังต้องการใช้ C ++ เพื่อเขียนภายในของ lib ของคุณ แต่ส่งออกฟังก์ชันบางอย่างที่ไม่มีการเชื่อมต่อเพื่อใช้ภายนอก C ++ โปรดดูส่วนที่สองด้านล่าง

การส่งออก / นำเข้า DLL Libs ใน VC ++

สิ่งที่คุณต้องการทำจริงๆคือกำหนดมาโครเงื่อนไขในส่วนหัวที่จะรวมอยู่ในไฟล์ต้นฉบับทั้งหมดในโครงการ DLL ของคุณ:

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

จากนั้นในฟังก์ชันที่คุณต้องการส่งออกคุณใช้LIBRARY_API:

LIBRARY_API int GetCoolInteger();

ในโครงการสร้างไลบรารีของคุณให้สร้างการกำหนดLIBRARY_EXPORTSสิ่งนี้จะทำให้ฟังก์ชันของคุณถูกส่งออกสำหรับบิลด์ DLL ของคุณ

เนื่องจากLIBRARY_EXPORTSจะไม่ถูกกำหนดไว้ในโปรเจ็กต์ที่ใช้ DLL เมื่อโปรเจ็กต์นั้นรวมไฟล์ส่วนหัวของไลบรารีของคุณฟังก์ชันทั้งหมดจะถูกนำเข้าแทน

หากไลบรารีของคุณเป็นแบบข้ามแพลตฟอร์มคุณสามารถกำหนด LIBRARY_API เป็นอะไรก็ได้เมื่อไม่ได้อยู่บน Windows:

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

เมื่อใช้ dllexport / dllimport คุณไม่จำเป็นต้องใช้ไฟล์ DEF หากคุณใช้ไฟล์ DEF คุณไม่จำเป็นต้องใช้ dllexport / dllimport ทั้งสองวิธีนี้ทำงานได้หลายวิธีเหมือนกันฉันเชื่อว่า dllexport / dllimport เป็นวิธีที่แนะนำจากทั้งสองวิธี

การเอ็กซ์พอร์ตฟังก์ชันที่ไม่มีการเชื่อมจาก C ++ DLL สำหรับ LoadLibrary / PInvoke

หากคุณต้องการสิ่งนี้เพื่อใช้ LoadLibrary และ GetProcAddress หรืออาจนำเข้าจากภาษาอื่น (เช่น PInvoke จาก. NET หรือ FFI ใน Python / R เป็นต้น) คุณสามารถใช้extern "C"อินไลน์กับ dllexport ของคุณเพื่อบอกคอมไพเลอร์ C ++ ไม่ให้ยุ่งเกี่ยวกับชื่อ และเนื่องจากเราใช้ GetProcAddress แทน dllimport เราจึงไม่จำเป็นต้องเต้น ifdef จากด้านบนเพียง dllexport ง่ายๆ:

รหัส:

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

และนี่คือลักษณะของการส่งออกด้วย 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)

ดังนั้นรหัสนี้จึงทำงานได้ดี:

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
"C" ภายนอกดูเหมือนจะลบชื่อสไตล์ c ++ ที่ยุ่งเหยิง การนำเข้าทั้งหมดเทียบกับสิ่งที่ส่งออก (ซึ่งฉันพยายามแนะนำไม่ให้รวมไว้ในคำถาม) ไม่ใช่สิ่งที่ฉันถามจริงๆ (แต่เป็นข้อมูลที่ดี) ฉันคิดว่ามันจะทำให้ปัญหาขุ่นมัว
Aardvark

เหตุผลเดียวที่ฉันคิดได้ว่าคุณต้องการสำหรับ LoadLibrary และ GetProcAddress ... นี่ได้รับการดูแลแล้วฉันจะอธิบายในเนื้อหาคำตอบของฉัน ...
joshperry

EXTERN_DLL_EXPORT == extern "C" __declspec (dllexport) หรือไม่ อยู่ใน SDK หรือไม่
Aardvark

3
อย่าลืมเพิ่มไฟล์ข้อกำหนดโมดูลลงในการตั้งค่าตัวเชื่อมโยงของโปรเจ็กต์ - แค่ "เพิ่มรายการที่มีอยู่ในโปรเจ็กต์" เท่านั้นยังไม่พอ!
Jimmy

1
ฉันใช้สิ่งนี้เพื่อรวบรวม DLL ด้วย VS แล้วเรียกจาก R โดยใช้. C เยี่ยมมาก!
Juancentro

34

สำหรับ C ++:

ฉันเพิ่งประสบปัญหาเดียวกันและฉันคิดว่ามันคุ้มค่าที่จะกล่าวถึงปัญหาที่เกิดขึ้นเมื่อใช้ทั้ง __stdcall(หรือWINAPI) และ extern "C" :

ดังที่คุณทราบแล้วextern "C"จะลบการตกแต่งออกดังนั้นแทนที่จะเป็น:

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

คุณได้รับชื่อสัญลักษณ์ที่ไม่ได้ตกแต่ง:

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

อย่างไรก็ตาม _stdcall (= มาโคร WINAPI ซึ่งเปลี่ยนรูปแบบการเรียก) ยังตกแต่งชื่อด้วยดังนั้นถ้าเราใช้ทั้งสองเราจะได้รับ:

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

และประโยชน์ของ extern "C"จะหายไปเนื่องจากสัญลักษณ์ถูกตกแต่ง (ด้วย _ @bytes)

โปรดสังเกตว่าสิ่งนี้เกิดขึ้นสำหรับสถาปัตยกรรม x86 เท่านั้นเนื่องจากการ__stdcallประชุมถูกละเว้นบน x64 ( msdn :บนสถาปัตยกรรม x64 ตามแบบแผนอาร์กิวเมนต์จะถูกส่งผ่านในรีจิสเตอร์เมื่อเป็นไปได้และอาร์กิวเมนต์ที่ตามมาจะถูกส่งไปบนสแต็ก)

นี่เป็นเรื่องยุ่งยากอย่างยิ่งหากคุณกำหนดเป้าหมายทั้งแพลตฟอร์ม x86 และ x64


สองวิธีแก้ปัญหา

  1. ใช้ไฟล์นิยาม แต่สิ่งนี้บังคับให้คุณรักษาสถานะของไฟล์ def

  2. วิธีที่ง่ายที่สุด: กำหนดมาโคร (ดูmsdn ):

# กำหนดความคิดเห็น EXPORT (ตัวเชื่อมโยง "/ EXPORT:" __FUNCTION__ "=" __FUNCDNAME__)

จากนั้นรวม pragma ต่อไปนี้ไว้ในตัวฟังก์ชัน:

#pragma EXPORT

ตัวอย่างเต็ม:

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

การดำเนินการนี้จะส่งออกฟังก์ชันที่ไม่มีการตกแต่งสำหรับเป้าหมาย x86 และ x64 ในขณะที่ยังคงรูป__stdcallแบบสำหรับ x86 ไว้ __declspec(dllexport) จะไม่จำเป็นต้องใช้ในกรณีนี้


5
ขอบคุณสำหรับคำแนะนำที่สำคัญนี้ ฉันสงสัยอยู่แล้วว่าทำไม 64Bit DLL ของฉันถึงแตกต่างจาก 32 บิตหนึ่ง ฉันพบว่าคำตอบของคุณมีประโยชน์มากกว่าคำตอบที่ยอมรับ
Elmue

1
ฉันชอบแนวทางนี้มาก คำแนะนำเดียวของฉันคือเปลี่ยนชื่อมาโครเป็น EXPORT_FUNCTION เนื่องจาก__FUNCTION__มาโครทำงานในฟังก์ชันเท่านั้น
Luis

3

ฉันมีปัญหาเดียวกันทุกประการวิธีแก้ปัญหาของฉันคือใช้ไฟล์ข้อกำหนดโมดูล (.def) แทน__declspec(dllexport)การกำหนดการส่งออก ( http://msdn.microsoft.com/en-us/library/d91k01sh.aspx ) ฉันไม่รู้ว่าทำไมถึงได้ผล แต่มันก็เป็นเช่นนั้น


หมายเหตุสำหรับใครก็ตามที่พบปัญหานี้: การใช้.defไฟล์เอ็กซ์พอร์ตโมดูลจะใช้งานได้ แต่ต้องเสียค่าใช้จ่ายในการจัดหาexternนิยามในไฟล์ส่วนหัวเช่นข้อมูลส่วนกลางในกรณีนี้คุณต้องระบุexternนิยามด้วยตนเองในการใช้งานภายในของ ข้อมูลนั้น (ใช่มีหลายครั้งที่คุณต้องการ) มันดีกว่าทั้งโดยทั่วไปและโดยเฉพาะอย่างยิ่งสำหรับโค้ดข้ามแพลตฟอร์มเพียงแค่ใช้__declspec()กับมาโครเพื่อให้คุณสามารถส่งข้อมูลได้ตามปกติ
Chris Krycho

2
สาเหตุน่าจะเป็นเพราะถ้าคุณใช้__stdcallงานแล้ว__declspec(dllexport)จะไม่ถอดของตกแต่งออก อย่างไรก็ตามการเพิ่มฟังก์ชันลงใน.defพินัยกรรม
Björn Lindqvist

1
@ BjörnLindqvist +1 โปรดทราบว่ามันเป็นเพียงกรณีสำหรับ x86 เท่านั้น ดูคำตอบของฉัน
Malick

-1

ฉันคิดว่า _naked อาจได้รับสิ่งที่คุณต้องการ แต่ยังป้องกันไม่ให้คอมไพเลอร์สร้างรหัสการจัดการสแต็กสำหรับฟังก์ชัน "C" ภายนอกทำให้เกิดการตกแต่งชื่อสไตล์ C ลบสิ่งนั้นออกและควรกำจัด _ ของคุณ ตัวเชื่อมโยงไม่เพิ่มขีดล่างคอมไพเลอร์ทำ stdcall ทำให้ขนาดกองอาร์กิวเมนต์ถูกต่อท้าย

สำหรับข้อมูลเพิ่มเติมโปรดดู: http://en.wikipedia.org/wiki/X86_calling_conventions http://www.codeproject.com/KB/cpp/calling_conventions_demystified.aspx

คำถามที่ใหญ่กว่าคือทำไมคุณถึงต้องการทำเช่นนั้น? ชื่อที่แหลกเหลวมีอะไรผิดปกติ?


ชื่อที่แหลกเหลวน่าเกลียดเมื่อเรียกใช้ LoadLibrary / GetProcAddress หรือวิธีการอื่น ๆ ที่ไม่ต้องพึ่งพาการมีส่วนหัว ac / c ++
Aardvark

4
สิ่งนี้จะไม่เป็นประโยชน์คุณเพียงต้องการลบโค้ดการจัดการสแต็กที่สร้างโดยคอมไพเลอร์ในสถานการณ์พิเศษเท่านั้น (การใช้ __cdecl จะเป็นวิธีที่อันตรายน้อยกว่าในการสูญเสียของตกแต่ง - โดยค่าเริ่มต้น __declspec (dllexport) ดูเหมือนจะไม่รวมคำนำหน้า _ ปกติด้วยวิธี __cdecl)
Ian Griffiths

ฉันไม่ได้บอกว่ามันจะมีประโยชน์จริงๆดังนั้นข้อควรระวังของฉันเกี่ยวกับผลกระทบอื่น ๆ และตั้งคำถามว่าทำไมเขาถึงอยากทำ
Rob K
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.