Есть ли лучший способ загрузить DLL в C ++? - PullRequest
10 голосов
/ 14 января 2010

Прямо сейчас я делаю что-то вроде этого, и мне кажется, что у меня заканчивается множество функций, на которые я хочу ссылаться в своей DLL. Есть ли лучший и более чистый способ доступа к функциям без необходимости создавать typedef для каждого определения функции, чтобы он правильно компилировал и загружал функцию. Я имею в виду, что определения функций уже находятся в файле .h, и мне не нужно их повторно объявлять после загрузки функции (или нет?). Есть ли лучшее решение, чем использование LoadLibary? Мне не обязательно нужна эта функция, если есть способ сделать то же самое в настройках проекта Visual Studio 2005.


BHannan_Test_Class.h

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

BHannan_Test_Class.cpp

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

dll_test.cpp

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

}

Ответы [ 7 ]

22 голосов
/ 14 января 2010

В мире Windows существует (как минимум) 4 способа использования DLL:

  1. Динамическое связывание во время выполнения (Что вы делаете сейчас)
  2. Динамическое связывание во время загрузки («типичный» способ использования DLL)
  3. Динамическое связывание с задержкой нагрузки
  4. Пересылка DLL

Мне не нужно объяснять Динамическое связывание во время выполнения , поскольку вы уже это делаете.Я предпочитаю не объяснять Delay-Load Dynamic Linking теперь за пределами простого описания того, что это в общих чертах.Задержка загрузки, по сути, такая же, как динамическое связывание во время загрузки, за исключением того, что она выполняется точно в срок, а не при загрузке приложения.Это не так полезно и не так выгодно, как вы можете подумать, с ним трудно работать и сложно его кодировать.Так что давайте не будем идти туда, по крайней мере сейчас. Пересылка DLL даже более экзотична, чем Delay-Loading - настолько экзотична, что я даже не слышал об этом, пока @mox не упомянул об этом в комментариях.Я позволю вам прочесть ссылку выше, чтобы узнать об этом, но достаточно сказать, что перенаправление DLL - это когда вы вызываете экспортированную функцию в одной DLL, но этот запрос на самом деле перенаправляет в другую функцию в другойDLL.

Динамическое связывание во время загрузки

Это то, что я бы назвал Связывание с Vanilla DLL .

Это то, что большинство людей имеют в видуони ссылаются на использование DLL в своих приложениях.Вы просто #include заголовочный файл DLL и ссылку на файл LIB.Нет необходимости GetProcAddress() или создавать указатель функции typedefs.Вот как это работает в двух словах:

1) Обычно вы получаете 3 файла: DLL с кодом времени выполнения, файл LIB и файл заголовка.Заголовочный файл - это просто заголовочный файл - он описывает все возможности DLL, которые вы можете использовать.

2) Вы пишете свое приложение, #include извлекаете заголовочный файл из DLL и делаете вызовыэти функции, как если бы вы использовали любую функцию в любом заголовочном файле.Компилятор знает имена функций и объектов, которые вы используете, потому что они находятся в заголовочном файле DLL.Но он еще не знает, где они находятся в памяти.Вот где появляется LIB-файл ...

3) Перейдите в настройки компоновщика для своего проекта и добавьте «дополнительную библиотечную зависимость», указав файл LIB.Файл LIB сообщает компоновщику, где функции и объекты, которые вы используете из файла H, находятся в памяти (в относительных, а не абсолютных терминах, очевидно).

4) Скомпилируйте ваше приложение.Если вы все настроили правильно, он должен скомпилировать, связать и запустить.Когда вы получаете «неразрешенную внешнюю ссылку» ошибки компоновщика обычно это происходит из-за неправильной настройки.Возможно, вы либо не указали правильный путь к файлу LIB, либо вам нужно добавить больше файлов LIB.

9 голосов
/ 14 января 2010

После сборки .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 в настройках проекта и экспортируются функции, в клиентском приложении не нужно ничего определять.

3 голосов
/ 14 января 2010

Импорт библиотек (.lib) упрощает использование DLL в коде пользователя, см., Например, здесь для базового урока.
Они освобождают пользователей от загрузки DLL, используя GetProcAddress() и сами указатели функций - вместо этого они статически связываются с библиотекой импорта, которая выполняет работу за них.

1 голос
/ 14 января 2010

Почему бы вам не заставить VS сгенерировать статическую библиотеку shim вокруг вашей DLL. Таким образом, все, что вам нужно сделать, это добавить соглашение о вызовах в заголовочный файл и добавить пару директив препроцессора. Самый простой способ выяснить, как это сделать, - создать новый проект DLL (Visual C ++> Win32 Project, выбрать проект DLL, проверить символы импорта)

alt text

Используйте основной файл заголовка в качестве примера того, как украсить ваши классы с соглашением о вызовах импорта / экспорта. Этот заголовок является важным битом, поскольку объясняет, как использовать объявленные там функции и классы:

// 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, и вы можете использовать все функции, как если бы они были статически связаны.

0 голосов
/ 14 января 2010

Конечно, вам не нужен 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 немного сложны, если вы не используете их регулярно. (если вы используете их регулярно, то ваше программное обеспечение может быть несколько неортодоксальным).

Расширения и компилятор Microsoft dllimport создают статическую библиотеку, которая выполняет загрузку для вас и предоставляет батуты или блоки, как и другие. Если вы не создаете систему плагинов, которая не знает, какую DLL она будет загружать, используйте ее вместо этого.

0 голосов
/ 14 января 2010

Вы можете напрямую ссылаться на символы 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 вы бы #define MY_LIB_EXPORTS перед включением заголовка.

В dll_test.cpp вы должны включить заголовок и просто использовать Factorial(), как если бы вы использовали обычную функцию. При связывании вы хотите сделать ссылку на библиотеку импорта , созданную вашей DLL. Это делает символы из DLL доступными для кода, который ссылается на него.

0 голосов
/ 14 января 2010

Когда вы создаете свою DLL, вы также должны получить файл lib, с которым вы можете связать. Это сделает тяжелую работу в фоновом режиме для вас. Включите заголовочный файл, который вы создали, но вместо dllexport измените на dllimport. Для этого вы можете использовать определение, так что для вашего проекта dll он использует dllexport, а все остальные, которые не используют это определение, будут использовать dllimport.

Вам нужно только вручную выполнить LoadLibrary и typedef, если вы хотите динамически загрузить dll самостоятельно. Если вы сделаете вышеупомянутый подход, ваш exe потерпит неудачу, если dll отсутствует.

Вы также можете встроить проект dll в статическую библиотеку и загрузить ее вместо этого, что также избавит от этой проблемы, но увеличит размер вашего исполняемого файла. Это, конечно, не вариант, если вы действительно хотите, чтобы это была dll.

...