Windows 跨平台 C/C++ 项目中的 UTF-8 路径陷阱

从 ASCII 到 Unicode

ASCII 时代:一切都很简单

最早的计算机只需要处理英文。ASCII 用 7 位编码了 128 个字符------26 个大小写字母、数字、标点和控制字符,一个 char 就是一个字符,一个字节就是一个编码单元。那时候写文件路径,fopen("data/output.txt", "r") 毫无问题,全世界的计算机都能正确理解。

ANSI 代码页:各国各自为政

当计算机走出英语世界,问题来了------一个字节最多 256 个码位(8 位),装不下中文的几万个汉字。于是各国自行扩展:

  • 中国大陆制定了 GB2312 ,后来扩展为 GBK(代码页 936)
  • 日本制定了 Shift-JIS(代码页 932)
  • 韩国制定了 EUC-KR(代码页 949)
  • 西欧使用 Latin-1(代码页 1252)

这些编码统称为 ANSI 编码 。它们在 ASCII 范围内(0x00--0x7F)完全一致,但在 0x80 以上各不相同。同一串字节 B0 A1,在 GBK 里是"啊",在 Shift-JIS 里是完全不同的字符。

这意味着:一个文件在中文电脑上显示正常,复制到日文电脑上就成乱码。

在这个时代,同一个系统上只要编码统一,文件路径不会有问题。中文 Windows 上的应用程序传 GBK 路径给 fopen,系统按 GBK 解码,一切正常。

Unicode 的诞生:统一字符集

为了终结编码混战,Unicode 联盟定义了一个 全球统一的字符集------每个字符分配一个唯一的码点(Code Point),比如"中"是 U+4E2D,"A"是 U+0041。

但 Unicode 只是字符集,不是编码方案。怎么把这些码点存成字节?这就产生了两条路线:

UTF-16 :用 2 个字节(或 4 个字节用于代理对)表示一个字符。优点是大多数常用字符只需要固定 2 字节,处理效率高。缺点是不兼容 ASCII------英文字母 A 在 UTF-16 里是 0x00 0x41,多了一个 \0,会破坏所有基于 char*\0 结尾的 C 字符串函数。

UTF-8 :用 1--4 个变长字节表示一个字符。ASCII 范围的字符只占 1 个字节且编码完全相同,中文通常占 3 个字节。它最大的优势是 向后兼容 ASCII------任何合法的 ASCII 字符串同时也是合法的 UTF-8 字符串。

时间线上的错位:问题的真正根源

1993 年 ,Windows NT 3.1 发布。微软需要让 Windows 支持全球语言,而此时 UTF-8 刚刚被 Ken Thompson 和 Rob Pike 发明(1993 年 1 月),还没有被广泛采用。微软选择了当时看起来更合理的方案------UTF-16 作为内核的原生编码。Windows NT 的所有内核 API、文件系统、注册表都统一使用 UTF-16(当时叫 UCS-2)。

这个决策在当时是正确的:UTF-16 对大多数字符固定 2 字节,处理方便;而 UTF-8 的变长编码在那个年代被认为不够高效。

但历史的发展出乎意料。

类 Unix 系统(Linux、macOS、FreeBSD)晚了几年才认真处理国际化。到它们需要做决策时,UTF-8 已经被证明是一个绝佳方案------完全兼容现有的 char* 字符串、\0 结尾约定、所有 POSIX API。于是类 Unix 系统直接拥抱了 UTF-8

  • Linux 从 2000 年代开始,主流发行版默认 locale 切换为 en_US.UTF-8
  • macOS 从 OS X 开始就默认 UTF-8
  • Android 基于 Linux,天然 UTF-8

在这些系统上,char* 就是 UTF-8,fopen("中文路径/文件.txt", "r") 直接送到内核,内核也理解 UTF-8,一切顺畅。

