Спасибо за ответ Нуно Андре, который показал, как использовать ctypes для взаимодействия с Windows API. Я написал пример реализации, используя его подсказки.
Библиотека ctypes
включена в Python, начиная с версии 2.5, а это значит, что она есть почти у каждого пользователя. И это намного более чистый интерфейс, чем старые и мертвые библиотеки, такие как win32gui
(последнее обновление в 2017 году на момент написания этой статьи). ((Обновление в конце 2020 года: мертвая библиотека win32gui
ожила с переименованием в pywin32, поэтому, если вам нужна поддерживаемая библиотека, теперь это снова допустимый вариант. Но эта библиотека на 6% медленнее, чем мой код.))
Документация находится здесь: https://docs.python.org/3/library/ctypes.html (вы должны прочитать справку по его использованию, если хотите написать свой собственный код, иначе вы можете вызвать сбои из-за ошибки сегментации, хе-хе.)
По сути, ctypes включает привязки для наиболее распространенных библиотек DLL Windows. Вот как вы можете получить заголовок окна переднего плана на чистом Python без каких-либо внешних библиотек! Только встроенные типы! :-)
Самое крутое в ctypes - это то, что вы можете использовать любой Windows API в Google для всего, что вам нужно, и если вы хотите его использовать, вы можете сделать это через ctypes!
Код Python 3:
from typing import Optional
from ctypes import wintypes, windll, create_unicode_buffer
def getForegroundWindowTitle() -> Optional[str]:
hWnd = windll.user32.GetForegroundWindow()
length = windll.user32.GetWindowTextLengthW(hWnd)
buf = create_unicode_buffer(length + 1)
windll.user32.GetWindowTextW(hWnd, buf, length + 1)
# 1-liner alternative: return buf.value if buf.value else None
if buf.value:
return buf.value
else:
return None
Чрезвычайно хорошая производительность: 0.01
МИЛЛИСЕКУНД на моем компьютере (0.00001
секунд).
Также будет работать на Python 2 с очень небольшими изменениями. Если вы используете Python 2, я думаю, вам нужно только удалить аннотации типов (from typing import Optional
и -> Optional[str]
). :-)
Наслаждаться!
Технические пояснения к Win32:
Переменная length
- это длина фактического текста в СИМВОЛАХ UTF-16 (Windows Wide Unicode). (Это НЕ количество БАЙТОВ.) Мы должны добавить + 1
чтобы добавить место для нулевого терминатора в конце строк в стиле C. Если мы этого не сделаем, у нас не будет достаточно места в буфере, чтобы уместить последний реальный символ фактического текста, и Windows усечет возвращаемую строку (она делает это, чтобы гарантировать, что она соответствует очень важной конечной строке Null -терминатор).
Функция create_unicode_buffer
выделяет место для такого количества СИМВОЛОВ UTF-16.
Большинство (или все? Всегда читайте документы Microsoft MSDN!) Windows API, относящиеся к тексту Unicode, принимают длину буфера как СИМВОЛЫ, НЕ как байты.
Также внимательно посмотрите на вызовы функций. Некоторые заканчиваются на W
(например, GetWindowTextLengthW
). Это означает широкую строку, которая является именем Windows для строк Unicode. Очень важно, чтобы вы выполняли эти W
вызовы для получения правильных строк Unicode (с поддержкой международных символов).
PS: Windows уже давно использует Unicode. Я точно знаю, что Windows 10 является полностью Unicode и требует только W
вызовов функций. Я не знаю точной даты окончания, когда старые версии Windows использовали другие форматы многобайтовых строк, но я думаю, что это было до Windows Vista, и кого это волнует? Старые версии Windows (даже 7 и 8.1) мертвы и не поддерживаются Microsoft.
Снова ... наслаждайтесь! :-)
ОБНОВЛЕНИЕ в конце 2020 г., сравнительный анализ и библиотека pywin32
:
import time
import win32ui
from typing import Optional
from ctypes import wintypes, windll, create_unicode_buffer
def getForegroundWindowTitle() -> Optional[str]:
hWnd = windll.user32.GetForegroundWindow()
length = windll.user32.GetWindowTextLengthW(hWnd)
buf = create_unicode_buffer(length + 1)
windll.user32.GetWindowTextW(hWnd, buf, length + 1)
return buf.value if buf.value else None
def getForegroundWindowTitle_Win32UI() -> Optional[str]:
# WARNING: This code sometimes throws an exception saying
# "win32ui.error: No window is is in the foreground."
# which is total nonsense. My function doesn't fail that way.
return win32ui.GetForegroundWindow().GetWindowText()
iterations = 1_000_000
start_time = time.time()
for x in range(iterations):
foo = getForegroundWindowTitle()
elapsed1 = time.time() - start_time
print("Elapsed 1:", elapsed1, "seconds")
start_time = time.time()
for x in range(iterations):
foo = getForegroundWindowTitle_Win32UI()
elapsed2 = time.time() - start_time
print("Elapsed 2:", elapsed2, "seconds")
win32ui_pct_slower = ((elapsed2 / elapsed1) - 1) * 100
print("Win32UI library is", win32ui_pct_slower, "percent slower.")
Типичный результат после нескольких запусков на AMD Ryzen 3900x:
Моя функция: 4.5769994258880615 секунд
Библиотека Win32UI: 4.8619983196258545 секунд
Библиотека Win32UI на 6,226762715455125 процентов медленнее.
Однако разница небольшая, поэтому вы можете использовать библиотеку сейчас, когда она ожила (ранее она была мертва с 2017 года). Но вам придется иметь дело с этой странной библиотекой: нет окна в исключении переднего плана, от которого мой код не страдает (см. Комментарии к коду в тестовом коде).
В любом случае ... наслаждайтесь!
person
Mitch McMabers
schedule
12.10.2019