├── resource.rc ├── appicon.ico ├── PyStand.int ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── PyStand.h ├── README.md ├── .github └── workflows │ └── build.yml └── PyStand.cpp /resource.rc: -------------------------------------------------------------------------------- 1 | IDI_ICON1 ICON DISCARDABLE "appicon.ico" 2 | 3 | -------------------------------------------------------------------------------- /appicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/skywind3000/PyStand/HEAD/appicon.ico -------------------------------------------------------------------------------- /PyStand.int: -------------------------------------------------------------------------------- 1 | # vim: set ts=4 sw=4 tw=0 et ft=python : 2 | import os 3 | import sys 4 | 5 | msg = 'Hello from %s'%(os.path.abspath(__file__)) 6 | print(msg) 7 | print() 8 | 9 | for path in sys.path: 10 | print('>', path) 11 | 12 | os.MessageBox(msg) 13 | 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | *.pch 35 | *.sdf 36 | 37 | /.root 38 | 39 | __pycache__/* 40 | *.pyc 41 | *.pyo 42 | 43 | /runtime/* 44 | /source/runtime/* 45 | 46 | /source/objs/* 47 | /source/Debug/* 48 | /source/Release/* 49 | /source/x64/* 50 | /source/.vs/* 51 | /source/build/* 52 | 53 | /objs/* 54 | /Debug/* 55 | /Release/* 56 | /x64/* 57 | /.vs/* 58 | /build/* 59 | 60 | /.tasks 61 | /site-packages/* 62 | /publish/* 63 | /test/ztest_*.cpp 64 | /ztest_*.cpp 65 | 66 | /.vscode/* 67 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project (PyStand LANGUAGES CXX RC) 4 | 5 | option(PYSTAND_CONSOLE "Build PyStand as a console application." OFF) 6 | 7 | # sources 8 | set(sources 9 | PyStand.cpp 10 | resource.rc 11 | ) 12 | 13 | if (PYSTAND_CONSOLE) 14 | add_executable(PyStand ${sources}) 15 | target_compile_definitions(PyStand PRIVATE PYSTAND_CONSOLE) 16 | else() 17 | add_executable(PyStand WIN32 ${sources}) 18 | endif() 19 | 20 | # static link 21 | if (CMAKE_GENERATOR MATCHES "Visual Studio") 22 | set(CMAKE_FIND_LIBRARY_SUFFIXES ".lib") 23 | target_compile_options(PyStand 24 | PUBLIC 25 | $<$:/MTd> 26 | $<$:/MT> 27 | $<$:/MT> 28 | ) 29 | target_link_options(PyStand PUBLIC /INCREMENTAL:NO /NODEFAULTLIB:MSVCRT) 30 | else() 31 | # for mingw 32 | set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS_RELEASE} -s") 33 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_RELEASE} -s") 34 | target_link_libraries(PyStand 35 | -static 36 | shlwapi 37 | winmm 38 | ) 39 | endif() 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Linwei 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PyStand.h: -------------------------------------------------------------------------------- 1 | //===================================================================== 2 | // 3 | // PyStand.h - 4 | // 5 | // Created by skywind on 2022/02/03 6 | // Last Modified: 2022/02/03 23:39:52 7 | // 8 | //===================================================================== 9 | #ifndef _PYSTAND_H_ 10 | #define _PYSTAND_H_ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | //--------------------------------------------------------------------- 20 | // PyStand 21 | //--------------------------------------------------------------------- 22 | class PyStand 23 | { 24 | public: 25 | virtual ~PyStand(); 26 | PyStand(const wchar_t *runtime); 27 | PyStand(const char *runtime); 28 | 29 | public: 30 | std::wstring Ansi2Unicode(const char *text); 31 | 32 | int RunString(const wchar_t *script); 33 | int RunString(const char *script); 34 | 35 | int DetectScript(); 36 | 37 | protected: 38 | bool CheckEnviron(const wchar_t *rtp); 39 | bool LoadPython(); 40 | 41 | 42 | protected: 43 | typedef int (*t_Py_Main)(int argc, wchar_t **argv); 44 | t_Py_Main _Py_Main; 45 | 46 | protected: 47 | HINSTANCE _hDLL; 48 | std::wstring _cwd; // current working directory 49 | std::wstring _args; // arguments 50 | std::wstring _pystand; // absolute path of pystand 51 | std::wstring _runtime; // absolute path of embedded python runtime 52 | std::wstring _home; // home directory of PyStand.exe 53 | std::wstring _script; // init script like PyStand.int or PyStand.py 54 | std::vector _argv; 55 | std::vector _py_argv; 56 | std::vector _py_args; 57 | }; 58 | 59 | 60 | #endif 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PyStand 2 | 3 | Python 独立部署环境。Python 3.5 以后,Windows 下面都有一个 Embedded Python 的独立 Python 运行环境,这个 PyStand 就是配合 Embedded Python 使用的。 4 | 5 | ## 特性介绍 6 | 7 | - 使用 PyStand + PyQt5 精简版发布 PyQt 程序,打包大小只有 14MB。 8 | - 使用 PyStand 发布普通 Python 程序,打包大小仅 5MB。 9 | 10 | ## 功能说明 11 | 12 | - Windows 下独立 Python 环境的启动器。 13 | - 自动加载 `PyStand.exe` 同级目录下面 `runtime` 子目录内的 Embedded Python。 14 | - 自动启动 `PyStand.exe` 同级目录下面的 `PyStand.int` 程序(Python 代码)。 15 | - 如果改名,会加载对应名称的 `.int` 文件,比如改为 `MyDemo.exe` 就会加载 `MyDemo.int`。 16 | - 窗口程序,无 Console,但是如果在 cmd.exe 内运行,可以看到 print 的内容。 17 | - 会自动添加 `PyStand.exe` 同级目录下的 `site-packages` 目录,库可以放到里面。 18 | 19 | ## 使用方式 20 | 21 | - 用 CMake 生成 `PyStand.exe` (或者到 Release 里下个现成的)。 22 | - 下载 Python Embedded 版本,放到 `PyStand.exe` 所在目录的 runtime 子目录内。 23 | - 注意 Python Embedded 如果是 32 位,PyStand 配置 CMake 时也需要指明 `-A Win32`。 24 | - 在 `PyStand.exe` 所在目录创建 Python 源代码 PyStand.int。 25 | - 双击 `PyStand.exe` 就会运行 `PyStand.int` 里的代码。 26 | - 可以编译成命令行版方便调试,CMake 的时候加 `-DPYSTAND_CONSOLE=ON` 即可。 27 | 28 | ## 常见问题 29 | 30 | ### 安装依赖 31 | 32 | 用一个同 Embedded Python 相同版本的 Python 做一个 venv,然后 `pip` 独立安装好模块后将 site-packages 内对应的包复制到 `PyStand.exe` 的 `site-packages` 下直接使用。 33 | 34 | ### 查看错误 35 | 36 | 如果在 `cmd.exe` 内部运行 `PyStand.exe` 可以看到标准输出和标准错误。不过推荐的做法是 PyStand.int 里尽量精简,比如: 37 | 38 | ```python 39 | import main 40 | main.main() 41 | ``` 42 | 43 | 把你的主程序写到 `main.py` 里面,用你常规方式把程序调试通顺了,然后再在 `PyStand.int` 里 `import` 一下即可,实在 `PyStand.int` 有错误,再到命令行下面去运行 `PyStand.exe` 查看错误。 44 | 45 | ### MessageBox 46 | 47 | PyStand 添加了一个 `os.MessageBox(msg, title)` 的接口,可以用来简单显示个对话框。 48 | 49 | ### 更换图标 50 | 51 | 可以替换 `appicon.ico` 文件并重新编译 `PyStand.exe` ,或者使用 `Resource Hacker` 直接 52 | 替换 `Release` 内下载的 `PyStand.exe` 文件的程序图标。 53 | 54 | ### 脚本组织 55 | 56 | 可以在 `PyStand.exe` 同级目录新建一个 `script` 文件夹,将脚本放进去,`PyStand.int` 里面就是添加一下 `sys.path` 然后 `import` 即可。 57 | 58 | 发布打包时将 `script` 文件夹用 `zip` 压缩成 `script.egg` 文件,`PyStand.int` 里检测到该文件存在就加入到 `sys.path`,然后再 `import`。 59 | 60 | ### 静态入口 61 | 62 | 部分网友有个需求,担心用户把可执行文件改名,但 `.int` 文件没改名导致出错的问题,因此增加了一个名为 `_pystand_static.int` 的入口文件,在 `PyStand` 主程序启动时,如果检测到在主程序同目录下存在该文件,则会优先加载,不存在才回去找 `PyStand.int` 文件。 63 | 64 | 这样主程序就允许随意改名了,只要把入口写在 `_pystand_static.int` 中即可。 65 | 66 | ## 使用例子 67 | 68 | 这个回答里我说了详细的用法以及 PyInstaller 的优缺点: 69 | 70 | - [Skywind Inside:打包 PyQt 应用的最佳方案是什么?](https://skywind.me/blog/archives/3002) 71 | 72 | 更多的用法可以见 Release 下面的例子。 73 | 74 | ## 常见问题 75 | 76 | 其他用户使用中碰到什么疑问?如何解决的? 77 | 78 | - [Wiki:常见问题](https://github.com/skywind3000/PyStand/wiki/Frequently-Asked-Questions) 79 | 80 | 碰到疑问不妨快速过一下。 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: PyStand 2 | 3 | on: 4 | push: 5 | pull_request: 6 | types: [ opened, synchronize, reopened, ready_for_review ] 7 | 8 | env: 9 | BUILD_TYPE: Release 10 | 11 | jobs: 12 | build-msvc: 13 | if: >- 14 | github.event.pull_request.draft == false 15 | 16 | runs-on: windows-latest 17 | 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | arch: [ Win32, x64 ] 22 | subsystem: [ GUI, CLI ] 23 | include: 24 | - generator: Visual Studio 17 2022 25 | 26 | steps: 27 | - name: Checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: Configure 31 | run: > 32 | cmake 33 | -G "${{ matrix.generator }}" 34 | -A ${{ matrix.arch }} 35 | -B build 36 | ${{ matrix.subsystem == 'GUI' && '-DPYSTAND_CONSOLE=OFF' || '-DPYSTAND_CONSOLE=ON' }} 37 | 38 | - name: Build 39 | run: | 40 | cmake --build build --config ${{ env.BUILD_TYPE }} 41 | 42 | - name: Upload artifacts 43 | uses: actions/upload-artifact@v4 44 | with: 45 | name: PyStand-${{ matrix.arch }}-${{ matrix.subsystem }} 46 | path: build/${{ env.BUILD_TYPE }} 47 | 48 | build-gcc: 49 | if: >- 50 | github.event.pull_request.draft == false 51 | 52 | runs-on: windows-latest 53 | 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | sys: [ mingw32, mingw64 ] 58 | subsystem: [ GUI, CLI ] 59 | 60 | steps: 61 | - name: Checkout 62 | uses: actions/checkout@v4 63 | 64 | - name: Setup WinLibs 65 | run: | 66 | if ( '${{ matrix.sys }}' -eq 'mingw32' ) 67 | { 68 | Invoke-WebRequest -Uri https://github.com/brechtsanders/winlibs_mingw/releases/download/7.5.0-7.0.0-r1/winlibs-i686-posix-dwarf-gcc-7.5.0-mingw-w64-7.0.0-r1.7z -OutFile ${{ matrix.sys }}.7z 69 | } 70 | else 71 | { 72 | Invoke-WebRequest -Uri https://github.com/brechtsanders/winlibs_mingw/releases/download/7.5.0-7.0.0-r1/winlibs-x86_64-posix-seh-gcc-7.5.0-mingw-w64-7.0.0-r1.7z -OutFile ${{ matrix.sys }}.7z 73 | } 74 | 7z x ${{ matrix.sys }}.7z 75 | "${{ github.workspace }}\${{ matrix.sys }}\bin" >> $env:GITHUB_PATH 76 | 77 | - name: Configure 78 | run: > 79 | cmake 80 | -G "MinGW Makefiles" 81 | -B build 82 | -DCMAKE_BUILD_TYPE=${{ env.BUILD_TYPE }} 83 | ${{ matrix.subsystem == 'GUI' && '-DPYSTAND_CONSOLE=OFF' || '-DPYSTAND_CONSOLE=ON' }} 84 | 85 | - name: Build 86 | run: | 87 | cmake --build build --config ${{ env.BUILD_TYPE }} 88 | 89 | - name: Upload artifacts 90 | uses: actions/upload-artifact@v4 91 | with: 92 | name: PyStand-${{ matrix.sys }}-${{ matrix.subsystem }} 93 | path: build\PyStand.exe 94 | 95 | release: 96 | if: startsWith(github.ref, 'refs/tags/') 97 | 98 | runs-on: windows-latest 99 | 100 | needs: [ build-msvc, build-gcc ] 101 | 102 | steps: 103 | - name: Download artifacts 104 | uses: actions/download-artifact@v4 105 | 106 | - name: List downloaded files 107 | run: ls -R 108 | 109 | - name: Create archives 110 | run: > 111 | 7z 112 | a 113 | PyStand-v${{ github.ref_name }}-exe.zip 114 | PyStand-Win32-CLI 115 | PyStand-Win32-GUI 116 | PyStand-x64-CLI 117 | PyStand-x64-GUI 118 | PyStand-mingw32-CLI 119 | PyStand-mingw32-GUI 120 | PyStand-mingw64-CLI 121 | PyStand-mingw64-GUI 122 | 123 | - name: Release 124 | uses: softprops/action-gh-release@v2 125 | env: 126 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 127 | with: 128 | generate_release_notes: true 129 | files: | 130 | PyStand*.zip 131 | -------------------------------------------------------------------------------- /PyStand.cpp: -------------------------------------------------------------------------------- 1 | //===================================================================== 2 | // 3 | // PyStand.cpp - 4 | // 5 | // Created by skywind on 2022/02/03 6 | // Last Modified: 2024/06/19 11:16 7 | // 8 | //===================================================================== 9 | #ifdef _MSC_VER 10 | #define _CRT_SECURE_NO_WARNINGS 1 11 | #endif 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "PyStand.h" 20 | 21 | #ifdef _MSC_VER 22 | #pragma comment(lib, "shlwapi.lib") 23 | #endif 24 | 25 | 26 | //--------------------------------------------------------------------- 27 | // dtor 28 | //--------------------------------------------------------------------- 29 | PyStand::~PyStand() 30 | { 31 | FreeLibrary(_hDLL); 32 | } 33 | 34 | 35 | //--------------------------------------------------------------------- 36 | // ctor 37 | //--------------------------------------------------------------------- 38 | PyStand::PyStand(const wchar_t *runtime) 39 | { 40 | _hDLL = NULL; 41 | _Py_Main = NULL; 42 | if (CheckEnviron(runtime) == false) { 43 | exit(1); 44 | } 45 | if (LoadPython() == false) { 46 | exit(2); 47 | } 48 | } 49 | 50 | 51 | //--------------------------------------------------------------------- 52 | // ctor for ansi 53 | //--------------------------------------------------------------------- 54 | PyStand::PyStand(const char *runtime) 55 | { 56 | _hDLL = NULL; 57 | _Py_Main = NULL; 58 | std::wstring rtp = Ansi2Unicode(runtime); 59 | if (CheckEnviron(rtp.c_str()) == false) { 60 | exit(1); 61 | } 62 | if (LoadPython() == false) { 63 | exit(2); 64 | } 65 | } 66 | 67 | 68 | //--------------------------------------------------------------------- 69 | // char to wchar_t 70 | //--------------------------------------------------------------------- 71 | std::wstring PyStand::Ansi2Unicode(const char *text) 72 | { 73 | int len = (int)strlen(text); 74 | std::wstring wide; 75 | int require = MultiByteToWideChar(CP_ACP, 0, text, len, NULL, 0); 76 | if (require > 0) { 77 | wide.resize(require); 78 | MultiByteToWideChar(CP_ACP, 0, text, len, &wide[0], require); 79 | } 80 | return wide; 81 | } 82 | 83 | 84 | //--------------------------------------------------------------------- 85 | // init: _args, _argv, _cwd, _pystand, _home, _runtime, 86 | //--------------------------------------------------------------------- 87 | bool PyStand::CheckEnviron(const wchar_t *rtp) 88 | { 89 | // init: _args, _argv 90 | LPWSTR *argvw; 91 | int argc; 92 | _args = GetCommandLineW(); 93 | argvw = CommandLineToArgvW(_args.c_str(), &argc); 94 | if (argvw == NULL) { 95 | MessageBoxA(NULL, "Error in CommandLineToArgvW()", "ERROR", MB_OK); 96 | return false; 97 | } 98 | _argv.resize(argc); 99 | for (int i = 0; i < argc; i++) { 100 | _argv[i] = argvw[i]; 101 | } 102 | LocalFree(argvw); 103 | 104 | // init: _cwd (current working directory) 105 | wchar_t path[MAX_PATH + 10]; 106 | GetCurrentDirectoryW(MAX_PATH + 1, path); 107 | _cwd = path; 108 | 109 | // init: _pystand (full path of PyStand.exe) 110 | GetModuleFileNameW(NULL, path, MAX_PATH + 1); 111 | #if 0 112 | wsprintf(path, L"e:\\github\\tools\\pystand\\pystand.exe"); 113 | #endif 114 | _pystand = path; 115 | 116 | // init: _home 117 | int size = (int)wcslen(path); 118 | for (; size > 0; size--) { 119 | if (path[size - 1] == L'/') break; 120 | if (path[size - 1] == L'\\') break; 121 | } 122 | path[size] = 0; 123 | SetCurrentDirectoryW(path); 124 | GetCurrentDirectoryW(MAX_PATH + 1, path); 125 | _home = path; 126 | SetCurrentDirectoryW(_cwd.c_str()); 127 | 128 | // init: _runtime (embedded python directory) 129 | bool abspath = false; 130 | if (wcslen(rtp) >= 3) { 131 | if (rtp[1] == L':') { 132 | if (rtp[2] == L'/' || rtp[2] == L'\\') 133 | abspath = true; 134 | } 135 | } 136 | if (abspath == false) { 137 | _runtime = _home + L"\\" + rtp; 138 | } 139 | else { 140 | _runtime = rtp; 141 | } 142 | GetFullPathNameW(_runtime.c_str(), MAX_PATH + 1, path, NULL); 143 | _runtime = path; 144 | 145 | // check home 146 | std::wstring check = _runtime; 147 | if (!PathFileExistsW(check.c_str())) { 148 | std::wstring msg = L"Missing embedded Python3 in:\n" + check; 149 | MessageBoxW(NULL, msg.c_str(), L"ERROR", MB_OK); 150 | return false; 151 | } 152 | 153 | // check python3.dll 154 | std::wstring check2 = _runtime + L"\\python3.dll"; 155 | if (!PathFileExistsW(check2.c_str())) { 156 | std::wstring msg = L"Missing python3.dll in:\r\n" + check; 157 | MessageBoxW(NULL, msg.c_str(), L"ERROR", MB_OK); 158 | return false; 159 | } 160 | 161 | // setup environment 162 | SetEnvironmentVariableW(L"PYSTAND", _pystand.c_str()); 163 | SetEnvironmentVariableW(L"PYSTAND_HOME", _home.c_str()); 164 | SetEnvironmentVariableW(L"PYSTAND_RUNTIME", _runtime.c_str()); 165 | 166 | // unnecessary to init PYSTAND_SCRIPT here. 167 | #if 0 168 | SetEnvironmentVariableW(L"PYSTAND_SCRIPT", _script.c_str()); 169 | #endif 170 | 171 | #if 0 172 | wprintf(L"%s - %s\n", _pystand.c_str(), path); 173 | MessageBoxW(NULL, _pystand.c_str(), _home.c_str(), MB_OK); 174 | #endif 175 | 176 | return true; 177 | } 178 | 179 | 180 | //--------------------------------------------------------------------- 181 | // load python 182 | //--------------------------------------------------------------------- 183 | bool PyStand::LoadPython() 184 | { 185 | std::wstring runtime = _runtime; 186 | std::wstring previous; 187 | 188 | // save current directory 189 | wchar_t path[MAX_PATH + 10]; 190 | GetCurrentDirectoryW(MAX_PATH + 1, path); 191 | previous = path; 192 | 193 | // python dll must be load under "runtime" 194 | SetCurrentDirectoryW(runtime.c_str()); 195 | SetDllDirectoryW(runtime.c_str()); 196 | 197 | auto pydll = runtime + L"\\python3.dll"; 198 | // LoadLibrary 199 | _hDLL = (HINSTANCE)LoadLibraryW(pydll.c_str()); 200 | if (_hDLL) { 201 | _Py_Main = (t_Py_Main)GetProcAddress(_hDLL, "Py_Main"); 202 | } 203 | 204 | // restore director 205 | SetCurrentDirectoryW(previous.c_str()); 206 | 207 | if (_hDLL == NULL) { 208 | std::wstring msg = L"Cannot load python3.dll from:\r\n" + runtime; 209 | MessageBoxW(NULL, msg.c_str(), L"ERROR", MB_OK); 210 | return false; 211 | } 212 | else if (_Py_Main == NULL) { 213 | std::wstring msg = L"Cannot find Py_Main() in:\r\n"; 214 | msg += pydll; 215 | MessageBoxW(NULL, msg.c_str(), L"ERROR", MB_OK); 216 | return false; 217 | } 218 | return true; 219 | } 220 | 221 | 222 | //--------------------------------------------------------------------- 223 | // run string 224 | //--------------------------------------------------------------------- 225 | int PyStand::RunString(const wchar_t *script) 226 | { 227 | if (_Py_Main == NULL) { 228 | return -1; 229 | } 230 | int hr = 0; 231 | int i; 232 | _py_argv.resize(0); 233 | // init arguments 234 | _py_argv.push_back(_argv[0]); 235 | _py_argv.push_back(L"-I"); 236 | _py_argv.push_back(L"-s"); 237 | _py_argv.push_back(L"-S"); 238 | _py_argv.push_back(L"-c"); 239 | _py_argv.push_back(script); 240 | for (i = 1; i < (int)_argv.size(); i++) { 241 | _py_argv.push_back(_argv[i]); 242 | } 243 | // finalize arguments 244 | _py_args.resize(0); 245 | for (i = 0; i < (int)_py_argv.size(); i++) { 246 | _py_args.push_back((wchar_t*)_py_argv[i].c_str()); 247 | } 248 | hr = _Py_Main((int)_py_args.size(), &_py_args[0]); 249 | return hr; 250 | } 251 | 252 | 253 | //--------------------------------------------------------------------- 254 | // run ansi string 255 | //--------------------------------------------------------------------- 256 | int PyStand::RunString(const char *script) 257 | { 258 | std::wstring text = Ansi2Unicode(script); 259 | return RunString(text.c_str()); 260 | } 261 | 262 | 263 | 264 | //--------------------------------------------------------------------- 265 | // static init script 266 | //--------------------------------------------------------------------- 267 | #ifndef PYSTAND_STATIC_NAME 268 | #define PYSTAND_STATIC_NAME "_pystand_static.int" 269 | #endif 270 | 271 | 272 | //--------------------------------------------------------------------- 273 | // LoadScript() 274 | //--------------------------------------------------------------------- 275 | int PyStand::DetectScript() 276 | { 277 | // init: _script (init script like PyStand.int or PyStand.py) 278 | int size = (int)_pystand.size() - 1; 279 | for (; size >= 0; size--) { 280 | if (_pystand[size] == L'.') break; 281 | } 282 | if (size < 0) size = (int)_pystand.size(); 283 | std::wstring main = _pystand.substr(0, size); 284 | std::vector exts; 285 | std::vector scripts; 286 | _script.clear(); 287 | #if !(PYSTAND_DISABLE_STATIC) 288 | std::wstring test; 289 | test = _home + L"\\" + Ansi2Unicode(PYSTAND_STATIC_NAME); 290 | if (PathFileExistsW(test.c_str())) { 291 | _script = test; 292 | } 293 | #endif 294 | if (_script.empty()) { 295 | exts.push_back(L".int"); 296 | exts.push_back(L".py"); 297 | exts.push_back(L".pyw"); 298 | for (int i = 0; i < (int)exts.size(); i++) { 299 | std::wstring test = main + exts[i]; 300 | scripts.push_back(test); 301 | if (PathFileExistsW(test.c_str())) { 302 | _script = test; 303 | break; 304 | } 305 | } 306 | if (_script.size() == 0) { 307 | std::wstring msg = L"Can't find either of:\r\n"; 308 | for (int j = 0; j < (int)scripts.size(); j++) { 309 | msg += scripts[j] + L"\r\n"; 310 | } 311 | MessageBoxW(NULL, msg.c_str(), L"ERROR", MB_OK); 312 | return -1; 313 | } 314 | } 315 | SetEnvironmentVariableW(L"PYSTAND_SCRIPT", _script.c_str()); 316 | return 0; 317 | } 318 | 319 | 320 | //--------------------------------------------------------------------- 321 | // init script 322 | //--------------------------------------------------------------------- 323 | const char *init_script = 324 | "import sys\n" 325 | "import os\n" 326 | "import copy\n" 327 | "import site\n" 328 | "PYSTAND = os.environ['PYSTAND']\n" 329 | "PYSTAND_HOME = os.environ['PYSTAND_HOME']\n" 330 | "PYSTAND_RUNTIME = os.environ['PYSTAND_RUNTIME']\n" 331 | "PYSTAND_SCRIPT = os.environ['PYSTAND_SCRIPT']\n" 332 | "sys.path_origin = [n for n in sys.path]\n" 333 | "sys.PYSTAND = PYSTAND\n" 334 | "sys.PYSTAND_HOME = PYSTAND_HOME\n" 335 | "sys.PYSTAND_SCRIPT = PYSTAND_SCRIPT\n" 336 | "def MessageBox(msg, info = 'Message'):\n" 337 | " import ctypes\n" 338 | " ctypes.windll.user32.MessageBoxW(None, str(msg), str(info), 0)\n" 339 | " return 0\n" 340 | "os.MessageBox = MessageBox\n" 341 | #ifndef PYSTAND_CONSOLE 342 | "try:\n" 343 | " fd = os.open('CONOUT$', os.O_RDWR | os.O_BINARY)\n" 344 | " fp = os.fdopen(fd, 'w')\n" 345 | " sys.stdout = fp\n" 346 | " sys.stderr = fp\n" 347 | " attached = True\n" 348 | "except Exception as e:\n" 349 | " attached = False\n" 350 | " try:\n" 351 | " fp = open(os.devnull, 'w', errors='ignore')\n" 352 | " sys.stdout = fp\n" 353 | " sys.stderr = fp\n" 354 | " except:\n" 355 | " pass\n" 356 | #endif 357 | "for n in ['.', 'lib', 'site-packages', 'runtime']:\n" 358 | " test = os.path.abspath(os.path.join(PYSTAND_HOME, n))\n" 359 | " if os.path.exists(test):\n" 360 | " site.addsitedir(test)\n" 361 | "sys.argv = [PYSTAND_SCRIPT] + sys.argv[1:]\n" 362 | "text = open(PYSTAND_SCRIPT, 'rb').read()\n" 363 | "environ = {'__file__': PYSTAND_SCRIPT, '__name__': '__main__'}\n" 364 | "environ['__package__'] = None\n" 365 | #ifndef PYSTAND_CONSOLE 366 | "try:\n" 367 | " code = compile(text, PYSTAND_SCRIPT, 'exec')\n" 368 | " exec(code, environ)\n" 369 | "except Exception:\n" 370 | " if attached:\n" 371 | " raise\n" 372 | " import traceback, io\n" 373 | " sio = io.StringIO()\n" 374 | " traceback.print_exc(file = sio)\n" 375 | " os.MessageBox(sio.getvalue(), 'Error')\n" 376 | #else 377 | "code = compile(text, PYSTAND_SCRIPT, 'exec')\n" 378 | "exec(code, environ)\n" 379 | #endif 380 | ""; 381 | 382 | 383 | //--------------------------------------------------------------------- 384 | // main 385 | //--------------------------------------------------------------------- 386 | 387 | //! flag: -static 388 | //! src: 389 | //! link: stdc++, shlwapi, resource.o 390 | //! prebuild: windres resource.rc -o resource.o 391 | //! mode: win 392 | //! int: objs 393 | 394 | #ifdef PYSTAND_CONSOLE 395 | int main() 396 | #else 397 | int WINAPI 398 | WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR args, int show) 399 | #endif 400 | { 401 | PyStand ps("runtime"); 402 | if (ps.DetectScript() != 0) { 403 | return 3; 404 | } 405 | #ifndef PYSTAND_CONSOLE 406 | if (AttachConsole(ATTACH_PARENT_PROCESS)) { 407 | freopen("CONOUT$", "w", stdout); 408 | freopen("CONOUT$", "w", stderr); 409 | int fd = _fileno(stdout); 410 | if (fd >= 0) { 411 | std::string fn = std::to_string(fd); 412 | SetEnvironmentVariableA("PYSTAND_STDOUT", fn.c_str()); 413 | } 414 | fd = _fileno(stdin); 415 | if (fd >= 0) { 416 | std::string fn = std::to_string(fd); 417 | SetEnvironmentVariableA("PYSTAND_STDIN", fn.c_str()); 418 | } 419 | } 420 | #endif 421 | int hr = ps.RunString(init_script); 422 | // printf("finalize\n"); 423 | return hr; 424 | } 425 | 426 | 427 | --------------------------------------------------------------------------------