diff --git a/examples/win32-hello/README.md b/examples/win32-hello/README.md index d331dd30..c3d59304 100644 --- a/examples/win32-hello/README.md +++ b/examples/win32-hello/README.md @@ -1,120 +1,235 @@ # Win32 Hello World 示例 -这是一个使用 PHPX 编译器创建的最简单 Windows 图形界面程序示例。 +这个示例展示了如何使用 PHPX 编译器创建 Windows 程序,包括两种模式: -## 项目结构 +## 📁 示例文件 -``` -win32-hello/ -├── hello-win.php # 主程序(使用 C++ 辅助函数) -├── window.php # 纯 PHP 版本(需要 Windows API 声明) -├── main.php # 最简单的消息框示例 -├── cpp-src/ -│ └── winapi.cc # C++ 实现的 Windows API 封装 -└── project.yml # 项目配置文件 -``` +### 1. hello-console.php - 控制台版本(默认) -## 编译和运行 +**特点:** +- ✅ 显示黑色控制台窗口 +- ✅ 可以使用 `echo`、`print` 等控制台输出 +- ✅ 可以显示消息框 +- ✅ 适合命令行工具和调试 -### 方法 1: 使用项目配置(推荐) - -```powershell -cd examples\win32-hello -php ..\..\bin\compiler.php project.yml -.\build\win32-hello.exe +**编译命令:** +```bash +php ../../cli.php build --mode=bin hello-console.php ``` -### 方法 2: 直接编译单个文件 +**运行效果:** +- 显示控制台窗口 +- 输出文本信息到控制台 +- 弹出消息框 -```powershell -# 编译最简单的版本 -php bin\compiler.php examples\win32-hello\main.php +--- -# 运行 -.\main.exe +### 2. hello-gui.php - GUI 版本(无控制台) + +**特点:** +- ❌ 不显示控制台窗口 +- ❌ `echo`、`print` 等控制台输出无效 +- ✅ 只显示消息框等 GUI 元素 +- ✅ 适合纯图形界面应用程序 + +**编译命令:** +```bash +php ../../cli.php build --mode=bin --no-console hello-gui.php ``` -## 代码说明 +**运行效果:** +- 没有控制台窗口 +- 直接弹出消息框 +- 纯图形界面体验 -### C++ 函数导出规范 +--- -要让 C++ 函数能被 PHP 调用,必须满足以下条件: +## 🔧 核心功能 -1. **函数名必须以 `php_` 为前缀** - - 例如:`php_messagebox()` 在 PHP 中调用时为 `messagebox()` +### C++ 函数导出 -2. **只能使用 PHPX 类型作为参数和返回值** - - `Int`, `Bool`, `String`, `Double`, `Array`, `Object`, `Variant` 等 - - 不能使用原生 C/C++ 类型(如 `int`, `char*` 等) +本示例展示了如何将 C++ 函数导出为 PHP 函数: -3. **必须在 `.stub.php` 文件中声明** - - stub 文件只包含函数签名(参数和返回值) - - 不包含具体实现代码 - - 实现代码在对应的 `.cc` 或 `.cpp` 文件中 +1. **函数命名规范**:必须以 `php_` 为前缀 +2. **类型要求**:只能使用 PHPX 类型(Int, String, Bool 等) +3. **Stub 声明**:必须在 `.stub.php` 文件中声明函数签名 -### 示例结构 +**示例:** -**1. Stub 声明文件** (`cpp-src/winapi.stub.php`): +C++ 实现 (`cpp-src/winapi.cc`): +```cpp +Int php_messagebox(Int hWnd, String text, String caption, Int uType) { + // 实现代码 +} +``` + +PHP Stub 声明 (`cpp-src/winapi.stub.php`): ```php - -#include +// Convert UTF-8 to UTF-16 for Windows API +int wtext_len = MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, NULL, 0); +wchar_t* wtext = new wchar_t[wtext_len]; +MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len); -using namespace php; +int result = MessageBoxW((HWND)hWnd, wtext, wcaption, (UINT)uType); -// 函数名必须以 php_ 为前缀 -Int php_messagebox(Int hWnd, String text, String caption, Int uType) { - return MessageBox((HWND)hWnd, text.c_str(), caption.c_str(), (UINT)uType); -} +delete[] wtext; +delete[] wcaption; ``` -**3. PHP 调用文件** (`hello-win.php`): +#### PHP 层(时区设置) + ```php -buildMode === 'bin' && $this->noConsole) { + $cmd .= ' /SUBSYSTEM:WINDOWS'; +} +``` + +### 2. 中文支持 + +#### C++ 层(消息框) + +在 C++ 中使用 `MultiByteToWideChar` 将 UTF-8 转换为 UTF-16,然后调用 `MessageBoxW`: + +```cpp +// Convert UTF-8 to UTF-16 for Windows API +int wtext_len = MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, NULL, 0); +wchar_t* wtext = new wchar_t[wtext_len]; +MultiByteToWideChar(CP_UTF8, 0, text.data(), -1, wtext, wtext_len); + +int result = MessageBoxW((HWND)hWnd, wtext, wcaption, (UINT)uType); + +delete[] wtext; +delete[] wcaption; +``` + +#### PHP 层(时区设置) + +```php +// Set timezone to China (UTC+8) +date_default_timezone_set('Asia/Shanghai'); +``` + +### 3. 输出方式 + +由于没有控制台窗口,所有输出必须通过 GUI 元素显示: + +- **消息框**:使用 `messagebox()` 或 `MessageBox()` 函数 +- **自定义窗口**:需要实现完整的窗口类和消息循环 +- **日志文件**:可以写入文件进行调试 + +## 编译和运行 + +### 带控制台窗口(默认) + +```bash +# 进入示例目录 +cd examples/win32-hello + +# 编译为带控制台的程序(默认) +php ../../cli.php build --mode=bin hello-win.php + +# 运行程序(显示控制台和消息框) +.\hello-win.exe +``` + +### 不带控制台窗口(GUI 模式) + +```bash +# 使用 --no-console 参数编译纯 GUI 程序 +php ../../cli.php build --mode=bin --no-console hello-win.php + +# 运行程序(只显示消息框,无控制台) +.\hello-win.exe +``` + +## 注意事项 + +### ⚠️ 重要限制 + +1. **没有标准输入输出** + - `echo`、`print`、`printf` 等控制台输出函数无效 + - `stdin`、`stdout`、`stderr` 不可用 + - 所有输出必须通过 GUI 元素 + +2. **调试困难** + - 无法在控制台查看调试信息 + - 建议使用: + - 消息框显示调试信息 + - 写入日志文件 + - 使用 Visual Studio 调试器附加进程 + +3. **入口点要求** + - 使用 `/SUBSYSTEM:WINDOWS` 后,默认入口点是 `WinMain` 而不是 `main` + - PHPX 编译器会自动处理这个转换 + +### ✅ 最佳实践 + +1. **使用消息框进行用户交互** + ```php + messagebox(0, "操作成功!", "提示", 0); + ``` + +2. **错误处理** + ```php + try { + // 你的代码 + } catch (\Exception $e) { + messagebox(0, "错误: " . $e->getMessage(), "错误", 16); // MB_ICONERROR + } + ``` + +3. **日志记录** + ```php + file_put_contents('app.log', date('Y-m-d H:i:s') . ": 消息\n", FILE_APPEND); + ``` + +## 两种模式对比 + +| 特性 | 控制台模式 (默认) | 窗口模式 (`--no-console`) | +|------|------------------|-------------------------| +| 编译命令 | `php cli.php build app.php` | `php cli.php build --no-console app.php` | +| 链接选项 | `/SUBSYSTEM:CONSOLE` | `/SUBSYSTEM:WINDOWS` | +| 控制台窗口 | ✅ 显示 | ❌ 隐藏 | +| echo/print | ✅ 可用 | ❌ 不可用 | +| stdin/stdout | ✅ 可用 | ❌ 不可用 | +| 消息框 | ✅ 可用 | ✅ 可用 | +| 适用场景 | 命令行工具、调试 | GUI 应用程序 | +| 用户体验 | 黑色终端窗口 | 纯图形界面 | + +## 切换模式 + +### 开发时(带控制台,便于调试) + +```bash +# 默认编译,显示控制台 +php cli.php build --mode=bin app.php +``` + +### 发布时(不带控制台,纯 GUI) + +```bash +# 使用 --no-console 参数 +php cli.php build --mode=bin --no-console app.php +``` + +### 在 project.yml 中配置 + +```yaml +build: + mode: bin + no_console: true # 隐藏控制台窗口 +``` + +## 常见问题 + +### Q: 为什么我的 echo 不显示? + +A: 因为使用了 `/SUBSYSTEM:WINDOWS`,控制台被隐藏了。请改用消息框或其他 GUI 元素。 + +### Q: 如何调试没有控制台的程序? + +A: +1. 使用消息框显示变量值 +2. 写入日志文件 +3. 使用 Visual Studio 的"附加到进程"功能 +4. 临时切换回控制台模式进行调试 + +### Q: 可以同时显示控制台和窗口吗? + +A: 技术上可以,但不推荐。通常的做法是: +- 开发时使用控制台模式便于调试 +- 发布时使用窗口模式提供更好的用户体验 + +### Q: 中文显示乱码怎么办? + +A: 确保: +1. C++ 文件使用 UTF-8 编码保存 +2. 使用 `MultiByteToWideChar` 转换编码 +3. 使用 `MessageBoxW` 而不是 `MessageBoxA` +4. 在 PHP 中设置正确的时区 + +## 示例代码 + +### 简单的消息框程序 + +```php +getMessage(), "错误", 16); // MB_ICONERROR + } +} +``` + +## 相关资源 + +- [Windows Subsystem 文档](https://docs.microsoft.com/en-us/cpp/build/reference/subsystem-specify-subsystem) +- [MessageBoxW API](https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-messageboxw) +- [MultiByteToWideChar](https://docs.microsoft.com/en-us/windows/win32/api/stringapiset/nf-stringapiset-multibytetowidechar) diff --git a/examples/win32-hello/hello-console.php b/examples/win32-hello/hello-console.php new file mode 100644 index 00000000..aef1b8cc --- /dev/null +++ b/examples/win32-hello/hello-console.php @@ -0,0 +1,33 @@ + nul 2>&1'); - } - // Set timezone to China (UTC+8) date_default_timezone_set('Asia/Shanghai'); - echo "========================================\n"; - echo " Win32 Hello World 程序\n"; - echo "========================================\n\n"; - - // Method 1: Use message box (simplest) - echo "显示消息框...\n"; - $result = messagebox(0, "Hello from PHP Compiler!\n\n这是一个使用 PHPX 编译器创建的 Windows 程序。\n\n当前时间: " . date('Y-m-d H:i:s'), "Hello World", 0); - echo "消息框返回值: " . $result . "\n\n"; - - // Method 2: Create window (requires more code) - echo "提示:要创建完整窗口,需要实现窗口过程函数和消息循环。\n"; - echo "这需要在 C++ 层实现 WNDCLASS 注册和消息泵。\n\n"; + // Show welcome message box + messagebox(0, + "欢迎使用 PHP Compiler!\n\n" . + "这是一个纯图形界面的 Windows 程序。\n" . + "没有控制台窗口,只显示 GUI。\n\n" . + "当前时间: " . date('Y-m-d H:i:s'), + "Win32 Hello World", + 0); - echo "程序结束。按任意键退出...\n"; + // Show goodbye message + messagebox(0, + "程序即将退出。\n\n" . + "感谢使用 PHPX 编译器!", + "再见", + 0); } diff --git a/examples/win32-hello/main.php b/examples/win32-hello/main.php index 4488cda3..f35ccb4e 100644 --- a/examples/win32-hello/main.php +++ b/examples/win32-hello/main.php @@ -48,17 +48,14 @@ function MessageBox(int $hWnd, string $lpText, string $lpCaption, int $uType): i function main() { - // Set console to UTF-8 for proper Chinese character display - if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { - exec('chcp 65001 > nul 2>&1'); - } - // Set timezone to China (UTC+8) date_default_timezone_set('Asia/Shanghai'); - // Show a simple message box - $result = MessageBox(0, "Hello from PHP Compiler!\n\n这是一个使用 PHPX 编译器创建的 Windows 程序。", "Hello World", 0); - - echo "消息框返回值: " . $result . "\n"; - echo "程序结束。\n"; + // Show welcome message box + MessageBox(0, + "Hello from PHP Compiler!\n\n" . + "这是一个使用 PHPX 编译器创建的 Windows 程序。\n\n" . + "当前时间: " . date('Y-m-d H:i:s'), + "Hello World", + 0); } diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 9e3e227b..641a82fb 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -164,6 +164,7 @@ class CompilerBase extends \PhpAot\Core\Translator protected bool $formatCode = true; protected bool $printBacktraceOnError = false; protected bool $noLiteralStrings = false; + protected bool $noConsole = false; // Windows: hide console window protected string $file; protected string $dir; @@ -2473,6 +2474,16 @@ class CompilerBase extends \PhpAot\Core\Translator // 链接选项 if ($link) { $cmd .= ' ' . $this->parseWindowsLdflags(); + + // 对于 bin 模式且指定了 --no-console,使用 Windows 子系统(不显示控制台窗口) + if ($this->buildMode === 'bin' && $this->noConsole) { + $cmd .= ' /SUBSYSTEM:WINDOWS'; + // 指定入口点为 mainCRTStartup,这样可以使用 main() 而不是 WinMain() + $cmd .= ' /ENTRY:mainCRTStartup'; + // 注意:使用 WINDOWS 子系统后,程序将没有控制台窗口 + // 所有输出需要通过 GUI 元素(如消息框)显示 + } + $cmd .= ' ' . $this->parseWindowsLibs(); if ($this->ldflags) { $cmd .= ' ' . $this->ldflags; diff --git a/src/Php/Constants.php b/src/Php/Constants.php index 83941af4..5ece3ba4 100644 --- a/src/Php/Constants.php +++ b/src/Php/Constants.php @@ -137,5 +137,11 @@ class Constants 'required' => false, 'defaultValue' => 4, ], + 'no-console' => [ + 'longPrefix' => 'no-console', + 'description' => 'Hide console window (Windows only, use /SUBSYSTEM:WINDOWS)', + 'required' => false, + 'noValue' => true, + ], ]; } diff --git a/src/Php/Translator.php b/src/Php/Translator.php index 1044a5e4..7a9dbfd1 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -62,6 +62,7 @@ class Translator extends Preprocessor $this->debugInfo = $this->climate->arguments->defined('debug-info'); $this->noLiteralStrings = $this->climate->arguments->get('noLiteralStrings'); $this->enableProfiler = $this->climate->arguments->defined('profile'); + $this->noConsole = $this->climate->arguments->defined('no-console'); $this->internalFunctions = array_flip(get_defined_functions()['internal']); unset($this->internalFunctions['main']); $this->internalConstants = get_defined_constants(); @@ -103,6 +104,7 @@ class Translator extends Preprocessor $climate->tab()->out('-m, --mode Compilation mode, -m bin(binary) or -m ext(extension), default: bin'); $climate->tab()->out('-j, --job Number of parallel compilation jobs (default: 4)'); $climate->tab()->out('--no-literal-strings Disable literal strings optimization'); + $climate->tab()->out('--no-console Hide console window (Windows only, GUI application)'); $climate->br(); $climate->bold('EXAMPLES:'); @@ -111,6 +113,7 @@ class Translator extends Preprocessor $climate->tab()->out($cmd . ' project/config.yml -O2'); $climate->tab()->out($cmd . ' my-ext/ -O2 -o myapp -m ext'); $climate->tab()->out($cmd . ' app.php -O3 -o myapp -v'); + $climate->tab()->out($cmd . ' gui-app.php --no-console (Windows GUI app, no console)'); $climate->br(); }