I stumbled on a case where a user of https://pypi.org/project/OpenImageIO/'s wheels on Windows was reporting that they could not import the library in Nuke 14.1.
To debug the issue, I created an EC2 instance with a GPU using the NVidia WinServer 2022 AMI, installed Nuke and tried to import OpenImageIO
. I was able to reproduce immediately.
Let's install OpenImageIO using Nuke's python interpreter:
& 'C:/Program Files/Nuke14.1v6/python.exe' -m pip install OpenImageIO==3.0.3.1 --prefix C:/Users/Administrator/asd
Inside Nuke's script editor:
import sys
sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
import OpenImageIO.OpenImageIO
# Result: Traceback (most recent call last):
File "<string>", line 3, in <module>
File "C:/Program Files/Nuke14.1v6/pythonextensions\site-packages\shiboken2\files.dir\shibokensupport\__feature__.py", line 142, in _import
return original_import(name, *args, **kwargs)
File "C:\Users/Administrator/asd/Lib/site-packages\OpenImageIO\__init__.py", line 33, in <module>
from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402
File "C:/Program Files/Nuke14.1v6/pythonextensions\site-packages\shiboken2\files.dir\shibokensupport\__feature__.py", line 142, in _import
return original_import(name, *args, **kwargs)
ImportError: DLL load failed while importing OpenImageIO: The specified procedure could not be found.
Now, that's weird, why is shiboken involved here? I don't know...
The first thing that came to mind to debug this was to try directly inside Nuke's python interpreter outside Nuke. This produced the same error. Which is good.
PS C:\Users\Administrator\source\repos> & 'C:/Program Files/Nuke14.1v6/python.exe'
Python 3.9.10 (remotes/origin/foundry/v3.9.10:fc80903a66, Jul 21 2023, 12:01:36) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
>>> import OpenImageIO
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Users\Administrator\asd\Lib\site-packages\OpenImageIO\__init__.py", line 33, in <module>
from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402
ImportError: DLL load failed while importing OpenImageIO: The specified procedure could not be found.
So shiboken was a red herring. This is reassuring because debugging an issue with shiboken could have been painful.
Next, I needed to get a better view on the problem. I tried to attach the Visual Studio debugger to the python interpreter, but it didn't tell me anything. It doesn't even catch the problem.
So the next thing to try was to use https://www.dependencywalker.com/. Again, this was not successful. I wasn't able to see anything obvious (though I'm not very familiar with the tool, so maybe I just don't know where to look).
If that doesn't work, then we are left with doing some runtime analysis to find which DLL is missing symbols. dlltrace is very useful here. Its designed exactly to help solve issues with DLL loading in Python on Windows.
Note
Why chasing missing symbols? Because the error isn't about a missing library. It's about a missing "precedure", which is probably a symbol I think.
Here is how it can be used:
import sys
import dlltracer
sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
with dlltracer.Trace(out=sys.stdout):
import OpenImageIO
Running this code led to this output:
LoadLibrary C:/Windows/System32/kernel.appcore.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd
LoadLibrary C:/Program Files/Nuke14.1v6/msvcp140.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO_Util.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll
LoadLibrary C:/Program Files/Nuke14.1v6/vcruntime140_1.dll
LoadLibrary C:/Windows/System32/shell32.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Imath_v_3_1_10_OpenImageIO_v3_0.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/zlib1.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Iex_v_3_2_4_OpenImageIO_v3_0.dll
LoadLibrary C:/Program Files/Nuke14.1v6/tiff.dll
LoadLibrary C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/IlmThread_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/zlib1.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/IlmThread_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Iex_v_3_2_4_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Program Files/Nuke14.1v6/tiff.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Imath_v_3_1_10_OpenImageIO_v3_0.dll
Failed /Device/HarddiskVolume1/Windows/System32/shell32.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO_Util.dll
Failed /Device/HarddiskVolume1/Program Files/Nuke14.1v6/msvcp140.dll
Failed /Device/HarddiskVolume1/Program Files/Nuke14.1v6/vcruntime140_1.dll
Failed /Device/HarddiskVolume1/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd
Traceback (most recent call last):
File "<stdin>", line 2, in <module>
File "C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/__init__.py", line 33, in <module>
from .OpenImageIO import * # type: ignore # noqa: F401, F403, E402
ImportError: DLL load failed while importing OpenImageIO: The specified procedure could not be found.
We are getting somewhere! That's very useful information right there. Although it's kind of hard to really pinpoint to the problematic library, we at least know which ones failed.
If we look at these paths more closely, we see libraries being loaded from C:/Program Files/Nuke14.1v6
even if the libraries are already provided by the installed OpenTimelineIO:
PS C:\Users\Administrator\source\repos> tree /f C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO
Folder PATH listing
Volume serial number is 004C005C 1CE9:5F0A
C:\USERS\ADMINISTRATOR\ASD\LIB\SITE-PACKAGES\OPENIMAGEIO
│ OpenImageIO.cp39-win_amd64.pyd
│ __init__.py
│
├───bin
│ Iex_v_3_2_4_OpenImageIO_v3_0.dll
│ IlmThread_v_3_2_4_OpenImageIO_v3_0.dll
│ Imath_v_3_1_10_OpenImageIO_v3_0.dll
│ maketx.exe
│ oiiotool.exe
│ OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll
│ OpenEXRUtil_v_3_2_4_OpenImageIO_v3_0.dll
│ OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll
│ OpenImageIO.dll
│ OpenImageIO_Util.dll
│ tiff.dll
│ zlib1.dll
│
├───lib
... more files
If I had to guess, the missing symbol(s) are problaly in tiff.dll
or zlib1.dll
since both are provided by Nuke and our OIIO wheel.
Note
At this point, I didn't realize yet that I could just try to use ctypes.cdll.LoadLibrary
to load OIIO's own tiff.dll
or zlib1.dll
manually before importing OpenImageIO to check that if that would fix the issue... This would have saved me some time. Anyway, let's continue with my thought process. We'll come back to the LoadLibrary
trick at the end).
So how can we find which symbol(s) is causing issues?
We can use our good old friend dumpbin or we could script our way into the problem. At this point I got nerd sniped and decided to write a script that would tell me exactly which symbol is missing and which library is trying to load it.
I came up with this script:
import os
import collections
import sys
sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
import lief
# List of DLLs to inspect.
# The list was generated from the paths provided by dlltracer.
# At least I started with that. I then ran the script and
# looked at the errors. For each "unknown" DLL, I searched where it was
# and added it in the list. Rinse and repeat until no more errors.
dlls = [
"C:/Program Files/Nuke14.1v6/msvcp140.dll",
"C:/Program Files/Nuke14.1v6/tiff.dll",
"C:/Program Files/Nuke14.1v6/vcruntime140_1.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Iex_v_3_2_4_OpenImageIO_v3_0.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/IlmThread_v_3_2_4_OpenImageIO_v3_0.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/Imath_v_3_1_10_OpenImageIO_v3_0.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXRCore_v_3_2_4_OpenImageIO_v3_0.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenEXR_v_3_2_4_OpenImageIO_v3_0.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO_Util.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/zlib1.dll",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd",
"C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/OpenImageIO.cp39-win_amd64.pyd",
"C:/Windows/System32/kernel.appcore.dll",
"C:/Windows/System32/shell32.dll",
"C:/Program Files/Nuke14.1v6/python39.dll",
"C:/Windows/System32/GDI32.dll",
"C:/Windows/System32/KERNEL32.dll",
"C:/Windows/System32/KERNELBASE.dll",
"C:/Windows/System32/MSVCP140.dll",
"C:/Windows/System32/SHELL32.dll",
"C:/Windows/System32/USER32.dll",
"C:/Windows/System32/VCRUNTIME140.dll",
"C:/Windows/System32/VCRUNTIME140_1.dll",
"C:/Windows/System32/msvcp_win.dll",
"C:/Windows/System32/msvcrt.dll",
"C:/Windows/System32/ntdll.dll",
"C:/Windows/System32/ADVAPI32.dll",
"C:/Windows/System32/win32u.dll",
"C:/Windows/System32/VERSION.dll",
"C:/Windows/System32/WS2_32.dll",
"C:/Windows/System32/SECHOST.dll",
"C:/Windows/System32/RPCRT4.dll",
"C:/Windows/System32/bcrypt.dll"
]
# Map DLL names to full paths.
dlls = {os.path.basename(dll): dll for dll in dlls}
# Map of DLL names to symbols
symbols = collections.defaultdict(list)
# Get the exported symbol for each DLL
for name, dllpath in dlls.items():
dll = lief.parse(dllpath)
for export in dll.get_export().entries:
symbols[name].append(export.name)
# Now go over each DLL and check if the imported symbols
# are provided by any of the DLLs in the list.
for name, dllpath in dlls.items():
dll = lief.parse(dllpath)
# Imports in PE are organized by DLLs. So loop through
# each DLL.
for imported_dll in dll.imports:
# The api-ms-win-crt-runtime-l1-1-0.dll and co don't exist. I'm not too sure
# how they work, but I know they are not real files. I think. So let's skip them.
if imported_dll.name.startswith("api-"):
continue
if imported_dll.name not in symbols:
raise RuntimeError(f"We have not yet processed symbols for {imported_dll.name!r}")
# Now go over each symbol imported from that DLL
for imported_symbol in imported_dll.entries:
# For some reason, some symbols are empty... Skip them.
if imported_symbol.name == "":
continue
# Check if the imported symbol is in the list of known symbols for the DLL.
if imported_symbol.name not in symbols[imported_dll.name]:
# We found a missing symbol!
print(f"{dllpath} imports {imported_symbol.name!r} from {dlls[imported_dll.name]!r} but the symbol was not found")
I added comments to the script, hopefully that is enough to explain what it does. Before running it, we need to install lief:
& 'C:/Program Files/Nuke14.1v6/python.exe' -m pip install lief --prefix C:/Users/Administrator/asd
lief
is a cross-platform library to parse, modify and abstract ELF, PE and MachO formats. PE
here is what we want. This is the file format used on Windows for executables and shared libraries.
With that out of the way, I ran the script and it told me:
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsAlloc' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsFree' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenWExt' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFClientOpenExt' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsSetWarningHandlerExtR' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll imports 'TIFFOpenOptionsSetErrorHandlerExtR' from 'C:/Program Files/Nuke14.1v6/tiff.dll' but the symbol was not found
Tada 🎉! The problem couldn't be more clear now. The problematic library is the tiff
library provided by Nuke. The wheel's OpenImageIO/bin/OpenImageIO.dll
library imports (uses) TIFFOpenOptionsAlloc
, TIFFOpenOptionsFree
, TIFFOpenWExt
, TIFFClientOpenExt
, TIFFOpenOptionsSetWarningHandlerExtR
and TIFFOpenOptionsSetErrorHandlerExtR
but Nuke's tiff library doesn't export or provide them.
These symbols were added in libtiff 4.5.0. Nuke 14.1 ships with 4.3.0. OIIO supports 4.0 and above but was compiled with 4.6.0. And this is why it uses symbols that don't exist in 4.3.0.
And why is python loading C:/Program Files/Nuke14.1v6/tiff.dll
instead of C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/tiff.dll
? And why isn't zlib1
loaded from Nuke's DLL? First of all, Nuke doesn't have zlib1.dll
, so that why it's being loaded from OIIO as it should. As for tiff.dll
, that's because Nuke does provide that DLL. When executing C:/Program Files/Nuke14.1v6/python.exe
, because of the DLL search order in Windows, Windows will load C:/Program Files/Nuke14.1v6/tiff.dll
first it's in the same folder as the python executable (which is the case).
So let's test to see if we are correct and that it's really the problem. For that, we can try to load tiff.dll
that comes with OIIO's wheel before importing OpenImageIO
. In Python, this can be achieved by using ctypes.cdll.LoadLibrary. So let's give that a try:
PS C:\Users\Administrator\source\repos> & 'C:/Program Files/Nuke14.1v6/python.exe'
Python 3.9.10 (remotes/origin/foundry/v3.9.10:fc80903a66, Jul 21 2023, 12:01:36) [MSC v.1929 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.path.append("C:/Users/Administrator/asd/Lib/site-packages")
>>> import ctypes
>>> ctypes.cdll.LoadLibrary("C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/tiff.dll")
<CDLL 'C:\Users\Administrator\asd\Lib\site-packages\OpenImageIO\bin\tiff.dll', handle 7ffc47d80000 at 0x178ede76910>
>>> import OpenImageIO
>>>
Bingo! This worked. We loaded the good tiff shared library first, and this makes importing OpenImageIO
work!
Note
And like I said earlier, this is what I would have tried from the beginning to narrow down which library was causing issues.
We can then verify that C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/tiff.dll
contains the missing symbols:
PS C:\Users\Administrator\source\repos> dumpbin /imports:tiff.dll C:/Users/Administrator/asd/Lib/site-packages/OpenImageIO/bin/OpenImageIO.dll
Microsoft (R) COFF/PE Dumper Version 14.43.34808.0
Copyright (C) Microsoft Corporation. All rights reserved.
Dump of file C:\Users\Administrator\asd\Lib\site-packages\OpenImageIO\bin\OpenImageIO.dll
File Type: DLL
Section contains the following imports:
tiff.dll
1806A2C48 Import Address Table
1808635E0 Import Name Table
0 time date stamp
0 Index of first forwarder reference
66 TIFFReadEXIFDirectory
4A TIFFIsTiled
47 TIFFIsByteSwapped
35 TIFFGetField
7D TIFFSetDirectory
73 TIFFReadScanline
6C TIFFReadRGBAImageOriented
6F TIFFReadRGBATile
5E TIFFRGBAImageOK
53 TIFFOpenOptionsAlloc
54 TIFFOpenOptionsFree
9 TIFFClose
59 TIFFOpenWExt
6 TIFFClientOpen
74 TIFFReadTile
67 TIFFReadEncodedStrip
71 TIFFReadRawStrip
72 TIFFReadRawTile
90 TIFFSwabArrayOfShort
8E TIFFSwabArrayOfLong
43 TIFFGetVersion
E TIFFCreateEXIFDirectory
80 TIFFSetField
AB TIFFWriteDirectory
AA TIFFWriteCustomDirectory
4 TIFFCheckpointDirectory
B0 TIFFWriteScanline
7 TIFFClientOpenExt
36 TIFFGetFieldDefaulted
B1 TIFFWriteTile
AE TIFFWriteRawStrip
57 TIFFOpenOptionsSetWarningHandlerExtR
AF TIFFWriteRawTile
22 TIFFFieldReadCount
21 TIFFFieldPassCount
1E TIFFFieldDataType
2C TIFFFindField
55 TIFFOpenOptionsSetErrorHandlerExtR
Summary
160000 .data
3A000 .pdata
1D1000 .rdata
D000 .reloc
1000 .rsrc
6A0000 .text
1000 _RDATA
Yep, they are all there.
Tip
Note how I used /imports:tiff.dll
instead of a bare /imports
. This instructs dumpbin to only list imports that are provided by the given DLL.
And that's it, that's one way to debug symbol issues on Windows.
This is absolutely brilliant. Thank you for going through all the effort of documenting your efforts here.