Loading DLLs from a PyInstaller-packaged PyQt standalone application

I wanted to package a PyQt script as a standalone Windows .exe using PyInstaller, and I wanted the app to display a jpeg.

It turned out that displaying a PNG was fine, but displaying a JPEG took a while to get working. PyQt uses a dll called `qjpeg4.dll` for decoding the jpeg, and PyInstaller doesn’t automatically bundle it into the package, so we have to tell PyInstaller to do so.

There is still one problem though: when you run the .exe, where does PyQt try to load `qjpeg4.dll` from? It turns out that *it depends*..

If you use the `–onedir` option for PyInstaller, the `imageformats/qjpeg4.dll` just needs to be in a directory called `imageformats`. No problem.

If you use the `–onefile` option, PyInstaller unzips the binaries into a temp directory, and loads dlls from there. But for some reason, in the `–onefile` case, PyQt tries to load the dll from `qt4_plugins/imageformats/qjpeg4.dll`.

I used SysInternals Process Monitor to figure this out…

To make things easy, I just added an `imageformats` directory to my repo, and checked in the qjpeg4.dll.

I also wanted to load a dll called `discid.dll`, required by python-discid. I checked into the root level of the repo, but couldn’t get the python package to load the dll properly, until I modified the PATH environment variable:

#fix for loading discid.dll
if getattr(sys, 'frozen', None):
     BASE_DIR = sys._MEIPASS
     BASE_DIR = os.path.dirname(__file__)
os.environ['PATH'] = BASE_DIR + '\;' + os.environ.get('PATH', '')
import discid

My PyInstaller .spec file for the `–onefile` case looks like this:

# -*- mode: python -*-
a = Analysis(['wizard.py'],
onefile_binaries = a.binaries + [('discid.dll', 'discid.dll', 'BINARY'),
                         ('qt4_plugins/imageformats/qjpeg4.dll', 'imageformats/qjpeg4.dll', 'BINARY'),
pyz = PYZ(a.pure)
exe = EXE(pyz,
          Tree('images', prefix='images'),
          console=True )