Конечно, очень выполнимо, но имейте в виду, что код в этом ответе становится довольно продвинутым .
Это без использования каких-либо внешних утилит.
Так что мы заинтересованы в EnumAudioEndpoints
метод интерфейса IMMDeviceEnumerator
.
С помощью этого метода мы можем получить список желаемых аудиоустройств на основе некоторых критериев.
Проблема:
Как мы используем такого метода в AHK?
По DllCall
. И для этого нам нужен его адрес в памяти (указатель), поскольку DllCall
может вызывать функции / методы по адресу.
Итак, мы начнем с получения IMMDeviceEnumerator
указатель интерфейса.
Для этого нам потребуется CLSID , а в этом случае также его IID . Я нашел их с помощью поиска Google.
Затем мы используем функцию ComObjCreate
AHK (и, чтобы сделать ее еще более сложной, да, мы работаем с ComObjects )
И, как указано в документации AHK, мы действительно получаем указатель вместо объекта из функции, потому что мы указали IID.
CLSID := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
pDeviceEnumerator := ComObjCreate(CLSID, IID)
Теперь, когда у нас есть указатель на интерфейс pDeviceEnumerator
, нам нужен указатель на метод интерфейса EnumAudioEndpoints
.
Так что это наша следующая проблема.
Во-первых, нам нужно понять, что наш требуемый метод является первым методом интерфейса.
Но , потому что интерфейс наследуется от IUnknown
интерфейса первые три метода на самом деле AddRef
, QueryInterface
и Release
.
Поэтому наш желаемый метод интерфейса - это на самом деле четвертый метод интерфейса.
Итак, мы хотим получить указатель на 4-й метод этого интерфейса.
Чтобы сделать это, мы сначала хотим получить указатель на виртуальную таблицу интерфейса . Vtable содержит указатель каждого метода. И после того, как у нас есть указатель на vtable, мы можем получить указатель на нужный нам метод из этой vtable.
Чтобы получить эти указатели, мы будем использовать функцию AHK NumGet
.
Обычно vtable располагается в начале ComObject (со смещением 0), поэтому NumGet
наш pDeviceEnumerator
указатель на смещение 0 позволяет добраться до vtable:
vtable := NumGet(pDeviceEnumerator+0)
* Указано 1077 *, поэтому AHK не обрабатывает переменную pDeviceEnumerator
как переменную ByRef, а вместо этого работает по желаемому адресу в памяти. И мы опускаем второй и третий параметры, чтобы использовать значения по умолчанию, смещение 0 (это именно то, что мы хотим), а также тип UPtr нам подходит.
Теперь, когда у нас есть адрес памяти vtable, давайте, наконец, получим указатель на метод EnumAudioEndpoints
.
Теперь вспомним, как это первый (но на самом деле четвертый ) метод в vtable (смещение 3).
Итак, мы хотим чтобы получить адрес в памяти, который смещен на 3 метода от адреса памяти виртуальной таблицы.
А теперь вспомните, как виртуальная таблица содержала указатели, поэтому мы хотим go переслать размер 3 указатели в памяти. Размер указателя составляет 4 байта на 32-битной машине и 8 байтов на 64-битной машине.
Так что можно с уверенностью сказать, что в настоящее время размер указателя всегда составляет 8 байтов, когда наша программа выполняется на современном настольный компьютер. Мы также можем использовать встроенную переменную AHK A_PtrSize
. Он будет содержать 4 или 8.
Визуальное представление указателей, хранящихся в виртуальной таблице:
vtable offset (bytes)
AddRef 0
QueryInterface 8
Release 16
EnumAudioEndpoints 24
GetDefaultAudioEndpoint 32
GetDevice 40
...
Итак, мы хотим NumGet
со смещением 24 байта:
pEnumAudioEndpoints := NumGet(vtable+0, 3*A_PtrSize)
(Демонстрация использования A_PtrSize
для обеспечения совместимости вашего скрипта и на 32-битных машинах, но вы также можете не иметь этого и указать 24)
Хорошо, фу, теперь мы наконец иметь указатель на метод IMMDeviceEnumerator::EnumAudioEndpoints
, что означает, что мы наконец можем его использовать.
Итак, следующая проблема, как мы его используем?
Во-первых, нам нужно решить как мы хотим это использовать. Я могу думать о двух способах, которыми мы хотели бы использовать это.
Первое - это список всех активных и подключенных устройств, выполнение с ними нужных задач и полное отказ от использования nircmd, а второе - небольшое упрощение, которое будет работать только для вашего конкретного случая c.
Я продемонстрирую второй способ для вас, и если вы хотите сделать правильную реализацию, вы можете сами попробовать реализовать первый способ. Если у вас возникли проблемы, вы, конечно, можете обратиться за помощью.
Итак, второй способ, упрощение. Для этого я подумал о том, чтобы перечислить неподключенные устройства, и если оно есть, вы узнаете, что в указанном вами c корпусе телевизор отключен от сети.
Если его нет , вы будете знать, что телевизор подключен.
Хорошо, так что используйте метод. Он ожидает три аргумента:
dataFlow
Для этого параметра мы указываем значение из перечисления EDataFlow
. Наше желаемое значение - eRender
, которое является первым членом перечисления, поэтому 0 . dwStateMask
Для этого мы указываем желаемые побитовые флаги. Нам нужны только неподключенные устройства, поэтому у нас все будет хорошо только с флагом DEVICE_STATE_UNPLUGGED
(0x00000008
). **ppDevices
Здесь мы указываем указатель на переменная, которая будет получать указатель на адрес памяти, где расположен результирующий интерфейс IMMDeviceCollection
.
А теперь на DllCall
ing. Способ вызова метода с помощью DllCall
еще больше AHK magi c, вы даже вряд ли найдете его в документации, но он вроде как.
Методы вызываются в экземплярах, поэтому для первого параметр DllCall
мы передадим указатель метода, который мы сохранили в pEnumAudioEndpoints
, а для второго параметра мы хотим передать указатель объекта (экземпляр интерфейса), с которым мы работаем, который мы сохранили в pDeviceEnumerator
.
После этого мы обычно передаем аргументы методу.
DllCall(pEnumAudioEndpoints, Ptr, pDeviceEnumerator, UInt, 0, UInt, 0x00000008, PtrP, pDeviceCollection)
Синтаксис DllCall
имеет тип Type, а затем Argument.
Сначала мы передаем указатель, Ptr.
Затем мы передаем два неотрицательных числа, с типом unsigned integer, UInt, все будет в порядке.
Затем мы передаем PtrP , чтобы передать указатель переменной pDeviceCollection
.
Вы заметите, что эта переменная даже не была объявлена, но это нормально, AHK - такой прощающий язык, поэтому он автоматически создаст переменную для нас.
Хорошо, теперь DllCall
все готово и у нас есть указатель на результирующий интерфейс IMMDeviceCollection
.
Вы заметите, как интерфейс включает два метода: GetCount
и Item
.
Для упрощенного способа, который я демонстрирую для вас, нас интересует метод GetCount
.
Итак, еще раз, мы получим адрес vtable этого интерфейса:
vtable := NumGet(pDeviceCollection+0)
И снова нас интересует первый (но на самом деле четвертый) метод интерфейса (смещение 3):
pGetCount := NumGet(vtable+0, 3*A_PtrSize)
И тогда мы уже можем использовать метод, поэтому давайте снова наберем DllCall
.
И на этот раз объект, над которым мы действуем: IMMDeviceCollection
, и его указатель хранится в переменной pDeviceCollection
.
функции ожидают только один аргумент *pcDevices
, указатель на переменную, которая будет получать число устройств в нашей коллекции устройств.
DllCall(pGetCount, Ptr, pDeviceCollection, UIntP, DeviceCount)
И вот мы go, упрощенный способ все готово.
Мы успешно получили количество отключенных, но включенных воспроизведения аудио d evices.
Теперь, когда мы узнаем, что мы закончили с ComObjects, мы должны выпустить их (как указано в документации). Это не обязательно на 100%, но это определенно хорошая практика, поэтому давайте выпустим ComObjects:
ObjRelease(pDeviceEnumerator)
ObjRelease(pDeviceCollection)
И вот полный пример сценария для упрощенного способа:
#NoEnv ;unquoted types in DllCall don't hinder performance
CLSID := "{BCDE0395-E52F-467C-8E3D-C4579291692E}"
IID := "{A95664D2-9614-4F35-A746-DE8DB63617E6}"
pDeviceEnumerator := ComObjCreate(CLSID, IID)
vtable := NumGet(pDeviceEnumerator+0)
pEnumAudioEndpoints := NumGet(vtable+0, 3*A_PtrSize)
DllCall(pEnumAudioEndpoints, Ptr, pDeviceEnumerator, UInt, 0, UInt, 0x00000008, PtrP, pDeviceCollection)
vtable := NumGet(pDeviceCollection+0)
pGetCount := NumGet(vtable+0, 3*A_PtrSize)
DllCall(pGetCount, Ptr, pDeviceCollection, UIntP, DeviceCount)
ObjRelease(pDeviceEnumerator)
ObjRelease(pDeviceCollection)
if (DeviceCount = 0)
MsgBox, % "No unplugged, but enabled, devices found`nI'll assume my TV is plugged in and I have three audio devices enabled"
else if (DeviceCount = 1)
MsgBox, % "One unplugged, but enabled, device found`nI'll assume my TV is unplugged and I have only two audio devices enabled"
else
MsgBox, % "There are " DeviceCount "unplugged audio devices"
Если это кажется очень сложным / сложным, то это потому, что вроде как.
Я бы сказал, что это почти так же сложно, как AHK DllCall
ing.
Но хорошо, это так, когда вы не используете внешнюю утилиту, которая делает все самое интересное для вас.
Если вы решили реализовать правильное решение для обработки ваших аудиоустройств, о которых я говорил, this может быть хорошей библиотекой, которую вы можете использовать или использовать. Я сам этим не пользовался, поэтому не могу сказать, устарел ли там какой-то материал.
Он сделан самим Лексикосом.