Python Ctypes – loading dll throws OSError: [WinError 193] %1 is not a valid Win32 application

Mentioning [Python.Docs]: ctypes – A foreign function library for Python (although this doesn’t have very much to do with it) just in case.

The underlying error is ERROR_BAD_EXE_FORMAT (193, 0xC1). Check it in [MS.Docs]: System Error Codes (0-499). It’s a general Win error (not related to Python). In the current case (related to Python), the exception is a (Python) wrapper over it.

1. The error

The error message is confusing (especially because of %1 placeholder). For more details on that, check [SO]: Why is %1 rarely substituted in “%1 is not a valid Win32 application.”.

This error occurs when Win tries to load what it thinks it’s an executable (PE) image (.exe, .dll, …), but it actually isn’t. There’s a variety of situations when this is encountered (Googleing the error, would yield lots of results).

There are a bunch of possible reasons for this to happen when the image is loaded from a file (existing and readable, otherwise the error would differ – look at one of the bullets at the answer end):

  • Was downloaded and the download is incomplete
  • Was (mistakenly) overwritten (or messed up with)
  • Is corrupt because of filesystem problem
  • Many many more

2 main usecases lead to this error:

  1. Attempting to run a file which is not an .exe ([SO]: OSError: [WinError 193] %1 is not a valid Win32 application)
  2. Trying to load a .dll in a process (running .exe). This is the one that I’m going to focus on

Below, it’s an example of a dummy executable attempting to load a .dll.

main00.c:

#include <stdio.h>
#include <Windows.h>


int main()
{
    DWORD gle = 0;
    HMODULE hMod = LoadLibraryA(".\\dll00.dll");
    if (hMod == NULL) {
        gle = GetLastError();
        printf("LoadLibrary failed: %d (0x%08X)\n", gle, gle);
    } else {
        FreeLibrary(hMod);
    }
    return gle;
}

Output:

  • Note: I’ll be reusing this cmd console, even if the copy / paste snippets will be scattered across the answer
[cfati@CFATI-5510-0:e:\Work\Dev\StackOverflow\q057187566]> sopr.bat
### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ###

[prompt]> :: Build for 064bit (pc064)
[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x64 > nul

[prompt]> dir /b
code00.py
dll00_v0.c
main00.c

[prompt]> cl /nologo main00.c  /link /NOLOGO /OUT:main00_064.exe
main00.c

[prompt]> :: Creating an invalid .dll
[prompt]> echo garbage> dll00.dll

[prompt]> dir /b
code00.py
dll00.dll
dll00_v0.c
main00.c
main00.obj
main00_064.exe

[prompt]> main00_064.exe
LoadLibrary failed: 193 (0x000000C1)

As seen, I created a file dll00.dll containing the text “garbage“, so it’s a .dll file with invalid contents.

The most common case for this error, is an architecture mismatch:

  • 064bit process attempting to load a 032bit .dll
  • 032bit process attempting to load a 064bit .dll

In any of the above 2 cases, even if the .dll contains a valid image (for a different architecture), it’s still invalid from the current process PoV. For things to run OK, the 2 involved CPU architectures must match (1).

2. Python context

CTypes does the same thing when loading a .dll: it calls [MS.Docs]: LoadLibraryW function on the .dll name.
So this is the exact same case for the Python process where CTypes tries to load the .dll in.

code00.py:

#!/usr/bin/env python3

import sys
import os
import ctypes as ct


DLL_BASE_NAME = "dll00"


def main(*argv):
    dll_name = os.path.join(os.path.abspath(os.path.dirname(__file__)), (argv[0] if argv else DLL_BASE_NAME) + ".dll")
    print("Attempting to load: [{0:s}]".format(dll_name))
    dll00 = ct.CDLL(dll_name)
    func00 = dll00.dll00Func00
    func00.restype = ct.c_int

    res = func00()
    print("{0:s} returned {1:d}".format(func00.__name__, res))


if __name__ == "__main__":
    print("Python {0:s} {1:03d}bit on {2:s}\n".format(" ".join(item.strip() for item in sys.version.split("\n")),
                                                      64 if sys.maxsize > 0x100000000 else 32, sys.platform))
    rc = main(*sys.argv[1:])
    print("\nDone.")
    sys.exit(rc)

Output:

[prompt]> :: dll00.dll still contains garbage
[prompt]>
[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.09_test0\Scripts\python.exe" code00.py
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 bit (AMD64)] 064bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00.dll]
Traceback (most recent call last):
  File "code00.py", line 25, in <module>
    rc = main(*sys.argv[1:])
  File "code00.py", line 14, in main
    dll00 = ct.CDLL(dll_name)
  File "c:\Install\pc064\Python\Python\03.07.09\lib\ctypes\__init__.py", line 364, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: [WinError 193] %1 is not a valid Win32 application

Here’s an example for (#1) (from above), which attempts all 4 combinations.

dll00_v0.c:

#include <inttypes.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif


DLL00_EXPORT_API size_t dll00Func00()
{
    return sizeof(void*);
}

Output:

[prompt]> :: Still building for pc064 from previous vcvarsall call
[prompt]>
[prompt]> cl /nologo /DDLL dll00_v0.c  /link /NOLOGO /DLL /OUT:dll00_064.dll
dll00_v0.c
   Creating library dll00_064.lib and object dll00_064.exp

[prompt]>
[prompt]> :: Build for 032bit (pc032)
[prompt]> "c:\Install\pc032\Microsoft\VisualStudioCommunity\2017\VC\Auxiliary\Build\vcvarsall.bat" x86
**********************************************************************
** Visual Studio 2017 Developer Command Prompt v15.9.40
** Copyright (c) 2017 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x86'

[prompt]> cl /nologo /DDLL dll00_v0.c  /link /NOLOGO /DLL /OUT:dll00_032.dll
dll00_v0.c
   Creating library dll00_032.lib and object dll00_032.exp

[prompt]> dir /b *.dll
dll00.dll
dll00_032.dll
dll00_064.dll

[prompt]>
[prompt]> :: Python pc064
[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.09_test0\Scripts\python.exe" code00.py dll00_064
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 bit (AMD64)] 064bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00_064.dll]
dll00Func00 returned 8

Done.

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc064_03.07.09_test0\Scripts\python.exe" code00.py dll00_032
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:58:18) [MSC v.1900 64 bit (AMD64)] 064bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00_032.dll]
Traceback (most recent call last):
  File "code00.py", line 25, in <module>
    rc = main(*sys.argv[1:])
  File "code00.py", line 14, in main
    dll00 = ct.CDLL(dll_name)
  File "c:\Install\pc064\Python\Python\03.07.09\lib\ctypes\__init__.py", line 364, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: [WinError 193] %1 is not a valid Win32 application

[prompt]>
[prompt]> :: Python pc032
[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc032_03.07.09_test0\Scripts\python.exe" code00.py dll00_032
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:01:55) [MSC v.1900 32 bit (Intel)] 032bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00_032.dll]
dll00Func00 returned 4

Done.

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc032_03.07.09_test0\Scripts\python.exe" code00.py dll00_064
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:01:55) [MSC v.1900 32 bit (Intel)] 032bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00_064.dll]
Traceback (most recent call last):
  File "code00.py", line 25, in <module>
    rc = main(*sys.argv[1:])
  File "code00.py", line 14, in main
    dll00 = ct.CDLL(dll_name)
  File "c:\Install\pc032\Python\Python\03.07.09\lib\ctypes\__init__.py", line 364, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: [WinError 193] %1 is not a valid Win32 application

3. Bonus

In the above examples, the .dll was loaded “on demand” by explicitly calling LoadLibrary (or LoadLibraryEx).
The other case is when a .exe or .dll depends on (was linked against) another .dll, and loads it automatically when itself is being loaded (although I’m almost certain that LoadLibrary – or maybe a lower level function – is automatically called under the hood on the dependent .dll).
In the example below, dll00*.dll depends on dll01*.dll.
Only exemplifying for 032bit (as this is the current build environment set by previous operation).

dll01.h:

#if defined(_WIN32)
#  if defined(DLL01_EXPORTS)
#    define DLL01_EXPORT_API __declspec(dllexport)
#  else
#    define DLL01_EXPORT_API __declspec(dllimport)
#  endif
#else
#  define DLL01_EXPORT_API
#endif


DLL01_EXPORT_API void dll01Func00();

dll01.c:

#include <stdio.h>
#define DLL01_EXPORTS
#include "dll01.h"


void dll01Func00()
{
    printf("In [%s]\n", __FUNCTION__);
}

dll00_v1.c: (modified dll00_v0.c):

#include <inttypes.h>

#if defined(_WIN32)
#  define DLL00_EXPORT_API __declspec(dllexport)
#else
#  define DLL00_EXPORT_API
#endif

#include "dll01.h"


