2014-01-28 5 views
5

Я пытаюсь использовать js-ctypes в Firefox для получения уведомлений о USB-носителе USB, но у меня есть несколько проблем, и я не могу сказать, связано ли это с тем, что я «м очень неопытный в Win32 API или ужасны в JS-ctypes (или оба!)jsctypes - проблемы с использованием SHChangeNotifyRegister для событий MEDIA/DRIVE

Я начал, адаптируя пример я нашел в блоге Александра Пуаро:

Этот пример использует js-ctypes для создания окна «только для сообщений», а затем взаимодействует со службой оболочки для связи с лотком уведомлений Windows.

Это кажется достаточно простым, поэтому после некоторого исследования по существу RegisterDeviceNotification против SHChangeNotifyRegister, я пытаюсь адаптировать этот (рабочий!) Пример для регистрации обновлений устройства через SHChangeNotifyRegister.

Код находится в загрузочном (повторном) расширении Firefox (код ниже).

Реализация WindowProc хорошо работает, как в исходном примере. В моем обратном вызове JavaScript регистрируются входящие в Windows сообщения Window (только для этого примера).

Проблемы:

Во-первых, это, кажется, что вызов DestroyWindow падает Firefox (почти всегда) на shutdown() расширения. Есть ли какое-то сообщение Windows, которое я должен обрабатывать в окне «только для сообщений», чтобы изящно обрабатывать DestryWindow?

Во-вторых, хотя это выглядит с выхода консоли (ниже), что я получаю значимых значений из звонков в SHGetSpecialFolderLocation и SHChangeNotifyRegister (возвращаемые значения не являются ошибками и PIDLISTITEM указатель некоторый реальный адрес) Я не получаю сообщения Device/Drive в обратном вызове JavaScript.

Кроме того, я попытался воспроизвести PIDLISTITEM структуры без толка (не мог js-ctypes распознать их в вызовах SHChangeNotifyRegister) и после изучения некоторых других примеров не являющихся C++, кажется, что большинство людей просто используя long* вместо этого - - Надеюсь, это источник моего недоразумения!

Я проверял с помощью подобной C++ sample project from Microsoft, что сами сообщения получены, когда SHChangeNotifyRegistration успешно, и я произвожу USB медиа-события (пу вставляя & удаления USB флэш-носитель).

Минимальный код для воспроизведения проблемы следующим образом:


install.rdf:

<?xml version="1.0"?> 
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" 
    xmlns:em="http://www.mozilla.org/2004/em-rdf#"> 
    <Description about="urn:mozilla:install-manifest"> 
    <em:id>[email protected]</em:id> 
    <em:type>2</em:type> 
    <em:name>TEST WNDPROC</em:name> 
    <em:version>1.0</em:version> 
    <em:bootstrap>true</em:bootstrap> 
    <em:unpack>true</em:unpack> 
    <em:description>Testing wndProc via JS-CTYPES on WIN32.</em:description> 
    <em:creator>David</em:creator> 

    <!-- Firefox Desktop --> 
    <em:targetApplication> 
    <Description> 
    <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> 
    <em:minVersion>4.0.*</em:minVersion> 
    <em:maxVersion>29.0.*</em:maxVersion> 
    </Description> 
    </em:targetApplication> 
    </Description> 
</RDF> 

самозагрузки.JS:

const Cc = Components.classes; 
const Ci = Components.interfaces; 
const Cu = Components.utils; 

Components.utils.import("resource://gre/modules/ctypes.jsm"); 
let consoleService = Cc["@mozilla.org/consoleservice;1"] 
         .getService(Ci.nsIConsoleService); 
function LOG(msg) { 
    consoleService.logStringMessage("TEST-WNDPROC: "+msg); 
} 

var WindowProcType, DefWindowProc, RegisterClass, CreateWindowEx, 
    DestroyWindow, SHGetSpecialFolderLocation, WNDCLASS, wndclass, 
    messageWin, libs = {}; 

var windowProcJSCallback = function(hWnd, uMsg, wParam, lParam) { 
    LOG("windowProc: "+JSON.stringify([uMsg, wParam, lParam])); 
    // 
    // TODO: decode uMsg, wParam, lParam to interpret 
    //  the incoming ShChangeNotifyEntry messages! 
    // 
    return DefWindowProc(hWnd, uMsg, wParam, lParam); 
}; 

function startup(data, reason) { 
    try { 
    LOG("loading USER32.DLL ..."); 
    libs.user32 = ctypes.open("user32.dll"); 

    LOG("loading SHELL32.DLL ..."); 
    libs.shell32 = ctypes.open("shell32.dll"); 

    LOG("registering callback ctype WindowProc ..."); 
    WindowProc = ctypes.FunctionType(
     ctypes.stdcall_abi, ctypes.int, 
     [ctypes.voidptr_t, ctypes.int32_t, 
     ctypes.int32_t, ctypes.int32_t]).ptr; 

    LOG("registering API CreateWindowEx ..."); 
    CreateWindowEx = libs.user32.declare("CreateWindowExA", 
     ctypes.winapi_abi, ctypes.voidptr_t, ctypes.long, 
     ctypes.char.ptr, ctypes.char.ptr, ctypes.int, 
     ctypes.int, ctypes.int, ctypes.int, ctypes.int, 
     ctypes.voidptr_t, ctypes.voidptr_t, ctypes.voidptr_t, 
     ctypes.voidptr_t); 

    LOG("registering API DestroyWindow ..."); 
    DestroyWindow = libs.user32.declare("DestroyWindow", 
     ctypes.winapi_abi, ctypes.bool, ctypes.voidptr_t); 

    /* 

    // previously using.... 

    LOG("registering ctype SHITEMID ..."); 
    var ShItemId = ctypes.StructType("ShItemId", [ 
     { cb: ctypes.unsigned_short }, 
     { abID: ctypes.uint8_t.array(1) } 
    ]); 

    LOG("registering ctype ITEMIDLIST ..."); 
    var ItemIDList = ctypes.StructType("ItemIDList", [ 
     { mkid: ShItemId } 
    ]); 

    */ 

    LOG("registering ctype SHChangeNotifyEntry ..."); 
    var SHChangeNotifyEntry = ctypes.StructType(
     "SHChangeNotifyEntry", [ 
      { pidl: ctypes.long.ptr }, /* ItemIDList.ptr ??? */ 
      { fRecursive: ctypes.bool } 
     ]); 

    LOG("registering API SHChangeNotifyRegister ..."); 
    SHChangeNotifyRegister = libs.shell32.declare(
     "SHChangeNotifyRegister", ctypes.winapi_abi, 
     ctypes.unsigned_long, 
     ctypes.voidptr_t, ctypes.int, ctypes.long, 
     ctypes.unsigned_int, ctypes.int, 
     SHChangeNotifyEntry.array() /* SHChangeNotifyEntry.ptr ??? */ 
    ); 

    LOG("registering ctype WNDCLASS ..."); 
    WNDCLASS = ctypes.StructType("WNDCLASS", [ 
     { style   : ctypes.uint32_t }, 
     { lpfnWndProc : WindowProc  }, 
     { cbClsExtra  : ctypes.int32_t }, 
     { cbWndExtra  : ctypes.int32_t }, 
     { hInstance  : ctypes.voidptr_t }, 
     { hIcon   : ctypes.voidptr_t }, 
     { hCursor  : ctypes.voidptr_t }, 
     { hbrBackground : ctypes.voidptr_t }, 
     { lpszMenuName : ctypes.char.ptr }, 
     { lpszClassName : ctypes.char.ptr } 
    ]); 

    LOG("registering API SHGetSpecialFolderLocation ..."); 
    SHGetSpecialFolderLocation = libs.shell32.declare(
     "SHGetSpecialFolderLocation", ctypes.winapi_abi, 
     ctypes.long, ctypes.voidptr_t, ctypes.int, 
     ctypes.long.ptr  /* ItemIDList.ptr ??? */ 
    ); 

    LOG("registering API RegisterClass ..."); 
    RegisterClass = libs.user32.declare("RegisterClassA", 
     ctypes.winapi_abi, ctypes.voidptr_t, WNDCLASS.ptr); 

    LOG("registering API DefWindowProc ..."); 
    DefWindowProc = libs.user32.declare("DefWindowProcA", 
     ctypes.winapi_abi, ctypes.int, ctypes.voidptr_t, 
     ctypes.int32_t, ctypes.int32_t, ctypes.int32_t); 

    LOG("instatiating WNDCLASS (using windowProcJSCallback) ..."); 
    var cName = "class-testingmessageonlywindow"; 
    wndclass = WNDCLASS(); 
    wndclass.lpszClassName = ctypes.char.array()(cName); 
    wndclass.lpfnWndProc = WindowProc(windowProcJSCallback); 

    LOG("calling API: RegisterClass ..."); 
    RegisterClass(wndclass.address()); 

    LOG("calling API: CreateWindowEx ..."); 
    var HWND_MESSAGE = -3; // message-only window 
    messageWin = CreateWindowEx(
     0, wndclass.lpszClassName, 
     ctypes.char.array()("my-testing-window"), 
     0, 0, 0, 0, 0, 
     ctypes.voidptr_t(HWND_MESSAGE), 
     null, null, null 
    ); 

    LOG("instantiating pidl ..."); 
    var pidl = ctypes.long(); 
    LOG("Prior to call, pidl = "+pidl); 

    LOG("calling API: SHGetSpecialFolderLocation ..."); 
    var CSIDL_DESKTOP = 0; 
    var hr = SHGetSpecialFolderLocation(
     messageWin, 
     CSIDL_DESKTOP, 
     pidl.address() 
    ); 
    LOG("got back: "+hr); 
    LOG("After the call, pidl = "+pidl); 

    LOG("instantiating pschcne ..."); 
    var SHCNE = SHChangeNotifyEntry.array(1); 
    var shcne = SHCNE(); 
    shcne[0].pidl = pidl.address(); 
    shcne[0].fRecursive = false; 

    var WM_SHNOTIFY   = 1025; // 0x401 
    var SHCNE_DISKEVENTS  = 145439; // 0x2381F 
    var SHCNE_DRIVEADD  = 256;  // 256 
    var SHCNE_DRIVEREMOVED = 128;  // 128 
    var SHCNE_MEDIAINSERTED = 32;  // 32 
    var SHCNE_MEDIAREMOVED = 64;  // 64 
    var SHCNRF_ShellLevel  = 2;  // 0x0002 
    var SHCNRF_InterruptLevel = 1;  // 0x0001 
    var SHCNRF_NewDelivery = 32768; // 0x8000 

    var nSources = SHCNRF_ShellLevel | 
        SHCNRF_InterruptLevel | 
        SHCNRF_NewDelivery; 
    var lEvents = SHCNE_DISKEVENTS | SHCNE_DRIVEADD | 
        SHCNE_DRIVEREMOVED | SHCNE_MEDIAINSERTED | 
        SHCNE_MEDIAREMOVED; 
    var uMsg  = WM_SHNOTIFY; 

    LOG("DEBUG: nSources="+nSources); 
    LOG("DEBUG: lEvents="+lEvents); 
    LOG("DEBUG: uMsg="+uMsg); 

    LOG("calling API: SHChangeNotifyRegister ..."); 
    var reg_id = SHChangeNotifyRegister(
     messageWin, nSources, lEvents, uMsg, 1, shcne 
    ); 
    if (reg_id > 0) { 
     LOG("SUCCESS: Registered with ShellService for "+ 
      "DRIVE/MEDIA notifications! reg-id: "+reg_id); 
    } else { 
     LOG("ERROR: Couldn't register for DRIVE/MEDIA "+ 
      "notifications from ShellService!"); 
    }  

    LOG("done!"); 
    } catch (e) { 
    LOG("ERROR: "+e); 
    } 
} 

