Skip to content

fix: use _wfopen for non-ASCII log paths on Windows#1326

Closed
Windsland52 wants to merge 4 commits into
MaaXYZ:mainfrom
Windsland52:fix/logdirpath
Closed

fix: use _wfopen for non-ASCII log paths on Windows#1326
Windsland52 wants to merge 4 commits into
MaaXYZ:mainfrom
Windsland52:fix/logdirpath

Conversation

@Windsland52
Copy link
Copy Markdown
Member

@Windsland52 Windsland52 commented May 10, 2026

问题

在 Windows 上,当 set_log_dir 的路径包含非 ASCII 字符(如中文 新建文件夹)时,Python 绑定测试会崩溃,抛出 OSError: 0xe06d7363

原因

Logger::open() 中使用 fopen 打开日志文件时,通过 path.string() 将内部 std::wstring 转换为窄字符串。在 Windows 上,这个转换使用的是系统活动代码页(ACP),而非 UTF-8。

在中文 Windows(ACP=936/GBK)上,UTF-8 导出的宽字符串再通过 GBK 转回窄字符串会产生乱码,导致 fopen 失败并抛出 C++ 异常。

日志也印证了这一点:log_dir_=C:/MaaFramework/install/bin/debug/?????

修复

fopen 改为 _wfopen,直接使用宽字符串路径,绕过有损的 .string() 转换:

// Before:
std::string str_log_path = log_path_.string();
FILE* file_ptr = fopen(str_log_path.c_str(), append ? "a" : "w");

// After:
FILE* file_ptr = _wfopen(log_path_.c_str(), append ? L"a" : L"w");

相关

Summary by Sourcery

确保 Python 绑定正确传递 UTF-8 编码的日志目录路径,并扩展测试以覆盖包含非 ASCII 字符的 Windows 路径。

Bug Fixes:

  • 修复 Python 中的 Tasker.set_log_dir,使其向底层框架发送 UTF-8 编码的路径,避免在使用含非 ASCII 字符的目录时发生崩溃。

Tests:

  • 新增一个 Python 绑定测试,将日志目录设置为包含非 ASCII 字符的路径,以验证处理是否正确。

Chores:

  • 更新 MaaUtils 子模块引用,以获取 Windows 宽路径日志记录相关的修复。
Original summary in English

Summary by Sourcery

Ensure Python bindings correctly pass UTF-8 encoded log directory paths and extend tests to cover non-ASCII Windows paths.

Bug Fixes:

  • Fix Python Tasker.set_log_dir to send UTF-8 encoded paths to the underlying framework, avoiding crashes with non-ASCII directories.

Tests:

  • Add a Python binding test that sets the log directory to a path containing non-ASCII characters to validate correct handling.

Chores:

  • Update MaaUtils submodule reference to pick up Windows wide-path logging fixes.

Tasker.set_log_dir encoded the path as UTF-8 but passed the character
count to MaaGlobalSetOption. Non-ASCII paths were truncated before native
path conversion on Windows, which could surface as an uncaught C++
filesystem exception.

Use the encoded byte length and cover set_log_dir with a non-ASCII path
in the binding test.
When the log directory path contains non-ASCII characters (e.g. CJK),
path.string() converts the internal wstring to narrow using the system
active code page (ACP), not UTF-8. On Chinese Windows (ACP=936/GBK),
this produces garbled bytes that cause fopen to throw an exception.

Using _wfopen with the wide string path directly preserves the characters
regardless of ACP setting.

Fixes Python binding test crash when set_log_dir is called with a path
containing CJK characters on Windows.
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - 我发现了 1 个问题

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="source/binding/Python/maa/tasker.py" line_range="648-653" />
<code_context>
             bool: 是否成功 / Whether successful
         """
-        strpath = str(path)
+        encoded_path_bytes = str(path).encode("utf-8")
         return bool(
             Library.framework().MaaGlobalSetOption(
                 MaaOption(MaaGlobalOptionEnum.LogDir),
-                strpath.encode(),
-                len(strpath),
+                encoded_path_bytes,
+                len(encoded_path_bytes),
             )
         )
</code_context>
<issue_to_address>
**suggestion:** 建议在编码之前使用 `os.fspath(path)` 而不是 `str(path)`,以更好地支持所有类路径对象。

`str(path)` 适用于 `str``Path`,但 `os.fspath(path)` 是处理任意类路径对象(包括实现了 `__fspath__` 的对象)的标准方式。例如:

```python
from os import fspath
encoded_path_bytes = fspath(path).encode("utf-8")
```

建议的实现:

```python
        Returns:
            bool: 是否成功 / Whether successful
        """
        encoded_path_bytes = fspath(path).encode("utf-8")
        return bool(
            Library.framework().MaaGlobalSetOption(
                MaaOption(MaaGlobalOptionEnum.LogDir),
                encoded_path_bytes,
                len(encoded_path_bytes),
            )
        )

