目前我在使用 cx_Freeze 对 python 程序打包成可执行文件,但是 cx_Freeze 的核心功能是将 Python 脚本、Python 解释器以及所有依赖的库文件打包到一个独立的可执行文件(如 Windows 下的 .exe 文件)或一个包含所有文件的目录中。打包后的文件中包含的是 Python 的字节码 .pyc 文件,这个文件是可以被反编译回近似的源代码的。

通过使用 Cython 将 Python 源代码编译成 C 语言,然后再生成本地二进制文件(.pyd)。然后正常使用 cx_Freeze 打包,这样做可以极大地提高代码的保护级别,防止被轻易逆向。

安装必要的 Python 包

需要安装 Cython 和 Numpy,在终端或命令行中运行:

pip install Cython numpy

安装 C/C++ 编译器

Cython 将 Python 代码转换成 C 代码,但最终需要一个 C 编译器来将 C 代码编译成机器码。这是最关键的一步。

对于 Windows 用户:

  • 访问 Visual Studio 下载页面:https://visualstudio.microsoft.com/zh-hans/downloads/
  • 在 "Tools for Visual Studio" (所有下载 -> Visual Studio 工具) 中找到并下载 "Build Tools for Visual Studio"。
  • 运行安装程序,在 "工作负荷" 标签页中,勾选 "使用 C++ 的桌面开发"。
  • 点击安装。安装完成后,您可能需要重启电脑。

对于 macOS 用户:
打开终端并运行 xcode-select --install。这会安装苹果的命令行开发者工具,其中包含了 Clang 编译器。

对于 Linux 用户 (例如 Ubuntu/Debian):
打开终端并运行 sudo apt update && sudo apt install build-essential

修改 setup.py 文件

如果项目目录结构如下:

/my_project
    |-- main.py           # 你的主程序文件
    |-- /src              # 你的其他模块目录
    |   |-- func1.py
    |   |-- func2.py
    |-- setup.py            # cx_Freeze 的配置文件

根据以上目录结构,下面是一个配置文件示例:

from cx_Freeze import setup, Executable
import sys, os, io

# =============================================================================
# Cython 自动化编译集成
# =============================================================================
try:
    from Cython.Build import cythonize
    from setuptools import Extension
    import numpy
except ImportError:
    print("\n[错误] 缺少必要的库。请先安装 Cython 和 Numpy:")
    print("pip install Cython numpy")
    sys.exit(1)

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')  # 修复非中文系统打包时报错

# 增加递归调用深度限制
sys.setrecursionlimit(1500)

# 定义相关路径
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
ENTRY_POINT = "main.py"

# 检查是否为打包命令
build_commands = {"build", "bdist_msi", "bdist_dmg", "bdist_mac"}
is_building = any(cmd in sys.argv for cmd in build_commands)

# --- Cython 编译配置 ---
# 此函数会自动查找 src 目录下的所有 .py 文件并准备将它们编译
def find_extensions_to_compile(dir_path="src"):
    """Find all .py files to be compiled by Cython."""
    extensions = []
    # 添加 numpy 的头文件路径,这对于编译依赖 numpy/scipy 的代码至关重要
    numpy_include = numpy.get_include()
    
    for root, dirs, files in os.walk(dir_path):
        for file in files:
            # 我们只编译 .py 文件,但跳过 __init__.py 文件
            if file.endswith(".py") and file != "__init__.py":
                path = os.path.join(root, file)
                # 将文件路径转换为模块路径,例如 "src/library/function.py" -> "src.library.function"
                module_path = path.replace(os.sep, '.')[:-3]
                
                extensions.append(
                    Extension(
                        name=module_path,
                        sources=[path],
                        include_dirs=[numpy_include]  # 包含 numpy 头文件
                    )
                )
    print(f"--- 找到 {len(extensions)} 个模块准备通过 Cython 编译...")
    return extensions

# 仅在执行打包命令时才准备编译列表
extensions = []
if is_building:
    # 1. 编译 src 目录下的所有模块
    extensions.extend(find_extensions_to_compile("src"))
    
    # 2. 明确地将 main.py 添加到编译列表
    print(f"--- 添加入口文件 '{ENTRY_POINT}' 到编译列表...")
    main_extension = Extension(
        name="main",  # 编译后的模块名
        sources=[ENTRY_POINT],
        include_dirs=[numpy.get_include()]
    )
    extensions.append(main_extension)

# =============================================================================
# cx_Freeze 配置
# =============================================================================

# 安装依赖
build_exe_options = {
    "packages": [
    ],
    "excludes": ["email"] + [ext.name for ext in extensions], # 排除 Cython 编译的模块
    "include_files": [
        ],
    "includes": [],

    # 性能优化选项
    "optimize": 2,           # 使用Python优化
    "include_msvcr": False,  # 不包含MSVC运行库
}

# 基础设置
base = "Win32GUI" if sys.platform == "win32" else None

directory_table = [
    # ...
]

shortcut_table = [
    (
        # ...
    ),
    (
        # ...
    ),
]

msi_data = {"Directory": directory_table, "Shortcut": shortcut_table}

bdist_msi_options = {
    # ...
}

executables = [
    Executable(
        "main.py", # 入口文件 依然调用 py 程序,cx_Freeze 会自动识别并使用加密后的文件
        # ...
    )
]

# =============================================================================
# 清理函数
# =============================================================================
def cleanup_generated_files():
    """查找并删除由 Cython 生成的所有 .c 文件。"""
    print("\n--- 正在运行清理程序:删除生成的 C 文件... ---")
    for root, dirs, files in os.walk(ROOT_DIR):
        # 避免进入不相关的目录
        if 'myenv' in root or '.git' in root or 'build' in root or 'dist' in root:
            continue
        for file in files:
            if file.endswith('.c'):
                file_path = os.path.join(root, file)
                try:
                    os.remove(file_path)
                    print(f"--- 已删除: {file_path}")
                except OSError as e:
                    print(f"--- 删除失败 {file_path}: {e}")

# =============================================================================
# 执行打包
# =============================================================================

try:
    setup(
        # ...
        # 关键步骤:将找到的 .py 文件交给 Cythonize 进行编译
        ext_modules=cythonize(
            extensions,
            compiler_directives={'language_level': "3"}, # 使用 Python 3 语法
            quiet=True # 减少不必要的编译输出
        ) if is_building else [],
        # ...
    )
finally:
    # 只有在执行打包命令时才运行清理
    if is_building:
        cleanup_generated_files()

运行打包命令打包即可,如:

python setup.py bdist_msi

检查加密情况

安装完成后,进入安装路径的 Lib - site-packages 文件夹,会看到加密后的 .pyd 程序文件。.pyd 文件是 Windows 上的二进制动态链接库,本质上和 .dll 文件一样。如果加密失败:您会在这里看到 .pyc 文件或者甚至原始的 .py 文件。

标签:无

你的评论