Как использовать функции из dll

Обновлено: 04.07.2024

Вызов неуправляемого кода — это служба, позволяющая управляемому коду вызывать неуправляемые функции, реализованные в библиотеках динамической компоновки (DLL), например функции библиотек API Windows. Он обнаруживает и вызывает экспортированную функцию и при необходимости маршалирует ее аргументы (целые числа, строки, массивы, структуры и так далее) через границы взаимодействия.

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

Использование экспортированных функций DLL

Как минимум, необходимо указать имя функции и имя библиотеки DLL, содержащей ее.

Можно использовать существующий класс, создать отдельный класс для каждой неуправляемой функции или создать один класс, содержащий набор связанных неуправляемых функций.

[Visual Basic] Используйте оператор Declare с ключевыми словами Function и Lib. В редких случаях можно использовать атрибут DllImportAttribute с ключевыми словами Shared Function. Такие случаи рассматриваются далее в этом разделе.

[C++] Используйте атрибут DllImportAttribute для идентификации библиотеки DLL и функции. Пометьте метод или функцию оболочки модификатором extern "C" .

Вызовите метод управляемого класса так же, как и любой другой управляемый метод. Передача структур и реализация функций обратного вызова представляют собой особые случаи.

Подробный обзор вызова неуправляемого кода

Вызов неуправляемого кода использует метаданные для нахождения экспортированных функций и маршалинга их аргументов во время выполнения. Данный процесс показан на следующем рисунке.

Когда неуправляемый код вызывает неуправляемую функцию, то он выполняет следующую последовательность действий:

Определяет библиотеку DLL, содержащую функцию.

Загружает библиотеку DLL в память.

Находит адрес функции в памяти и помещает ее аргументы в стек, выполнив по необходимости маршалинг данных.

Поиск и загрузка библиотеки DLL, а также определение адреса функции в памяти выполняются только при первом вызове функции.

Передает управление неуправляемой функции.

Вызов неуправляемого кода вызывает исключения, создаваемые неуправляемой функцией для управляемого вызывающего объекта.


Вызов функции из dll. Dll не определена в коде и подключается программно
Я написал простую функцию которая возвращает негатив bitmap'а Function Negative(ByVal Bmp As.

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

Вызов программой, написанной на С++, функции из dll, написанной на Assembler (явное подключение dll)
Доброго времени суток!Задали лабораторную работу: программа, написанная на с++ должна подключать.

Пробовал и динамически и статически , не получается. DLL можно подключать только динамически (на то она и динамическая библиотека).
Вариантов подключения dll есть два: явно (LoadLibrary + GetProcAddress) и неявно (нужен header и .lib файл). При явном подключении функция идентифицируется по имени, поэтому надо знать правильный идентификатор функции. При неявном подключении функция, описанная в header файле, которая вызываются в пользовательском коде, линкуется при компиляции с помощью .lib файла и для дальнейшего использования полученного модуля .lib файл не нужен.
так вот вопрос возможно ли использование dll-ки в такой конфигурации если у кого есть желание помоч выложу и проект и DLL и опишу что пробовал Выложите как минимум код, в котором вы пытаетесь подключить и использовать библиотеку. Проверьте DLL файл утилитой dumpbin.exe, экспортируются ли какие-либо функции, какие идентификаторы (имена) у этих функций. Если не получается проверить DLL файл, выкладывайте его.

Извините что долго не отвечал (на работе был)
Сейчас подготовлю все и выложу

Добавлено через 19 минут
Один из вариантов который пробывал
Это основной файл

Вообще задумка такая сделать типа обвертки чтобы можно было комфортно работать в MSVS 2010 - 2015 C++

Так же использую "DLL Export Viewer v1.65"
для получения списка функций (привожу ниже) Загрузить библиотеку не удалось, так как есть зависимость от ещё трёх DLL.
Учитывая, предоставленную информацию, у вас проблемы именно с вызовом функции? Эти 3 DLL- лежат рядом с EXE-ником , и как таковая сама DLL-ка подключается , а вот работать с функциями не получается

