From f8380af3bdf91e462aab36b78787f14351bdbbc4 Mon Sep 17 00:00:00 2001 From: alex Date: Sun, 24 May 2026 13:59:21 +0200 Subject: [PATCH] fix: pre-load tesseract51.dll by absolute path to fix transitive DLL deps os.add_dll_directory alone is not enough on Windows. When Python loads the tesserocr .pyd via LOAD_LIBRARY_SEARCH_USER_DIRS, Windows finds tesseract51.dll in the added directory but then resolves tesseract's own transitive deps (leptonica, zlib, libpng etc.) using only the standard system search path -- not the user DLL dirs. Those libs live in Library\bin, not System32, so they're invisible and the load fails even though every DLL is present. Fix: call ctypes.WinDLL(absolute_path_to_tesseract51.dll) before the tesserocr import. LoadLibraryW with a full path anchors tesseract51.dll to Library\bin, so Windows searches that directory for its transitive deps. The already-loaded DLL is then returned from cache when the .pyd requests it, making the import succeed. Applied to ocr.py (test entry point), main.py, and shopper.py. Also updated install.bat smoke test and diagnostic to use the same fix. Co-Authored-By: Claude Sonnet 4.6 --- install.bat | 8 ++++---- src/d2r_image/ocr.py | 13 ++++++++++++- src/main.py | 8 +++++++- src/shopper.py | 4 ++++ 4 files changed, 27 insertions(+), 6 deletions(-) diff --git a/install.bat b/install.bat index 718a356..7df49e2 100644 --- a/install.bat +++ b/install.bat @@ -99,15 +99,15 @@ echo Installing tesserocr wheel... :: --- Smoke test: import key dependencies --- echo. echo Verifying dependencies... -"%PYTHON%" -c "import os,sys;[os.add_dll_directory(p) for p in [os.path.join(sys.prefix,'Library',d) for d in ['bin','mingw-w64\\bin','usr\\bin']] if os.path.isdir(p)];import cv2,mss,numpy,tesserocr,discord,transitions,rapidfuzz" >nul 2>&1 +"%PYTHON%" -c "import os,sys,ctypes;lb=os.path.join(sys.prefix,'Library','bin');[os.add_dll_directory(os.path.join(sys.prefix,'Library',d)) for d in ['bin','mingw-w64\\bin','usr\\bin'] if os.path.isdir(os.path.join(sys.prefix,'Library',d))];ctypes.WinDLL(os.path.join(lb,'tesseract51.dll')) if os.path.isfile(os.path.join(lb,'tesseract51.dll')) else None;import cv2,mss,numpy,tesserocr,discord,transitions,rapidfuzz" >nul 2>&1 if %errorlevel% neq 0 ( echo. echo WARNING: Dependency check failed. Running diagnostics... echo. - "%PYTHON%" -c "import os,sys; lb=os.path.join(sys.prefix,'Library','bin'); print(' Prefix:',sys.prefix); print(' Library/bin exists:',os.path.isdir(lb)); dlls=[f for f in (os.listdir(lb) if os.path.isdir(lb) else []) if any(k in f.lower() for k in ['tess','lepton'])]; print(' Tesseract DLLs found:',dlls if dlls else 'NONE - re-run install.bat'); [os.add_dll_directory(os.path.join(sys.prefix,'Library',d)) for d in ['bin','mingw-w64\\bin','usr\\bin'] if os.path.isdir(os.path.join(sys.prefix,'Library',d))]; __import__('tesserocr')" 2>&1 + "%PYTHON%" -c "import os,sys,ctypes; lb=os.path.join(sys.prefix,'Library','bin'); print(' Prefix:',sys.prefix); dlls=[f for f in (os.listdir(lb) if os.path.isdir(lb) else []) if any(k in f.lower() for k in ['tess','lepton'])]; print(' Tesseract DLLs in Library/bin:',dlls or 'NONE'); [os.add_dll_directory(os.path.join(sys.prefix,'Library',d)) for d in ['bin','mingw-w64\\bin','usr\\bin'] if os.path.isdir(os.path.join(sys.prefix,'Library',d))]; t=os.path.join(lb,'tesseract51.dll'); r=ctypes.WinDLL(t) if os.path.isfile(t) else None; print(' ctypes load tesseract51.dll:','OK' if r else 'skipped'); __import__('tesserocr'); print(' tesserocr import: OK')" 2>&1 echo. - echo If you see "NONE" above, re-run install.bat once more to let conda finish. - echo If the error persists, see development.md for manual steps. + echo *** Do NOT run "pip install tesserocr" manually - it builds from source and + echo *** will always fail on Windows. Re-run install.bat to fix dependencies. ) else ( echo All key dependencies verified. ) diff --git a/src/d2r_image/ocr.py b/src/d2r_image/ocr.py index d34e757..08ab186 100644 --- a/src/d2r_image/ocr.py +++ b/src/d2r_image/ocr.py @@ -1,7 +1,8 @@ import os import sys if os.name == 'nt': - # Python 3.8+ requires explicit DLL directories; conda puts tesseract/leptonica in Library\bin + import ctypes + # Add all conda DLL dirs so Python's loader can find them for _dll_dir in [ os.path.join(sys.prefix, 'Library', 'bin'), os.path.join(sys.prefix, 'Library', 'mingw-w64', 'bin'), @@ -9,6 +10,16 @@ if os.name == 'nt': ]: if os.path.isdir(_dll_dir): os.add_dll_directory(_dll_dir) + # Pre-load tesseract51.dll by absolute path so Windows anchors it to + # Library\bin when resolving its own transitive deps (leptonica, zlib, etc.). + # os.add_dll_directory alone is not enough: when Python loads the .pyd via + # LOAD_LIBRARY_SEARCH_USER_DIRS, Windows finds tesseract51.dll there, but + # then resolves tesseract's transitive deps using only System32 — not the + # user DLL dirs. Loading by absolute path first puts tesseract in the cache + # with Library\bin as its home, fixing the transitive search. + _tess = os.path.join(sys.prefix, 'Library', 'bin', 'tesseract51.dll') + if os.path.isfile(_tess): + ctypes.WinDLL(_tess) from tesserocr import PyTessBaseAPI, OEM import numpy as np import cv2 diff --git a/src/main.py b/src/main.py index 6fa9648..7d080cf 100644 --- a/src/main.py +++ b/src/main.py @@ -3,8 +3,8 @@ # This must run BEFORE any import that might trigger tesserocr loading. import os, sys if sys.platform == "win32": + import ctypes _conda_dll_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "conda_env", "Library", "bin") - # Also check the conda env where this python lives (sys.prefix is reliable on Windows) for _dll_candidate in [ _conda_dll_dir, os.path.join(sys.prefix, "Library", "bin"), @@ -13,6 +13,12 @@ if sys.platform == "win32": ]: if os.path.isdir(_dll_candidate): os.add_dll_directory(_dll_candidate) + # Pre-load tesseract51.dll by absolute path so Windows anchors its transitive + # deps (leptonica, zlib, etc.) to Library\bin. os.add_dll_directory alone is + # insufficient: transitive deps of a user-dir DLL are not searched there. + _tess = os.path.join(sys.prefix, "Library", "bin", "tesseract51.dll") + if os.path.isfile(_tess): + ctypes.WinDLL(_tess) from dataclasses import dataclass from input_layer import keyboard diff --git a/src/shopper.py b/src/shopper.py index 6d9d47a..40939d3 100644 --- a/src/shopper.py +++ b/src/shopper.py @@ -1,6 +1,7 @@ # Fix: On Windows, Python 3.8+ requires os.add_dll_directory for conda-forge DLLs import os, sys if sys.platform == "win32": + import ctypes for _dll_dir in [ os.path.join(sys.prefix, "Library", "bin"), os.path.join(sys.prefix, "Library", "mingw-w64", "bin"), @@ -8,6 +9,9 @@ if sys.platform == "win32": ]: if os.path.isdir(_dll_dir): os.add_dll_directory(_dll_dir) + _tess = os.path.join(sys.prefix, "Library", "bin", "tesseract51.dll") + if os.path.isfile(_tess): + ctypes.WinDLL(_tess) from beautifultable import BeautifulTable import logging