1

I have a Windows python3.7 function which successfully calls the kernel32.dll GetSystemPowerStatus function using ctypes to interrogate the power status to see if my laptop is on AC or battery power. This is a pure python solution.

I want to port this function to cygwin python3.7. Out of the box, python3 for cygwin's ctypes does not seem to allow calling a windows dll. I would prefer a pure python solution, but I can use C/C++ if necessary. Does anyone have an example of how to do this?

Edited to add the code (lines 63-67) and error messages:

elif _os.name == 'posix' and _sys.platform == 'cygwin':
    # c:\Windows\System32\kernel32.dll
    kernel32_name = '/proc/cygdrive/c/Windows/System32/kernel32.dll'
    kernel32 = CDLL(kernel32_name)
    _GetSystemPowerStatus = kernel32.GetSystemPowerStatus


$ python3.7 GetSystemPowerStatus.py
Traceback (most recent call last):
  File "GetSystemPowerStatus.py", line 82, in <module>
    result, systemPowerStatus = GetSystemPowerStatus()
  File "GetSystemPowerStatus.py", line 66, in GetSystemPowerStatus
    kernel32 = CDLL(kernel32_name)
  File "/usr/lib/python3.7/ctypes/__init__.py", line 356, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: Invalid argument

python2.7 gives the same error, but at line 366.

Solved. See my own answer below.

Doug Henderson
  • 785
  • 8
  • 17
  • Please give the error message that you get when you run your working windows code inside cygwin – Eric Mar 03 '19 at 06:36
  • '$ python3.7 GetSystemPowerStatus.py⏎ Traceback (most recent call last):⏎ File "GetSystemPowerStatus.py", line 69, in ⏎ result, systemPowerStatus = GetSystemPowerStatus()⏎ File "GetSystemPowerStatus.py", line 57, in GetSystemPowerStatus⏎ _GetSystemPowerStatus = cdll.kernel32.GetSystemPowerStatus⏎ File "/usr/lib/python3.7/ctypes/__init__.py", line 426, in __getattr__⏎ dll = self._dlltype(name)⏎ File "/usr/lib/python3.7/ctypes/__init__.py", line 356, in __init__⏎ self._handle = _dlopen(self._name, mode)⏎ OSError: No such file or directory⏎' – Doug Henderson Mar 03 '19 at 07:37
  • Edit the question with additional, formatted information. Comments obviously are a mess to read. – Mark Tolonen Mar 03 '19 at 18:17
  • Quite puzzling, actually this works for me: `ctypes.CDLL("/cygdrive/c/windows/system32/user32.dll")` but not with `kernel32.dll`. I get exactly the same error. So it's possible to load a windows library from cygwin, but **not** kernel32 :( – Neitsa Mar 04 '19 at 15:26
  • Thanks. Very interesting. Must dig in source of ctypes, I guess. – Doug Henderson Mar 04 '19 at 15:29

2 Answers2

0

As you I wasn't able to get kernel32.dll (although it works with other DLLs like user32, msvcrt, kernelbase, etc.)

I found a pretty contrived way of doing it... This uses kernelbase.dll (which exports GetModuleHandle) to get an handle on kernel32.dll, then call CDLL with the handle optional keyword:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import ctypes

def main():
    # the idea is to load kernelbase.dll which will allow us to call GetModuleHandleW() on kernel32.dll
    try:
        kernel_base = ctypes.CDLL("/cygdrive/c/windows/system32/kernelbase.dll")
    except OSError:
        print("Can't load kernelbase.dll...")
        return -1

    gmhw = kernel_base.GetModuleHandleW
    gmhw.argtypes = (ctypes.c_wchar_p, )
    gmhw.restype = ctypes.c_void_p

    # call GetModuleHandleW on kernel32.dll (which is loaded by default in the Python process)
    kernel32_base_addr = gmhw("kernel32.dll")
    print(f"Got kernel32 base address: {kernel32_base_addr:#x}")

    # now call CDLL with optional arguments
    kernel32 = ctypes.CDLL("/cygdrive/c/windows/system32/kernel32.dll", handle=kernel32_base_addr, use_last_error=True)

    # test with GetSystemPowerStatus
    print(f"GetSystemPowerStatus: {kernel32.GetSystemPowerStatus}")

    return 0

if __name__ == "__main__":
    sys.exit(main())
Neitsa
  • 7,693
  • 1
  • 28
  • 45
-1

After finding that I could load user32.dll easily, I did some more investigation with the source and pdb and ldd. What follows is my solution.

elif _os.name == 'posix' and _sys.platform == 'cygwin':
    RTLD_LOCAL = 0  # from /usr/include/dlfcn.h
    RTLD_LAZY = 1
    RTLD_NOW = 2
    RTLD_GLOBAL = 4
    RTLD_NODELETE = 8
    RTLD_NOLOAD = 16
    RTLD_DEEPBIND = 32
    kernel32_name = '/proc/cygdrive/c/Windows/System32/kernel32.dll'
    kernel32 = CDLL(kernel32_name, mode=RTLD_LAZY | RTLD_NOLOAD)
    _GetSystemPowerStatus = kernel32.GetSystemPowerStatus

The common code to call the function is:

_GetSystemPowerStatus.restype = c_bool
_GetSystemPowerStatus.argtypes = [c_void_p,]

_systemPowerStatus = _SystemPowerStatus()  # not shown, see ctypes module doc

result = _GetSystemPowerStatus(byref(_systemPowerStatus))

return result, SystemPowerStatus(_systemPowerStatus)

This works fine with Python 2.7, 3.6 and 3.6 on

$ uname -a
CYGWIN_NT-10.0 host 3.0.1(0.338/5/3) 2019-02-20 10:19 x86_64 Cygwin

Thanks to all for their help.

Doug Henderson
  • 785
  • 8
  • 17
  • Also check: https://stackoverflow.com/questions/53781370/is-there-an-alternative-way-to-import-ctypes-wintypes-in-cygwin. – CristiFati Mar 09 '19 at 03:08