```

你还需要确保在 `source/binding/Python/maa/tasker.py` 文件顶部导入 `fspath`,例如:

```python
from os import fspath
```

把它加在其他导入语句旁边(如果已经存在则避免重复导入)。
</issue_to_address>

Sourcery 对开源项目免费——如果你觉得我们的评审有帮助,欢迎分享 ✨
帮我变得更有用!请在每条评论上点 👍 或 👎,我会根据你的反馈改进后续评审。
Original comment in English

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="source/binding/Python/maa/tasker.py" line_range="648-653" />
<code_context>
             bool: 是否成功 / Whether successful
         """
-        strpath = str(path)
+        encoded_path_bytes = str(path).encode("utf-8")
         return bool(
             Library.framework().MaaGlobalSetOption(
                 MaaOption(MaaGlobalOptionEnum.LogDir),
-                strpath.encode(),
-                len(strpath),
+                encoded_path_bytes,
+                len(encoded_path_bytes),
             )
         )
</code_context>
<issue_to_address>
**suggestion:** Consider using `os.fspath(path)` instead of `str(path)` before encoding to better support all path-like objects.

`str(path)` covers `str` and `Path`, but `os.fspath(path)` is the standard way to handle any path-like object (including those with `__fspath__`). For example:

```python
from os import fspath
encoded_path_bytes = fspath(path).encode("utf-8")
```

Suggested implementation:

```python
        Returns:
            bool: 是否成功 / Whether successful
        """
        encoded_path_bytes = fspath(path).encode("utf-8")
        return bool(
            Library.framework().MaaGlobalSetOption(
                MaaOption(MaaGlobalOptionEnum.LogDir),
                encoded_path_bytes,
                len(encoded_path_bytes),
            )
        )

```

You also need to ensure `fspath` is imported at the top of `source/binding/Python/maa/tasker.py`, for example:

```python
from os import fspath
```

Add this alongside the other imports (and avoid duplicating it if it's already present).
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread source/binding/Python/maa/tasker.py Outdated
Comment on lines +648 to +653
encoded_path_bytes = str(path).encode("utf-8")
return bool(
Library.framework().MaaGlobalSetOption(
MaaOption(MaaGlobalOptionEnum.LogDir),
strpath.encode(),
len(strpath),
encoded_path_bytes,
len(encoded_path_bytes),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: 建议在编码之前使用 os.fspath(path) 而不是 str(path),以更好地支持所有类路径对象。

str(path) 适用于 strPath,但 os.fspath(path) 是处理任意类路径对象(包括实现了 __fspath__ 的对象)的标准方式。例如:

from os import fspath
encoded_path_bytes = fspath(path).encode("utf-8")

建议的实现:

        Returns:
            bool: 是否成功 / Whether successful
        """
        encoded_path_bytes = fspath(path).encode("utf-8")
        return bool(
            Library.framework().MaaGlobalSetOption(
                MaaOption(MaaGlobalOptionEnum.LogDir),
                encoded_path_bytes,
                len(encoded_path_bytes),
            )
        )

你还需要确保在 source/binding/Python/maa/tasker.py 文件顶部导入 fspath,例如:

from os import fspath

把它加在其他导入语句旁边(如果已经存在则避免重复导入)。

Original comment in English

suggestion: Consider using os.fspath(path) instead of str(path) before encoding to better support all path-like objects.

str(path) covers str and Path, but os.fspath(path) is the standard way to handle any path-like object (including those with __fspath__). For example:

from os import fspath
encoded_path_bytes = fspath(path).encode("utf-8")

Suggested implementation:

        Returns:
            bool: 是否成功 / Whether successful
        """
        encoded_path_bytes = fspath(path).encode("utf-8")
        return bool(
            Library.framework().MaaGlobalSetOption(
                MaaOption(MaaGlobalOptionEnum.LogDir),
                encoded_path_bytes,
                len(encoded_path_bytes),
            )
        )

You also need to ensure fspath is imported at the top of source/binding/Python/maa/tasker.py, for example:

from os import fspath

Add this alongside the other imports (and avoid duplicating it if it's already present).

- Use os.fspath(path) instead of str(path) in Python binding to support
  any path-like object (including those with __fspath__)
- Add null check after _wfopen in Logger::open() to avoid undefined
  behavior when file open fails
@Windsland52
Copy link
Copy Markdown
Member Author

Closing in favor of #1310

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant