Bir DLL'den dinamik olarak bir işlev yükleyin


88

.Dll dosyalarına biraz bakıyorum, kullanımlarını anlıyorum ve nasıl kullanılacağını anlamaya çalışıyorum.

Funci () adında bir tamsayı döndüren bir işlev içeren bir .dll dosyası oluşturdum

Bu kodu kullanarak .dll dosyasını projeye aktardım (şikayet yok):

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

Ancak .dll'yi içe aktardığını düşündüğüm bu .cpp dosyasını derlemeye çalıştığımda şu hatayı alıyorum:

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 ===|

Bir .dll'nin başlık dosyasından farklı olduğunu biliyorum, bu yüzden böyle bir işlevi içeri aktaramayacağımı biliyorum ama denediğimi göstermek için bulabildiğim en iyisi bu.

Sorum şu, hGetProcIDDLL.dll içindeki işleve erişmek için işaretçiyi nasıl kullanabilirim .

Umarım bu soru mantıklıdır ve bir daha yanlış ağaca havlamıyorum.


statik / dinamik bağlantı araması.
Mitch Wheat

Teşekkür ederim,

Kodumu girintili yaptım ama onu buraya

Yanıtlar:


153

LoadLibrarysandığın şeyi yapmaz. Mevcut sürecin belleğe DLL yükler, ancak yok değil sihirli içinde tanımlanmış fonksiyonlar içe! Bu mümkün olmazdı, çünkü işlev çağrıları derleme zamanında bağlayıcı tarafından çözülürken LoadLibraryçalışma zamanında çağrılır (C ++ 'nın statik olarak yazılmış bir dil olduğunu unutmayın).

Dinamik yüklenen fonksiyonların adresini almak için ayrı bir WinAPI işlevini gerekir: GetProcAddress.

Misal

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

Ayrıca, işlevinizi DLL'den doğru bir şekilde dışa aktarmalısınız. Bu şu şekilde yapılabilir:

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

Lundin'in belirttiği gibi, eğer daha uzun süre ihtiyacınız yoksa , kütüphaneyi serbest bırakmak iyi bir uygulamadır . Bu, başka hiçbir işlem aynı DLL için bir tutamaç tutmuyorsa, kaldırılmasına neden olur.


Aptalca bir soru gibi gelebilir ama f_funci'nin türü nedir / ne olmalıdır?

8
Bunun dışında cevap mükemmel ve kolayca anlaşılabilir

6
f_funciAslında bir tür olduğunu unutmayın ( bir türe sahip olmaktan çok ). Tür f_funci, "bir döndüren intve hiçbir argüman almayan bir işleve işaretçi" olarak okunur . C'deki işlev işaretçileri hakkında daha fazla bilgi newty.de/fpt/index.html adresinde bulunabilir .
Niklas B.

Cevap için tekrar teşekkürler, funci argüman almaz ve bir tamsayı döndürür; Derlenen işlevi göstermek için soruyu düzenledim mi? .dll içine. " Typedef int ( f_funci) ();" ekledikten sonra çalıştırmayı denediğimde Bu hatayı aldım: C: \ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp || 'int main ()' işlevinde: | C: \ Documents and Settings \ User \ Desktop \ fgfdg \ onemore.cpp | 18 | hata: 'int ( ) ()', '2' bağımsız değişkeni için 'sabit CHAR *' olarak dönüştürülemiyor (* GetProcAddress (HINSTANCE__ , sabit CHAR )) () '| || === Derleme tamamlandı: 1 hata, 0 uyarı === |

Orada bir oyuncu kadrosunu unuttum (içinde düzenledim). Ancak hata başka bir hata gibi görünüyor, doğru kodu kullandığınızdan emin misiniz? Cevabınız evet ise, lütfen başarısız olan kodunuzu ve derleyici çıktısının tamamını pastie.org'a yapıştırabilir misiniz? Ayrıca, yorumunuzda yazdığınız yazı yanlış (bir *eksik, hataya neden olmuş olabilir)
Niklas B.

34

Önceden gönderilen cevaba ek olarak, her fonksiyon için ayrı bir GetProcAddress çağrısı yazmadan, tüm DLL fonksiyonlarını fonksiyon işaretçileri aracılığıyla programa yüklemek için kullandığım kullanışlı bir numarayı paylaşmam gerektiğini düşündüm. Ayrıca, OP'de denendiği gibi işlevleri doğrudan çağırmayı seviyorum.

Genel bir işlev işaretçi türü tanımlayarak başlayın:

typedef int (__stdcall* func_ptr_t)();

Hangi türlerin kullanıldığı gerçekten önemli değil. Şimdi, DLL'de sahip olduğunuz işlevlerin miktarına karşılık gelen bu türde bir dizi oluşturun:

func_ptr_t func_ptr [DLL_FUNCTIONS_N];

Bu dizide, DLL bellek alanına işaret eden gerçek işlev işaretlerini saklayabiliriz.

Bir sonraki sorun, GetProcAddressişlev adlarının dizeler olarak beklenmesidir. Öyleyse, DLL'deki işlev adlarından oluşan benzer bir dizi oluşturun:

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

Artık bir döngüde GetProcAddress () 'i kolayca çağırabilir ve her işlevi bu dizinin içinde saklayabiliriz:

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
  }
}

Döngü başarılı olduysa, şu anda sahip olduğumuz tek sorun işlevleri çağırmaktır. Daha önceki typedef işaretçisi, her işlevin kendi imzasına sahip olacağı için yararlı değildir. Bu, tüm fonksiyon türleriyle bir yapı oluşturarak çözülebilir:

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;

