Если верить обещаниям Microsoft, в скором времени Windows обзаведется возможностью запускать Linux приложения без каких-либо доработок напильником и сторонних сред окружения вроде Cygwin; одни восприняли эти обещания скептически, дескать, с поддержкой «андроид»-приложений ведь все сошло на нет, другие стали ерничать, мол, ожидайте в недалеком будущем Lindows, ну а третьи… Впрочем, найдутся и четвертые и пятые, — обещания — в будущем, а мы с вами живем здесь и сейчас, решаем задачи также здесь и сейчас, а следовательно какой резон толочь воду в ступе? Приятно, конечно, когда в системе уже предусмотрено нечто в разы упрощающее администрирование, но если этого нет, всегда найдется альтернативный вариант.

Cygwin для многих разработчиков стал своего рода стандартом де-факто: все что нужно для «быстрого старта» в одном инсталляторе, — кто-то под Cygwin даже создает отдельный раздел на диске; но вот что действительно непонятно, так это, например, для чего при развернутом в Cygwin Python устанавливать еще дистрибутив последнего с официального сайта, мотивируя это отсутствием windll в первом? Не знаю, может я не прав, но по-моему это — блажь, ибо Python суть вещь кроссплатформенная и хорошо документированная, а значит все, что написано с использованием windll заработает в окружении Cygwin. Чтобы было понятно о чем речь, приведу примеры вывода «виндового» Python:

>>> from ctypes import windll
>>> 'windll' in globals()
True
>>>

и того, что устанавливается в Cygwin:

>>> from ctypes import windll
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: cannot import name 'windll'
>>>

Так как же оно тогда заработает в Cygwin?! Все предельно просто, если обратиться к документации.

>>> from ctypes import cdll
>>> cdll.kernel32.GetCurrentProcessId()
1592
>>>

Дабы не переписывать в сценариях windll на cdll:

from sys import platform

if platform == 'cygwin':
   from ctypes import cdll as windll
else:
   from ctypes import windll

А чтобы головоломка сложилась, попробуем это применить на практике. Запускам bash (или mintty):

greg@greg:~$ cd /usr/local/bin/
greg@greg:/usr/local/bin$ vim uptime.py

Конечно же код ниже только для примера.

#!/usr/bin/env python3

from sys import platform

if platform == 'cygwin':
   from ctypes import cdll as windll
else:
   from ctypes import windll

from ctypes import byref, c_void_p, c_wchar_p
from struct import pack, unpack
from sys    import exit

sti = pack('qqqLLQQ', 1, 2, 3, 4, 5, 6, 7)
nts = windll.ntdll.NtQuerySystemInformation(3, sti, len(sti), 0)
if nts != 0:
   msg = c_void_p()
   windll.kernel32.FormatMessageW(
      0x00001100, None, windll.ntdll.RtlNtStatusToDosError(nts),
      1024, byref(msg), 0, None
   )
   err = c_wchar_p(msg.value).value
   windll.kernel32.LocalFree(msg)
   print(err.strip() if err else 'Unknown error has been occured.')
   exit(1)
sti = unpack('qqqLLQQ', sti)
sec = (sti[1] - sti[0]) / 1e7
print('%.u.%.2u:%.2u:%.2u' % (
   sec / 86400, (sec / 3600) % 24, (sec % 3600) / 60, sec % 60
))

Сохраняем и выходим. Далее:

greg@greg:/usr/local/bin$ chmod 510 uptime.py
greg@greg:/usr/local/bin$ ln -T uptime.py /bin/uptime
greg@greg:/usr/local/bin$ cd
greg@greg:~$ uptime
13.07:53:29
greg@greg:~$ exit

Если путь до папки bin Cygwin прописан в переменной PATH командной строки Windows:

E:\src>sh -c uptime
13.07:53:59

Как видим, все работает должным образом и из-под Cygwin.

Комментарии (10)


  1. Lertmind
    28.04.2016 22:04
    +1

    Вместо

    from os import name
    
    if name is 'posix':  # name в Cygwin возвращает posix
       from ctypes import cdll
    else:
       from ctypes import windll
    
    windll = cdll if 'cdll' in globals() else windll
    

    можно написать короче:
    from os import name
    
    if name is 'posix':
       from ctypes import cdll as windll
    else:
       from ctypes import windll
    

    И лучше использовать sys.platform, для более точного определения платформы:
    import sys
    
    if sys.platform == 'cygwin':
        from ctypes import cdll as windll
    else:
        from ctypes import windll
    


    1. mwizard
      29.04.2016 01:07
      +6

      Никогда не используйте «is» в качестве строкового сравнения.

      >>> a = 'a' * 100
      >>> b = 'a' * 100
      >>> a == b
      True
      >>> a is b
      False

      Это identity-оператор, он проверяет на равенство указатели, а не значения. И работать как оператор сравнения может исключительно случайно, если обе строки были объявлены как строковые литералы или неявно интернированы.


      1. Lertmind
        29.04.2016 01:46

        Верно, только не «указатели», а «ссылки» (reference). Знал об этом, но не обратил внимания когда брал код из статьи до исправления. Спасибо за указание.


        1. mwizard
          29.04.2016 02:12

          Для иммутабельных типов (чисел, строк, NoneType и т.д.) понятие «ссылки» не имеет смысла, т.к. не определено, что такое экземпляр иммутабельного типа — у них бывают только значения. Поэтому is сравнивает именно что указатели, хоть это и является дырявой абстракцией.


        1. yarulan
          29.04.2016 18:29

          В чем разница между указателем и ссылкой?


          1. mwizard
            29.04.2016 22:28

            Над ссылкой не определена арифметика указателей, плюс ссылки имеют значение с точки зрения сборщиков мусора. Указатель это просто число, адрес в памяти.


            1. yarulan
              29.04.2016 23:59

              Я к тому, что если бы в языке присутствовали одновременно ссылки (для работы с управляемой памятью) и указатели (для работы с неуправляемой памяью), то не следовало бы их путать, а так можно воспринимать их как синонимы. Ссылка ведь тоже просто адрес в памяти, ну может еще пару бит служебной информации.


              1. mwizard
                30.04.2016 00:02

                Но ссылки и указатели отличаются семантически. Да, ссылки, как правило, реализованы через указатели, но это не значит, что они взаимозаменяемы. Ну и, если вернуться к оператору is, то проблема лишь в том, что из-за особенностей реализации он сравнивает указатели, а не ссылки. Это не значит, что это правильно — просто у авторов CPython так получилось. Больше, насколько мне известно, указатели в Python себя не проявляют — за исключением лишь функции id().


  1. GrigoriZakharov
    28.04.2016 22:29

    Спасибо за конструктивные замечания — про as совсем забыл.


  1. taulatin_one
    29.04.2016 18:30

    Коллеги, я что-то про лису и курятник вспомнил.