2013-08-03 5 views
3

Я чувствую, что есть намного лучший способ, чем просто иметь сотни подписей в typedefs, а затем загружать указатели с помощью GetProcAddress. Насколько мне известно, это самый простой - но грязный - когда дело доходит до загрузки DLL-функций.Лучший способ загрузить DLL-функции?

Есть ли менее грязный способ загрузки DLL-функций? В частности, работает большое количество библиотек Winapi и Tool Help? Я знаю, что вы можете просто «включить .lib», но я чувствую, что это вызовет ненужное раздувание; и у меня нет доступа к исходному коду (хотя Джейсон С упоминал, что можно перейти с .dll в .lib).

Я искал код для этого. Я чувствую, что главный блокпост имеет дело с функциями, которые имеют разные подписи; или это именно то, почему каждый использует typedefs вместо «какой-то причудливый цикл» для загрузки своих DLL-функций?

+2

Единственная причина использования 'GetProcAddress' заключается в том, что вам нужно принять решение о загрузке библиотеки (например, если у вас есть несколько вариантов, плагинов или если библиотека не существует). В противном случае вы должны использовать '.lib' и позволить системе следить за загрузкой и связыванием библиотеки. – nneonneo

+1

@Saustin, что заставляет вас сказать, что библиотека импорта вызовет ненужное раздувание? Все это - заглушка, чтобы помочь компоновщику исправить символы экспорта в DLL. И компоновщик не будет тянуть ненужные символы, если ни один из объектных файлов не ссылается на них. – greatwolf

+2

Итак, вы загружаете функции Windows API? Почему бы просто не связать их с .lib, как это делает каждый другой человек? Подписи функций уже находятся в 'Windows.h'! – nneonneo

ответ

2

«Bloat», связанный с .lib-файлом («раздуванием», я предполагаю, что вы имеете в виду несколько лишних килобайт на исполняемый файл, который, я знаю ...) не «лишним», если вы использовать его для удобства избежать использования сотен вызовов GetProcAddress. :)

Не совсем уверен, что вы подразумеваете под «некоторой причудливой петлей»; используемые в этом контексте типы typedefs служат аналогичной цели для деклараций в заголовке - они предоставляют компилятору и информацию о человеческом читателе о сигнатуре вызываемой функции. Так или иначе, вы должны это предоставить.

Есть инструменты для создания .lib из .dll; но вы все равно должны иметь заголовок, объявляющий функции, которые вы вызываете, чтобы компилятор знал, что вы делаете. По сути, .lib, сгенерированный для .dll, является просто «заглушкой», которая загружает DLL и получает адреса функций для вас. Различные API, но по сути то же самое.

Единственный способ создать свой DLL-интерфейс по-разному, чтобы у вас не было столько функций, с которыми можно было бы справиться. Однако в большинстве случаев я бы не сказал, что избегать typedefs/declarations для функций является достаточной мотивацией, чтобы сделать что-то вроде этого. Например. вы могли бы выставить ровно одну функцию, как (например):

void PerformAction (LPCSTR action, DWORD parameter); 

И есть ваше внедрение PerformAction сделать что-то другое в зависимости от «действия». Это, безусловно, разумно в определенных ситуациях, которые не связаны с вашим постом, но это не совсем подходящий «обходной путь» для «проблемы», которую вы описываете.

Вы в значительной степени просто должны иметь дело с этим. Либо создайте заголовок с объявлениями и сгенерируйте заглушку .lib, либо создайте заголовок с typedefs и используйте LoadLibrary/GetProcAddress. Первая избавит вас от ввода текста. Последний позволяет обрабатывать случаи, когда DLL отсутствует, или загружать библиотеки DLL, которые неизвестны во время разработки (например, плагины, как указано в другом ответе).

+0

Интересно, я знал .libs содержал код, но я понятия не имел, что есть инструменты для их получения из DLL - и я не знал, что они были дешевой заглушкой. Что касается вашего объяснения с помощью PerformAction, мне кажется, что это лучший способ загрузить специфику, а не сразу удалить все вызовы GetProcAddress. – Saustin

+1

С MSVC существует промежуточный этап создания файла .def, но вы можете сделать это с помощью dumpbin.exe, чтобы перечислить символы. Затем вы можете использовать lib.exe для создания библиотеки импорта из файла .def (вам вообще не нужна DLL, а только .def). В MinGW вы можете использовать dlltool вместо lib.exe. –

+0

Я понятия не имел, что есть инструмент MSVC, такой как lib.exe, я попробую это сразу. – Saustin

-1

Должна быть предусмотрена функция для загрузки dll вручную с учетом его имени файла. В API окон это LoadLibrary(). Обычно он не используется, если вы не разрабатываете приложение типа «плагин» или вам необходимо вручную управлять загрузкой и разгрузкой.

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

+1

LoadLibrary загружает DLL в память; GetProcAddress по-прежнему требуется, чтобы найти адрес рассматриваемой функции. –

1

Visual C++ имеет возможность delay load a DLL. Ваш код, который вы связываете в lib-файле DLL, включает его заголовки и вызывает функции как обычно. Компилятор Visual C++ и среда выполнения заботятся о вызове LoadLibrary() и GetProcAddress() при первом вызове функции.

Однако это приведет к ошибке runtime exception, если DLL не может быть загружена по какой-либо причине, вместо того, чтобы приложение не загружалось, как с обычной DLL.

Если эта DLL всегда загружается и требуется для запуска вашего приложения, то вы должны использовать DLL как обычно (используя файл .lib). В этом случае вы ничего не получаете, задерживая загрузку DLL в этом случае.

+0

Интересно, так ли это означает, что единственный способ иметь загрузку DLL внутри пути? Мне кажется, что это делается по вызову WinMain/main - значит, не будет никакого способа сказать «Эй, загрузите ЭТУ DLL из этого файла - не тот!» То есть, если вы не закодировали какой-нибудь исполняемый файл обертки, который разместил для вас библиотеки DLL. – Saustin

+2

@Saustin, если DLL не находится в пути поиска DLL, вы все равно можете обработать его, вызвав LoadLibary() самостоятельно в уведомлении dliStartProcessing и вернув HMODULE. http://msdn.microsoft.com/en-us/library/z9h1h6ty.aspx – shf301

+0

Это похоже на правильный путь, но я чувствую себя довольно смущенным - я использую крюк уведомлений для обновления таблицы адресов процессов или это сделано для меня при загрузке? Мне, возможно, придется немного поработать с этим. – Saustin

3

Ваши функции должны быть объявлены как-то, поэтому вам нужны сигнатуры функций. Но если вы чувствуете, что дублируете сигнатуры функций, вы можете использовать decltype(&func_name), чтобы устранить это дублирование (с учетом функции func_name с той же целевой сигнатурой). Вы можете обернуть это в макрос, чтобы сделать его более приемлемым.

Еще один распространенный подход заключается в том, чтобы перетащить все указатели на одну большую структуру (или кучу структурированных задач) и загрузить вместо этого указатель на структуру (например, путем загрузки функции, возвращающей указатель на структуру). Это выглядит следующим образом:

Общий заголовок

struct func_table { 
    void (*f1)(int); 
    int (*f2)(void); 
}; 

DLL

static struct func_table table = { 
    &f1_Implementation, 
    &f2_Implementation, 
}; 

DLLEXPORT struct func_table *getFuncTable(void) { 
    return &table; 
} 

программы с использованием DLL

static struct func_table *funcs; 

void loadFuncTable() { 
    struct func_table (*getFuncTable)(void) = GetProcAddress(..., "getFuncTable"); 
    funcs = getFuncTable(); 
} 

/* call funcs->f1(n) and funcs->f2() */ 
+0

Либо я явно пропускаю что-то, либо использование таблицы функций потребует от меня доступа к исходному коду DLL! Невозможно реализовать это для меня совместимым способом. – Saustin

+1

Вы не дали понять, что у вас нет исходного кода. – nneonneo

+0

Я должен был сделать это ясно, я обновил свой вопрос. Сожалею. – Saustin

2

Существует несколько различных способов загрузки библиотек Windows, но каждый из них служит другой цели.

Использование LoadLibrary() и GetProcAddress() предназначено для того, чтобы позволить решениям времени выполнения использовать несколько различных ситуаций динамической компоновки. Эти вызовы - это просто системные вызовы, которые не затрагивают какой-либо аспект результирующего файла PE, а адреса, возвращаемые GetProcAddress(), не являются особыми в отношении генерации кода. Это означает, что вызывающий объект отвечает за правильное использование этих символов на самом синтаксическом уровне (например, используя правильное соглашение о вызове и правильное количество, размер и выравнивание аргументов, если они есть, для вызова функции).