__stdcall пробовали добавлять в объявлении указателей функций? Так как по умолчанию в студии функции объявляются с соглашением вызова __cdecl .

Добавлено через 5 минут
И (void (__stdcall *)(hcHDB))GetProcAddress(hDll, "hcSqlAllocConnect"); соответственно

ОК, попробую только уже утром , а то у нас уже ночь

Добавлено через 8 часов 53 минуты

(void (__stdcall *)(hcHDB))GetProcAddress(hDll, "hcSqlAllocConnect");

Попробовал выхлоп следующий :

------ Построение начато: проект: dll2hytech, Конфигурация: Debug Win32 ------
dll2hytech.cpp
c:\users\admin\desktop\с++dll\dll2hytech\dll2hytech\dll2hyte ch.cpp(27): error C2440: =: невозможно преобразовать "void (__cdecl *)(hcHDB)" в "void (__stdcall *)(hcHDB)"
Для преобразования требуется reinterpret_cast, приведение в стиле С или приведение в стиле функции
========== Построение: успешно: 0, с ошибками: 1, без изменений: 0, пропущено: 0 ==========

Добавлено через 4 минуты
замечу, что так:

делать нельзя, это разыменовывание неинициализированного указателя Попробовал
Выхлоп
c:\users\admin\desktop\с++dll\dll2hytech\dll2hytech\dll2hyte ch.cpp(55): error C2664: void (hcHDB): невозможно преобразовать параметр 1 из "hcHDB *" в "hcHDB"

В описании к этой DLL написано

Параметры
Тип Аргумент Значение
hcHDB * (выходной) pdb Адрес памяти, куда будет записан идентификатор созданного соединения.

в прикрепленном файле

Т.е. требуется переменная типа указатель, я правильно понял ?

Добавлено через 25 минут
Вот немножко переделал

vova-f, держите, собрал lib-файл для вашей dll
шаг первый: генерируем hscli.lib
hscli.cpp:

шаг второй: добавляем сгенерированную либу к основному проекту
идем в настройки linker-a и добавляем hscli.lib
кидаем оригинальные dll в папку с проектом

в аттаче подправленный hscli.h+hscli.lib
собирал в VS2015 CE

Большое спасибо, сейчас устанавливаю MS VS 2015 , а то я еже пробовал этот вариант но в MSVS2010

Добавлено через 12 минут
Не могу открыть проект выдает ошибки

Добавлено через 27 минут
Извиняюсь это у меня MS VS 2015 не до конца установилась , сейчас доустановится и продолжу

image

Недавно меня заинтересовала тема использования DLL из Python. Кроме того было интересно разобраться в их структуре, на тот случай, если придется менять исходники библиотек. После изучения различных ресурсов и примеров на эту тему, стало понятно, что применение динамических библиотек может сильно расширить возможности Python. Собственные цели были достигнуты, а чтобы опыт не был забыт, я решил подвести итог в виде статьи — структурировать свой знания и полезные источники, а заодно ещё лучше разобраться в данной теме.

Под катом вас ожидает статья с различными примерами, исходниками и пояснениями к ним.