function shutdown(data, reason) { 
    if (reason == APP_SHUTDOWN) return; 
    try { 

    //LOG("destroying hidden window... "); 
    //DestroyWindow(messageWin); // crash!!! 

    LOG("unloading USER32.DLL ..."); 
    libs.user32.close(); 

    LOG("unloading SHELL32.DLL ..."); 
    libs.shell32.close(); 

    LOG("done!"); 
    } catch (e) { 
    LOG("ERROR: "+e); 
    } 
} 

Консоль вывода:

17:08:25.518 TEST-WNDPROC: loading USER32.DLL ... 
17:08:25.518 TEST-WNDPROC: loading SHELL32.DLL ... 
17:08:25.518 TEST-WNDPROC: registering callback ctype WindowProc ... 
17:08:25.518 TEST-WNDPROC: registering API CreateWindowEx ... 
17:08:25.518 TEST-WNDPROC: registering API DestroyWindow ... 
17:08:25.518 TEST-WNDPROC: registering ctype SHChangeNotifyEntry ... 
17:08:25.518 TEST-WNDPROC: registering API SHChangeNotifyRegister ... 
17:08:25.518 TEST-WNDPROC: registering ctype WNDCLASS ... 
17:08:25.518 TEST-WNDPROC: registering API SHGetSpecialFolderLocation ... 
17:08:25.518 TEST-WNDPROC: registering API RegisterClass ... 
17:08:25.518 TEST-WNDPROC: registering API DefWindowProc ... 
17:08:25.519 TEST-WNDPROC: instatiating WNDCLASS (using windowProcJSCallback) ... 
17:08:25.519 TEST-WNDPROC: calling API: RegisterClass ... 
17:08:25.519 TEST-WNDPROC: calling API: CreateWindowEx ... 
17:08:25.519 TEST-WNDPROC: windowProc: [36,0,2973696] 
17:08:25.519 TEST-WNDPROC: windowProc: [129,0,2973652] 
17:08:25.519 TEST-WNDPROC: windowProc: [131,0,2973728] 
17:08:25.519 TEST-WNDPROC: windowProc: [1,0,2973608] 
17:08:25.519 TEST-WNDPROC: instantiating pidl ... 
17:08:25.519 TEST-WNDPROC: Prior to call, pidl = ctypes.long(ctypes.Int64("0")) 
17:08:25.519 TEST-WNDPROC: calling API: SHGetSpecialFolderLocation ... 
17:08:25.519 TEST-WNDPROC: got back: 0 
17:08:25.519 TEST-WNDPROC: After the call, pidl = ctypes.long(ctypes.Int64("224974424")) 
17:08:25.519 TEST-WNDPROC: instantiating pschcne ... 
17:08:25.519 TEST-WNDPROC: DEBUG: [nSources=32771][lEvents=145919][uMsg=1025] 
17:08:25.519 TEST-WNDPROC: calling API: SHChangeNotifyRegister ... 
17:08:25.520 TEST-WNDPROC: SUCCESS: Registered with ShellService for DRIVE/MEDIA 
          notifications! reg-id: 15 
