डीएनएल से एक फ़ंक्शन को गतिशील रूप से लोड करें


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 में फ़ंक्शन तक पहुंचने के लिए पॉइंटर का उपयोग कैसे कर सकता हूं ।

मुझे उम्मीद है कि यह सवाल समझ में आता है और मैं अभी तक फिर से कुछ गलत पेड़ नहीं भौंक रहा हूं।


लुकिंग स्टेटिक / डायनेमिक लिंकिंग।
मिच गेहूं

धन्यवाद, मैं इस पर ध्यान दूंगा

मैं अपने कोड को इंडेंट करता हूं, लेकिन जब मैं इसे यहां से

जवाबों:


152

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() {
   // ...
}

यदि लंडिन नोट करता है, तो लाइब्रेरी को हैंडल को मुक्त करने के लिए यह अच्छा अभ्यास है यदि आपको इसकी आवश्यकता नहीं है। यदि कोई अन्य प्रक्रिया अभी भी उसी DLL के लिए हैंडल नहीं रखती है तो यह अनलोड हो जाएगा।


एक बेवकूफ सवाल की तरह लग सकता है लेकिन f_funci का प्रकार क्या होना चाहिए?

8
इसके अलावा, उत्तर उत्कृष्ट है और आसानी से समझ में आता है

6
ध्यान दें कि f_funciवास्तव में एक प्रकार है (बजाय है एक प्रकार)। प्रकार f_funci"सूचक के रूप में पढ़ता है एक फ़ंक्शन पर लौटता है intऔर कोई तर्क नहीं लेता है"। C में फ़ंक्शन पॉइंटर्स के बारे में अधिक जानकारी newty.de/fpt/index.html पर देखी जा सकती है ।
निकलैस बी।

उत्तर के लिए फिर से धन्यवाद, सनक कोई तर्क नहीं लेता है और पूर्णांक देता है; मैंने उस फ़ंक्शन को दिखाने के लिए प्रश्न संपादित किया जो संकलित किया गया था? .dll में जब मैंने " टाइपडिफ इंट ( f_funci) ();" मुझे यह त्रुटि मिली: C: \ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp || फ़ंक्शन में 'int main ()': | C: \ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp | 18 | error: 'int ( ) () को' const CHAR * 'में तर्क' 2 'के लिए' int (* GetPadAddress (HINSTANCE__) नहीं कर सकते । const CHAR )) () ’| || === बिल्ड समाप्त: 1 त्रुटियां, 0 चेतावनी === |

खैर मैं वहां एक कास्ट (इसे संपादित कर) भूल गया। हालाँकि त्रुटि एक और है, क्या आप सुनिश्चित हैं कि आप सही कोड का उपयोग करते हैं? यदि हाँ, तो क्या आप pastie.org पर अपना असफल कोड और पूरा संकलक आउटपुट पेस्ट कर सकते हैं ? साथ ही, आपने जो टिप्पणी लिखी है, वह गलत है (एक *अनुपस्थित है, जो त्रुटि का कारण हो सकता है)
निकल्स बी।

34

पहले से ही पोस्ट किए गए उत्तर के अलावा, मैंने सोचा कि मुझे एक आसान ट्रिक साझा करना चाहिए जिसका उपयोग मैं सभी DLL फ़ंक्शन को फ़ंक्शन पॉइंटर्स के माध्यम से प्रोग्राम में लोड करने के लिए करता हूं, प्रत्येक और प्रत्येक फ़ंक्शन के लिए एक अलग GetProcAddress कॉल लिखे बिना। मैं भी ओपी में किए गए कार्यों को सीधे कॉल करना पसंद करता हूं।

एक सामान्य फ़ंक्शन पॉइंटर प्रकार को परिभाषित करके प्रारंभ करें:

typedef int (__stdcall* func_ptr_t)();

जो प्रकार उपयोग किए जाते हैं वे वास्तव में महत्वपूर्ण नहीं हैं। अब उस प्रकार की एक सरणी बनाएं, जो DLL में आपके द्वारा किए जाने वाले कार्यों की मात्रा से मेल खाती है:

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

इस सरणी में हम वास्तविक फ़ंक्शन पॉइंटर्स को संग्रहीत कर सकते हैं जो डीएलएल मेमोरी स्पेस में इंगित करते हैं।

अगली समस्या यह है कि 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 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;

अब आप डीएलएल से सभी कार्यों को सुविधाजनक लूप के साथ लोड कर सकते हैं, लेकिन उन्हें by_typeयूनियन सदस्य के माध्यम से कॉल कर सकते हैं ।

लेकिन निश्चित रूप से, यह कुछ बाहर टाइप करने के लिए थोड़ा बोझ है

functions.by_type.dll_add_ptr(1, 1); जब भी आप किसी फंक्शन को कॉल करना चाहते हैं।

