cython 生成的 C 文件如何编译成可执行的 exe 文件?

我们知道 cython 可以生成 pyd 文件,有两个作用,一是提高速度,二是对代码进行保护,增加反编译的难度。

但是总是有这样的需求,你需要把 python 写的程序让非技术人员来使用,最简单的方法就是他们的电脑上安装 Python,然后安装依赖包,但这样会非常麻烦,很多非技术人员不知道什么是 Python,更不会使用 pip,就算你写好了 bat 文件自动运行,他们也不放心。

最好的方式就是编译成 exe 文件,我们知道有 py2exe,pyinstaller 来做这些事情,但这两个工具的本质是打包,将 Python 环境也源代码一起打包,由于并不是编译,因此可以使用 7z 压缩软件来提取源文件,从而轻松反编译,起不到保护代码的目的。

我就在网上寻找 cython to exe 的解决方案,找到两个有价值的文章:

https://stackoverflow.com/questions/2581784/can-cython-compile-to-an-exe

https://www.jianshu.com/p/670a7bed72b2

于是就开始行动。

首先说下我的电脑已安装的软件:

  • Python 3.7.4 64位,已添加环境变量,其他均为默认设置
  • Microsoft Visual Studio2019 社区版,安装了 C/C++ 信 windows 10 SDK,安装这个的主要目的就是为了可以生成 pyd,cython 需要 vs2019。

然后编写一个 test.py 供测试使用,内容如下:

# cython: language_level=3
name = input("please input you name:")
print(f"hello {name}")

先生成 c 文件,命名如下:

set PROJECT_NAME=test
set PYTHON_DIR=C:\Users\Administrator\AppData\Local\Programs\Python\Python37
%PYTHON_DIR%\python -m cython --embed -o %PROJECT_NAME%.c %PROJECT_NAME%.py

这一步非常顺利,执行成功。

下一步,将 c 文件编译成 exe,我尝试了两种方法:

尝试使用 gcc

一开始按网上的方法,报各种找不到文件的错误,我一一添加 include 目录,lib 目录,最终不在提示找到文件。最终的 bat 文件内容如下:

set PROJECT_NAME=test
set CLEXE="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\bin\Hostx64\x64"
set PYTHON_DIR=C:\Users\Administrator\AppData\Local\Programs\Python\Python37
set INCLUDE="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\include"
set INCLUDE2="C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt"
set INCLUDE3="C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared"

set LIB_PATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\lib\onecore\x64"
set LIB_PATH2="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64"
set LIB_PATH3="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\ucrt_enclave\x64"

::gcc -Os -I %PYTHON_DIR%\include -I %INCLUDE% -I %INCLUDE2% -I %INCLUDE3% -o %PROJECT_NAME%.exe %PROJECT_NAME%.c -lpython37 -lm -L %PYTHON_DIR%\libs -L %LIB_PATH% -L %LIB_PATH2% -L %LIB_PATH3% -DMS_WIN64
%

当我满怀期待的执行这个 bat 文件时,它却给我沉重一击:

test.c:3059:124: error: expected ')' before 'digits'
              return (Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
                                                                                                                            ^
test.c:3064:49: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
                                                 ^
test.c:3064:87: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
                                                                                       ^
test.c:3064:125: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (size_t)digits[0]));
                                                                                                                             ^
test.c:3069:50: error: expected ')' before 'digits'
              return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (si
ze_t)digits[0]));
                                                  ^
test.c:3069:88: error: expected ')' before 'digits'
              return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (si
ze_t)digits[0]));
                                                                                        ^
test.c:3069:126: error: expected ')' before 'digits'
              return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (si
ze_t)digits[0]));
                                                                                                                              ^
test.c:3069:164: error: expected ')' before 'digits'
              return (Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (si
ze_t)digits[0]));

     ^
test.c:3074:51: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (s
ize_t)digits[0]));
                                                   ^
test.c:3074:89: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (s
ize_t)digits[0]));
                                                                                         ^
test.c:3074:127: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (s
ize_t)digits[0]));
                                                                                                                               ^
test.c:3074:165: error: expected ')' before 'digits'
              return -(Py_ssize_t) (((((((((size_t)digits[3]) << PyLong_SHIFT) | (size_t)digits[2]) << PyLong_SHIFT) | (size_t)digits[1]) << PyLong_SHIFT) | (s
ize_t)digits[0]));

      ^