而 Windows 被自己的历史选择锁住了。它的内核是 UTF-16,char* API 是为 ANSI 代码页设计的兼容层。当全世界都转向 UTF-8 时,Windows 的 char* 还停留在 ANSI 时代。

C/C++ 中的 wchar_t 与 std::wstring

为了在 C/C++ 语言层面支持宽字符,标准引入了 wchar_t 类型和 std::wstring。但这个设计也有先天不足:

  • wchar_t 的大小没有被标准固定------Windows 上是 2 字节(UTF-16),Linux/macOS 上是 4 字节(UTF-32)
  • 这意味着涉及 wchar_t 的代码天然不可移植
  • 标准库的宽字符函数(wfopenwstat 等)在 POSIX 系统上几乎没人用,因为 char* + UTF-8 已经够用

结果就是:wchar_tstd::wstring 在实践中变成了 Windows 专用的解决方案,跨平台项目无法依赖它们作为统一接口。

Windows 的编码架构

Windows API 的 A/W 后缀体系

Windows 为几乎所有涉及字符串的 API 都提供了两个版本:

c 复制代码
// A 后缀:接受 char*,按系统 ANSI 代码页解释
HANDLE CreateFileA(LPCSTR lpFileName, ...);

// W 后缀:接受 wchar_t*,UTF-16 编码
HANDLE CreateFileW(LPCWSTR lpFileName, ...);

而我们平时写的"无后缀"版本 CreateFile,其实是一个宏:

c 复制代码
#ifdef UNICODE
#define CreateFile  CreateFileW
#else
#define CreateFile  CreateFileA
#endif

定义 UNICODE 宏后,CreateFile 展开为 CreateFileW,函数签名变成了接受 wchar_t*------但这只是让编译器要求你传宽字符串 ,它不会自动帮你把 UTF-8 的 char* 变成 wchar_t*

C/C++ 标准库:不走 A/W 体系

很多人以为 fopen 内部调用的是 CreateFileA事实并非如此。

fopenstd::ifstreamstat 等 C/C++ 标准库函数有自己独立的实现路径。以 MSVC 的 CRT(C Runtime)为例,fopen 的内部流程是:

复制代码
fopen(const char* path, ...)
  │
  ├─ MultiByteToWideChar(CP_ACP, ..., path, ...)   ← 用 ANSI 代码页转换
  │                          │
  │                     CP_ACP = 系统代码页
  │                     中文 Windows = GBK (936)
  │                     日文 Windows = Shift-JIS (932)
  │
  └─ CreateFileW(wchar_t* wide_path, ...)           ← 最终调用的是 W 版本

注意到关键点了吗?CRT 内部已经统一调用 W 版本了 ,但转换那一步用的是 CP_ACP(当前系统 ANSI 代码页),而不是 CP_UTF8。所以:

  • 传入 GBK 编码的 char*(中文 Windows)→ 按 GBK 解码 → 正确
  • 传入 UTF-8 编码的 char*按 GBK 解码 → 乱码 → 文件找不到

UNICODE 宏对这个流程毫无影响。 C/C++ 标准库函数的签名是语言标准规定的,永远接受 char*,不会因为任何宏定义而变成接受 wchar_t*

Windows 10 1903+ 的 UTF-8 代码页方案

微软也意识到了这个历史包袱的严重性。从 Windows 10 1903 开始,可以通过应用程序清单(manifest)将进程的 ACP 设置为 UTF-8:

xml 复制代码
<application>
  <windowsSettings>
    <activeCodePage xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">UTF-8</activeCodePage>
  </windowsSettings>
</application>

设置后,CRT 内部的 MultiByteToWideChar(CP_ACP, ...) 就等价于 MultiByteToWideChar(CP_UTF8, ...)fopen 可以正确处理 UTF-8 路径。

但这个方案有限制:

  • 要求 Windows 10 1903 及以上------如果你的软件需要支持更早的 Windows 版本,就不能依赖它
  • 它改变了整个进程的 ACP,可能影响第三方库中假设 ACP = GBK 的代码
  • 一些边缘情况下行为可能与预期不同

