The following code is supposed to start a new process calc.exe
in debug mode. However, it fails with the code 2 or ERROR_FILE_NOT_FOUND
. However, this file calc.exe
does exist on the system. What could be wrong with this code? Is there an issue with the path that can be improved so this works?
from ctypes import *
kernel32 = windll.kernel32
WORD = c_ushort
DWORD = c_ulong
LPBYTE = POINTER(c_ubyte)
LPTSTR = POINTER(c_char)
HANDLE = c_void_p
class STARTUPINFO(Structure):
_fields_ = [
("cb", DWORD),
("lpReserved", LPTSTR),
("lpDesktop", LPTSTR),
("lpTitle", LPTSTR),
("dwX", DWORD),
("dwY", DWORD),
("dwXSize", DWORD),
("dwYSize", DWORD),
("dwXCountChars", DWORD),
("dwYCountChars", DWORD),
("dwFillAttribute",DWORD),
("dwFlags", DWORD),
("wShowWindow", WORD),
("cbReserved2", WORD),
("lpReserved2", LPBYTE),
("hStdInput", HANDLE),
("hStdOutput", HANDLE),
("hStdError", HANDLE),
]
class PROCESS_INFORMATION(Structure):
_fields_ = [
("hProcess", HANDLE),
("hThread", HANDLE),
("dwProcessId", DWORD),
("dwThreadId", DWORD),
]
DEBUG_PROCESS = 0x00000001
creation_flags = DEBUG_PROCESS
startupinfo = STARTUPINFO()
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0
startupinfo.cb = sizeof(startupinfo)
process_information = PROCESS_INFORMATION()
result = kernel32.CreateProcessA("C:\\Windows\\System32\\calc.exe",
None,
None,
None,
None,
creation_flags,
None,
None,
byref(startupinfo),
byref(process_information)
)
print(result)
print(kernel32.GetLastError())
There are a number of things that are wrong:
Missing ArgTypes and ResType definition which produces Undefined Behavior (although might not be visible). Check [SO]: C function called from Python via ctypes returns incorrect value (@CristiFati's answer) for this common pitfall when working with CTypes (calling functions)
Confusion between A (ANSI, ASCII) and W (WIDE, UNICODE) variants of a function or string related type (check [MS.Learn]: Conventions for Function Prototypes) and the Python arguments it expects. You're using A so you, must pass a Bytes instance. Check [SO]: Passing utf-16 string to a Windows function (@CristiFati's answer) for more details
Don't reinvent the wheel, use ctypes.wintypes
that defines lots of Win specific types. As a note here, I didn't check structures for errors (just adapted their definitions)
code00.py:
#!/usr/bin/env python
import ctypes as cts
import sys
from ctypes import wintypes as wts
class STARTUPINFOA(cts.Structure):
_fields_ = (
("cb", wts.DWORD),
("lpReserved", wts.LPSTR),
("lpDesktop", wts.LPSTR),
("lpTitle", wts.LPSTR),
("dwX", wts.DWORD),
("dwY", wts.DWORD),
("dwXSize", wts.DWORD),
("dwYSize", wts.DWORD),
("dwXCountChars", wts.DWORD),
("dwYCountChars", wts.DWORD),
("dwFillAttribute", wts.DWORD),
("dwFlags", wts.DWORD),
("wShowWindow", wts.WORD),
("cbReserved2", wts.WORD),
("lpReserved2", wts.LPBYTE),
("hStdInput", wts.HANDLE),
("hStdOutput", wts.HANDLE),
("hStdError", wts.HANDLE),
)
LPSTARTUPINFOA = cts.POINTER(STARTUPINFOA)
class PROCESS_INFORMATION(cts.Structure):
_fields_ = (
("hProcess", wts.HANDLE),
("hThread", wts.HANDLE),
("dwProcessId", wts.DWORD),
("dwThreadId", wts.DWORD),
)
LPPROCESS_INFORMATION = cts.POINTER(PROCESS_INFORMATION)
LPSECURITY_ATTRIBUTES = cts.c_void_p
DEBUG_PROCESS = 0x00000001
def main(*argv):
kernel32 = cts.windll.kernel32
GetLastError = kernel32.GetLastError
GetLastError.argtypes = ()
GetLastError.restype = wts.DWORD
CreateProcessA = kernel32.CreateProcessA
CreateProcessA.argtypes = (
wts.LPCSTR, wts.LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES,
wts.BOOL, wts.DWORD, wts.LPVOID, wts.LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION
)
CreateProcessA.restype = wts.BOOL
creation_flags = DEBUG_PROCESS
creation_flags = 0 # Not automatically killed when Python terminates
startupinfo = STARTUPINFOA()
startupinfo.cb = cts.sizeof(STARTUPINFOA)
startupinfo.dwFlags = 0x1
startupinfo.wShowWindow = 0x0
process_information = PROCESS_INFORMATION()
result = kernel32.CreateProcessA(
b"C:\\Windows\\System32\\calc.exe",
None,
None,
None,
False,
creation_flags,
None,
None,
cts.byref(startupinfo),
cts.byref(process_information)
)
print(f"CreateProcessA returned {result}")
if not result:
print(f" Error: {GetLastError()}")
else:
print(f" Created process (PId: {process_information.dwProcessId},"
f" TId: {process_information.dwThreadId})")
if __name__ == "__main__":
print(
"Python {:s} {:03d}bit on {:s}\n".format(
" ".join(elem.strip() for elem in sys.version.split("\n")),
64 if sys.maxsize > 0x100000000 else 32,
sys.platform,
)
)
rc = main(*sys.argv[1:])
print("\nDone.\n")
sys.exit(rc)
Output:
(py_pc064_03.10_test0) [cfati@CFATI-5510-0:e:\Work\Dev\StackExchange\StackOverflow\q078680399]> sopr.bat ### Set shorter prompt to better fit when pasted in StackOverflow (or other) pages ### [prompt]> python ./code00.py Python 3.10.11 (tags/v3.10.11:7d4cc5a, Apr 5 2023, 00:38:17) [MSC v.1929 64 bit (AMD64)] 064bit on win32 CreateProcessA returned 1 Created process (PId: 32444, TId: 22832) Done. [prompt]> :: Calculator window is pops up
As a CTypes alternative, you could try [GitHub]: mhammond/pywin32 - Python for Windows (pywin32) Extensions which is a Python wrapper over WinAPIs (and contains lots of boilerplate code that Python programmers would not have to write). Documentation (WiP) can be found at [GitHub.MHammond]: Python for Win32 Extensions Help (or [ME.TimGolden]: Python for Win32 Extensions Help).