Ссылка на файл .lib, связанный с кодом .dll, отличается. Клиентский код относится к содержимому среды выполнения с использованием внешних идентификаторов, как если бы они были статически связанными символами. Во время процесса сборки компоновщик будет разрешать эти идентификаторы с символами, найденными в файле .lib. Хотя в принципе эти символы могут указывать на что-либо вообще (как и любой другой символ), автоматически сгенерированный файл .lib просто предоставит символы, которые действуют как крошечные прокси на содержимое памяти, которое будет заполнено во время загрузки загрузчиком PE.

Способ, которым эти прокси реализованы, зависит от ожидаемого типа доступа к памяти. Например, символ, который ссылается на функцию в .dll, будет реализован как одна косвенная инструкция jmp, таким образом, чтобы исходная команда call из кода клиента попала в команду jmp из содержимого .lib и будет перенаправлена ​​на адрес, разрешенный загрузчиком PE во время загрузки. По этому адресу находится код динамически загруженной функции.

Много больше деталей в сторону, все это используется, чтобы инструктировать PE загрузчика делать ту же работу, что и клиентский код будет делать сам по себе: вызов LoadLibrary() для отображения PE в память и прорыть экспорт таблиц с GetProcAddress(), чтобы найти и рассчитать правильный адрес для каждого символа. Загрузчик PE выполняет это автоматически во время загрузки из информации, найденной в таблице импорта исполняемого файла клиента.

Других слов, время загрузки динамическое связывание происходит медленнее и толще, чем время выполнения динамического связывания с помощью крошечной суммы, если таковые имеется, но она предназначена для обеспечения более простого интерфейса программирования для обычного разработчика , особенно потому, что многие библиотеки всегда необходимы и всегда доступны. Попытка предоставить какую-либо дополнительную логику при ручной загрузке не пригодится в этих обстоятельствах.

Подводя итоги, не беспокойтесь, чтобы использовать LoadLibrary() и GetProcAddress() только потому, что они, похоже, обеспечивают большую производительность или надежность. Ссылка на файлы .lib по возможности.

Идет далее по этой теме, возможно даже создать изображения PE, которые не содержат любых таблиц импорта () и могут получать доступ к системным вызовам и другим экспортированным программам. Этот подход используется вредоносным ПО, чтобы скрыть подозрительное использование API (например, вызовы Winsock) путем удаления любой информации о времени загрузки.

0

Создание класса как оболочки dll, где функция открытого члена класса - это не что иное, как указатель на функцию из dll, полученную с помощью get proc address, - это один прекрасный способ загрузить функции из dll.

Было бы лучше использовать возможности RAII C++ для бесплатных библиотек, загружаемых путем освобождения библиотек в деструкторах класса.

Это:

typedef int(WINAPI *ShellAboutProc)(HWND, LPCSTR, LPCSTR, HICON); 

int main() { 
    HMODULE hModule = LoadLibrary(TEXT("Shell32.dll")); 

    ShellAboutProc shellAbout = 
     (ShellAboutProc)GetProcAddress(hModule, "ShellAboutA"); 

    shellAbout(NULL, "hello", "world", NULL); 

    FreeLibrary(hModule); 
} 

Может быть достигнуто, как это:

class ShellApi { 
    DllHelper _dll{"Shell32.dll"}; 

public: 
    decltype(ShellAboutA) *shellAbout = _dll["ShellAboutA"]; 
}; 

int main() { 
    ShellApi shellApi; 
    shellApi.shellAbout(NULL, "hello", "world", NULL); 
} 

Если класс DllHelper является:

class ProcPtr { 
public: 
    explicit ProcPtr(FARPROC ptr) : _ptr(ptr) {} 

    template <typename T, typename = std::enable_if_t<std::is_function_v<T>>> 
    operator T *() const { 
    return reinterpret_cast<T *>(_ptr); 
    } 

private: 
    FARPROC _ptr; 
}; 

class DllHelper { 
public: 
    explicit DllHelper(LPCTSTR filename) : _module(LoadLibrary(filename)) {} 

    ~DllHelper() { FreeLibrary(_module); } 

    ProcPtr operator[](LPCSTR proc_name) const { 
    return ProcPtr(GetProcAddress(_module, proc_name)); 
    } 

    static HMODULE _parent_module; 

private: 
    HMODULE _module; 
}; 

Все кредиты Benoit

Я могу» Найти более элегантный способ, чем это.

Смежные вопросы