feat(compiler): 添加 Windows GUI 模式编译支持

- 新增 --no-console 参数用于隐藏控制台窗口
- 实现 Windows 子系统编译选项 (/SUBSYSTEM:WINDOWS)
- 添加 mainCRTStartup 入口点配置
- 更新编译器常量定义和参数解析
- 创建 GUI 模式示例程序 hello-gui.php
- 创建控制台模式示例程序 hello-console.php
- 完善 Windows GUI 程序开发文档
- 更新项目 README 说明和使用指南
pull/1/head
韩天峰 4 months ago
parent 09ea347c11
commit 8994813b8b
  1. 267
      examples/win32-hello/README.md
  2. 238
      examples/win32-hello/WINDOWS_GUI_GUIDE.md
  3. 33
      examples/win32-hello/hello-console.php
  4. 30
      examples/win32-hello/hello-gui.php
  5. 32
      examples/win32-hello/hello-win.php
  6. 17
      examples/win32-hello/main.php
  7. 11
      src/Php/CompilerBase.php
  8. 6
      src/Php/Constants.php
  9. 3
      src/Php/Translator.php

@ -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
<?php
// 只声明函数签名,不包含实现
function messagebox(int $hWnd, string $text, string $caption, int $uType): int {}
```
**2. C++ 实现文件** (`cpp-src/winapi.cc`):
### 中文支持
#### C++ 层(消息框)
使用 `MultiByteToWideChar` 将 UTF-8 转换为 UTF-16:
```cpp
#include <phpx.h>
#include <windows.h>
// 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
<?php
// 直接调用,无需额外声明
function main() {
$result = messagebox(0, "Hello!", "Title", 0);
}
// Set timezone to China (UTC+8)
date_default_timezone_set('Asia/Shanghai');
```
### 最简单的版本 (main.php)
---
直接使用 Windows API(需要编译器支持原生函数声明):
## 📖 详细文档
```php
#[NativeFunction]
function MessageBox(int $hWnd, string $lpText, string $lpCaption, int $uType): int {}
- [WINDOWS_GUI_GUIDE.md](./WINDOWS_GUI_GUIDE.md) - Windows GUI 程序完整指南
- [CHINESE_SUPPORT_GUIDE.md](./CHINESE_SUPPORT_GUIDE.md) - 中文支持详细说明
- [CPP_FUNCTION_EXPORT_GUIDE.md](./CPP_FUNCTION_EXPORT_GUIDE.md) - C++ 函数导出规范
function main() {
MessageBox(0, "Hello World!", "标题", 0);
}
---
## 🚀 快速开始
### 编译控制台版本
```bash
# 进入示例目录
cd examples/win32-hello
# 编译
php ../../cli.php build --mode=bin hello-console.php
# 运行
.\hello-console.exe
```
## 扩展:创建完整窗口
### 编译 GUI 版本
要创建真正的 Windows 窗口(而不是消息框),需要:
```bash
# 进入示例目录
cd examples/win32-hello
1. **注册窗口类** (WNDCLASS)
2. **实现窗口过程函数** (WindowProc)
3. **创建消息循环** (GetMessage/TranslateMessage/DispatchMessage)
# 编译(使用 --no-console 参数)
php ../../cli.php build --mode=bin --no-console hello-gui.php
# 运行
.\hello-gui.exe
```
这些功能需要在 C++ 层实现,因为涉及到回调函数和复杂的 Windows 数据结构。
---
## 注意事项
## 💡 使用场景对比
| 场景 | 推荐模式 | 原因 |
|------|---------|------|
| 命令行工具 | 控制台模式 | 需要文本输出和输入 |
| 后台服务 | 控制台模式 | 需要日志输出 |
| 开发调试 | 控制台模式 | 便于查看调试信息 |
| 桌面应用 | GUI 模式 | 提供更好的用户体验 |
| 游戏 | GUI 模式 | 全屏或窗口化运行 |
| 工具软件 | GUI 模式 | 图形界面更友好 |
---
## ⚙ 编译选项说明
### --no-console 参数
- **作用**:隐藏控制台窗口,创建纯 GUI 应用程序
- **适用平台**:仅 Windows
- **链接选项**:添加 `/SUBSYSTEM:WINDOWS`
- **注意事项**
- 控制台输出函数(echo、print 等)将无效
- 需要使用消息框或其他 GUI 元素进行输出
- 调试时建议使用控制台模式
### 其他常用选项
```bash
# 优化级别
php cli.php build -O2 app.php
# 指定输出文件名
php cli.php build -o myapp app.php
# 启用调试信息
php cli.php build --debug-info app.php
# 并行编译
php cli.php build -j 8 app.php
```
---
## 🐛 常见问题
### Q: 为什么我的 echo 不显示?
A: 如果使用了 `--no-console` 参数,控制台被隐藏了。请改用消息框:
```php
messagebox(0, "你的消息", "标题", 0);
```
### Q: 如何调试没有控制台的程序?
A:
1. 使用消息框显示变量值
2. 写入日志文件:`file_put_contents('app.log', $msg, FILE_APPEND)`
3. 临时移除 `--no-console` 参数进行调试
### Q: 中文显示乱码怎么办?
A: 确保:
1. 源文件使用 UTF-8 编码保存
2. C++ 中使用 `MultiByteToWideChar` 转换编码
3. 使用 `MessageBoxW` 而不是 `MessageBoxA`
4. 在 PHP 中设置正确的时区
### Q: 可以同时显示控制台和窗口吗?
A: 技术上可以,但不推荐。通常的做法是:
- 开发时使用控制台模式便于调试
- 发布时使用 GUI 模式提供更好的用户体验
---
## 📝 项目结构
```
win32-hello/
├── hello-console.php # 控制台版本示例
├── hello-gui.php # GUI 版本示例
├── main.php # 另一个示例文件
├── window.php # 窗口创建示例
├── cpp-src/
│ ├── winapi.cc # C++ 实现文件
│ └── winapi.stub.php # PHP Stub 声明
├── WINDOWS_GUI_GUIDE.md # GUI 程序指南
├── CHINESE_SUPPORT_GUIDE.md # 中文支持指南
├── CPP_FUNCTION_EXPORT_GUIDE.md # C++ 函数导出指南
└── README.md # 本文件
```
- Windows API 函数需要通过 `#[NativeFunction]` 声明
- 复杂的 Windows API 建议在 C++ 层封装
- 编译时需要链接 Windows 系统库(user32.lib, gdi32.lib 等)
- 程序必须是 `bin` 模式才能创建图形界面
---
## 参考
## 🔗 相关资源
- [PHPX 编译器文档](https://github.com/swoole/phpx)
- [Windows API 文档](https://docs.microsoft.com/en-us/windows/win32/api/)
- [PHPX 编译器文档](../../docs/README.md)
- [examples/prime](../prime) - 混合 PHP 和 C++ 的示例
- [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)

@ -0,0 +1,238 @@
# Windows GUI 程序指南
## 概述
本示例展示了如何使用 PHPX 编译器创建纯图形界面的 Windows 程序,不显示控制台窗口。
## 关键特性
### 1. 隐藏控制台窗口
通过添加 `--no-console` 编译参数,程序将以 Windows 子系统模式运行,不会显示黑色的控制台终端窗口。
**使用方法:**
```bash
php cli.php build --mode=bin --no-console your-app.php
```
**实现位置:**
- 选项定义:`src/Php/Constants.php`
- 参数读取:`src/Php/Translator.php`
- 链接配置:`src/Php/CompilerBase.php`
```php
// 对于 bin 模式且指定了 --no-console,使用 Windows 子系统(不显示控制台窗口)
if ($this->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
<?php
function main()
{
date_default_timezone_set('Asia/Shanghai');
// 欢迎消息
messagebox(0,
"欢迎使用!\n\n" .
"当前时间: " . date('Y-m-d H:i:s'),
"Hello",
0);
}
```
### 带错误处理的程序
```php
<?php
function main()
{
date_default_timezone_set('Asia/Shanghai');
try {
// 你的业务逻辑
$result = someOperation();
messagebox(0, "操作成功!结果: " . $result, "成功", 64); // MB_ICONINFORMATION
} catch (\Exception $e) {
messagebox(0, "发生错误:\n" . $e->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)

@ -0,0 +1,33 @@
<?php
/**
* Win32 Hello World - Console Version (With Console)
* This example demonstrates a console application with message boxes
*
* Compile with: php ../../cli.php build --mode=bin hello-console.php
*/
function main()
{
// Set timezone to China (UTC+8)
date_default_timezone_set('Asia/Shanghai');
echo "========================================\n";
echo " Win32 Hello World 程序(控制台版)\n";
echo "========================================\n\n";
echo "当前时间: " . date('Y-m-d H:i:s') . "\n\n";
// Show message box
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";
echo "程序结束。按任意键退出...\n";
}

@ -0,0 +1,30 @@
<?php
/**
* Win32 Hello World - GUI Version (No Console)
* This example demonstrates a pure GUI application without console window
*
* Compile with: php ../../cli.php build --mode=bin --no-console hello-gui.php
*/
function main()
{
// Set timezone to China (UTC+8)
date_default_timezone_set('Asia/Shanghai');
// 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);
// Show goodbye message
messagebox(0,
"程序即将退出。\n\n" .
"感谢使用 PHPX 编译器!",
"再见",
0);
}

@ -9,26 +9,22 @@
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');
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);
}

@ -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);
}

@ -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;

@ -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,
],
];
}

@ -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 <mode> Compilation mode, -m bin(binary) or -m ext(extension), default: bin');
$climate->tab()->out('-j, --job <num> 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();
}

Loading…
Cancel
Save