Я немного посмотрел на файлы .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.
Надеюсь, этот вопрос имеет смысл, и я снова не лаю на какое-то неправильное дерево.
Ответы:
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
на самом деле представляет собой тип (а не имеет тип). Типf_funci
читается как «указатель на функцию, возвращающуюint
и не принимающую аргументов». Дополнительную информацию о указателях функций в C можно найти на newty.de/fpt/index.html .*
отсутствует, что могло вызвать ошибку)В дополнение к уже опубликованному ответу я подумал, что должен поделиться удобным трюком, который я использую для загрузки всех функций 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);
всякий раз, когда вы хотите вызвать функцию.Как оказалось, именно по этой причине я добавил к именам постфикс «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)
И вуаля, теперь вы можете использовать имена функций с правильным типом и параметрами, как если бы они были статически связаны с вашим проектом:
int result = dll_add(1, 1);
Отказ от ответственности: строго говоря, преобразования между разными указателями функций не определены стандартом C и небезопасны. Итак, формально то, что я здесь делаю, - это неопределенное поведение. Однако в мире Windows указатели на функции всегда имеют один и тот же размер независимо от их типа, и преобразования между ними предсказуемы в любой версии Windows, которую я использовал.
Кроме того, теоретически в объединение / структуру может быть вставлено дополнение, что приведет к сбою всего. Однако указатели оказываются того же размера, что и требование выравнивания в Windows. A,
static_assert
чтобы гарантировать, что структура / объединение не имеет заполнения, все еще может быть в порядке.источник
#define
s?auto dll_add = ...
, но в C ++ 03 нет конструкции, которая могла бы упростить задачу (я также не вижу здесь особых проблем с#define
s)func_ptr_t
. Вместо этого вы можете использоватьFARPROC
, который является типом возвращаемого значенияGetProcAddress
. Это может позволить вам выполнить компиляцию с более высоким уровнем предупреждения без добавления приведения кGetProcAddress
вызову.auto
одну функцию за раз, что противоречит идее делать это раз и навсегда в цикле. но что не так с массивом std :: functionЭто не совсем актуальная тема, но у меня есть фабричный класс, который позволяет dll создавать экземпляр и возвращать его как DLL. Это то, что я искал, но не нашел.
Это называется как,
где 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
Тогда CPP:
#include "sn_factory.h" #include <windows.h>
Создайте внешнюю точку входа. Вы можете проверить, что он существует, с помощью depends.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"
источник