はじめに
PHPからWindowsAPIを呼び出すことはできるのだろうか?と、たまたま気になったので調べてみると
PHP FFI(Foreign Function Interface)で呼び出せるそうです
FFIのマニュアルのサンプルを確認すると、
Basic FFI usage
- FFI::cdef()で、関数のプロトタイプ宣言(と、ライブラリファイル名)を渡す
- $ffiオブジェクト経由で、動的に関数を呼び出すことができる
という仕組みのようです
$ffi = FFI::cdef(
"int printf(const char *format, ...);", // this is a regular C declaration
"libc.so.6");
// call C's printf()
$ffi->printf("Hello %s!\n", "world");
FFIを有効にする
(PHP8.4.4で試しています)
php.iniでffiを読み込むように変更します
-;extension_dir = "ext"
+extension_dir = "ext"
;extension=bz2
;extension=curl
-;extension=ffi
+extension=ffi
MessageBoxを表示するサンプル
WindowsAPIを呼び出すサンプルを作ってみましたが、呼び出し規約(stdcall)を指定できませんでした
(WindowsでFFIを使うのは、厳しそう・・・)
- WindowsAPIは文字がUFT16。そのまま文字を渡せないので
$ffi->new("unsigned short[" . <文字列のながs> . "]");
で配列を作ってそこへセット - 呼び出し規約の指定はできなかったので、多分
__cdecl
で呼び出しされている(実行はできるが、スタックポインタがずれてエラーになる可能性がある)
<?php
/**
* PHPからFFIでWindowsAPI(MessageBoxW)を呼び出すサンプル
* ・WindowsAPIはstdcall呼び出し規約に従う必要があるが、FFIで指定できない(詳細不明)
*/
$stdcall = ""; // "__stdcall"; // 呼び出し規約を指定するとエラーになる
// MessageBoxW APIの定義
$ffi = FFI::cdef("int $stdcall MessageBoxW(void* hWnd, unsigned short* lpText, unsigned short* lpCaption, unsigned int uType);", 'user32.dll');
$hWnd = null; // 親ウィンドウハンドル無し
// 文字列をUTF16に変換
$lpText = utf16("PHPからWindowsAPI(MessageBoxW)を呼び出しました!"); // メッセージ
$lpCaption = utf16("FFI Example"); // タイトルバー
$uType = 0; // MB_OK (OKボタンのみ)
// APIの呼び出し
$result = $ffi->MessageBoxW($hWnd, $lpText, $lpCaption, $uType);
if ($result == 1) { // IDOK (OKボタンが押された場合)
echo "MessageBoxW が表示されました。\n";
} else {
echo "MessageBoxW の表示に失敗しました。\n";
}
/**
* UTF16の文字列に変換
*/
function utf16(string $string): ?FFI\CData
{
$ffi = FFI::cdef();
$utf16le_string = iconv("UTF-8", "UTF-16BE", $string);
$hexed_string = bin2hex($utf16le_string);
$buffer = $ffi->new("unsigned short[" . (strlen($string) + 1) . "]");
for ($i = 0; $i < strlen($string); $i++) {
$buffer[$i] = hexdec(substr($hexed_string, $i * 4, 4));
}
return $buffer;
}