DLL00_EXPORT_API size_t dll00Func00()
{
    dll01Func00();
    return sizeof(void*);
}

Output:

[prompt]> :: Still building for pc032 from previous vcvarsall call
[prompt]>
[prompt]> cl /nologo /DDLL dll01.c  /link /NOLOGO /DLL /OUT:dll01_032.dll
dll01.c
   Creating library dll01_032.lib and object dll01_032.exp

[prompt]> cl /nologo /DDLL dll00_v1.c  /link /NOLOGO /DLL /OUT:dll00_032.dll
dll00_v1.c
   Creating library dll00_032.lib and object dll00_032.exp
dll00_v1.obj : error LNK2019: unresolved external symbol __imp__dll01Func00 referenced in function _dll00Func00
dll00_032.dll : fatal error LNK1120: 1 unresolved externals

[prompt]>
[prompt]> cl /nologo /DDLL dll00_v1.c  /link /NOLOGO /DLL /OUT:dll00_032.dll dll01_032.lib
dll00_v1.c
   Creating library dll00_032.lib and object dll00_032.exp

[prompt]>
[prompt]> "e:\Work\Dev\VEnvs\py_pc032_03.07.09_test0\Scripts\python.exe" code00.py dll00_032
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:01:55) [MSC v.1900 32 bit (Intel)] 032bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00_032.dll]
In [dll01Func00]
dll00Func00 returned 4

Done.

[prompt]> :: Messing up dll01_032.dll
[prompt]> echo garbage> dll01_032.dll

[prompt]> "e:\Work\Dev\VEnvs\py_pc032_03.07.09_test0\Scripts\python.exe" code00.py dll00_032
Python 3.7.9 (tags/v3.7.9:13c94747c7, Aug 17 2020, 18:01:55) [MSC v.1900 32 bit (Intel)] 032bit on win32

Attempting to load: [e:\Work\Dev\StackOverflow\q057187566\dll00_032.dll]
Traceback (most recent call last):
  File "code00.py", line 25, in <module>
    rc = main(*sys.argv[1:])
  File "code00.py", line 14, in main
    dll00 = ct.CDLL(dll_name)
  File "c:\Install\pc032\Python\Python\03.07.09\lib\ctypes\__init__.py", line 364, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: [WinError 193] %1 is not a valid Win32 application

Stating the obvious: Same error would occur if instead of writing garbage data into dll01_032.dll, I would have build it for 064bit, but I chose this variant as it’s shorter.

4. Conclusions

Everything that I’ll state in each of the next bullets, also applies to the ones that follow it.

  • In the examples above, the error occurred when the corruption was in the very .dll being loaded, or in one of its direct dependents (level 1 of indirection). It’s not hard to figure out that applying the same principle multiple times, the behavior wouldn’t change, so it’s valid for any level of indirection.
    Imagine a .dll that depends on several other .dlls, and each of those depends in turn on several others, and so on … . That is called a dependency tree. So no matter where in the tree this error will occur, it will be propagated up to the root node (which is the .dll)
  • The dependency tree propagation applies to other errors as well. Another one that it’s widely encountered is ERROR_MOD_NOT_FOUND (126, 0x7E). It means that the .dll with the specified name (restating: or any other .dll that it (recursively) depends on) was not found.
    As a side note, in order to check a .dll (or .exe) dependencies, use Dependency Walker (newer [GitHub]: lucasg/Dependencies) or dumpbin (part of VStudio installation), or as a matter of fact, any tool that is capable of getting PE dependency information
  • Everything discussed also applies:
    • If the .dll is an extension module (.pyd) that is being imported
    • If the .dll is being loaded as a result of another module being imported
  • Everything discussed also applies to Nix systems, the errors (and corresponding messages), obviously differ

Summarizing:

Make sure that:

  • A 064bit process only attempts to load 064bit .dlls
  • A 032bit process only attempts to load 032bit .dlls

otherwise, ending up in this (nasty) situation is (almost) certain.

Some real life scenario steps leading here:

  1. Installing a software (Python – in this case)

  2. Installing (creating, building) some kind of plugin (that contains .dlls) for that software (an (extension) module – in this case)

Although in general this kind of check is performed at install time, one needs to check (as already stated), that the CPU architectures of the (above) 2, must match.
If that doesn’t happen, change one to match the other, and whenever possible (as there might be some (few) cases when it isn’t), aim for 064bit (as it doesn’t have many of 032bit‘s limitations). In this case install (and run) Python 064bit).

Leave a Comment