2525#include < string>
2626#include < thread>
2727
28+ namespace
29+ {
30+ // Convert UTF-8 text from Flutter/Dart to UTF-16 for Windows APIs.
31+ std::wstring Utf8ToWide (const std::string &value)
32+ {
33+ if (value.empty ())
34+ {
35+ return L" " ;
36+ }
37+ UINT codepage = CP_UTF8;
38+ DWORD flags = MB_ERR_INVALID_CHARS;
39+ int size = MultiByteToWideChar (codepage, flags, value.data (),
40+ static_cast <int >(value.size ()), nullptr , 0 );
41+ if (size <= 0 )
42+ {
43+ codepage = CP_ACP;
44+ flags = 0 ;
45+ size = MultiByteToWideChar (codepage, flags, value.data (),
46+ static_cast <int >(value.size ()), nullptr , 0 );
47+ }
48+ if (size <= 0 )
49+ {
50+ return L" " ;
51+ }
52+ std::wstring wide (size, L' \0 ' );
53+ MultiByteToWideChar (codepage, flags, value.data (),
54+ static_cast <int >(value.size ()), wide.data (), size);
55+ return wide;
56+ }
57+
58+ // Convert UTF-16 Windows strings to UTF-8 for logs/interop.
59+ std::string WideToUtf8 (const std::wstring &value)
60+ {
61+ if (value.empty ())
62+ {
63+ return " " ;
64+ }
65+ int size = WideCharToMultiByte (CP_UTF8, 0 , value.data (),
66+ static_cast <int >(value.size ()), nullptr , 0 ,
67+ nullptr , nullptr );
68+ if (size <= 0 )
69+ {
70+ return " " ;
71+ }
72+ std::string utf8 (size, ' \0 ' );
73+ WideCharToMultiByte (CP_UTF8, 0 , value.data (),
74+ static_cast <int >(value.size ()), utf8.data (), size,
75+ nullptr , nullptr );
76+ return utf8;
77+ }
78+
79+ // Set environment variables using Unicode-safe Windows APIs.
80+ void SetEnvWide (const std::wstring &key, const std::wstring &value)
81+ {
82+ if (key.empty ())
83+ {
84+ return ;
85+ }
86+ _wputenv_s (key.c_str (), value.c_str ());
87+ SetEnvironmentVariableW (key.c_str (), value.c_str ());
88+ }
89+
90+ // Join paths without narrowing, preserving non-ASCII characters.
91+ std::wstring JoinPaths (const std::vector<std::wstring> &paths, wchar_t sep)
92+ {
93+ std::wstring joined;
94+ for (size_t i = 0 ; i < paths.size (); ++i)
95+ {
96+ joined += paths[i];
97+ if (i + 1 < paths.size ())
98+ {
99+ joined += sep;
100+ }
101+ }
102+ return joined;
103+ }
104+ }
105+
28106namespace serious_python_windows
29107{
30108
@@ -134,55 +212,49 @@ namespace serious_python_windows
134212 return ;
135213 }
136214
137- std::string exe_dir = std::filesystem::path (exe_path).parent_path ().string ();
138- std::string app_dir = std::filesystem::path (app_path).parent_path ().string ();
215+ // Treat Dart strings as UTF-8 to avoid lossy conversions.
216+ std::filesystem::path exe_path_fs (Utf8ToWide (exe_path));
217+ std::filesystem::path app_path_fs (Utf8ToWide (app_path));
218+ std::wstring exe_dir = exe_path_fs.parent_path ().wstring ();
219+ std::wstring app_dir = app_path_fs.parent_path ().wstring ();
139220
140221 printf (" exePath: %s\n " , exe_path.c_str ());
141- printf (" exeDir: %s\n " , exe_dir.c_str ());
222+ printf (" exeDir: %s\n " , WideToUtf8 ( exe_dir) .c_str ());
142223 printf (" appPath: %s\n " , app_path.c_str ());
143224
144- std::vector<std::string > python_paths;
225+ std::vector<std::wstring > python_paths;
145226
146227 // add user module paths to the top
147228 for (const auto &item : module_paths)
148229 {
149230 if (auto str_value = std::get_if<std::string>(&item))
150231 {
151232 printf (" module_path: %s\n " , str_value->c_str ());
152- python_paths.push_back (*str_value);
233+ python_paths.push_back (Utf8ToWide ( *str_value) );
153234 }
154235 }
155236
156237 // add system paths
157238 python_paths.push_back (app_dir);
158- python_paths.push_back (app_dir + " \\ __pypackages__" );
159- python_paths.push_back (exe_dir + " \\ site-packages" );
160- python_paths.push_back (exe_dir + " \\ DLLs" );
161- python_paths.push_back (exe_dir + " \\ Lib" );
162- python_paths.push_back (exe_dir + " \\ Lib\\ site-packages" );
163-
164- std::string python_path;
165- for (int i = 0 ; i < python_paths.size (); i++)
166- {
167- python_path += python_paths[i];
168- if (i < python_paths.size () - 1 )
169- { // Don't add separator after the last element
170- python_path += " ;" ;
171- }
172- }
173-
174- printf (" PYTHONPATH: %s\n " , python_path.c_str ());
175-
176- // set python-related env vars
177- _putenv_s (" PYTHONINSPECT" , " 1" );
178- _putenv_s (" PYTHONDONTWRITEBYTECODE" , " 1" );
179- _putenv_s (" PYTHONNOUSERSITE" , " 1" );
180- _putenv_s (" PYTHONUNBUFFERED" , " 1" );
181- _putenv_s (" LC_CTYPE" , " UTF-8" );
182- _putenv_s (" PYTHONHOME" , exe_dir.c_str ());
183- _putenv_s (" PYTHONPATH" , python_path.c_str ());
184-
185- // set user environment variables
239+ python_paths.push_back (app_dir + L" \\ __pypackages__" );
240+ python_paths.push_back (exe_dir + L" \\ site-packages" );
241+ python_paths.push_back (exe_dir + L" \\ DLLs" );
242+ python_paths.push_back (exe_dir + L" \\ Lib" );
243+ python_paths.push_back (exe_dir + L" \\ Lib\\ site-packages" );
244+
245+ std::wstring python_path = JoinPaths (python_paths, L' ;' );
246+ printf (" PYTHONPATH: %s\n " , WideToUtf8 (python_path).c_str ());
247+
248+ // Set Python-related env vars using wide APIs so Unicode paths survive.
249+ SetEnvWide (L" PYTHONINSPECT" , L" 1" );
250+ SetEnvWide (L" PYTHONDONTWRITEBYTECODE" , L" 1" );
251+ SetEnvWide (L" PYTHONNOUSERSITE" , L" 1" );
252+ SetEnvWide (L" PYTHONUNBUFFERED" , L" 1" );
253+ SetEnvWide (L" LC_CTYPE" , L" UTF-8" );
254+ SetEnvWide (L" PYTHONHOME" , exe_dir);
255+ SetEnvWide (L" PYTHONPATH" , python_path);
256+
257+ // Set user-provided env vars as UTF-16 for Windows.
186258 for (const auto &kv : env_vars)
187259 {
188260 auto key = kv.first ;
@@ -191,7 +263,7 @@ namespace serious_python_windows
191263 auto str_value = std::get_if<std::string>(&value))
192264 {
193265 printf (" env_var: %s=%s\n " , str_key->c_str (), str_value->c_str ());
194- _putenv_s (str_key-> c_str ( ), str_value-> c_str ( ));
266+ SetEnvWide ( Utf8ToWide (*str_key ), Utf8ToWide (*str_value ));
195267 }
196268 }
197269
@@ -256,7 +328,9 @@ namespace serious_python_windows
256328 Py_Initialize ();
257329
258330 FILE *file;
259- errno_t err = fopen_s (&file, appPath.c_str (), " r" );
331+ // Use wide fopen to open Unicode paths on Windows.
332+ std::wstring appPathWide = Utf8ToWide (appPath);
333+ errno_t err = _wfopen_s (&file, appPathWide.c_str (), L" r" );
260334 if (err == 0 && file != NULL )
261335 {
262336 PyRun_SimpleFileEx (file, appPath.c_str (), 1 );
0 commit comments