Przyglądam się plikom .dll, rozumiem ich użycie i próbuję zrozumieć, jak ich używać.
Utworzyłem plik .dll, który zawiera funkcję zwracającą liczbę całkowitą o nazwie funci ()
używając tego kodu, (chyba) zaimportowałem plik .dll do projektu (nie ma żadnych skarg):
#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;
}
Jednak kiedy próbuję skompilować ten plik .cpp, który moim zdaniem zaimportował .dll, pojawia się następujący błąd:
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 ===|
Wiem, że plik .dll różni się od pliku nagłówkowego, więc wiem, że nie mogę zaimportować takiej funkcji, ale jest to najlepsze, co mogłem wymyślić, aby pokazać, że próbowałem.
Moje pytanie brzmi: w jaki sposób mogę użyć hGetProcIDDLL
wskaźnika, aby uzyskać dostęp do funkcji w pliku .dll.
Mam nadzieję, że to pytanie ma sens i nie szczekam po raz kolejny niewłaściwego drzewa.
Odpowiedzi:
LoadLibrary
nie robi tego, co myślisz, że robi. Ładuje bibliotekę DLL do pamięci bieżącego procesu, ale nie importuje w magiczny sposób zdefiniowanych w niej funkcji! Nie byłoby to możliwe, ponieważ wywołania funkcji są rozwiązywane przez konsolidator w czasie kompilacji, podczas gdyLoadLibrary
jest wywoływane w czasie wykonywania (pamiętaj, że C ++ jest językiem z typowaniem statycznym ).Trzeba osobnej funkcji WinAPI, aby uzyskać adres dynamicznie obciążonych funkcji:
GetProcAddress
.Przykład
#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; }
Powinieneś także poprawnie wyeksportować swoją funkcję z biblioteki DLL. Można to zrobić w następujący sposób:
int __declspec(dllexport) __stdcall funci() { // ... }
Jak zauważa Lundin, dobrą praktyką jest zwolnienie uchwytu do biblioteki, jeśli nie potrzebujesz go dłużej. Spowoduje to zwolnienie go, jeśli żaden inny proces nie ma jeszcze uchwytu do tej samej biblioteki DLL.
źródło
f_funci
w rzeczywistości jest typem (a nie ma typ). Typ jestf_funci
odczytywany jako „wskaźnik do funkcji zwracającejint
i nie pobierającej argumentów”. Więcej informacji o wskaźnikach funkcji w C można znaleźć na stronie newty.de/fpt/index.html .*
brak znaku , który mógł spowodować błąd)Oprócz już opublikowanej odpowiedzi, pomyślałem, że powinienem podzielić się poręczną sztuczką, której używam do ładowania wszystkich funkcji DLL do programu za pomocą wskaźników funkcji, bez pisania oddzielnego wywołania GetProcAddress dla każdej funkcji. Lubię również wywoływać funkcje bezpośrednio, jak próbowano w PO.
Zacznij od zdefiniowania ogólnego typu wskaźnika funkcji:
typedef int (__stdcall* func_ptr_t)();
Jakie typy są używane, nie są naprawdę ważne. Teraz utwórz tablicę tego typu, która odpowiada liczbie funkcji, które masz w bibliotece DLL:
func_ptr_t func_ptr [DLL_FUNCTIONS_N];
W tej tablicy możemy przechowywać rzeczywiste wskaźniki funkcji, które wskazują na przestrzeń pamięci DLL.
Następnym problemem jest to, że
GetProcAddress
oczekuje się , że nazwy funkcji są łańcuchami. Stwórz więc podobną tablicę składającą się z nazw funkcji w bibliotece DLL:const char* DLL_FUNCTION_NAMES [DLL_FUNCTIONS_N] = { "dll_add", "dll_subtract", "dll_do_stuff", ... };
Teraz możemy łatwo wywołać GetProcAddress () w pętli i przechowywać każdą funkcję w tej tablicy:
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 } }
Jeśli pętla się powiedzie, jedynym problemem, jaki mamy teraz, jest wywoływanie funkcji. Wcześniejszy wskaźnik funkcji typedef nie jest pomocny, ponieważ każda funkcja będzie miała swój własny podpis. Można to rozwiązać, tworząc strukturę ze wszystkimi typami funkcji:
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;
I na koniec, aby połączyć je z tablicą sprzed wcześniej, utwórz sumę:
typedef union { functions_struct by_type; func_ptr_t func_ptr [DLL_FUNCTIONS_N]; } functions_union;
Teraz możesz załadować wszystkie funkcje z biblioteki DLL za pomocą wygodnej pętli, ale wywołać je za pośrednictwem elementu
by_type
członkowskiego unii.Ale oczywiście napisanie czegoś takiego jest trochę uciążliwe
functions.by_type.dll_add_ptr(1, 1);
kiedykolwiek chcesz wywołać funkcję.Jak się okazuje, jest to powód, dla którego dodałem do nazw przedrostek „ptr”: chciałem, aby różniły się one od rzeczywistych nazw funkcji. Możemy teraz wygładzić składnię icky struct i uzyskać żądane nazwy, używając kilku makr:
#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)
I voilà, możesz teraz używać nazw funkcji z odpowiednim typem i parametrami, tak jakby były statycznie połączone z Twoim projektem:
int result = dll_add(1, 1);
Zastrzeżenie: Ściśle mówiąc, konwersje między różnymi wskaźnikami funkcji nie są zdefiniowane w standardzie C i nie są bezpieczne. Więc formalnie to, co tutaj robię, to niezdefiniowane zachowanie. Jednak w świecie Windows wskaźniki funkcji są zawsze tego samego rozmiaru bez względu na ich typ, a konwersje między nimi są przewidywalne w każdej wersji systemu Windows, z której korzystałem.
Teoretycznie może istnieć wypełnienie wstawione do union / struct, co spowoduje, że wszystko zawiedzie. Jednak wskaźniki mają taki sam rozmiar, jak wymóg wyrównania w systemie Windows.
static_assert
Aby upewnić się, że struct / union nie ma wyściółka może być w porządku nadal.źródło
#define
s?auto dll_add = ...
, ale w C ++ 03 nie ma konstrukcji, o której mógłbym pomyśleć, który uprościłby zadanie (nie widzę tutaj również żadnego problemu z#define
s)func_ptr_t
. Zamiast tego możesz użyćFARPROC
, który jest zwracanym typemGetProcAddress
. Może to pozwolić na kompilację z wyższym poziomem ostrzeżenia bez dodawania rzutowania doGetProcAddress
wywołania.auto
dla jednej funkcji naraz, co niweczy pomysł zrobienia tego raz na zawsze w pętli. ale co jest nie tak z tablicą std :: functionNie jest to gorący temat, ale mam klasę fabryczną, która umożliwia dll utworzenie instancji i zwrócenie jej jako biblioteki DLL. To jest to, czego szukałem, ale nie mogłem dokładnie znaleźć.
Nazywa się to
gdzie IHTTP_Server jest czystym interfejsem wirtualnym dla klasy utworzonej w innej lub tej samej bibliotece DLL.
DEFINE_INTERFACE służy do nadania identyfikatorowi klasy interfejsu. Umieść interfejs wewnątrz;
Klasa interfejsu wygląda następująco:
class IMyInterface { DEFINE_INTERFACE(IMyInterface); public: virtual ~IMyInterface() {}; virtual void MyMethod1() = 0; ... };
Plik nagłówkowy jest taki
#if !defined(SN_FACTORY_H_INCLUDED) #define SN_FACTORY_H_INCLUDED #pragma once
Biblioteki są wymienione w tej definicji makra. Jedna linia na bibliotekę / plik wykonywalny. Byłoby fajnie, gdybyśmy mogli wywołać inny plik wykonywalny.
#define SN_APPLY_LIBRARIES(L, A) \ L(A, sn, "sn.dll") \ L(A, http_server_lib, "http_server_lib.dll") \ L(A, http_server, "")
Następnie dla każdego dll / exe definiujesz makro i wypisujesz jego implementacje. Def oznacza, że jest to domyślna implementacja interfejsu. Jeśli nie jest to ustawienie domyślne, podajesz nazwę interfejsu używanego do jego identyfikacji. To znaczy specjalne, a nazwa będzie miała postać 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)
W przypadku wszystkich konfiguracji bibliotek plik nagłówkowy używa definicji makr do zdefiniowania potrzebnych.
#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,
Spowoduje to utworzenie wyliczenia dla bibliotek.
enum LibraryValues { SN_APPLY_LIBRARIES(DEFINE_LIBRARY_ENUM, "") LastLibrary }; #define DEFINE_ENTRY_ENUM(I, C, L, D) \ I##_##D##_entry,
Spowoduje to utworzenie wyliczenia dla implementacji interfejsu.
enum EntryValues { SN_APPLY_LIBRARIES(APPLY_ENTRY, DEFINE_ENTRY_ENUM) LastEntry }; long CallEntryPoint(long id, long interfaceId);
Definiuje klasę fabryczną. Niewiele do tego tutaj.
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
Następnie CPP jest,
#include "sn_factory.h" #include <windows.h>
Utwórz zewnętrzny punkt wejścia. Możesz sprawdzić, czy istnieje, używając 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; } } }
Makra konfigurują wszystkie potrzebne dane.
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); } }
Każda biblioteka zawiera ten „cpp” ze skrótowym cpp dla każdej biblioteki / pliku wykonywalnego. Wszelkie konkretne skompilowane elementy nagłówka.
#include "sn_pch.h"
Skonfiguruj tę bibliotekę.
#define SN_APPLY_CURRENT_LIBRARY(L, A) \ L(A, sn, "sn.dll")
Dołącz do głównego cpp. Myślę, że ten cpp może być .h. Ale można to zrobić na różne sposoby. To podejście zadziałało dla mnie.
#include "../inc/sn_factory.cpp"
źródło