Пишем dll для php

Обновлено: 05.07.2024

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

При статической загрузке DLL автоматически загружается при запуске исользующего ее приложения. Такая DLL содержит экспортируемые функции, описание которых находится в файле библиотеки импорта(import library file - .lib). Для использования статической загрузки вы должны на этапе компоновки к программе додключить .lib файл вашей DLL. В C++ Builder это сводится к включения в проект .lib файла через менджер проектов.

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

Статическая загрузка

Создадим сперва проект (File / New / DLL). Будет создан проект, содержащий следующее:

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

Для экспорта и импорта из DLL необходимо использовать моди фикаторы __export и __import соответсвенно. Но в C++ Builder можно использовать новое ключевое слово __delspec() с параметрами dllexport и dllimport соответсвенно. Сами понимаете, что для того чтобы эспортировать функции из библиотеки еужен один заголовочный файл с описаниями _delspec(dllexport) для экспортируемых функций, для импорта функций в приложение вам необходимо будет поставить анологичный заголовочный файл но с _delspec(dllimport) описаниями, что достаточно неудобно. Эта проблема решается легко: добавте в заголовочный файл библиотеки следующее:

Пример DLL: файл P.cpp

Не забудьте об объявлениях в начале файла. Зайдите в менеджер проектов.Там откройте свой проект и добавте .lib файл из предыдушего проект с DLL( правый клик, пункт ADD). Запустите проект.

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

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

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

Давайте рассмотрим на примере, как производится динамическая загрузка. Создайте новый прокт DLL и внесите в него следующее:

Cкомпилируйте проект, в результате чего будет создана DLL.

Теперь создайте проект приложения анологичный проекту для использования статической загрузки (форма с кнопкой и обработчиком события кнопки OnClick) ниже приведен код приложения:(Unit11.cpp)

запустите это проект, при нажатии на кнопку должно выдаватся сообшение. Теперь разберемся, как это работает.

  • void (__stdcall *Message)(char *s);-объявление указателя на функцию.
  • HINSTANCE dllp = LoadLibrary("p.dll");- загрузка библиотеки в память.
  • Message= (void(__stdcall *) (char*)) GetProcAddress(dllp, "_Message"); присвоение указателю адреса функции DLL.
  • Message("Hi From Dinamic DLL"); рабочий вызов фунциий (собственно то для чего все это и делается).
  • FreeLibrary(dllp);- выгрузка библиотеки из памяти.

Обратите внимание на то, что призагрузке можно указать точное местоположние библиотеки (необезательно в том же каталоге где и приложение).

А давайте сегодня взглянем на PHP немного с другой точки зрения, и напишем к нему расширение. Так как на эту тему уже были публикации на Хабре (здесь и здесь), то не будем углубляться в причины того, для чего это может оказаться полезным и для чего может быть использовано на практике. Эта статья расскажет, как собирать простые расширения под Windows с использованием Visual C++ и под Debian с использованием GCC. Также я постараюсь немного осветить работу с PHP-массивами внутри расширений и провести сравнение производительности алгоритма, написанного на native PHP и использующего код, написанный на C.

Компиляция под Win32

Итак, начнем с Windows. Как известно, разработчики PHP используют Visual C++ 9 или Visual Studio 2008 для компиляции своего детища под Windows. Поэтому мы будем использовать Visual Studio 2008, бесплатная Express версия тоже подойдет, как впрочем, наверное, и более поздние и ранние версии студии.

  • Скомпилированные бинарники PHP 5.3, которые можно взять здесь,
  • Исходники PHP 5.3, которые можно скачать с сайта или вытянуть из общедоступного SVN,
  • Желание поэкспериментировать и немного терпения.

Теперь давайте напишем hello world, добавим в наш cpp файл следующее:


Теперь подключим этот модуль в PHP и попробуем запустить что-нибудь такое:
На что мы должны получить ответ «hello habr».

Компиляция под *nix

  • Иметь установленный PHP на машине,
  • Иметь установленный PHP-dev. Для этого нужно выполнить всего одну команду:

Первый нужен для магической компиляции расширения, а во втором будет его исходный код. В config.m4 напишем следующее:

Внутри test.c добавьте

И после этой сроки скопируйте содержимое cpp-файла из Windows-версии.
Теперь идем в консоль и:

На этом все. Теперь можно открыть php.ini, добавить там свое расширение:

И проверить его работоспособность командой

Обработка аргументов и возвращаемые значения

Для начала посмотрим, как можно принимать аргументы:

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

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

Полный список функций можно найти здесь

Если кого-то заинтересует, я могу в следующей статье рассмотреть пример работы с объектами (классический пример расширения на объектах — mysqli). Тут есть очень хорошая статья на эту тему.

Производительность

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

У меня получилась такая реализация, сильно не пинайте за код, я все-таки больше пишу на PHP, чем на C:


Этот код выдает следующий результат:

А теперь давайте сравним скорость выполнения этого кода и аналогичного на native PHP:


Сравнивать я буду время выполнения обоих решений с помощью функции microtime. Возьмем строку в 100 символов, строку в 5000 символов, и строку в 69000 символов (я взял книгу A Message from the Sea, написанную Чарльзом Диккенсом, надеюсь, что он мне это простит), и для каждого варианта прогоним оба решения по несколько тысяч раз. Результаты приведены в таблице ниже. Тестирование проводилось на моем не самом сильном домашнем ноутбуке и VDS с Debian на борту, и да, я отчетливо понимаю, что результаты могут зависеть от конфигурации, от версии операционной системы, PHP, атмосферного давления и направления ветра, но я хотел показать лишь примерные цифры.
Полный код тестового скрипта можно скачать здесь. Исходники и бинарники самих расширений можно скачать здесь (win) и здесь (nix).

Кол-во итераций PHP code / Win32 PHP code / Debian PHP extension / Win32 PHP extension / Debian Win32 выигрыш Debian выигрыш
1. Строка 100 символов 1000000 84.7566 сек 72.5617 сек 8.4750 сек 4.4175 сек в 10 раз в 16.43 раз
2. Строка 5000 символов 10000 39.1012 сек 31.7541 сек 0.5001 сек 0.134 сек в 78.19 раз в 236.98 раз
3. Строка 69000 символов 1000 52.3378 сек 44.0647 сек 0.4875 сек 0.0763 сек в 107.36 раз в 577.51 раз

Выводы

Если судить о производительности модуля по сравнению с интерпретируемым кодом, то мы видим, что ощутимые результаты можно получить на больших объемах данных и на малых количествах итераций. То есть, для часто использующихся, однако, не очень ресурсоемких алгоритмов не имеет смысла вынесение их в компилируемый код. Но для алгоритмов, работающих с большими объемами данных, это может иметь практический смысл. Также, опираясь на мои измерения, можно заметить, что результаты работы PHP-кода сравнимы на разных системах (напомню, что это были две разные машины), а вот результаты работы расширения очень сильно отличаются. Из этого лично я делаю вывод, что существуют какие-то особенности компиляции, которые мне не известны. Впрочем, я сильно сомневаюсь, что кто-то использует Windows-сервера для PHP-проектов. Хотя я также очень сомневаюсь, что кто-то прямо сейчас побежит переписывать что-то на С, эта статья все-таки больше just for fun, чем руководство к действию. Просто я хотел показать, что написать PHP extension очень просто, и иногда может быть очень полезно.


В предыдущей статье я рассказывал как скомпилировать свое расширение PHP под Linux. Теперь разберемся, ка сделать это же под Windows.

Исходные данные

Среда Visual Studio C++ 6.0 выбрана исходя из того что все остальные расширения находящиеся в папке ext исходных кодов php интерпретатора собраны с ее помощью.

Несколько первых строк из файла проекта расширения gd.

Необходимо создать новый проект Win32 dynamic dll.
Меню File->New


Рис.1
Выбираем тип проекта Win32 Dynamic-Link Library.


Рис.2

Сохранить проект в папку с остальными php расширениями. В каталог проекта скопировать файлы php_hello.c php_hello.h config.m4 config.w32. Результат на картинках далее.


Рис.3


Рис.4

Затем следует настроить проект.
Установить активную конфигурацию сборки. Set active configuration.


Рис.5
Активной конфигурацией должен быть Win32 – Release.


Рис.6

В свойствах проекта (Alt-F7) для конфигурации release .
Далее настройки компиляции. Category General.


Рис.7

Preprocessor definitions.
WIN32,_DEBUG,_WINDOWS,_MBCS,_USRDLL,PHP_HELLO_EXPORTS,ZEND_DEBUG=0,COMPILE_DL_HELLO,ZTS=1,ZEND_WIN32,PHP_WIN32
Должно получиться примерно так. Однако их могут быть и какие дополнительные еще препроцессорные определения.

Настройки компиляции. Code generation.


Рис. 8
Use run-time library. Выбрать Multithreaded DLL.

Также настройки компиляции. Category preprocessor.


Рис.9
Additional include directories. ..\. \..\main. \..\Zend. \..\TSRM. \..\bindlib_w32

Устанавливаем дополнительные каталоги для поиска *.h файлов относительно каталога с проектом.

Создать каталог C:\php_source\dev . Скопировать библиотеку php5ts.lib из откомпилированной сборки php интерпретатора (C:\php5\dev) той же версии в каталог C:\php_source\dev.


Рис.10
Добавить в Object/Library modules ссылку на библиотеку php5ts.lib
Добавить в Additional library path путь к дополнительному каталогу для поиска lib файлов.

Добавить файлы с исходными кодами к проекту предварительно скопировав их в папку проекта.


Рис.11

Выбрать тип файлов: *.*
Можно выбрать и добавить сразу все файлы нажимая Ctrl.
Нажимаем ОК.


Рис.12

сonfig.m4 необязателен. Он нужен для сборки расширения под Unix.


Рис.13

Скопировать полученную dll в рабочий каталог интерпретатора с расширениями php.


Рис.14

Исходники

test.php

php_hello.c

php_hello.h

config.w32

Спасибо!

Если вам помогла статья, или вы хотите поддержать мои исследования и блог - вот лучший способ сделать это:

После работы над очень старым проектом, основанным на PHP 5.3, где для его работы требовалась библиотека APC, я понял, насколько трудно найти расширение APC, чтобы оно работало на платформе Windows. Для Linux этот процесс может быть довольно простым с PECL, однако в Window это было настоящей проблемой.

Вот почему сегодня я собираюсь показать вам, как с нуля компилировать расширение PHP из его исходного кода в среде Windows, в частности с помощью библиотеки APC и PHP 5.3.8.

Требования

  • Visual Studio: версия должна обеспечивать некоторую совместимость с принятыми версиями Visual C ++, см. Первый пункт для получения дополнительной информации.
  • Исходный код PHP: Исходный код PHP для Windows в версии, которая должна быть совместима с расширением.
  • Исходный код расширения: Исходный код расширения для PHP, который вы хотите скомпилировать в динамическую библиотеку (файл DLL).

Сказав это, давайте начнем с компиляции!

1. Подготовьте среду Visual Studio

Прежде чем приступить к компиляции расширения PHP, вы должны знать, что не каждый VS-компилятор совместим с любой версией PHP, поэтому вам нужно знать, какую Visual Studio вам нужно использовать в соответствии с версией PHP, которую вы хотите. скомпилируйте ваше расширение (получите файл DLL). Ознакомьтесь со следующей таблицей, в которой указывается, какая версия Visual Studio необходима для компиляции желаемой версии PHP:

  • Да: эта версия поддерживается, и команда PHP предоставляет двоичные файлы, созданные с помощью этого компилятора.
  • Нет: не поддерживается
  • (*) Может быть удалено до окончательного выпуска
  • (**) Официально не поддерживается, но известно, что работает
  • (***) Поддерживается для данного компилятора в соответствии с текущей официальной поддержкой (или известной работой)

Таким образом, чтобы облегчить понимание, если вы хотите скомпилировать расширение для PHP 7, вам потребуется исходный код PHP 7, а также код расширения и, по крайней мере, Visual Studio 2012, предполагающий, что вы хотите скомпилировать его. для PHP 7.0, потому что если вы хотите его для PHP 7.1, то вам понадобится хотя бы Visual Studio 2015.

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

Заметка

В этом руководстве мы создадим расширение APC для PHP 5.3.8, поэтому в нашем случае нам нужно будет работать с Visual Studio 2008 SP1 для Visual C ++ 9.

2. Откройте командную строку Visual Studio

Чтобы выполнить некоторые административные команды, связанные с разработкой и сборкой вашего расширения, вам потребуется командная строка с контекстом Visual Studio. А именно, Командная строка разработчика для VS, которую вы можете найти по пути:

C:\ProgramData\Microsoft\Windows\Start Menu\Programs\\Visual Studio Tools

Здесь вы найдете исполняемый файл командной строки, который имеет доступ ко всем инструментам, которые вам понадобятся для создания расширения, выполнить его с правами администратора:

Командная строка разработчика Visual Studio

Как уже упоминалось, это обычная командная строка, которая содержит инструменты, которые нам нужны для следующих шагов:

Командная строка Visual Studio

Пусть он откроется, как нам нужно в шаге 5.

Теперь, когда у вас есть необходимые инструменты для компиляции PHP вместе с вашим расширением, вам также потребуется исходный код PHP. Вы можете получить исходный код нужной вам версии PHP из официальная веб-страница релизов PHP здесь.

Как только вы загрузите tar / zip-файл с кодом, извлеките его в каталог с коротким путем, чтобы избежать проблем с длиной путей в Windows. В нашем случае мы извлечем исходный код PHP 5.3.8 в c:/php-src каталог:

PHP 5.3.8 Папка с исходным кодом

Мы запустим команды для сборки PHP вместе с вашим кодом в этом каталоге в командной строке позже. Стоит отметить, что папка ext будет содержать код вашего расширения, но мы добавим его на следующем шаге.

4. Подготовьте расширение исходного кода

Чтобы создать свое расширение, либо вы создаете расширение с открытым исходным кодом, либо создаете свое собственное расширение. В нашем случае мы будем строить расширение APC, версия 3.1.6 специально для PHP 5.3.8 (та же версия нашего исходного кода PHP). Это гарантирует, что сгенерированная DLL PHP будет совместима с нужной нам версией PHP.

Как только вы загрузите исходный код вашего расширения (или напишите свой собственный код для него), распакуйте его внутри ext папка с исходным кодом PHP. В нашем случае каталог для расширения будет c:/php-src/ext/apc и он будет содержать код расширения (это очень важно, так как расширение будет скомпилировано как динамическая библиотека с PHP):

APC DLL Компиляция в Windows

Также убедитесь, что config.w32 файл в корневом каталоге исходного кода расширения, в строке, которая регистрирует расширение с РАСПРОСТРАНЕНИЕ функция, третий параметр имеет значение true для генерации DLL (динамический режим):

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

5. Настройте PHP и расширение build

Вернитесь в командную строку разработчика Visual Studio и перейдите в каталог с исходным кодом PHP:

Затем запустите buildconf.bat файл с --force аргумент:

Это создаст новый файл .bat, а именно configure.bat это будет иметь конфигурацию вашей сборки PHP. Запустите bat с помощью следующей команды и включите расширение, используя --enable- аргумент. В нашем случае это расширение APC, и мы сохранили исходный код в c:/php-src/ext/apc каталог, поэтому имя расширения будет APC. Запустите команду, чтобы настроить его:

Важная заметка

Если вы выполнили предыдущую команду и столкнулись с исключением, потому что bison.exe не был найден:

Загрузите бинарные файлы Bison с сайта Source Forge здесь. Затем вставьте bison.exe а также m4.exe в каталоге bin Visual Studio, например C:\Program Files (x86)\\VC\bin , Это сделает исполняемый файл зубра доступным для пути, и исключение, которое требуется, зуб больше не будет отображаться.

--disable-all Флаг поможет нам предотвратить любые другие исключения расширений, которые нам не нужны, просто сосредоточившись на расширении APC, которое нам нужно. И все, вы получите подробный вывод, включающий список со всеми расширениями, которые будут собраны вместе с PHP и его режимом, например, Static (встроенные классы в PHP) или Shared (DLL-библиотека динамических ссылок):

PHP Configure Build

Как и ожидалось, наше расширение PHP будет встроено в файл DLL так, как мы этого хотим. По умолчанию это создаст «потокобезопасную» сборку (PHP 5.3.8 TS). Если вы хотите не-потокобезопасную сборку, просто добавьте --disable-zts флаг для команды настройки.

6. Сборка PHP и расширение

Просто запустите команду:

Это запустит компиляцию и займет некоторое время. Он также сгенерирует подробный вывод с предупреждениями и т.д .:

PHP Cli Встроенный Windows Nmake

Как только он закончится, новый каталог появится внутри c:/php-src каталог, а именно Release или Release_TS в соответствии с конфигурацией Thread Safety. Там вы найдете очень простую сборку PHP, но наиболее важную причину, почему вы создали исключительно PHP, свой файл DLL расширения (в нашем случае php_apc.dll ):

PHP 5.3.8 APC 3.1.6 DLL

Спасибо

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

Добрый день. Перед мной стоит задача импортировать приличное количество кода из Делфи в PHP. Для этого использую DynamicWrapper так как это проще(удобнее) и быстрее чем использовать свои COM объекты. Но не в этом суть. Не получается выполнить функции из dll в PHP, хотя в самом делфи все прекрасно работает.

uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ComCtrls, Grids, ExtCtrls, Spin;

procedure mydec(var c:integer);
begin
c:=c-1;
end;

function mydiv2(c:integer):Integer;
begin
Result:=c div 2;
end;

На выходе имеем dll-ку, все Ок.

DynaWrap.dll зарегистрировал, тут все без ошибок. С WinApi функциями работает без проблем. Думал проблема в порядке параметров. Пробовал stdcall в делфи добавлять и с параметром "f=s" экспериментировать(хотя написано, что он игнорируется). В чем загвоздка?

2 Ответ от YMP 2012-03-22 11:58:24











Не совсем ясно, речь идёт о DynamicWrapper или DynamicWrapperX?

Если всё-таки о последнем, то "f=" действительно игнорируется. Поддерживаются только два соглашения вызова: stdcall и cdecl.

Судя по ошибке, могу только предположить, что хотя параметры методу Register передаются в переменных типа BSTR, но кодировка строк не UTF-16, поэтому содержимое параметров методу непонятно.

А в других языках — например, VBScript или JScript — не пробовали вашу библиотеку использовать? Там работает?

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