17:08:25.520 TEST-WNDPROC: done! 
----- &< ------- 
17:09:31.391 TEST-WNDPROC: unloading USER32.DLL ... 
17:09:31.391 TEST-WNDPROC: unloading SHELL32.DLL ... 
17:09:31.391 TEST-WNDPROC: done! 
+0

Вы уверены, что FF и оболочка работают на одном уровне UAC? Например, если FF работает как администратор, а проводник ограничен (что является поведением по умолчанию), они не могут просто легко общаться. –

+0

Я уверен, что и Firefox, и C++-образец работают на одном уровне UAC; пользователь оболочки является «Администратором» (для чего это стоит). Образец C++, с которым я связан, получает сообщения. Окно «только для сообщений», которое я создал в Firefox, похоже, (но я мог бы сделать что-то неправильно). –

+0

Тот факт, что пользователь оболочки является администратором, не связан с уровнем UAC оболочки. По умолчанию в Windows процессы оболочки (explorer.exe) работают на ограниченном уровне UAC и не могут общаться (с сообщениями Windows) с приложениями, запущенными на полном уровне UAC. –

ответ

1

Для тех, кто еще ищет, чтобы сделать это, я отправляю неприятную взломать из работы вокруг. (Я не буду принимать этот ответ в надежде, что в конечном итоге кто-то опубликует, как это сделать правильно).


После начитанности, единственным рекомендуемым способом я мог бы в перечислении и/или определения состояния томов USB использует WMI. Следующий запрос WQL сделал трюк:

