I'm using PyInstaller to package my PyQt app, which loads a DLL using
ctypes.cdll. The resulting.exeworks fine on my development machine, but on other computers I get aFileNotFoundError: Could not find module './FSetAccessibility.dll' (or one of its dependencies). How do I fix this?
This is a common stumbling block when packaging Python applications that use ctypes to load DLL files. The app runs perfectly in your IDE and even works as a packaged .exe on your own machine — but fails on someone else's computer. The reason comes down to how PyInstaller handles (or doesn't handle) external DLLs, and how file paths change once your app is bundled.
Let's walk through why this happens and how to fix it.
Why Does This Happen?
When you run your Python script normally, the working directory is typically the folder where your script lives. If you have a DLL sitting next to your script and you load it with a relative path like ./FSetAccessibility.dll, Python finds it without any trouble.
But when PyInstaller bundles your application into an .exe, two things change:
-
The working directory is no longer what you expect. When a user double-clicks your
.exe, the current working directory could be anything — the Desktop, the Start Menu folder, or somewhere else entirely. A relative path like./FSetAccessibility.dllwon't point where you think it does. -
PyInstaller doesn't automatically detect DLLs loaded via
ctypes. PyInstaller analyzes your imports and bundles the Python packages it finds, but it can't easily detect DLLs that you load dynamically at runtime withctypes.cdll. Those files simply won't be included in the bundle unless you explicitly tell PyInstaller to include them.
There's also a third, subtler issue: your DLL might depend on other DLLs that happen to be installed on your development machine (perhaps in C:\Windows\System32) but aren't present on the target machine. The error message even hints at this with "(or one of its dependencies)."
Fixing the DLL Path for Bundled Apps
When PyInstaller creates a one-file (--onefile) bundle, it extracts everything to a temporary directory at runtime. PyInstaller sets a special attribute sys._MEIPASS that points to this temporary directory. For one-folder builds, this path points to the folder containing the .exe.
You need to use this path to locate your DLL at runtime. Here's a helper function that gives you the correct base path whether you're running from source or from a PyInstaller bundle:
import sys
import os
def get_base_path():
"""Return the base path for resources, works both in development and PyInstaller bundle."""
if getattr(sys, 'frozen', False):
# Running as a PyInstaller bundle
return sys._MEIPASS
else:
# Running in normal Python environment
return os.path.dirname(os.path.abspath(__file__))
The sys.frozen attribute is set to True when your code is running inside a PyInstaller bundle. By checking for it, you can branch your path logic accordingly.
Now, use this base path when loading your DLL:
import ctypes
base_path = get_base_path()
dll_path = os.path.join(base_path, "FSetAccessibility.dll")
my_dll = ctypes.cdll.LoadLibrary(dll_path)
Using the full absolute path (instead of a relative ./ path) ensures the DLL is found regardless of what the current working directory happens to be.
Telling PyInstaller to Include the DLL
Since PyInstaller won't auto-detect DLLs loaded through ctypes, you need to explicitly tell it to include the file. There are two ways to do this.
Option 1: Using the Command Line
You can add the DLL as a binary file using the --add-binary flag:
pyinstaller --add-binary "FSetAccessibility.dll;." your_app.py
The format is source;destination. The . means "place it in the root of the bundle directory." On macOS and Linux, use a colon : instead of a semicolon ; as the separator.
Option 2: Using a .spec File
If you're using a .spec file (which is recommended for anything beyond simple projects), add your DLL to the binaries list:
a = Analysis(
['your_app.py'],
binaries=[('FSetAccessibility.dll', '.')],
# ... rest of your Analysis options
)
After editing your .spec file, rebuild with:
pyinstaller your_app.spec
Handling DLL Dependencies
Sometimes the DLL you're loading has its own dependencies on other DLLs. On your development machine these might be installed system-wide, but they won't be present on a clean machine.
To find out what other DLLs your file depends on, you can use a tool like Dependencies (a modern replacement for the classic Dependency Walker). Open your DLL in Dependencies, and it will show you the full tree of DLLs it needs to load.
Any non-system DLLs that show up in this tree need to be bundled alongside your app as well, using the same --add-binary approach.
You can also check for missing dependencies programmatically on Windows using ctypes.util.find_library:
from ctypes.util import find_library
result = find_library("FSetAccessibility")
print(result) # Will print the full path if found, or None if missing
If this returns None on the target machine, the DLL isn't on the system search path, confirming that it needs to be bundled.
A Complete Working Example
Here's a full example showing how to structure a PyQt6 application that loads a DLL via ctypes, with proper path handling for PyInstaller:
import sys
import os
import ctypes
from PyQt6.QtWidgets import QApplication, QMainWindow, QLabel, QVBoxLayout, QWidget
def get_base_path():
"""Return the base path for resources, works in dev and PyInstaller bundle."""
if getattr(sys, "frozen", False):
return sys._MEIPASS
else:
return os.path.dirname(os.path.abspath(__file__))
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("DLL Loading Example")
layout = QVBoxLayout()
# Load the DLL using the correct base path
base_path = get_base_path()
dll_path = os.path.join(base_path, "FSetAccessibility.dll")
status_label = QLabel()
if os.path.exists(dll_path):
try:
my_dll = ctypes.cdll.LoadLibrary(dll_path)
status_label.setText(f"DLL loaded successfully from:\n{dll_path}")
except OSError as e:
status_label.setText(f"DLL found but failed to load:\n{e}")
else:
status_label.setText(f"DLL not found at:\n{dll_path}")
layout.addWidget(status_label)
container = QWidget()
container.setLayout(layout)
self.setCentralWidget(container)
app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()
And the corresponding PyInstaller command to build it:
pyinstaller --onefile --add-binary "FSetAccessibility.dll;." your_app.py
Or as a .spec file:
# your_app.spec
a = Analysis(
['your_app.py'],
binaries=[('FSetAccessibility.dll', '.')],
datas=[],
hiddenimports=[],
hookspath=[],
runtime_hooks=[],
excludes=[],
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
name='your_app',
console=False,
)
Quick Checklist
If you're still running into issues after following the steps above, work through this checklist:
- Is the DLL included in the bundle? After building, check the output folder (or extract the one-file bundle) and verify the DLL is actually there.
- Are you using an absolute path? Replace any relative paths like
./MyLib.dllwith paths constructed usingsys._MEIPASSoros.path.dirname(os.path.abspath(__file__)). - Does the DLL have dependencies? Use the Dependencies tool to check. Bundle any non-system DLLs that your DLL needs.
- Are you testing on a clean machine? Your development machine may have Visual C++ runtimes, SDK libraries, or other DLLs installed that a user's machine won't have. Test in a virtual machine or on a computer that hasn't been used for development.
- Is it a 32-bit vs 64-bit issue? Make sure your Python installation, your DLL, and any dependency DLLs all match — either all 32-bit or all 64-bit.
By making sure the DLL is bundled, its dependencies are accounted for, and you're loading it from the correct path at runtime, the FileNotFoundError should be resolved on every machine, not just your own.
Packaging Python Applications with PyInstaller by Martin Fitzpatrick
This step-by-step guide walks you through packaging your own Python applications from simple examples to complete installers and signed executables.