โหลดฟังก์ชันจาก DLL แบบไดนามิก


88

ฉันดูไฟล์. dll นิดหน่อยฉันเข้าใจการใช้งานและพยายามทำความเข้าใจวิธีใช้

ฉันได้สร้างไฟล์. dll ที่มีฟังก์ชันที่ส่งคืนจำนวนเต็มชื่อ funci ()

ใช้รหัสนี้ฉัน (คิดว่า) ฉันได้นำเข้าไฟล์. dll เข้าสู่โครงการแล้ว (ไม่มีข้อร้องเรียน):

#include <windows.h>
#include <iostream>

int main() {
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop  \\fgfdg\\dgdg\\test.dll");

  if (hGetProcIDDLL == NULL) {
    std::cout << "cannot locate the .dll file" << std::endl;
  } else {
    std::cout << "it has been called" << std::endl;
    return -1;
  }

  int a = funci();

  return a;
}

# funci function 

int funci() {
  return 40;
}

อย่างไรก็ตามเมื่อฉันพยายามรวบรวมไฟล์. cpp ที่ฉันคิดว่าได้นำเข้า. dll ฉันมีข้อผิดพลาดต่อไปนี้:

C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp||In function 'int main()':|
C:\Documents and Settings\User\Desktop\fgfdg\onemore.cpp|16|error: 'funci' was not     declared in this scope|
||=== Build finished: 1 errors, 0 warnings ===|

ฉันรู้ว่า. dll แตกต่างจากไฟล์ส่วนหัวดังนั้นฉันจึงรู้ว่าฉันไม่สามารถนำเข้าฟังก์ชันเช่นนี้ได้ แต่เป็นวิธีที่ดีที่สุดที่ฉันสามารถทำได้เพื่อแสดงว่าฉันได้ลองแล้ว

คำถามของฉันคือฉันจะใช้hGetProcIDDLLตัวชี้เพื่อเข้าถึงฟังก์ชันภายใน. dll ได้อย่างไร

ฉันหวังว่าคำถามนี้จะสมเหตุสมผลและฉันจะไม่เห่าต้นไม้ที่ผิดพลาดอีก


ค้นหาการเชื่อมโยงแบบคงที่ / ไดนามิก
Mitch Wheat

ขอบคุณฉันจะตรวจสอบสิ่งนี้

ฉันเยื้องรหัสของฉัน แต่เมื่อฉันใส่ลงในที่นี่รูปแบบจะยุ่งเหยิงดังนั้นฉันจึงสิ้นสุดการเยื้องทั้งหมด 4 บรรทัด

คำตอบ:


153

LoadLibraryไม่ทำในสิ่งที่คุณคิด โหลด DLL ลงในหน่วยความจำของกระบวนการปัจจุบัน แต่ไม่ได้นำเข้าฟังก์ชันที่กำหนดไว้ในนั้นอย่างน่าอัศจรรย์! สิ่งนี้ไม่สามารถทำได้เนื่องจากการเรียกใช้ฟังก์ชันได้รับการแก้ไขโดยตัวเชื่อมโยงในเวลาคอมไพล์ในขณะที่LoadLibraryเรียกขณะรันไทม์ (โปรดจำไว้ว่า C ++ เป็นภาษาที่พิมพ์แบบคงที่ )

คุณจำเป็นต้องมีฟังก์ชั่น WinAPI GetProcAddressแยกต่างหากที่จะได้รับอยู่ของฟังก์ชั่นโหลดแบบไดนามิกนี้:

ตัวอย่าง

#include <windows.h>
#include <iostream>

/* Define a function pointer for our imported
 * function.
 * This reads as "introduce the new type f_funci as the type: 
 *                pointer to a function returning an int and 
 *                taking no arguments.
 *
 * Make sure to use matching calling convention (__cdecl, __stdcall, ...)
 * with the exported function. __stdcall is the convention used by the WinAPI
 */
typedef int (__stdcall *f_funci)();

int main()
{
  HINSTANCE hGetProcIDDLL = LoadLibrary("C:\\Documents and Settings\\User\\Desktop\\test.dll");

  if (!hGetProcIDDLL) {
    std::cout << "could not load the dynamic library" << std::endl;
    return EXIT_FAILURE;
  }

  // resolve function address here
  f_funci funci = (f_funci)GetProcAddress(hGetProcIDDLL, "funci");
  if (!funci) {
    std::cout << "could not locate the function" << std::endl;
    return EXIT_FAILURE;
  }

  std::cout << "funci() returned " << funci() << std::endl;

  return EXIT_SUCCESS;
}