对于需要支持 Windows 10 早期版本或 Windows 7 的项目,仍然需要自行处理 UTF-8 到 UTF-16 的转换。

影响范围

在跨平台 C/C++ 项目中,所有接受 char* 路径参数的 API 在 Windows 上都存在 UTF-8 路径问题。主要涉及以下几类:

  • C 标准库文件 I/Ofopenfreopenremoverename
  • C++ 文件流std::ifstreamstd::ofstreamstd::fstream 的构造函数和 .open() 方法
  • POSIX / CRT 文件操作stataccessmkdirgetcwdunlink
  • std::filesystemstd::filesystem::path(std::string) 构造函数内部同样按 ACP 转换,和 fopen 是一样的坑
  • 环境变量std::getenv / getenv 返回值按 ACP 编码,不是 UTF-8
  • 进程操作system()_popen() 等命令字符串含非 ASCII 路径会失败
  • Windows API 的 A 后缀版本GetFileAttributesACreateFileALoadLibraryA

完整的受影响接口清单见本文末尾[附录 A](#附录 A)。

解决方案

设计原则

  1. 对调用者透明 :调用者统一传入 UTF-8 的 char* / std::string,不需要关心平台差异
  2. 非 Windows 平台零开销:通过条件编译,在非 Windows 平台直接调用原始函数
  3. ANSI 编码兼容降级:如果传入的恰好不是 UTF-8(比如误传了 GBK 路径),不会崩溃,会降级到 ANSI 方式尝试
  4. C 和 C++ 双覆盖 :C 代码用纯 C 接口封装函数,C++ 代码用 std::filesystem::path + 类型推导

C 接口封装:utf8_fopen / utf8_remove / utf8_stat

对于 C 代码中大量使用的 fopenremovestat,提供一套接口签名完全对等的替换函数。

核心实现(以 utf8_fopen 为例):

c 复制代码
#include <stdio.h>
#ifdef _WIN32
#include <windows.h>
#include <stdlib.h>
#endif

FILE* utf8_fopen(const char *filename_utf8, const char *mode_utf8)
{
    if (!filename_utf8 || !mode_utf8) return NULL;

#ifdef _WIN32
    // 1. 尝试将 UTF-8 路径转换为 UTF-16
    //    MB_ERR_INVALID_CHARS:如果输入不是合法 UTF-8,转换失败返回 0
    int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
                                filename_utf8, -1, NULL, 0);
    if (n > 0) {
        wchar_t *filename_w = (wchar_t *)calloc(n, sizeof(wchar_t));
        if (filename_w) {
            int m = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS,
                                        mode_utf8, -1, NULL, 0);
            if (m > 0) {
                wchar_t *mode_w = (wchar_t *)calloc(m, sizeof(wchar_t));
                if (mode_w) {
                    MultiByteToWideChar(CP_UTF8, 0, filename_utf8, -1, filename_w, n);
                    MultiByteToWideChar(CP_UTF8, 0, mode_utf8, -1, mode_w, m);

                    // 2. 用宽字符版本打开文件
                    FILE *fh = _wfopen(filename_w, mode_w);
                    free(mode_w);
                    free(filename_w);

                    if (fh) return fh;

                    return fopen(filename_utf8, mode_utf8);
                }
            }
            free(filename_w);
        }
    }
#endif
    // 非 Windows 平台 / Windows 上 UTF-8 转换失败
    // 直接调用标准 fopen(按系统默认编码处理)
    return fopen(filename_utf8, mode_utf8);
}

降级策略解析:

复制代码
utf8_fopen("D:/素材/视频.mp4", "rb")
  │
  ├─ MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, ...)
  │   ├─ 输入是合法 UTF-8 → 转换成功 → _wfopen(L"D:/素材/视频.mp4")
  │   │   ├─ 成功 → 返回 FILE*  ✓
  │   │   └─ 失败(文件不存在等)→ 降级到 fopen(按 ACP 尝试)
  │   │
  │   └─ 输入不是合法 UTF-8(如 GBK)→ 返回 0 → 跳过宽字符路径
  │
  └─ fopen(filename_utf8, mode_utf8)    ← 按 ACP(GBK)处理,兼容旧代码

MB_ERR_INVALID_CHARS 标志是降级策略的关键------它让 MultiByteToWideChar 在遇到非法 UTF-8 序列时直接失败,而不是静默产生乱码。

utf8_removeutf8_stat 的实现思路完全一致,完整代码见本文末尾[附录 B](#附录 B)。

C++ 接口:to_path()

对于 C++ 的文件流和 std::filesystem 操作,提供一个跨平台的路径转换函数:

cpp 复制代码
#include <string>
#if defined(_WIN32)
#include <filesystem>
#endif

inline decltype(auto) to_path(const std::string& utf8_path) {
#if defined(_WIN32)
    return std::filesystem::u8path(utf8_path);  // → std::filesystem::path
#else
    return utf8_path;                            // → const std::string&
#endif
}

inline auto to_path(std::string&& utf8_path) {
#if defined(_WIN32)
    return std::filesystem::u8path(utf8_path);
#else
    return std::move(utf8_path);
#endif
}

inline auto to_path(const char* const utf8_path) {
    if (utf8_path == nullptr) {
        return decltype(to_path(std::string{})){};
    }
    return to_path(std::string(utf8_path));
}

这个函数涉及 decltype(auto) 返回类型推导、右值引用重载避免悬垂引用等 Modern C++ 细节,详细的设计分析可以参考我的另一篇博客:《C++不要返回const左值引用捕获的右值引用》

使用示例:

cpp 复制代码
// 替换前(Windows 上中文路径会失败)
std::ifstream ifs(model_path);
std::ofstream ofs(output_path, std::ios::binary);

// 替换后(全平台正确)
std::ifstream ifs(to_path(model_path));
std::ofstream ofs(to_path(output_path), std::ios::binary);

对于 std::filesystem 操作:

cpp 复制代码
// 替换前
std::filesystem::remove(file_path);              // string → path 按 ACP 解码

// 替换后
std::filesystem::remove(to_path(file_path));     // u8path 正确处理 UTF-8

Windows API 层:utf8_to_wstring + W 后缀

对于直接调用 Windows API 的场景,使用 utf8_to_wstring 转换后调用 W 后缀版本:

cpp 复制代码
#ifdef _WIN32
std::wstring utf8_to_wstring(const std::string& utf8_str) {
    const int len = static_cast<int>(utf8_str.size());
    int wstr_size = MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), len, nullptr, 0);
    if (wstr_size == 0) return L"";
    std::wstring wstr(wstr_size, 0);
    MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), len, &wstr[0], wstr_size);
    return wstr;
}
#endif

使用示例:

cpp 复制代码
// 替换前
DWORD dwAttrib = GetFileAttributes(szPath);              // 依赖 UNICODE 宏

// 替换后
DWORD dwAttrib = GetFileAttributesW(utf8_to_wstring(szPath).c_str());

关于 std::filesystem::u8path

std::filesystem::u8path 是 C++17 引入的函数,专门用于从 UTF-8 字符串构造 std::filesystem::path。在 Windows 上,它会正确地将 UTF-8 转为 UTF-16 存储在 path 内部。

C++20 已将 u8path 标记为 deprecated ,推荐的替代方式是使用 char8_t 类型:

cpp 复制代码
// C++17
auto p = std::filesystem::u8path(utf8_string);

// C++20
auto p = std::filesystem::path(reinterpret_cast<const char8_t*>(utf8_string.c_str()));

如果项目使用 C++17 标准,u8path 是正确的选择。

实践中踩过的坑

MultiByteToWideChar 的 -1 参数与 std::wstring 的隐蔽 Bug

MultiByteToWideChar 的第 4 个参数(cbMultiByte)可以传 -1,表示输入是 \0 结尾的字符串,让函数自动计算长度。但它的返回值会包含末尾的 \0

cpp 复制代码
// "abc" 的 UTF-8 是 3 字节,但传 -1 时返回 4(包含 \0)
int wstr_size = MultiByteToWideChar(CP_UTF8, 0, "abc", -1, nullptr, 0);
// wstr_size == 4

如果直接用这个值构造 std::wstring

cpp 复制代码
std::wstring wstr(wstr_size, 0);  // wstr.size() == 4,内部: ['a','b','c','\0']
MultiByteToWideChar(CP_UTF8, 0, "abc", -1, &wstr[0], wstr_size);

wstrsize() 是 4 而不是 3。std::wstringsize() 之后还会自动维护一个 \0,所以内存中实际是 ['a', 'b', 'c', '\0', '\0']------尾部有两个 \0

wstr.c_str() 传给 Windows API 不会有问题(API 遇到第一个 \0 就停了),但以下场景会出 bug:

cpp 复制代码
wstr == L"abc"           // false!长度不同(4 vs 3)
wstr + L".txt"           // 拼接结果中间嵌入了 \0
std::wcout << wstr;      // 可能只输出到第一个 \0

正确做法是传入实际长度而非 -1:

cpp 复制代码
const int len = static_cast<int>(utf8_str.size());
int wstr_size = MultiByteToWideChar(CP_UTF8, 0, utf8_str.c_str(), len, nullptr, 0);
// 此时 wstr_size 不包含 \0,是正确的字符数

struct stat 与 struct _stat 的强转

在封装 utf8_stat 时,内部需要调用 _wstat,而 _wstat 的第二个参数类型是 struct _stat*。函数签名接受的却是 struct stat*。直接强转安全吗?

c 复制代码
int ret = _wstat(path_w, (struct _stat *)buf);  // 安全吗?

答案是安全的 。MSVC 的 UCRT 头文件 <sys/stat.h> 中,标准 stat() 函数的内联实现就是这么做的:

c 复制代码
// UCRT 源码(ucrt/sys/stat.h)
static __inline int __CRTDECL stat(const char* _FileName, struct stat* _Stat)
{
    _STATIC_ASSERT(sizeof(struct stat) == sizeof(struct _stat64i32));
    return _stat64i32(_FileName, (struct _stat64i32*)_Stat);   // ← 微软自己也在强转
}

微软用 _STATIC_ASSERT 在编译期验证了两个结构体大小一致,然后直接强转指针。这个模式是 CRT 官方的做法。

总结

Windows 的 UTF-8 路径问题,本质上是一个历史时序错位导致的工程问题:Windows 在 UTF-8 诞生之前就选择了 UTF-16 作为内核编码,而后来 UTF-8 成为了事实标准。

核心经验:

  1. Windows 上的 char* 不是 UTF-8 ------这是与所有类 Unix 平台最大的区别。CRT 内部虽然最终调用 W 版本,但 char*wchar_t* 的转换用的是 ACP(ANSI 代码页),不是 UTF-8
  2. UNICODE 宏只影响 Windows API 的宏展开,不影响 C/C++ 标准库 ------fopenifstream 不走 A/W 宏体系
  3. 设计降级路径很重要 ------不能假设输入一定是 UTF-8,通过 MB_ERR_INVALID_CHARS 检测编码合法性,失败时降级到 ANSI 方式
  4. MultiByteToWideChar-1 参数有坑 ------返回值包含 \0,与 std::wstring 配合时要用实际长度而非 -1
  5. 如果目标平台是 Windows 10 1903+,可以考虑通过 manifest 设置 UTF-8 代码页,从根本上解决问题

附录 A:受影响的完整 API 清单

C 标准库文件 I/O

函数 Windows 宽字符版本 说明
fopen() _wfopen() 打开文件,最常见的问题源
fopen_s() _wfopen_s() 安全版本
freopen() _wfreopen() 重新打开文件到已有 FILE*
remove() _wremove() 删除文件
rename() _wrename() 重命名文件
tmpnam() _wtmpnam() 生成临时文件名

注意:remove()rename() 需要人工区分------STL 容器的 std::remove() 不是文件操作。

C++ 文件流

函数 说明
std::ifstream(path) 构造函数传入 char*std::string
std::ofstream(path) 同上
std::fstream(path) 同上
.open(path) 上述三者的 .open() 方法

MSVC 有非标准扩展,支持 wchar_t* / std::wstring 作为构造参数,这不是标准 C++ 行为,不可移植。

POSIX / CRT 文件与目录操作

函数 Windows 宽字符版本 说明
stat() / _stat() _wstat() 获取文件元信息
access() / _access() _waccess() 检查文件可访问性
mkdir() / _mkdir() _wmkdir() 创建目录
rmdir() / _rmdir() _wrmdir() 删除目录
open() / _open() _wopen() 低级文件打开
_sopen() / _sopen_s() _wsopen() / _wsopen_s() 共享模式打开
opendir() / readdir() _wopendir() / _wreaddir() 遍历目录
realpath() / _fullpath() _wfullpath() 解析完整路径
getcwd() / _getcwd() _wgetcwd() 获取当前工作目录
chdir() / _chdir() _wchdir() 切换工作目录
unlink() / _unlink() _wunlink() 删除文件

C++ std::filesystem

std::filesystem::path(std::string) 构造函数在 Windows 上按 ACP 解码,不是 UTF-8 。所有接受 std::string 路径参数的 std::filesystem 函数(existscreate_directoriesremovefile_sizecopy_file 等)都会隐式构造 path,因此全部受影响

安全的构造方式:

用法 C++ 标准
std::filesystem::u8path(str) C++17(C++20 已 deprecated)
std::filesystem::path(std::u8string) C++20
std::filesystem::path(std::wstring) 所有版本(Windows 原生 UTF-16)

环境变量

函数 Windows 宽字符版本 说明
std::getenv() / getenv() _wgetenv() 返回值按系统 ANSI 代码页编码,不是 UTF-8

进程 / 命令执行

函数 说明
system() 命令字符串含非 ASCII 路径会失败
_popen() / popen() 同上,_wpopen() 为宽字符版本
execv() / execvp() 系列 POSIX 进程执行(Windows 上少用)

Windows API(A/W 版本问题)

建议始终显式使用 W 后缀版本,避免依赖 UNICODE 宏展开。

ANSI 版本 应使用的宽字符版本 说明
LoadLibraryA / LoadLibrary LoadLibraryW 加载动态库
LoadLibraryExA / LoadLibraryEx LoadLibraryExW 加载动态库(带标志)
GetModuleHandleA / GetModuleHandle GetModuleHandleW 获取已加载模块句柄
GetModuleFileNameA / GetModuleFileName GetModuleFileNameW 获取模块路径
CreateFileA / CreateFile CreateFileW 创建/打开文件句柄
DeleteFileA / DeleteFile DeleteFileW 删除文件
MoveFileA / MoveFileExA MoveFileW / MoveFileExW 移动/重命名
CopyFileA / CopyFile CopyFileW 复制文件
ReplaceFileA / ReplaceFile ReplaceFileW 原子替换文件
CreateDirectoryA / CreateDirectory CreateDirectoryW 创建目录
RemoveDirectoryA / RemoveDirectory RemoveDirectoryW 删除目录
GetFileAttributesA / GetFileAttributes GetFileAttributesW 获取文件属性
SetFileAttributesA / SetFileAttributes SetFileAttributesW 设置文件属性
FindFirstFileA / FindFirstFile FindFirstFileW 文件查找
FindNextFileA / FindNextFile FindNextFileW 文件查找(续)
GetFullPathNameA / GetFullPathName GetFullPathNameW 获取完整路径
GetCurrentDirectoryA / GetCurrentDirectory GetCurrentDirectoryW 获取当前目录
SetCurrentDirectoryA / SetCurrentDirectory SetCurrentDirectoryW 设置当前目录
GetTempPathA / GetTempPath GetTempPathW 获取临时目录
GetTempFileNameA / GetTempFileName GetTempFileNameW 生成临时文件名
SearchPathA / SearchPath SearchPathW 搜索路径
GetEnvironmentVariableA / GetEnvironmentVariable GetEnvironmentVariableW 获取环境变量
SetEnvironmentVariableA / SetEnvironmentVariable SetEnvironmentVariableW 设置环境变量
ExpandEnvironmentStringsA / ExpandEnvironmentStrings ExpandEnvironmentStringsW 展开环境变量字符串
CreateProcessA / CreateProcess CreateProcessW 创建进程
ShellExecuteA / ShellExecute ShellExecuteW 执行外部程序
SHGetFolderPathA / SHGetFolderPath SHGetFolderPathW 获取特殊文件夹路径
SHGetSpecialFolderPathA / SHGetSpecialFolderPath SHGetSpecialFolderPathW 获取特殊文件夹路径(简化版)
SHFileOperationA / SHFileOperation SHFileOperationW Shell 文件操作

附录 B:utf8_remove 与 utf8_stat 实现

c 复制代码
int utf8_remove(const char *path_utf8)
{
    if (!path_utf8) return -1;

#ifdef _WIN32
    int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path_utf8, -1, NULL, 0);
    if (n > 0) {
        wchar_t *path_w = (wchar_t *)calloc(n, sizeof(wchar_t));
        if (path_w) {
            MultiByteToWideChar(CP_UTF8, 0, path_utf8, -1, path_w, n);
            int ret = _wremove(path_w);
            free(path_w);
            if (ret == 0) return 0;
            return remove(path_utf8);
        }
    }
#endif
    return remove(path_utf8);
}

int utf8_stat(const char *path_utf8, struct stat *buf)
{
    if (!path_utf8 || !buf) return -1;

#ifdef _WIN32
    int n = MultiByteToWideChar(CP_UTF8, MB_ERR_INVALID_CHARS, path_utf8, -1, NULL, 0);
    if (n > 0) {
        wchar_t *path_w = (wchar_t *)calloc(n, sizeof(wchar_t));
        if (path_w) {
            MultiByteToWideChar(CP_UTF8, 0, path_utf8, -1, path_w, n);
            int ret = _wstat(path_w, (struct _stat *)buf);
            free(path_w);
            return ret;
        }
    }
#endif
    return stat(path_utf8, buf);
}
相关推荐
minji...2 小时前
Linux 网络套接字编程(三)UDP服务器与客户端实现:Windows与Linux通信,新增字典翻译功能的 UDP 通信
linux·服务器·开发语言·网络·windows·算法·udp
艾莉丝努力练剑2 小时前
【Linux网络】计算机网络入门:网络通信——跨主机的进程间通信(IPC)与Socket编程入门
linux·运维·服务器·网络·c++·学习·计算机网络
炘爚2 小时前
深入解析C++多态:虚函数与动态联编
开发语言·c++·多态·虚函数
代钦塔拉2 小时前
Qt调试技巧:解决DLL输入点错误指南
c++·qt
熬夜敲代码的猫2 小时前
C++:模板精讲
c++·算法·模板
tankeven2 小时前
C++ 学习杂记04:std::vector 类
c++
兩尛2 小时前
C++面向对象和类相关
java·c++·面试
lclin_20202 小时前
大恒Windows上GigE网口相机固定IP设置
windows·机器视觉·工业相机·大恒相机·galaxysdk
H Journey2 小时前
windows下通过VSCode使用vcpkg 管理库开发opencv项目环境搭建
windows·vscode·opencv·vcpkg