test.c: At top level:
test.c:3091:56: error: expected declaration specifiers or '...' before 'size_t'
 static CYTHON_INLINE PyObject * __Pyx_PyInt_FromSize_t(size_t ival) {
                                                        ^

在此,我表示,我不服,算了,我使用 cl.exe,于是尝试了网上的方法二:

尝试使用 cl.exe

同样花了很长时间整理出以下 bat 命令

set PROJECT_NAME=test
set CLEXE="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\bin\Hostx64\x64"
set PYTHON_DIR=C:\Users\Administrator\AppData\Local\Programs\Python\Python37
set INCLUDE="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\include"
set INCLUDE2="C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt"
set INCLUDE3="C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared"

set LIB_PATH="C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\lib\onecore\x64"
set LIB_PATH2="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64"
set LIB_PATH3="C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\ucrt_enclave\x64"

%CLEXE%\cl.exe /nologo /Ox /MD /W3 /GS- /DNDEBUG -I %PYTHON_DIR%\include -I%INCLUDE% -I%INCLUDE2% -I%INCLUDE3% /Tc test.c /link /OUT:"test.exe" /SUBSYSTEM:CONSOLE /MACHINE:X64 /LIBPATH:%LIB_PATH% /LIBPATH:%LIB_PATH2% /LIBPATH:%LIB_PATH3% /LIBPATH:%PYTHON_DIR%\libs

同样满怀期待,结果却致命一击:

"C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Tools\MSVC\14.23.28105\bin\Hostx64\x64"\cl.exe /n
ologo /Ox /MD /W3 /GS- /DNDEBUG -I C:\Users\Administrator\AppData\Local\Programs\Python\Python37\include -I"C:\Program Files (x86)\Microsoft Visual Studio\2019
\Community\VC\Tools\MSVC\14.23.28105\include" -I"C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt" -I"C:\Program Files (x86)\Windows Kits\10\In
clude\10.0.18362.0\shared" /Tc test.c /link /OUT:"test.exe" /SUBSYSTEM:CONSOLE /MACHINE:X64 /LIBPATH:"C:\Program Files (x86)\Microsoft Visual Studio\2019\Commu
nity\VC\Tools\MSVC\14.23.28105\lib\onecore\x64" /LIBPATH:"C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x64" /LIBPATH:"C:\Program Files (x86)\Wind
ows Kits\10\Lib\10.0.18362.0\ucrt_enclave\x64" /LIBPATH:C:\Users\Administrator\AppData\Local\Programs\Python\Python37\libs
test.c
  正在创建库 test.lib 和对象 test.exp
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 _set_app_type,该符号在函数 "int __cdecl pre_c_initialization(void)" (?pre_c_initialization@@YAHX
Z) 中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 _configure_wide_argv,该符号在函数 "int __cdecl pre_c_initialization(void)" (?pre_c_initializatio
n@@YAHXZ) 中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 _initialize_wide_environment,该符号在函数 "int __cdecl pre_c_initialization(void)" (?pre_c_initi
alization@@YAHXZ) 中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 _get_initial_wide_environment,该符号在函数 "int __cdecl __scrt_common_main_seh(void)" (?__scrt_c
ommon_main_seh@@YAHXZ) 中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 _set_fmode,该符号在函数 "int __cdecl pre_c_initialization(void)" (?pre_c_initialization@@YAHXZ)
中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 __p___argc,该符号在函数 "int __cdecl __scrt_common_main_seh(void)" (?__scrt_common_main_seh@@YAH
XZ) 中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 __p___wargv,该符号在函数 "int __cdecl __scrt_common_main_seh(void)" (?__scrt_common_main_seh@@YA
HXZ) 中被引用
MSVCRT.lib(exe_wmain.obj) : error LNK2019: 无法解析的外部符号 __p__commode,该符号在函数 "int __cdecl pre_c_initialization(void)" (?pre_c_initialization@@YAHXZ
) 中被引用
MSVCRT.lib(utility.obj) : error LNK2019: 无法解析的外部符号 _configure_narrow_argv,该符号在函数 "public: static int __cdecl __scrt_narrow_argv_policy::configu
re_argv(void)" (?configure_argv@__scrt_narrow_argv_policy@@SAHXZ) 中被引用
MSVCRT.lib(utility.obj) : error LNK2019: 无法解析的外部符号 _initialize_narrow_environment,该符号在函数 "public: static int __cdecl __scrt_narrow_environment_
policy::initialize_environment(void)" (?initialize_environment@__scrt_narrow_environment_policy@@SAHXZ) 中被引用
test.exe : fatal error LNK1120: 10 个无法解析的外部命令

我表示,我输了,Cython 生成 exe,我不想再玩了,如果有高手生成成功了,请告诉我方法,定感谢。

推荐阅读更多精彩内容