जैसा कि यह पता चला है, यही कारण है कि मैंने नामों में "ptr" पोस्टफ़िक्स जोड़ा: मैं उन्हें वास्तविक फ़ंक्शन नामों से अलग रखना चाहता था। अब हम कुछ मैक्रोज़ का उपयोग करके icky संरचना सिंटैक्स को सुचारू कर सकते हैं और वांछित नाम प्राप्त कर सकते हैं:

#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)

और वॉइलिया, अब आप सही प्रकार और मापदंडों के साथ फ़ंक्शन नाम का उपयोग कर सकते हैं, जैसे कि वे सांख्यिकीय रूप से आपकी परियोजना से जुड़े थे:

int result = dll_add(1, 1);

डिस्क्लेमर: सख्ती से बोलना, विभिन्न फ़ंक्शन पॉइंटर्स के बीच रूपांतरण सी मानक द्वारा परिभाषित नहीं है और सुरक्षित नहीं है। इसलिए औपचारिक रूप से, मैं यहां जो कर रहा हूं वह अपरिभाषित व्यवहार है। हालाँकि, विंडोज की दुनिया में, फंक्शन पॉइंटर्स हमेशा एक ही आकार के होते हैं, चाहे उनका प्रकार कोई भी क्यों न हो और उनके बीच के रूपांतरण विंडोज के किसी भी संस्करण पर अनुमानित हों।

इसके अलावा, सिद्धांत में संघ / संरचना में डाला जा सकता है, जिससे सब कुछ विफल हो जाएगा। हालाँकि, पॉइंटर्स विंडोज में संरेखण आवश्यकता के समान आकार के होते हैं। एक static_assertसुनिश्चित करना है कि struct / संघ कोई पैडिंग क्रम में अभी भी हो सकता है है।


1
यह सी शैली दृष्टिकोण काम करेगा। लेकिन #defineएस से बचने के लिए सी ++ निर्माण का उपयोग करना उचित नहीं होगा ?
हार्पर

@harper वेल C C 11 में आप उपयोग कर सकते हैं auto dll_add = ..., लेकिन C ++ 03 में ऐसा कोई निर्माण नहीं है जिसके बारे में मैं सोच सकता था कि कार्य को सरल बनाया जाएगा (मैं यहां कोई विशेष समस्या नहीं देखता #define)
Niklas B

चूंकि यह सभी WinAPI- विशिष्ट है, इसलिए आपको अपना स्वयं का टाइअप करने की आवश्यकता नहीं है func_ptr_t। इसके बजाय आप उपयोग कर सकते हैं FARPROC, जो कि रिटर्न प्रकार है GetProcAddress। यह आपको GetProcAddressकॉल में कलाकारों को जोड़े बिना उच्च चेतावनी स्तर के साथ संकलन करने की अनुमति दे सकता है ।
एड्रियन मैककार्थी

@NiklasB। आप autoएक समय में केवल एक फ़ंक्शन के लिए उपयोग कर सकते हैं , जो एक बार सभी में एक बार करने के विचार को पराजित करता है। लेकिन क्या एक सरणी एसटीडी के साथ गलत है :: फ़ंक्शन
फ्रांसेस्को डोंडी

1
@Francesco एसटीडी :: फ़ंक्शन प्रकार फ़ंक्शनल प्रकार की तरह ही भिन्न होंगे। मुझे लगता है कि वैरेडिक टेम्प्लेट मदद करेंगे
बी।

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 का उपयोग क्लास आईडी को इंटरफ़ेस देने के लिए किया जाता है। इंटरफ़ेस के अंदर जगह;

एक इंटरफ़ेस वर्ग दिखता है,

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 LibraryValues
    {
        SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "")
        LastLibrary
    };

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

यह इंटरफ़ेस कार्यान्वयन के लिए एक एनुम बनाता है।

    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

फिर सीपीपी है,

#include "sn_factory.h"

#include <windows.h>

बाहरी प्रवेश बिंदु बनाएँ। आप देख सकते हैं कि यह depend.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);
    }
}

प्रत्येक पुस्तकालय में प्रत्येक पुस्तकालय / निष्पादन योग्य के लिए एक स्टब सीपीपी के साथ यह "सीपीपी" शामिल होता है। कोई भी विशिष्ट संकलित शीर्ष लेख सामग्री।

#include "sn_pch.h"

इस लाइब्रेरी को सेटअप करें।

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

मुख्य सीपीपी के लिए एक शामिल है। मुझे लगता है कि यह cpp एक .h हो सकता है। लेकिन अलग-अलग तरीके हैं जो आप ऐसा कर सकते हैं। इस दृष्टिकोण ने मेरे लिए काम किया।

#include "../inc/sn_factory.cpp"
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.