Содержание

  • Структура DLL
  • DLL & Python
    • Подключение DLL
    • Типы данных в С и Python
    • Аргументы функция и возвращаемые значения
    • Своя DLL и ее использование

    Надеюсь из содержания немного станет понятнее какую часть нужно открыть, чтобы найти ответы на свои вопросы.

    Структура DLL

    DLL — Dynamic Link Library — динамическая подключаемая библиотека в операционной системе (ОС) Windows. Динамические библиотеки позволяют сделать архитектуру более модульной, уменьшить количество используемых ресурсов и упрощают модификацию системы. Основное отличие от .EXE файлов — функции, содержащиеся в DLL можно использовать по одной.

    Учитывая, что статья не о самих библиотеках, лучше просто оставить здесь ссылку на довольно информативную статью от Microsoft: Что такое DLL?.

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

    DLL содержит набор различных функций, которые потом можно использовать по-отдельности. Но также есть возможность дополнительно указать функцию точки входа в библиотеку. Такая функция обычно имеет имя DllMain и вызывается, когда процессы или потоки прикрепляются к DLL или отделяются от неё. Это можно использовать для инициализации различных структур данных или их уничтожения.

    Рисунок 1 — Пустой template, предлагаемый Code Blocks для проекта DLL.

    На рисунке 1 приведен шаблон, который предлагает Code Blocks, при выборе проекта типа DLL. В представленном шаблоне есть две функции:

    Для начала стоит подробнее рассмотреть функцию DllMain . Через нее ОС может уведомлять библиотеку о нескольких событиях (fdwReason):

    DLL_PROCESS_ATTACH – подключение DLL. Процесс проецирования DLL на адресное пространство процесса. С этим значением DllMain вызывается всякий раз, когда какой-то процесс загружает библиотеку с явной или неявной компоновкой.

    DLL_PROCESS_DETACH – отключение DLL от адресного пространства процесса. С этим значением DllMain вызывается при отключении библиотеки.

    DLL_THREAD_ATTACH – создание процессом, подключившим DLL, нового потока. Зачем DLL знать о каких-то там потоках? А вот зачем, далеко не каждая динамическая библиотека может работать в многопоточной среде.

    DLL_THREAD_DETACH – завершение потока, созданного процессом, подключившим DLL. Если динамическая библиотека создает для каждого потока свои "персональные" ресурсы (локальные переменные и буфера), то это уведомление позволяет их своевременно освобождать.

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

    У DllMain не так много аргументов, самый важный fdwReason уже рассмотрен выше, теперь о двух других:

    • Аргумент lpvReserved указывает на способ подключения DLL:
      • 0 — библиотека загружена с явной компоновкой.
      • 1 — библиотека загружена с неявной компоновкой.

      О явной и неявной компоновке можно прочесть подробно в статье: Связывание исполняемого файла с библиотекой DLL.

      В предложенном на рисунке 1 шаблоне есть функция SomeFunction , которая может быть экспортирована из динамической библиотеки. Для того, чтобы это показать, при объявлении функции указывается __declspec(dllexport) . Например, так:

      Функции, не объявленные таким образом, нельзя будет вызывать снаружи.

      DLL & Python

      Первым делом, расскажу, как подключать уже собранные DLL, затем, как вызывать из них функции и передавать аргументы, а уже после этого, постепенно доделаю шаблон из Code Blocks и приведу примеры работы с собственной DLL.

      Подключение DLL

      Основной библиотекой в Python для работы с типами данных, совместимыми с типами языка С является ctypes . В документации на ctypes представлено много примеров, которым стоит уделить внимание.

      Чтобы начать работать с DLL, необходимо подключить библиотеку к программе на Python. Сделать это можно тремя способами:

      • cdll — загружает динамическую библиотеку и возвращает объект, а для использования функций DLL нужно будет просто обращаться к атрибутам этого объекта. Использует соглашение вызовов cdecl.
      • windll — использует соглашение вызовов stdcall. В остальном идентична cdll.
      • oledll — использует соглашение вызовов stdcall и предполагается, что функции возвращают код ошибки Windows HRESULT. Код ошибки используется для автоматического вызова исключения WindowsError.

      Для первого примера будем использовать стандартную Windows DLL библиотеку, которая содержит всем известную функцию языка С — printf() . Библиотека msvcrt.dll находится в папке C:\WINDOWS\System32 .

      Код Python:

      Результат:

      Можно использовать подключение библиотеки с помощью метода windll либо oledll , для данного кода разницы не будет, вывод не изменится.

      Если речь не идет о стандартной библиотеке, то конечно следует использовать вызов с указанием пути на dll. В ctypes для загрузки библиотек предусмотрен метод LoadLibrary . Но есть еще более эффективный конструктор CDLL , он заменяет конструкцию cdll.LoadLibrary . В общем, ниже показано два примера вызова одной и той же библиотеки msvcrt.dll.

      Код Python:

      Иногда случается, что необходимо получить доступ к функции или атрибуту DLL, имя которого Python не "примет"… ну бывает. На этот случай имеется функции getattr(lib, attr_name) . Данная функция принимает два аргумента: объект библиотеки и имя атрибута, а возвращает объект атрибута.

      Код Python:

      Результат:

      Теперь становится понятно, как подключить библиотеку и использовать функции. Однако, не всегда в DLL нужно передавать простые строки или цифры. Бывают случаи, когда требуется передавать указатели на строки, переменные или структуры. Кроме того, функции могут и возвращать структуры, указатели и много другое.

      Типы данных в С и Python

      Модуль ctypes предоставляет возможность использовать типы данных совместимые с типами в языке С. Ниже приведена таблица соответствия типов данных.

      Сtypes type C type Python type
      c_bool _Bool bool (1)
      c_char char 1-character string
      c_wchar wchar_t 1-character unicode string
      c_byte char int/long
      c_ubyte unsigned char int/long
      c_short short int/long
      c_ushort unsigned short int/long
      c_int int int/long
      c_uint unsigned int int/long
      c_long long int/long
      c_ulong unsigned long int/long
      c_longlong __int64 or long long int/long
      c_ulonglong unsigned __int64 or unsigned long long int/long
      c_float float float
      c_double double float
      c_longdouble long double float
      c_char_p char * (NUL terminated) string or None
      c_wchar_p wchar_t * (NUL terminated) unicode or None
      c_void_p void * int/long or None

      Таблица 1 — Соответствие типов данных языка Python и языка C, которое предоставляет модуль ctypes .

      Первое, что стоит попробовать — это использовать указатели, куда без них? Давайте напишем программу, где создадим строку и указатель на неё, а потом вызовем printf() для них:

      Результат:

      Если вы создали указатель, то разыменовать (получить доступ к значению, на которое он указывает) можно с использованием атрибута value , пример выше.

      Аргументы функций и возвращаемые значения

      По умолчанию предполагается, что любая экспортируемая функция из динамической библиотеки возвращает тип int . Другие возвращаемые типы можно указать при помощи атрибута restype . При этом, чтобы указать типы аргументов функции можно воспользоваться атрибутом argtypes .

      Например, стандартная функция strcat принимает два указателя на строки и возвращает один указатель на новую строку. Давайте попробуем ей воспользоваться.

      Код Python:

      Результат:

      На этом закончим с примерами использования готовых DLL. Давайте попробуем применить знания о структуре DLL и модуле ctypes для того, чтобы собрать и начать использовать собственную библиотеку.

      Своя DLL и ее использование

      Пример 1

      Шаблон DLL уже был рассмотрен выше, а сейчас, когда дело дошло до написания своей DLL и работы с ней, выскочили первые и очевидные грабли — несовместимость разрядности DLL и Python. У меня на ПК установлен Python x64, оказалось, что как бы не были DLL универсальны, разрядность DLL должна соответствовать разрядности Python. То есть, либо ставить компилятор x64 и Python x64, либо и то и то x32. Хорошо, что это не сложно сделать.

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

      Код DLL на С:

      Код Python:

      Функция SomeFunction получает указатель на строку и выводит её в окно. На рисунке ниже показана работа программы.




      Рисунок 2 — Демонстрация работы шаблона библиотеки из Code Blocks.

      Все действия происходящие в кейсе DLL_PROCESS_ATTACH , код которого приведен ниже, вызываются лишь одной строкой в Python коде:



      Рисунок 3 — Действия происходящие при подключении DLL.

      Пример 2

      Чтобы подвести итог по использованию DLL библиотек из Python, приведу пример, в котором есть начальная инициализация параметров и передача новых через указатели на строки и структуры данных. Этот код дает понять, как написать аналог структуры С в Python. Ниже привожу код main.c , man.h и main.py .

      Код DLL на С:

      В коде main.h определена структура Passport с тремя полями: два указателя и целочисленная переменная. Кроме того, четыре функции объявлены, как экспортируемые.

      Код DLL на С:

      Внутри кейса DLL_PROCESS_ATTACH происходит выделение памяти под строки и начальная инициализация полей структуры. Выше DllMain определены функции:

      GetPassport — вывод полей структуры passport в консоль.

      *SetName(char new_name)** — установка поля name структуры passport .

      *SetSurname(char new_surname)** — установка поля surname структуры passport .

      *SetPassport(Passport new_passport)** — установка всех полей структуры passport . Принимает в качестве аргумента указатель на структуру с новыми полями.

      Теперь можно подключить библиотеку в Python.

      Код на Python

      В коде выше многое уже знакомо, кроме создания структуры аналогичной той, которая объявлена в DLL и передачи указателя на эту структуру из Python в DLL.

      О чем же собственно речь?

      Допустим, вы хотите самостоятельно вызвать некоторую функцию Windows API. Для этого вам надо будет знать, в какой библиотеке она размещена. Вы можете узнать это, найдя данную функцию в Platform SDK и посмотрев в разделе Requirements значение пункта Library. Когда имя библиотеки найдено, можно считать, что пол дела уже сделано. Далее сделаем следующее.

      То же самое, но с использование атрибутов.

      Маршалинг вызова функций

      Немного теории



      Маршалинг вызова функций

      Давайте рассмотрим процесс вызова функции из DLL по порядку.

      Выбор набора символов

      Хотите, я вас удивлю? Для начала вспомним пример в начале статьи. Мы с вами импортировали функцию MessageBox из библиотеки User32.dll. Вспомнили? Что, ничего интересного не замечаете? А функции MessageBox в библиотеке User32.dll нет. Не верите? Тогда убедитесь в этом сами, выполнив команду:

      Вот что получилось у меня:

      Тоже самое, но с использование атрибутов.

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

      Для увеличения скорости маршалинга нужно правильно использовать параметры Unicode и ANSI. Если типы передаваемых строк совпадут, то маршалеру не понадобиться преобразовывать данные из Unocode в ANSI или обратно, тем самым вы увеличите скорость маршалинга.

      ПРИМЕЧАНИЕ
      Я бы вам советовал использовать везде, где только можно, набор символов Unicode, так как внутри вся система (здесь имеется ввиду Windows 2000) построена на Unicode, а ANSI-функции являются всего лишь заглушками для преобразования ANSI в Unicode.

      Определение собственных имен

      Разработчики стандартного маршалера явно думали о нас, и поэтому нам предоставлена такая нужная и полезная возможность как переименовывание функций. То есть вы можете импортировать функцию, присвоив ей другое имя. Эта возможность реализуется при помощи атрибута EntryPoint. Приведем пример:

      Тоже самое, но с использование атрибутов.

      Атрибут EntryPoint ведет себя немного хитрее, чем кажется, его поведение меняется в зависимости от формата строки, назначенной ему:

      Таким образом, вы можете импортировать функцию из динамической библиотеки и объявить ей любое имя. Данная возможность с первого взгляда может показаться не особо нужной. Но это на самом деле не так. Представьте себе, что вам нужно импортировать функцию в определенное пространство имен, а она конфликтует с уже существующим в нём именем. Как раз такие проблемы и призвана решить эта возможность.

      Конфликт зависимости имени от "кодировки"

      Большинство создателей динамически загружаемых библиотек не придерживается стандартов именования функций в соответствии с поддерживаемым набором символов (Unicode или ANSI), то есть они попросту не ставят постфиксов W и A. Если, к примеру, создатель библиотеки решил, что она будет работать с ANSI, то будьте уверены: ничто не заставит его сделать заглушки для Unicode. Тут-то и может возникнуть проблема, когда нужно вызвать функцию для соответствующего набора символов, которая не имеет нужного постфикса. Нам на помощь придет атрибут ExactSpelling, который запретит (разрешит) маршалеру изменять определенное нами имя функции, при помощи атрибута EntryPoint. А делается это вот так:

      По умолчанию значение атрибута ExactSpelling равно False. При значении False маршалер подставляет в имени постфикс W или A, в зависимости от значения параметра CharSet, а при значении True ему не позволено изменять имя функции.

      Обязательно обратите внимание на заданный для функции набор символов, он должна совпадать с реальным. То есть в данном случае вам самим предстоит выбирать между Unicode и ANSI. Если вы неправильно зададите кодировку, то ваш код попросту будет работать неправильно. Для того чтобы это понять, поробуйте задать EntryPoint="MessageBoxW", а CharSet=CharSet.Ansi. Или наоборот. Результат будет довольно интересный. У меня получилось вот что:



      Форматы вызова функций

      Существует несколько соглашений о вызове функции (call convention), я надеюсь что вы все представляете себе, что это такое. Если нет, то я поясню: грубо говоря, это набор правил, по которым передаются параметры и возвращаемое значение. Вы можете самостоятельно задать соглашение о вызове импортируемой функции при помощи атрибута CallingConvention. Этот атрибут иногда бывает просто жизненно необходим. Например, если вам потребовалось вызвать фукцию, подобную printf (она имеет переменное число аргументов и использует формат вызова cdecl). Как это делается, я продемонстрировал ниже.

      Анализ возвращаемого значения и обработка ошибок

      По умолчанию после вызова функции анализируется возвращаемое значение и если делается вывод о том, что функция потерпела неудачу, то среда исполнения генерирует исключение. Чтобы этого избежать нужно использовать атрибут PreserveSig, который заставляет среду исполнения игнорировать возвращаемое значение.

      Этот атрибут не так бесполезен, как кажется. Потому что значение функции может быть совершенно правильным, а среда исполнения, посчитав его "инвалидным", будет генерировать исключения при каждом вызове этой функции. Так что, по-вашему, будет легче: отключить генерацию исключений или при каждом вызове их отлавливать и обрабатывать?

      В дополнении ко всему сказанному, существует еще один атрибут: SetLastError. Он позволяет получить информацию об ошибке после вызова функции. Если его значение равно False, то вы не сможете получить значение кода ошибки при помощи функции GetLastError, потому что стандартный маршалер "любезно" заберет это значение себе. Для того чтобы обрабатывать ошибки самостоятельно, установите значение этого атрибута равным True.

      Значение по умолчанию для этого параметра для разных языков приведены в таблице ниже.

      Если вы пишете на MISL, то вам нужно будет использовать ключевое слово lasterr. То есть написать следующее:

      Более сложные методы взаимодействия

      Передача структур

      Ну а теперь, как обычно смотрите примерчик:

      Функции "обратного вызова"



      Обратный вызов функций

      Давайте рассмотрим, что нам придется для этого сделать.

      • Для начала надо описать нашу функцию, которая будет вызываться из DLL. Здесь главное правильно соблюсти типы аргументов и возвращаемого значения.
      • Необходимо описать делегат на нашу функцию.
      • Описать функцию, которую мы собираемся вызывать из DLL. В качестве типа одного из аргументов нам понадобиться описанный ранее делегат. Этот аргумент как раз и принимает указатель на нашу функцию.
      • Далее просто надо вызвать необходимую нам функцию из DLL и передать ей указатель на нашу функцию способом, специфичным для каждого языка.

      Что бы вам было более понятно, я приведу пример.

      Читайте также: