Вызываем Windows API функцию MessageBox из PHP через FFI

Table of contents

Introduction

FFI (Foreign Function Interface) - расширение ЭКСПЕРИМЕНТАЛЬНОЕ. Модуль позволяет загружать общие (shared) библиотеки (.DLL или .so), вызывать функции на языке C и получать доступ к структурам языка C из PHP без необходимости глубоко изучать API модулей Zend и стороннего, "промежуточного" языка.

В PHP >= 7.4.0 появилось новое расширение FFI, с помощью которого можно выйти за рамки привычного PHP и использовать сторонние библиотеки. Познакомимся с этим расширением на примере HelloWorld, который будем выводить в системное окно с помощью Windows API функции MessageBoxA.

Репозиторий с полным кодом проекта: https://github.com/superrosko/example-php-ffi-windows-hello-world-msgbox.

MessageBoxA function (winuser.h)

Обратимся к документации по Windows API для того, чтобы понять, как вызывать функцию MessageBoxA.

int MessageBoxA(
  HWND   hWnd,
  LPCSTR lpText,
  LPCSTR lpCaption,
  UINT   uType
);

Видим, что функция принимает 4 параметра:

  1. hWnd (HWND) - указатель на окно, которому будет принадлежать создаваемый нами MessageBox. Мы будем передавать NULL, т.е. наш MessageBox не будет иметь владельца.

  2. lpText (LPCSTR) - указатель на текст сообщения.

  3. lpCaption (LPCSTR) - указатель на текст заголовка сообщения.

  4. uType (UINT) - дополнительные параметры окна, которые опишем константами.

Также нам нужно узнать название библиотеки, из которой мы будем вызывать MessageBox. За эту функцию отвечает библиотека User32.dll.

Теперь у нас есть вся информация для вызова MessageBox из PHP-скрипта.

MessageBoxA call from PHP

Зададим имя библиотеки и декларацию функции MessageBox. Вместо LPCSTR используем const char*, а вместо UINT используем int в определении языка C.

$lib = 'User32.dll';
$declaration = 'int MessageBoxA(int hWnd, char * lpText, char * lpCaption, int uType);';

Создадим объект FFI:

$ffi = FFI::cdef($declaration, $lib);

Вызовем созданную функцию с нужными нам параметрами:

$ffi->MessageBoxA(null, 'Hello World!!!', 'MessageBox from PHP!',0x00000040 | 0x00000001);

После выполнения скрипта на экране мы увидим MessageBox текстом Hello World!!!, заголовком окна MessageBox from PHP!, иконкой информации и двумя кнопками OK и CANCEL.

MessageBoxA class

Обернем вызовы и все константы в класс:

<?php

declare(strict_types=1);

namespace Superrosko\ExamplePhpFFI\WindowsApi;

use FFI;

/**
 * Class MessageBoxA.
 */
class MessageBoxA
{
    /**
     * uType - Type: UINT
     * To indicate the buttons displayed in the message box, specify one of the following values.
     */
    const MB_ABORTRETRYIGNORE = 0x00000002; // The message box contains three push buttons: Abort, Retry, and Ignore.
    const MB_CANCELTRYCONTINUE = 0x00000006; // The message box contains three push buttons: Cancel, Try Again, Continue.
    const MB_HELP = 0x00004000; // Adds a Help button to the message box. When the user clicks the Help button or presses F1, the system sends a WM_HELP message to the owner.
    const MB_OK = 0x00000000; // The message box contains one push button: OK. This is the default.
    const MB_OKCANCEL = 0x00000001; // The message box contains two push buttons: OK and Cancel.
    const MB_RETRYCANCEL = 0x00000005; // The message box contains two push buttons: Retry and Cancel.
    const MB_YESNO = 0x00000004; // The message box contains two push buttons: Yes and No.
    const MB_YESNOCANCEL = 0x00000003; // The message box contains three push buttons: Yes, No, and Cancel.

    /**
     * uType - Type: UINT
     * To display an icon in the message box, specify one of the following values.
     */
    const MB_ICONEXCLAMATION = 0x00000030; // An exclamation-point icon appears in the message box.
    const MB_ICONWARNING = 0x00000030; // An exclamation-point icon appears in the message box.
    const MB_ICONINFORMATION = 0x00000040; // An icon consisting of a lowercase letter i in a circle appears in the message box.
    const MB_ICONASTERISK = 0x00000040; // An icon consisting of a lowercase letter i in a circle appears in the message box.
    const MB_ICONQUESTION = 0x00000020; // A question-mark icon appears in the message box.
    const MB_ICONSTOP = 0x00000010; // A stop-sign icon appears in the message box.
    const MB_ICONERROR = 0x00000010; // A stop-sign icon appears in the message box.
    const MB_ICONHAND = 0x00000010; // A stop-sign icon appears in the message box.

    /**
     * uType - Type: UINT
     * To indicate the default button, specify one of the following values.
     */
    const MB_DEFBUTTON1 = 0x00000000; // The first button is the default button.
    const MB_DEFBUTTON2 = 0x00000100; // The second button is the default button.
    const MB_DEFBUTTON3 = 0x00000200; // The third button is the default button.
    const MB_DEFBUTTON4 = 0x00000300; // The fourth button is the default button.

    /**
     * uType - Type: UINT
     * To indicate the modality of the dialog box, specify one of the following values.
     */
    const MB_APPLMODAL = 0x00000000;
    const MB_SYSTEMMODAL = 0x00001000;
    const MB_TASKMODAL = 0x00002000;

    /**
     * uType - Type: UINT
     * To specify other options, use one or more of the following values.
     */
    const MB_DEFAULT_DESKTOP_ONLY = 0x00020000; // Same as desktop of the interactive window station. For more information, see Window Stations.
    const MB_RIGHT = 0x00080000; // The text is right-justified.
    const MB_RTLREADING = 0x00100000; // Displays message and caption text using right-to-left reading order on Hebrew and Arabic systems.
    const MB_SETFOREGROUND = 0x00010000; // The message box becomes the foreground window. Internally, the system calls the SetForegroundWindow function for the message box.
    const MB_TOPMOST = 0x00040000; // The message box is created with the WS_EX_TOPMOST window style.
    const MB_SERVICE_NOTIFICATION = 0x00200000; // The caller is a service notifying the user of an event.

    /**
     * Return code/value.
     */
    const IDOK = 1; // The OK button was selected.
    const IDCANCEL = 2; // The Cancel button was selected.
    const IDABORT = 3; // The Abort button was selected.
    const IDRETRY = 4; // The Retry button was selected.
    const IDIGNORE = 5; // The Ignore button was selected.
    const IDYES = 6; // The Yes button was selected.
    const IDNO = 7; // The No button was selected.
    const IDCLOSE = 8;
    const IDHELP = 9;
    const IDTRYAGAIN = 10; // The Try Again button was selected.
    const IDCONTINUE = 11; // The Continue button was selected.

    /**
     * @var string
     */
    private string $lib = 'User32.dll';

    /**
     * @var string
     */
    private string $declaration = 'int MessageBoxA(int hWnd, char * lpText, char * lpCaption, int uType);';

    /**
     * WindowsMsgBox constructor.
     * @param  int|null  $hWnd
     * @param  string  $lpText
     * @param  string|null  $lpCaption
     * @param  int  $uType
     */
    public function __construct(
        private ?int $hWnd,
        private string $lpText,
        private ?string $lpCaption,
        private int $uType
    ) {
    }

    /**
     * @param  int|null  $hWnd
     * @param  string  $lpText
     * @param  string|null  $lpCaption
     * @param  int  $uType
     * @return MessageBoxA
     */
    public static function init(
        ?int $hWnd,
        string $lpText,
        ?string $lpCaption,
        int $uType
    ): MessageBoxA {
        return new self($hWnd, $lpText, $lpCaption, $uType);
    }

    /**
     * @return int
     */
    public function call(): int
    {
        /** @var FFI $ffi */
        $ffi = FFI::cdef($this->declaration, $this->lib);

        /** @psalm-suppress UndefinedMethod * */
        return (int) $ffi->MessageBoxA(
            $this->hWnd,
            $this->lpText,
            $this->lpCaption,
            $this->uType);
    }
}

MessageBoxA class example

Пример использования описанного выше класса:

<?php

declare(strict_types=1);

require __DIR__.'/../../vendor/autoload.php';

use Superrosko\ExamplePhpFFI\WindowsApi\MessageBoxA;

$uType = MessageBoxA::MB_ICONINFORMATION | MessageBoxA::MB_OKCANCEL;

echo 'uType = '.$uType.PHP_EOL;

$msgboxID = (new MessageBoxA(null, 'Hello World!!!', 'MessageBox from PHP!', $uType))->call();

echo 'Return code = '.$msgboxID.PHP_EOL;
switch ($msgboxID) {
    case MessageBoxA::IDOK:
        echo 'The OK button was selected.';
        break;
    case MessageBoxA::IDCANCEL:
        echo 'The Cancel button was selected.';
        break;
}

На таком простом примере мы показали возможности по работе с системными функциями из PHP-скриптов. PHP FFI позволяет сильно расширить применение PHP, но нужно не забывать, что это экспериментальное расширение. Также стоит отметить, что вызов функций поддерживают декларации на языке С, а не C++, что сужает применимость.

Дата публикации :
Дата редактирования : 2021-02-16 20:07:10
Автор :

Cookies and IP addresses allow us to deliver and improve our web content, resolve technical errors, and provide you with a personalized experience. Our website uses cookies and collects your IP address for these purposes.