นอกจากนี้คุณควรส่งออกฟังก์ชันของคุณจาก DLL อย่างถูกต้อง สามารถทำได้ดังนี้:

int __declspec(dllexport) __stdcall funci() {
   // ...
}

ดังที่ Lundin บันทึกไว้เป็นแนวทางปฏิบัติที่ดีในการปลดปล่อยแฮนเดิลไปยังห้องสมุดหากคุณไม่ต้องการใช้อีก สิ่งนี้จะทำให้ถูกยกเลิกการโหลดหากไม่มีกระบวนการอื่นที่ยังคงจัดการกับ DLL เดียวกัน


อาจฟังดูเหมือนคำถามโง่ ๆ แต่ f_funci คืออะไร / ควรเป็นอย่างไร?

8
นอกเหนือจากนั้นคำตอบนั้นยอดเยี่ยมและเข้าใจได้ง่าย

6
โปรดทราบว่าf_funciในความเป็นจริงเป็นประเภท (แทนที่จะมีประเภท) ประเภทf_funciอ่านว่า "ตัวชี้ไปยังฟังก์ชันที่ส่งคืนintและไม่มีอาร์กิวเมนต์" ข้อมูลเพิ่มเติมเกี่ยวกับคำแนะนำการทำงานใน C สามารถพบได้ที่newty.de/fpt/index.html
Niklas B.

ขอขอบคุณอีกครั้งสำหรับการตอบกลับ funci จะไม่โต้แย้งและส่งกลับจำนวนเต็ม ฉันแก้ไขคำถามเพื่อแสดงฟังก์ชันที่คอมไพล์? ลงใน. dll เมื่อฉันพยายามเรียกใช้หลังจากรวม "typedef int ( f_funci) ();" ฉันได้รับข้อผิดพลาดนี้: C: \ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp || ในฟังก์ชัน 'int main ()': | C: \ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp | 18 | ข้อผิดพลาด: ไม่สามารถแปลง 'int ( ) ()' เป็น 'const CHAR *' สำหรับอาร์กิวเมนต์ '2' ถึง 'int (* GetProcAddress (HINSTANCE__ , const CHAR )) () '| || === สร้างเสร็จแล้ว: 1 ข้อผิดพลาด 0 คำเตือน === |

ฉันลืมนักแสดงที่นั่น (แก้ไขใน) อย่างไรก็ตามข้อผิดพลาดดูเหมือนจะเป็นข้อผิดพลาดอื่นคุณแน่ใจหรือไม่ว่าใช้รหัสที่ถูกต้อง ถ้าใช่คุณช่วยวางรหัสที่ล้มเหลวและผลลัพธ์ของคอมไพเลอร์ที่สมบูรณ์ในpastie.org ได้ไหม นอกจากนี้ typedef ที่คุณเขียนในความคิดเห็นของคุณก็ไม่ถูกต้อง ( *หายไปซึ่งอาจทำให้เกิดข้อผิดพลาดได้)
Niklas B.

34

นอกเหนือจากคำตอบที่โพสต์ไว้แล้วฉันคิดว่าฉันควรแบ่งปันเคล็ดลับที่มีประโยชน์ที่ฉันใช้เพื่อโหลดฟังก์ชัน DLL ทั้งหมดลงในโปรแกรมผ่านตัวชี้ฟังก์ชันโดยไม่ต้องเขียนการเรียก GetProcAddress แยกต่างหากสำหรับแต่ละฟังก์ชัน ฉันยังต้องการเรียกใช้ฟังก์ชันโดยตรงเมื่อพยายามใน OP

เริ่มต้นด้วยการกำหนดประเภทตัวชี้ฟังก์ชันทั่วไป:

typedef int (__stdcall* func_ptr_t)();

ประเภทที่ใช้ไม่สำคัญจริงๆ ตอนนี้สร้างอาร์เรย์ของประเภทนั้นซึ่งสอดคล้องกับจำนวนฟังก์ชันที่คุณมีใน DLL:

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

ในอาร์เรย์นี้เราสามารถจัดเก็บพอยน์เตอร์ฟังก์ชันจริงที่ชี้ลงในพื้นที่หน่วยความจำ DLL

ปัญหาต่อไปคือGetProcAddressคาดว่าชื่อฟังก์ชันเป็นสตริง ดังนั้นสร้างอาร์เรย์ที่คล้ายกันซึ่งประกอบด้วยชื่อฟังก์ชันใน DLL:

const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = 
{
  "dll_add",
  "dll_subtract",
  "dll_do_stuff",
  ...
};

ตอนนี้เราสามารถเรียก GetProcAddress () แบบวนซ้ำได้อย่างง่ายดายและเก็บแต่ละฟังก์ชันไว้ในอาร์เรย์นั้น:

for(int i=0; i<DLL_FUNCTIONS_N; i++)
{
  func_ptr[i] = GetProcAddress(hinst_mydll, DLL_FUNCTION_NAMES[i]);

  if(func_ptr[i] == NULL)
  {
    // error handling, most likely you have to terminate the program here
  }
}

หากการวนซ้ำสำเร็จปัญหาเดียวที่เราพบในตอนนี้คือการเรียกใช้ฟังก์ชัน ตัวชี้ฟังก์ชัน typedef จากก่อนหน้านี้ไม่เป็นประโยชน์เนื่องจากแต่ละฟังก์ชันจะมีลายเซ็นของตัวเอง สิ่งนี้สามารถแก้ไขได้โดยการสร้างโครงสร้างที่มีฟังก์ชันทุกประเภท:

typedef struct
{
  int  (__stdcall* dll_add_ptr)(int, int);
  int  (__stdcall* dll_subtract_ptr)(int, int);
  void (__stdcall* dll_do_stuff_ptr)(something);
  ...
} functions_struct;

และในที่สุดหากต้องการเชื่อมต่อสิ่งเหล่านี้กับอาร์เรย์ก่อนหน้านี้ให้สร้างสหภาพ:

typedef union
{
  functions_struct  by_type;
  func_ptr_t        func_ptr [DLL_FUNCTIONS_N];
} functions_union;

ตอนนี้คุณสามารถโหลดฟังก์ชั่นทั้งหมดจาก DLL ด้วยลูปที่สะดวก แต่เรียกใช้ผ่านby_typeสมาชิกสหภาพ

แต่แน่นอนว่ามันเป็นเรื่องยากที่จะพิมพ์ออกมาเช่น

functions.by_type.dll_add_ptr(1, 1); เมื่อใดก็ตามที่คุณต้องการเรียกใช้ฟังก์ชัน

ปรากฎว่านี่คือเหตุผลที่ฉันเพิ่ม postfix "ptr" ลงในชื่อ: ฉันต้องการให้มันแตกต่างจากชื่อฟังก์ชันจริง ตอนนี้เราสามารถทำให้ไวยากรณ์ของโครงสร้างที่ยุ่งยากและรับชื่อที่ต้องการได้โดยใช้มาโครบางตัว:

#define dll_add (functions.by_type.dll_add_ptr)
#define dll_subtract (functions.by_type.dll_subtract_ptr)
#define dll_do_stuff (functions.by_type.dll_do_stuff_ptr)

และ voila ตอนนี้คุณสามารถใช้ชื่อฟังก์ชันด้วยประเภทและพารามิเตอร์ที่ถูกต้องราวกับว่าพวกมันเชื่อมโยงกับโปรเจ็กต์ของคุณแบบคงที่:

int result = dll_add(1, 1);

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

นอกจากนี้ในทางทฤษฎีอาจมีการเติมช่องว่างภายในสหภาพ / โครงสร้างซึ่งจะทำให้ทุกอย่างล้มเหลว อย่างไรก็ตามพอยน์เตอร์มีขนาดเท่ากับข้อกำหนดในการจัดตำแหน่งใน Windows A static_assertเพื่อให้แน่ใจว่าโครงสร้าง / สหภาพแรงงานไม่มีช่องว่างภายในอาจเป็นไปตามลำดับ


1
วิธีการสไตล์ C นี้จะใช้ได้ผล แต่จะไม่เหมาะสมที่จะใช้โครงสร้าง C ++ เพื่อหลีกเลี่ยงสิ่ง#defineนี้หรือไม่?
ฮาร์เปอร์

@harper ดีใน C ++ 11 คุณสามารถใช้ได้auto dll_add = ...แต่ใน C ++ 03 ไม่มีโครงสร้างใดที่ฉันคิดได้ว่าจะทำให้งานง่ายขึ้น (ฉันไม่เห็นปัญหาใด ๆ กับ#defines ที่นี่)
Niklas B.

เนื่องจากนี่คือทั้งหมด WinAPI เฉพาะคุณไม่จำเป็นต้อง typedef func_ptr_tของคุณเอง คุณสามารถใช้ได้แทนFARPROCซึ่งเป็นประเภทการส่งคืนของGetProcAddress. สิ่งนี้จะช่วยให้คุณรวบรวมด้วยระดับการเตือนที่สูงขึ้นโดยไม่ต้องเพิ่มนักแสดงในการGetProcAddressโทร
Adrian McCarthy

@NiklasB. คุณสามารถใช้ได้autoครั้งละหนึ่งฟังก์ชันเท่านั้นซึ่งจะเอาชนะความคิดที่จะทำครั้งเดียวสำหรับทุกคนในวง แต่เกิดอะไรขึ้นกับอาร์เรย์ std :: function
Francesco Dondi

1
@ Francesco ประเภทฟังก์ชัน std :: จะแตกต่างกันเช่นเดียวกับประเภท funcptr ฉันเดาว่าแม่แบบตัวแปรจะช่วยได้
Niklas B.

1

นี่ไม่ใช่ประเด็นร้อน แต่ฉันมีคลาสโรงงานที่อนุญาตให้ dll สร้างอินสแตนซ์และส่งคืนเป็น DLL มันคือสิ่งที่ฉันมองหา แต่หาไม่เจอ

เรียกว่าชอบ

IHTTP_Server *server = SN::SN_Factory<IHTTP_Server>::CreateObject();
IHTTP_Server *server2 =
      SN::SN_Factory<IHTTP_Server>::CreateObject(IHTTP_Server_special_entry);

โดยที่ IHTTP_Server เป็นอินเทอร์เฟซเสมือนจริงสำหรับคลาสที่สร้างขึ้นใน DLL อื่นหรือแบบเดียวกัน

DEFINE_INTERFACE ใช้เพื่อให้ ID คลาสมีอินเทอร์เฟซ วางอินเทอร์เฟซภายใน

คลาสอินเทอร์เฟซดูเหมือนว่า

class IMyInterface
{
    DEFINE_INTERFACE(IMyInterface);

public:
    virtual ~IMyInterface() {};

    virtual void MyMethod1() = 0;
    ...
};

ไฟล์ส่วนหัวเป็นแบบนี้

#if !defined(SN_FACTORY_H_INCLUDED)
#define SN_FACTORY_H_INCLUDED

#pragma once

ไลบรารีถูกระบุไว้ในข้อกำหนดมาโครนี้ หนึ่งบรรทัดต่อไลบรารี / ปฏิบัติการ มันจะดีมากถ้าเราสามารถเรียกใช้ปฏิบัติการอื่นได้

#define SN_APPLY_LIBRARIES(L, A)                          \
    L(A, sn, "sn.dll")                                    \
    L(A, http_server_lib, "http_server_lib.dll")          \
    L(A, http_server, "")

จากนั้นสำหรับแต่ละ dll / exe คุณกำหนดมาโครและแสดงรายการการใช้งาน Def หมายความว่าเป็นการใช้งานเริ่มต้นสำหรับอินเทอร์เฟซ หากไม่ใช่ค่าเริ่มต้นคุณต้องตั้งชื่อสำหรับอินเทอร์เฟซที่ใช้ระบุ กล่าวคือพิเศษและชื่อจะเป็น IHTTP_Server_special_entry

#define SN_APPLY_ENTRYPOINTS_sn(M)                                     \
    M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, def)                   \
    M(IHTTP_Handler, SNI::SNI_HTTP_Handler, sn, special)

#define SN_APPLY_ENTRYPOINTS_http_server_lib(M)                        \
    M(IHTTP_Server, HTTP::server::server, http_server_lib, def)

#define SN_APPLY_ENTRYPOINTS_http_server(M)

ด้วยการตั้งค่าไลบรารีทั้งหมดไฟล์ส่วนหัวจะใช้ข้อกำหนดมาโครเพื่อกำหนดสิ่งที่จำเป็น

#define APPLY_ENTRY(A, N, L) \
    SN_APPLY_ENTRYPOINTS_##N(A)

#define DEFINE_INTERFACE(I) \
    public: \
        static const long Id = SN::I##_def_entry; \
    private:

namespace SN
{
    #define DEFINE_LIBRARY_ENUM(A, N, L) \
        N##_library,

สิ่งนี้จะสร้าง enum สำหรับไลบรารี

    enum LibraryValues
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "")
        LastLibrary
    };

    #define DEFINE_ENTRY_ENUM(I, C, L, D) \
        I##_##D##_entry,

สิ่งนี้สร้าง enum สำหรับการใช้งานอินเทอร์เฟซ

    enum EntryValues
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_ENUM)
        LastEntry
    };

    long CallEntryPoint(long id, long interfaceId);

สิ่งนี้กำหนดระดับโรงงาน ไม่มากไปที่นี่

    template <class I>
    class SN_Factory
    {
    public:
        SN_Factory()
        {
        }

        static I *CreateObject(long id = I::Id )
        {
            return (I *)CallEntryPoint(id, I::Id);
        }
    };
}

#endif //SN_FACTORY_H_INCLUDED

CPP คือ

#include "sn_factory.h"

#include <windows.h>

สร้างจุดเข้าภายนอก คุณสามารถตรวจสอบว่ามีอยู่โดยใช้ขึ้นอยู่กับ.exe

extern "C"
{
    __declspec(dllexport) long entrypoint(long id)
    {
        #define CREATE_OBJECT(I, C, L, D) \
            case SN::I##_##D##_entry: return (int) new C();

        switch (id)
        {
            SN_APPLY_CURRENT_LIBRARY(APPLY_ENTRY, CREATE_OBJECT)
        case -1:
        default:
            return 0;
        }
    }
}

มาโครตั้งค่าข้อมูลทั้งหมดที่จำเป็น

namespace SN
{
    bool loaded = false;

    char * libraryPathArray[SN::LastLibrary];
    #define DEFINE_LIBRARY_PATH(A, N, L) \
        libraryPathArray[N##_library] = L;

    static void LoadLibraryPaths()
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_PATH, "")
    }

    typedef long(*f_entrypoint)(long id);

    f_entrypoint libraryFunctionArray[LastLibrary - 1];
    void InitlibraryFunctionArray()
    {
        for (long j = 0; j < LastLibrary; j++)
        {
            libraryFunctionArray[j] = 0;
        }

        #define DEFAULT_LIBRARY_ENTRY(A, N, L) \
            libraryFunctionArray[N##_library] = &entrypoint;

        SN_APPLY_CURRENT_LIBRARY(DEFAULT_LIBRARY_ENTRY, "")
    }

    enum SN::LibraryValues libraryForEntryPointArray[SN::LastEntry];
    #define DEFINE_ENTRY_POINT_LIBRARY(I, C, L, D) \
            libraryForEntryPointArray[I##_##D##_entry] = L##_library;
    void LoadLibraryForEntryPointArray()
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_POINT_LIBRARY)
    }

    enum SN::EntryValues defaultEntryArray[SN::LastEntry];
        #define DEFINE_ENTRY_DEFAULT(I, C, L, D) \
            defaultEntryArray[I##_##D##_entry] = I##_def_entry;

    void LoadDefaultEntries()
    {
        SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_DEFAULT)
    }

    void Initialize()
    {
        if (!loaded)
        {
            loaded = true;
            LoadLibraryPaths();
            InitlibraryFunctionArray();
            LoadLibraryForEntryPointArray();
            LoadDefaultEntries();
        }
    }

    long CallEntryPoint(long id, long interfaceId)
    {
        Initialize();

        // assert(defaultEntryArray[id] == interfaceId, "Request to create an object for the wrong interface.")
        enum SN::LibraryValues l = libraryForEntryPointArray[id];

        f_entrypoint f = libraryFunctionArray[l];
        if (!f)
        {
            HINSTANCE hGetProcIDDLL = LoadLibraryA(libraryPathArray[l]);

            if (!hGetProcIDDLL) {
                return NULL;
            }

            // resolve function address here
            f = (f_entrypoint)GetProcAddress(hGetProcIDDLL, "entrypoint");
            if (!f) {
                return NULL;
            }
            libraryFunctionArray[l] = f;
        }
        return f(id);
    }
}

แต่ละไลบรารีมี "cpp" นี้พร้อมกับต้นขั้ว cpp สำหรับแต่ละไลบรารี / ปฏิบัติการ เนื้อหาส่วนหัวที่คอมไพล์โดยเฉพาะ

#include "sn_pch.h"

ตั้งค่าไลบรารีนี้

#define SN_APPLY_CURRENT_LIBRARY(L, A) \
    L(A, sn, "sn.dll")

รวมสำหรับ cpp หลัก ฉันเดาว่า cpp นี้อาจเป็น. h แต่มีหลายวิธีที่คุณสามารถทำได้ วิธีนี้ใช้ได้ผลสำหรับฉัน

#include "../inc/sn_factory.cpp"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.