Repository: pyinstaller/pyinstaller

Issue:
Could not import `pywintypes` or `win32api` from `win32ctypes.pywin32`
## Description of the issue

Error when running the executable.

Issue is present in 6.6.0 and "latest development version". Issue is not present in 6.5.0.

Output differs between versions at this point:

6.6.0:
```
import 'win32ctypes.core' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001C6D9E41BB0>
# win32ctypes.core._common not found in PYZ
# win32ctypes.core.ctypes not found in PYZ
# destroy win32ctypes.pywin32.win32api
# destroy win32ctypes.pywin32
# destroy PyInstaller
Could not import `pywintypes` or `win32api` from `win32ctypes.pywin32`.
Please make sure that `pywin32-ctypes` is installed and importable, for example:

pip install pywin32-ctypes

```

6.5.0:
```
# cffi not found in PYZ
# code object from '[...]\\cffi\\__init__.pyc'
# cffi.api not found in PYZ
# code object from '[...]\\cffi\\api.pyc'
# cffi.lock not found in PYZ
# code object from '[...]\\cffi\\lock.pyc'
import 'cffi.lock' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB845C0>
# cffi.error not found in PYZ
# code object from '[...]\\cffi\\error.pyc'
import 'cffi.error' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB846B0>
# cffi.model not found in PYZ
# code object from '[...]\\cffi\\model.pyc'
import 'cffi.model' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB848F0>
import 'cffi.api' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB52330>
import 'cffi' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB51EB0>
import 'win32ctypes.core' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB51B80>
# win32ctypes.core._common not found in PYZ
# win32ctypes.core.cffi not found in PYZ
# code object from '[...]\\win32ctypes\\core\\cffi\\__init__.pyc'
import 'win32ctypes.core.cffi' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB86BA0>
# win32ctypes.core.cffi._common not found in PYZ
# code object from '[...]\\win32ctypes\\core\\cffi\\_common.pyc'
# win32ctypes.core.cffi._util not found in PYZ
# code object from '[...]\\win32ctypes\\core\\cffi\\_util.pyc'
# win32ctypes.core.compat not found in PYZ
# code object from '[...]\\win32ctypes\\core\\compat.pyc'
import 'win32ctypes.core.compat' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEB87440>
# _cffi_backend not found in PYZ
# extension module '_cffi_backend' loaded from '[...]\\_cffi_backend.cp312-win_amd64.pyd'
# extension module '_cffi_backend' executed from '[...]\\_cffi_backend.cp312-win_amd64.pyd'
import '_cffi_backend' # <_frozen_importlib_external.ExtensionFileLoader object at 0x000001F4AEB876B0>
# cffi.cparser not found in PYZ
# code object from '[...]\\cffi\\cparser.pyc'
# cffi.commontypes not found in PYZ
# code object from '[...]\\cffi\\commontypes.pyc'
import 'cffi.commontypes' # <_frozen_importlib_external.SourcelessFileLoader object at 0x000001F4AEBA91F0>
```


### Context information (for bug reports)

* 502 INFO: PyInstaller: 6.6.0, contrib hooks: 2024.6
* 503 INFO: Python: 3.12.0
* 541 INFO: Platform: Windows-10-10.0.19045-SP0





Discussion:
> ```
> # destroy PyInstaller
> Could not import `pywintypes` or `win32api` from `win32ctypes.pywin32`.
> Please make sure that `pywin32-ctypes` is installed and importable, for example:
> 
> pip install pywin32-ctypes
> ```

Well, you shouldn't be importing `PyInstaller` in your application in the first place. It doesn't work in general (use of `sys.executable`-based subprocesses, among other things), and it makes your application GPL'd.

---

The issue is caused by https://github.com/pyinstaller/pyinstaller/pull/8373/commits/1d9acecc97da94cd6edaf032b951828cb431d880, which blocks `cffi` to force the use of `ctypes` backend in `pywin32-ctypes`. This happens during initialization at build time, but in this case, it also happens at run-time, when application imports PyInstaller. In our [pywin32-ctypes hook](https://github.com/pyinstaller/pyinstaller/blob/develop/PyInstaller/hooks/hook-win32ctypes.core.py) we collect submodules of either `win32ctypes.core.cffi` or `win32ctypes.core.ctypes`, depending on availability of `cffi`. At the time when the hook is ran, `cffi` is already unblocked. So in this particular case, the application build ends up collecting `cffi` backend, but upon trying to import PyInstaller, it errors out due to lack of `ctypes` backend.

But I'm starting to think that perhaps we should always collect the `ctypes` backend, and add `cffi` one if `cffi` is available.

Resolving patch:
diff --git a/PyInstaller/hooks/hook-win32ctypes.core.py b/PyInstaller/hooks/hook-win32ctypes.core.py
--- a/PyInstaller/hooks/hook-win32ctypes.core.py
+++ b/PyInstaller/hooks/hook-win32ctypes.core.py
@@ -13,9 +13,10 @@
 
 from PyInstaller.utils.hooks import can_import_module, collect_submodules
 
-# We need to collect submodules from win32ctypes.core.cffi or win32ctypes.core.ctypes for win32ctypes.core to work. The
-# use of the backend is determined by availability of cffi.
+# We need to collect submodules from win32ctypes.core.cffi or win32ctypes.core.ctypes for win32ctypes.core to work.
+# Always collect the `ctypes` backend, and add the `cffi` one if `cffi` is available. Having the `ctypes` backend always
+# available helps in situations when `cffi` is available in the build environment, but is disabled at run-time or not
+# collected (e.g., due to `--exclude cffi`).
+hiddenimports = collect_submodules('win32ctypes.core.ctypes')
 if can_import_module('cffi'):
-    hiddenimports = collect_submodules('win32ctypes.core.cffi')
-else:
-    hiddenimports = collect_submodules('win32ctypes.core.ctypes')
+    hiddenimports += collect_submodules('win32ctypes.core.cffi')


Test patch: