Прямо сейчас я делаю что-то вроде этого, и кажется грязным, если я заканчиваю наличие большого количества функций, я хочу сослаться в своем DLL. Есть ли лучший и более чистый способ получить доступ к функциям, не имея необходимость создавать typedef для каждого определения функции так, чтобы это собрало и загрузило функцию правильно. Я подразумеваю, что определения функции уже находятся в.h файле, и мне не придется повторно объявить их после того, как я загружаю функцию (или делаю меня?) Есть ли лучшее решение, чем использование LoadLibary? Мне не обязательно нужна та функция, если есть способ, которым я могу сделать то же самое в рамках Визуальных параметров настройки проекта Студии 2005 года.
#include "stdafx.h"
#include <windows.h>
#ifndef BHANNAN_TEST_CLASS_H_
#define BHANNAN_TEST_CLASS_H_
extern "C" {
// Returns n! (the factorial of n). For negative n, n! is defined to be 1.
int __declspec (dllexport) Factorial(int n);
// Returns true iff n is a prime number.
bool __declspec (dllexport) IsPrime(int n);
}
#endif // BHANNAN_TEST_CLASS_H_
#include "stdafx.h"
#include "BHannan_Test_Class.h"
// Returns n! (the factorial of n). For negative n, n! is defined to be 1.
int Factorial(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// Returns true iff n is a prime number.
bool IsPrime(int n) {
// Trivial case 1: small numbers
if (n <= 1) return false;
// Trivial case 2: even numbers
if (n % 2 == 0) return n == 2;
// Now, we have that n is odd and n >= 3.
// Try to divide n by every odd number i, starting from 3
for (int i = 3; ; i += 2) {
// We only have to try i up to the squre root of n
if (i > n/i) break;
// Now, we have i <= n/i < n.
// If n is divisible by i, n is not prime.
if (n % i == 0) return false;
}
// n has no integer factor in the range (1, n), and thus is prime.
return true;
}
#include <BHannan_Test_Class.h>
typedef int (*FactorialPtr) (int);
FactorialPtr myFactorial=NULL;
// Tests factorial of negative numbers.
TEST(FactorialTest, Negative) {
HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll");
if(myDLL) {
myFactorial = (FactorialPtr) GetProcAddress(myDLL,"Factorial");
if(myFactorial)
{
EXPECT_EQ(1, myFactorial(-5));
EXPECT_EQ(1, myFactorial(-1));
EXPECT_TRUE(myFactorial(-10) > 0);
}
FreeLibrary(myDLL);
}
}
После создания вашего .dll Получите файл .lib рядом и свяжите свое тестовое приложение с ним. Используйте функции, поскольку они объявлены в .H
. Существует незначительное изменение, необходимое в файле заголовка:
#ifdef EXPORTS_API
#define MY_API_EXPORT __declspec (dllexport)
#else
#define MY_API_EXPORT __declspec (dllimport)
#endif
extern "C" {
int MY_API_EXPORT Factorial(int n);
// do the same for other functions
}
таким образом, при создании вашей DLL определяют Exports_api
в ваших настройках проекта и функции Вызвать, в клиентском приложении не нужно ничего определять.
Импорт библиотек (.lib) упрощает использование DLL в пользовательском коде, смотрите, например, здесь для базового руководства.
Они избавляют пользователей от загрузки DLL, используя GetProcAddress()
и сами указатели функций - они статически связываются с библиотекой импорта, которая делает работу за них.
Почему вы не получаете VS для генерации прокладки статической библиотеки вокруг вашей DLL. Таким образом, все, что вам нужно сделать, это добавить конвенцию о вызове в заголовочном файле и добавить пару директив Pre-Precesor. Самый простой способ выяснить, как это сделать, это создать новый проект DLL (Visual C ++> Win32 Project, выберите DLL Project, проверьте символы импорта)
Используйте основной файл заголовка в качестве примера, как украсить ваши классы с Конвенция об импорте / экспорте. Эта голова является важным битом, поскольку он объясняет, как использовать функции и классы, объявленные там:
// The following ifdef block is the standard way of creating macros which make exporting
// from a DLL simpler. All files within this DLL are compiled with the DLLTEST2_EXPORTS
// symbol defined on the command line. this symbol should not be defined on any project
// that uses this DLL. This way any other project whose source files include this file see
// DLLTEST2_API functions as being imported from a DLL, whereas this DLL sees symbols
// defined with this macro as being exported.
#ifdef DLLTEST2_EXPORTS
#define DLLTEST2_API __declspec(dllexport)
#else
#define DLLTEST2_API __declspec(dllimport)
#endif
// This class is exported from the dlltest2.dll
class DLLTEST2_API Cdlltest2 {
public:
Cdlltest2(void);
// TODO: add your methods here.
};
extern DLLTEST2_API int ndlltest2;
DLLTEST2_API int fndlltest2(void);
, затем в проекте, который использует, что DLL просто включает файл заголовка и .lib
, который генерируется проект DLL Отказ Таким образом, он автоматически загружает DLL, и вы можете использовать все функции, как будто статически связанные.
Когда вы создаете свою dll, вы также должны получить lib-файл, с которым вы можете связаться. Это сделает для вас тяжелую работу в фоновом режиме. Включите заголовочный файл, который вы создали, но вместо dllexport перейдите в dllimport. Вы можете использовать определение для этого, так что для вашего проекта dll будет использоваться dllexport, а все остальные, которые не используют это определение, будут использовать dllimport.
Вам нужно только сделать вручную LoadLibrary и typedef, если вы хотите динамически загружать dll самостоятельно. Если вы сделаете описанный выше подход, ваш exe не справится, если dll не будет присутствовать.
Вы также можете собрать проект dll в статическую библиотеку и загрузить ее, что также избавит вас от этой проблемы, но увеличит размер вашего exe. Конечно, это не вариант, если вы действительно хотите, чтобы это была dll.
Вы можете напрямую связываться с символами DLL вместо использования GetProcAddress()
, который получает адрес функции во время выполнения.
Пример фрагмента заголовочного файла:
#if defined(MY_LIB_STATIC)
#define MY_LIB_EXPORT
#elif defined(MY_LIB_EXPORTS)
#define MY_LIB_EXPORT __declspec(dllexport)
#else
#define MY_LIB_EXPORT __declspec(dllimport)
#endif
#ifdef __cplusplus
extern "C"
{
#endif
int MY_LIB_EXPORT Factorial(int n);
#ifdef __cplusplus
}
#endif
Затем в BHannan_Test_Class.cpp
вы бы #определили MY_LIB_EXPORTS
перед включением заголовка.
В dll_test.cpp
вы бы включили заголовок и просто использовали Factorial()
так же, как вы бы использовали обычную функцию. При линковке вы хотите связать с библиотекой import, которая собирает созданную вами DLL. Это делает символы из DLL доступными для кода, который на нее ссылается.
В Windows World есть (по крайней мере) 4 способа использовать DLL:
Мне не нужно объяснять Динамическое соединение времени работы ] Так как вы уже делаете это. Я предпочитаю не объяснять динамическое связывание нагрузки задержки , теперь только что описывая то, что он имеет в общих чертах. Задержка нагрузки по существу то же самое, что и динамическое соединение времени нагрузки, за исключением того, что он выполняется просто вовремя, а не при нагрузке приложения. Это не так полезно или полезно, насколько вы можете подумать, сложно работать и сложно для кода. Так что давайте не будем идти туда, по крайней мере, на данный момент. Переадресация DLL еще более экзотична, чем загрузка задержки - так экзотика, я бы даже не слышал об этом, пока @mox не упомянул об этом в комментариях. Я позволю вам прочитать ссылку выше, чтобы узнать об этом, но достаточно сказать, что DLL-переадресация, когда вы вызываете экспортированную функцию в одну DLL, но этот запрос на самом деле переадресован к другой функции в другом DLL.
Это то, что я бы считал связывание Vanilla DLL .
Это то, что большинство людей ссылаются, когда они ссылаются на использование DLL в своих приложениях. Вы просто #inClude
Файл заголовка DLL и ссылка на файл lib. Не надо GetProcaddress ()
или создать функцию указателя Typedefs. Вот как он работает в двух словах:
1) Вы обычно получаете 3 файла: DLL с кодом выполнения, файл lib и файл заголовка. Файл заголовка - это просто заголовочный файл - он описывает все средства в DLL, которую вы можете использовать.
2) Вы пишете свое приложение, #inClude
'in include файл заголовка из DLL и делая вызовы этих функций, которые просто используете любую функцию в любом файле заголовка. Компилятор знает имена функций и объектов, которые вы используете, потому что они находятся в заголовом файле DLL. Но это не знает, где они еще в памяти. Вот где находится файл lib ...
3) Вы идете в настройки линкера для вашего проекта и добавить «дополнительную доступность библиотеки», указав файл lib. Файл lib сообщает линкеру, где функции и объекты, которые вы используете из файла H, находитесь в памяти (в относительном выражении, не абсолютные условия, очевидно).
4) Составьте свое приложение. Если вы установили все правильно, он должен скомпилировать, ссылку и запустить. Когда вы получаете ошибки линкера «нерешенные внешние ссылки», обычно это связано с тем, что их не настроено. Вы можете либо не указать правильный путь к файлу lib, либо вам нужно включить больше файлов lib.
Конечно, вам не нужночтобы был набран typedef
int (* myFactorial)(int) = 0;
HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll");
if(myDLL) {
myFactorial = reinterpret_cast<int (*) (int)>( GetProcAddress(myDLL,"Factorial"));
...
}
или же, используя инициализацию C++ и тестовую идиому идиому
if ( HMODULE myDLL = LoadLibrary("BHannan_Sample_DLL.dll") ) {
if ( int (* myFactorial)(int) = GetProcAddress ( myFactorial, myDLL, "Factorial" ) ) {
...
}
}
дабы убрать повторение типа
template <typename T>
T GetProcAddress ( const T&, HMODULE mod, const char* name)
{
return reinterpret_cast<T> (GetProcAddress(mod,name));
}
но не использовать typedef обычно хуже, а не лучше, так как типы указателей на функции в C немного хитры, если только вы их не используете регулярно. (Если вы используете их регулярно, то ваша программа может быть несколько неортодоксальной).
Расширения dllimport от Microsoft
и компилятор создают статическую библиотеку, которая делает загрузку за вас и предоставляет батуты или фишки, как написали другие. Если вы не создаете подключаемую систему, которая не знает, какую dll она будет загружать, то используйте ее вместо этого.