杰瑞科技汇

Python py2exe打包如何处理依赖的DLL?

Of course! Using py2exe to handle DLLs is a common point of confusion, but it's manageable. The core idea is that py2exe tries to be smart and bundle the necessary DLLs automatically, but you often need to give it hints for external libraries, specific DLLs, or to control the output.

Python py2exe打包如何处理依赖的DLL?-图1
(图片来源网络,侵删)

Here's a comprehensive guide covering the most common scenarios, from basic usage to advanced DLL handling.


The Basics: How py2exe Finds DLLs

By default, py2exe does a pretty good job:

  1. It scans your imported Python modules.
  2. It finds the C extensions (.pyd files, which are DLLs) that those modules depend on.
  3. It bundles those .pyd and their required DLLs into your dist folder.

Example: A Simple Script with Pillow

Let's say you have a script that uses the Pillow library to generate an image.

Python py2exe打包如何处理依赖的DLL?-图2
(图片来源网络,侵删)

app.py

import sys
import os
from PIL import Image, ImageDraw
def main():
    # Create a simple image
    img = Image.new('RGB', (200, 100), color = 'red')
    d = ImageDraw.Draw(img)
    d.text((10,10), "Hello py2exe!", fill=(255,255,0))
    # Save the image
    img.save("hello.png")
    print("Image saved as hello.png")
    print("Press Enter to exit...")
    input()
if __name__ == "__main__":
    main()

setup.py

from distutils.core import setup
import py2exe
setup(
    name="MyApp",
    version="0.1",
    console=["app.py"], # This is a console application
)

To build:

python setup.py py2exe

After running this, you'll find a dist folder. Inside, you'll see app.exe, pythonXX.dll, and a library.zip containing your Python code. You'll also see DLLs related to Pillow, like libjpeg-9.dll, libpng16-16.dll, zlib1.dll, etc., because py2exe detected them as dependencies of the PIL module.

Python py2exe打包如何处理依赖的DLL?-图3
(图片来源网络,侵删)

You can now run dist/app.exe on another Windows machine with Python installed, and it will work.


Common DLL Scenarios and Solutions

This is where you need to take more control. Here are the most frequent situations you'll encounter.

Scenario 1: Bundling an External DLL (e.g., a game asset library)

Imagine you have a custom DLL mygame_engine.dll that your Python script needs. py2exe won't know about it automatically.

Solution: Use the data_files option.

The data_files option is for including non-Python files that your application needs. It's the standard way to handle external DLLs, images, config files, etc.

setup.py (Modified)

from distutils.core import setup
import py2exe
# Define the path to your external DLL
# This assumes the DLL is in the same folder as setup.py
dll_path = "mygame_engine.dll"
setup(
    name="MyApp",
    version="0.1",
    console=["app.py"],
    # Use data_files to include the DLL
    # The format is a list of tuples: (destination_directory, [list_of_files])
    data_files=[("", [dll_path])], # "" means put it in the root of the dist folder
)

Now, when you run python setup.py py2exe, mygame_engine.dll will be copied directly into the dist folder alongside your app.exe.

Scenario 2: Forcing py2exe to Include a Specific DLL

Sometimes, a DLL is a dependency of a dependency, and py2exe's scanner might miss it. You can force it to be included.

Solution: Use the dll_excludes or includes option.

  • includes: Forces py2exe to include a specific module (and its DLLs). This is the most common and recommended method.
  • dll_excludes: Explicitly tells py2exe not to bundle a specific DLL, which is useful for avoiding conflicts.

Example: Forcing inclusion of PyQt5's QtWebEngineCore.dll

If your PyQt5 app uses QtWebEngine, py2exe might not bundle all its DLLs by default. You can force it.

setup.py (Modified)

from distutils.core import setup
import py2exe
import sys
# A list of modules you want to ensure are included
# py2exe will find their dependencies automatically.
includes = [
    'PyQt5.QtWebEngineCore', # The main module
    'PyQt5.QtWebEngine',     # Often needed as well
    # Add any other specific modules from PyQt5 you use
]
setup(
    name="MyPyQtApp",
    version="0.1",
    console=["app.py"],
    options={"py2exe": {
        "includes": includes,
        # You can also exclude DLLs that cause problems
        # "dll_excludes": ["MSVCP90.dll"], # Example: exclude a specific C runtime
    }},
)

Scenario 3: Controlling DLL Output with bundle_files

By default, py2exe bundles all your Python code into a single library.zip file. However, the DLLs are placed loose in the dist folder. You can change this.

Solution: Use the bundle_files option.

  • bundle_files = 1: Puts everything (your code and all DLLs) into a single .exe file. This is very clean but can have a slight startup performance penalty and might be flagged by some antivirus as "packed".
  • bundle_files = 3 (default): Puts your code into library.zip and DLLs loose in the dist folder. This is the standard, most compatible method.

setup.py (Modified for a single .exe)

