为什么将python程序编译成pyd?

      因为.pyd文件是将.py文件编译为.c,再把.c编译为.pyd,反编译只能编译为.c文件,几乎没有可读性,能较好的保证应用代码的安全性及私密性。针对需要将python应用发布为客户端或在非安全环境下使用。

使用cython编译pyd

cython安装

pip install Cython
# 或
python -m pip install Cython

cython使用

在需要编译的.py平级目录创建build.py ,配置需要导出的python模块及文件路径。

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [Extension('main', ['main.py'],),  # 模块,对应模块文件。位置为当前文件的相对位置
               Extension('all.auth_util', ['all/auth_util.py'],),
               Extension('all.cfg', ['all/cfg.py'],),
               Extension('all.config_utils', ['all/config_utils.py'],),
               Extension('all.exe_main', ['all/exe_main.py'],),
               Extension('all.loading_mask_dlg', ['all/loading_mask_dlg.py'],),
               Extension('all.main_dlg', ['all/main_dlg.py'],),
               Extension('all.welcome_dlg', ['all/welcome_dlg.py'],),
               Extension('all.process_work_runner', ['all/process_work_runner.py'],),
               Extension('all.register_dlg', ['all/register_dlg.py'],),
               Extension('all.res_utils', ['all/res_utils.py'])
               ]

setup(
    name="runExe",
    version='1.0',
    cmdclass={'build_ext': build_ext},
    packages=['all'],
    package_dir={'': 'build_cython'},  # 这里指定编译后结果存放的路径
    ext_modules=ext_modules,

)

编译

build.py配置完成后,命令行进入到build.py存放的路劲下,执行

python build.py build_ext --inplace

# --inplace 表示当前运行目录在 build.py处

如上所示,编译结果将存储在/build_cython 下

 *python程序打包成pyd之后,只能以python模块的方式调用,所以,我在这里添加了一个run.py

# coding=utf-8
from main import runMain   # 这个模块是打包成.pyd后的主程序入口

if __name__ == '__main__':
    runMain()

到这里,已经完成了安全的python程序发布,很好的保护了我们的源码。美中不足是用户只能以命令行的方式启动程序。

python run.py

如果你的用户是专业人员,那么到这一步就可以了。以下可以忽略。然而,如果你的应用可能是客户端程序,或者其他面向普通用户的程序,那么,你就需要让用户以更习惯的方式打开程序。

比如说:exe 

EXE

python应用程序可以使用pyinstaller直接打包成exe.但它会将所有依赖和引用打入exe,导致exe启动缓慢,直观感受是双击exe之后有可能要去泡个茶,不清楚状况的用户会任务是程序未响应,从而反复双击exe。

我的应用因为引用了第三方模块,且第三方模块不能在独立python环境下运行。所以我选择使用c++制作exe,通过命令行调用python主程序运行。因为python主程序本身有可能加载较慢,为了避免这个问题,我们可以使用  (Splash Screen)启动画面。在双击之后做快速视觉响应。我这里界面框架使用了qt5

#include <QtWidgets/QApplication>
#include <QSplashScreen>
#include <QPixmap>
#include <QProcess>


int main(int argc, char* argv[])
{
	QApplication a(argc, argv);

	QPixmap pixmap("res/l.png");
	QSplashScreen screen(pixmap);
	//screen.showMessage(QStringLiteral("正在努力加载资源,请稍等..."),Qt::AlignBottom, QColor("#ffffff"));
	//screen.setStyleSheet("font-family:'微软雅黑';font-size:14px;");
	screen.show();    

	QProcess p(0);
	p.start("cmd.exe /C python run.py"); //也可使用startDetached()使python主程序与当前exe进程分离
	p.waitForStarted();

    ……

	return a.exec();
}

启动画面有了,也能成功通过命令行调起python主程序了。

这里省略了exe进程和主程序之间通讯的代码。就是当python主程序成功运行后,关闭当前exe创建的进程,及启动画面等。可以考虑使用共享文件锁的方式通讯。