Ve son olarak, bunları önceden diziye bağlamak için bir birleşim oluşturun:

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

Artık DLL'deki tüm işlevleri uygun döngü ile yükleyebilirsiniz, ancak bunları by_typesendika üyesi aracılığıyla çağırabilirsiniz .

Ancak elbette şöyle bir şey yazmak biraz külfetli

functions.by_type.dll_add_ptr(1, 1); bir işlevi ne zaman çağırmak isterseniz.

Görünüşe göre isimlere "ptr" sonekini eklememin nedeni bu: Onları gerçek işlev isimlerinden farklı tutmak istedim. Şimdi bazı makrolar kullanarak icky struct sözdizimini düzeltebilir ve istenen isimleri alabiliriz:

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

Ve işte şimdi, doğru tür ve parametrelerle işlev adlarını, sanki projenize statik olarak bağlıymış gibi kullanabilirsiniz:

int result = dll_add(1, 1);

Sorumluluk Reddi: Açıkça söylemek gerekirse, farklı işlev işaretçileri arasındaki dönüşümler C standardı tarafından tanımlanmamıştır ve güvenli değildir. Yani resmi olarak, burada yaptığım şey tanımlanmamış bir davranış. Bununla birlikte, Windows dünyasında, türlerinden bağımsız olarak işlev işaretçileri her zaman aynı boyuttadır ve aralarındaki dönüşümler, kullandığım herhangi bir Windows sürümünde tahmin edilebilir.

Ayrıca, teorik olarak birleşim / yapıya her şeyin başarısız olmasına neden olacak dolgu eklenmiş olabilir. Ancak, işaretçiler Windows'taki hizalama gereksinimi ile aynı boyuttadır. A static_assertyapı / birleşimin dolgusu olmadığından emin olmak için hala sıralı olabilir.


1
Bu C tarzı yaklaşım işe yarayacaktır. Ancak #defines'den kaçınmak için bir C ++ yapısı kullanmak uygun olmaz mıydı ?
harper

@harper Well C ++ 11'de kullanabilirsiniz auto dll_add = ..., ancak C ++ 03'te görevi basitleştirecek düşünebildiğim bir yapı yok (ayrıca #defineburada
s'lerle

Bunların tümü WinAPI'ye özgü olduğundan, kendi yazmanıza gerek yoktur func_ptr_t. Bunun yerine FARPROC, dönüş türü olan GetProcAddress. Bu, GetProcAddressçağrıya bir çevrim eklemeden daha yüksek bir uyarı seviyesi ile derlemenize izin verebilir .
Adrian McCarthy

@NiklasB. bir seferde yalnızca autobir işlev için kullanabilirsiniz , bu da bir döngüde hepsini bir kez yapma fikrini ortadan kaldırır. ama std :: function dizisindeki sorun
Francesco Dondi

1
@Francesco std :: işlev türleri, tıpkı funcptr türleri gibi farklı olacaktır. Sanırım çeşitli şablonlar yardımcı olur
Niklas B.

1

Bu tam olarak sıcak bir konu değil, ancak bir dll'nin bir örnek oluşturmasına ve onu DLL olarak döndürmesine izin veren bir fabrika sınıfım var. Aradığım şeydi ama tam olarak bulamadım.

Gibi denir,

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

Burada IHTTP_Server, başka bir DLL'de veya aynı DLL'de oluşturulan bir sınıf için saf sanal arabirimdir.

DEFINE_INTERFACE, bir sınıf kimliğine bir arabirim vermek için kullanılır. Arayüzün içine yerleştirin;

Bir arayüz sınıfı şuna benzer:

class IMyInterface
{
    DEFINE_INTERFACE(IMyInterface);

public:
    virtual ~IMyInterface() {};

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

Başlık dosyası şöyle

#if !defined(SN_FACTORY_H_INCLUDED)
#define SN_FACTORY_H_INCLUDED

#pragma once

Kitaplıklar bu makro tanımında listelenmiştir. Kitaplık / çalıştırılabilir dosya başına bir satır. Başka bir çalıştırılabilir dosyayı çağırabilirsek harika olurdu.

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

Daha sonra her dll / exe için bir makro tanımlar ve uygulamalarını listelersiniz. Def, arayüz için varsayılan uygulama olduğu anlamına gelir. Varsayılan değilse, onu tanımlamak için kullanılan arabirime bir ad verirsiniz. Yani, özel ve ad IHTTP_Server_special_entry olacaktır.

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

Kütüphanelerin tüm kurulumuyla, başlık dosyası gerekli olanı tanımlamak için makro tanımlarını kullanır.

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

Bu, kütüphaneler için bir enum oluşturur.

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

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

Bu, arabirim uygulamaları için bir enum oluşturur.

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

    long CallEntryPoint(long id, long interfaceId);

Bu, fabrika sınıfını tanımlar. Burada pek yok.

    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

O zaman CPP,

#include "sn_factory.h"

#include <windows.h>

Harici giriş noktasını oluşturun. Falls.exe kullanarak var olup olmadığını kontrol edebilirsiniz.

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;
        }
    }
}

Makrolar, ihtiyaç duyulan tüm verileri ayarlar.

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

Her kitaplık, her kitaplık / çalıştırılabilir dosya için bir stub cpp ile bu "cpp" yi içerir. Herhangi bir özel derlenmiş başlık malzemesi.

#include "sn_pch.h"

Bu kitaplığı kurun.

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

Ana cpp için bir dahil. Sanırım bu cpp bir .h olabilir. Ancak bunu yapmanın farklı yolları var. Bu yaklaşım benim için çalıştı.

#include "../inc/sn_factory.cpp"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.