from distutils.core import setup
import py2exe
setup(
    name="MyApp",
    version="0.1",
    console=["app.py"],
    options={"py2exe": {
        "bundle_files": 1, # Bundle everything into one .exe file
        "compressed": 1,   # Compress the library.zip
    }},
)

Important Note: When using bundle_files = 1, you must also include the pythonXX.dll inside the executable. py2exe does this automatically.


Troubleshooting DLL Issues

Problem: The procedure entry point ... could not be located in ... DLL

This usually means a DLL is missing from the dist folder.

  1. Identify the missing DLL: Look at the error message. It will tell you which DLL is missing.
  2. Find where it lives: Search for the DLL on your development machine (e.g., using Windows Search).
  3. Bundle it:
    • If it's a dependency of a Python module, use the includes list in setup.py to force the inclusion of that module.
    • If it's a truly external DLL, use data_files to copy it into the dist folder.

Problem: Error loading ...dll or "Not a valid Win32 application"

This can happen if you mix 32-bit and 64-bit code.

  • Check your Python: Is your Python interpreter 32-bit or 64-bit? (Run python -c "import struct; print(struct.calcsize('P') * 8)").
  • Check your DLLs: Right-click the DLL in the dist folder -> Properties -> "Details" tab. Look at "File version" and "Original file name". A 64-bit DLL will often have "64" in its name or properties.
  • Consistency is key: Your Python, your script, and all your DLLs must be the same architecture (all 32-bit or all 64-bit).

Problem: Antivirus Flags Your .exe

This is extremely common because py2exe (especially with bundle_files=1) creates a packed executable.

  • This is a false positive. The antivirus doesn't know what the executable does, only that it's compressed and contains many embedded DLLs.
  • Solution: You can try to submit the file to the antivirus vendor as a false positive. For personal use, you might add an exception. For distribution, you may need to find a different packaging tool like PyInstaller or Nuitka, which sometimes produce less "suspicious" executables, or look into code-signing your application.

A Complete, Robust Example

Let's combine everything into a final setup.py for a complex application.

app.py

# This script uses a standard library (os) and an external library (requests)
import os
import requests
def main():
    print("Hello from py2exe!")
    print("Current working dir:", os.getcwd())
    print("\nFetching data from httphttpbin.org...")
    try:
        response = requests.get('https://httpbin.org/get', timeout=5)
        print(f"Status Code: {response.status_code}")
        print(f"Response text (first 50 chars): {response.text[:50]}...")
    except Exception as e:
        print(f"An error occurred: {e}")
    print("\nPress Enter to exit.")
    input()
if __name__ == "__main__":
    main()

setup.py

from distutils.core import setup
import py2exe
import sys
# --- Configuration ---
# 1. List of modules to explicitly include to ensure their DLLs are bundled.
#    This is crucial for libraries like requests, numpy, PyQt, etc.
includes = [
    'requests',          # The requests library itself
    'idna',              # A dependency of requests
    'chardet',           # A dependency of requests
    'certifi',           # A dependency of requests
    'urllib3',           # A dependency of requests
    # Add any other specific modules you use
]
# 2. List of DLLs to explicitly exclude (if any).
#    This is useful if a DLL is causing a conflict or is already present
#    on the target system (e.g., a specific C++ runtime).
dll_excludes = [
    # 'MSVCP90.dll', # Example: exclude a problematic C runtime
]
# 3. List of files to bundle as data (e.g., config files, images, external DLLs).
#    The format is a list of tuples: (destination_directory, [list_of_files])
#    Use "" for the root of the dist folder.
data_files = [
    # ("config", ["my_app_config.ini"]), # Example: put a config file in a 'config' subdir
    # ("", ["my_custom_library.dll"]),   # Example: put a DLL in the root
]
# --- Setup Command ---
setup(
    name="MyRobustApp",
    version="1.0",
    description="A robust py2exe example.",
    console=["app.py"], # Use ['app.py'] for a console app, or 'windows' for a GUI app
    # py2exe specific options
    options={
        "py2exe": {
            # Force inclusion of modules
            "includes": includes,
            # Exclude specific DLLs
            "dll_excludes": dll_excludes,
            # Controls how Python code is bundled.
            # 1 = single .exe, 3 = .exe + library.zip (default)
            "bundle_files": 3,
            # Compress the library.zip file
            "compressed": 1,
            # Specify a custom icon for the .exe
            # "icon_resources": [(1, "my_icon.ico")]
        }
    },
    # Bundle non-Python files
    data_files=data_files,
)

Final Recommendation

While py2exe is a classic and powerful tool, many developers today prefer PyInstaller. It's often easier to use, has better support for modern Python packages, and produces fewer "false positive" antivirus alerts. The principles are similar (includes, excludes, data files), but the syntax in the spec file or command-line arguments can be more straightforward.

However, if you're working on an older codebase or have specific needs, py2exe is still a perfectly viable solution.

分享:
扫描分享到社交APP
上一篇
下一篇