select Caption, Size from win32_LogicalDisk where DriveType = 2 

Чтобы использовать WQL от C++, вы должны использовать COM. Используя это от js-ctypes, это не несущественная инженерная задача. Вам необходимо организовать загрузку и использование DLL из ChromeWorker, и иногда я обнаружил, что мне нужно было убедиться, что функции обратного вызова JavaScript вызываются из правильного потока Firefox и что COM не инициализируется в многопоточной квартире.

Caption - буква диска. Казалось бы, как только USB-накопитель выгружается, Size сообщает как ноль.

Было тогда достаточно просто назвать это в петле опроса внутри потока ChromeWorker, моделировать изменения в смонтированных томах и поднять синтетические события USB-Mounted/Ejected/Removed в моих окнах DOM.


К сожалению, была один огромной проблемы с этим. Если вы вставляете USB-накопитель, обычно требуется от 2 до 30 секунд (в зависимости от размера), который будет установлен Windows. В течение этого времени (особенно после 1-й секунды или около того), если вы запустите вышеуказанный запрос WQL, он будет БЛОКИРОВАТЬ USB-ОБЪЕМ ИЗ СОЕДИНЕНИЯ ОПЕРАЦИОННОЙ СИСТЕМОЙ (?!?) Эффективно вызывает отказ в обслуживании.

Однако гораздо недоверчивость это заставило меня, после того, как пытливый я был уверен, что если бы я использовал asynchronous (а не synchronous или semisynchronous) WQL запросов, что отказ в обслуживании не произойдет.

SELECT * FROM __instanceoperationevent WITHIN 2 
WHERE TargetInstance ISA 'Win32_LogicalDisk' and TargetInstance.DriveType = 2 

Если __instanceoperationevent ISA __InstanceCreationEvent затем добавляют объем. Если __instanceoperationevent ISA __InstanceDeletionEvent, то объем был удален.

Был бы показаться, что когда __instanceoperationevent ISA __InstanceModificationEvent то объем был выброшен, но это не для меня ясно, что другого вида операции может привести к этому. Поскольку этот уровень все еще подключен в данный момент, вероятно, можно детерминистически запросить его Size, используя первый запрос synchronous (см. Выше) для проверки.

asynchronous Запросы WQL можно назвать двумя разными способами: temporary или permanent WMI-события.Разница не огромна, но фильтры permanent + потребители, по-видимому, рекомендуются и, похоже, не реагируют на «квоты» запроса WQL.

В любом случае, нет разумного способа обработки полученных событий WMI с использованием обратных вызовов JavaScript, прошедших через js-ctypes. :-(Это оставило искал способ, чтобы потреблять события, а затем направляет их обратно в Firefox.

Я закончил с использованием Perl-скрипт, клубника, основанный на DBD::WMI на @ ответ Corion на a perlmonks question опрашивать асинхронно для событий каждые 2 секунды а затем использовали IO::Socket::INET отчет о результатах Firefox, отправляя их через сокет TCP (Вы можете сделать это на любом языке вообще - я бываю, чтобы быть удобным с Perl)..

тогда я реализовал nsIServerSocket из моей аддон, ожидая \n завершенных линий для анализа собранного ввода и проведения тех же моделей и синтетических событий, как описано выше.

+0

Большое спасибо за обмен этим решением! – Noitidart

+0

Вы пробовали WQL через js-ctypes? COM - это удовольствие от js-ctypes heres руководство по MDN: https://developer.mozilla.org/en-US/docs/Mozilla/js-ctypes/Examples/Using_COM_from_js-ctypes – Noitidart

+1

Проблема (назад, когда я был делая это) было то, что асинхронный обратный вызов будет доставлен в неправильный поток firefox (а не рабочий), что приведет к сбою Firefox. Примеры использования async WQL, которые я видел, используют обычную потоковую обработку (COINIT_MULTITHREADED). Похоже, автор статьи просто проигнорировал документы и попробовал Thread threading (COINIT_APARTMENTTHREADED), и это сработало в его случае. Думаю, это значит, что это может сработать в моем случае. Если у меня когда-нибудь будет шанс, я еще раз посмотрю. –