jsctypes-将SHChangeNotifyRegister用于MEDIA/DRIVE事件的问题 [英] jsctypes - problems using SHChangeNotifyRegister for MEDIA/DRIVE events

查看:104
本文介绍了jsctypes-将SHChangeNotifyRegister用于MEDIA/DRIVE事件的问题的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试在Firefox中使用js-ctypes来接收USB媒体/驱动器通知,但是我遇到了一些问题,我无法确定是否是因为我对Win32 API的经验不足或不了解js-ctypes(或两者都有!)

I'm trying to use js-ctypes in Firefox to receive USB media/drive notifications, but I'm having a few issues and I can't tell if it's because I'm very inexperienced at Win32 API or awful at js-ctypes (or both!)

我首先改编了我在亚历山大·波洛(Alexandre Poirot)博客中找到的示例:

I've started by adapting an example I found on Alexandre Poirot's blog:

  • Blog Entry
  • Full JS Source

该示例使用js-ctypes创建一个仅消息"窗口,然后与shell服务进行交互以与Windows通知托盘进行通信.

That example uses js-ctypes to create a "message-only" window, and then interacts with the shell service for the purpose of communicating with the Windows notification tray.

这似乎很简单,因此经过对

It seems simple enough, so after some research on the merits of RegisterDeviceNotification vs SHChangeNotifyRegister, I'm trying to adapt that (working!) example to register for device updates via SHChangeNotifyRegister.

代码驻留在自举(无重启)的Firefox扩展程序中(下面的代码).

The code resides in a bootstrapped (restartless) Firefox extension (code below).

与原始示例一样,WindowProc的实现效果很好.我的JavaScript回调记录了传入的Window消息(在此示例中仅为数字形式).

The implementation of the WindowProc works well, as in the original example. My JavaScript callback logs the Window messages that come in (just numerically for this example).

问题:

首先,似乎在扩展名shutdown()上调用DestroyWindow使Firefox崩溃(几乎总是).我应该在仅消息"窗口上处理一些Windows消息以优雅地处理DestryWindow吗?

Firstly, it seems that calling DestroyWindow crashes Firefox (almost always) on shutdown() of the extension. Is there some Windows message I should handle on the "message-only" window to gracefully handle DestryWindow ?

其次,尽管从控制台输出(如下)来看,我从对SHGetSpecialFolderLocationSHChangeNotifyRegister的调用中得到了有意义的值(返回值不是错误,而PIDLISTITEM指针是某个真实地址),但我没有在JavaScript回调中收到设备/驱动器消息.

Secondly, although it looks from the console output (below) that I'm getting meaningful values out of the calls to SHGetSpecialFolderLocation and SHChangeNotifyRegister (the return values aren't errors and the PIDLISTITEM pointer is some real address) I'm not getting Device/Drive messages in the JavaScript callback.

此外,我尝试重现PIDLISTITEM结构无济于事(在调用SHChangeNotifyRegister时无法得到js-ctypes识别它们),并且在研究了一些其他非C ++示例之后,似乎大多数人只是使用long*代替-我希望这是我误会的根源!

Also, I tried to reproduce the PIDLISTITEM structures to no avail (couldn't get js-ctypes to recognise them in calls to SHChangeNotifyRegister) and after studying some other non C++ examples, it seems that most folks are just using long* instead -- I hope that's the source of my misunderstanding!

我已经通过类似的

I've verified via similar C++ sample project from Microsoft that the messages themselves are received when the SHChangeNotifyRegistration succeeds and I generate USB media events (ny inserting & removing USB flash media).

重现问题的最小代码如下:

Minimal code to reproduce the issues follows:

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>testwndproc@foo.com</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>

bootstrap.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!

推荐答案

对于其他想要这样做的人,我发布了一个令人讨厌的 hack 解决方法. (我不会接受此答案,希望最终有人会发布如何正确执行此操作.)

For anyone else looking to do this, I'm posting a nasty hack of a work-around. (I won't accept this answer, in the hope that eventually someone will post how to do it properly).

广泛阅读之后,我枚举和/或确定USB卷状态的唯一推荐的其他方法是使用WMI.以下WQL查询可以解决问题:

After extensive reading, the only other recommended way I could of enumerating and/or determining the status of USB volumes was using WMI. The following WQL query did the trick:

select Caption, Size from win32_LogicalDisk where DriveType = 2

要使用C ++中的WQL,必须使用COM.从js-ctypes使用它并不是无关紧要的工程任务.您需要安排从ChromeWorker加载和使用DLL,有时我发现我特别需要确保从正确的Firefox线程中调用了JavaScript回调函数,并且未在多线程中初始化COM.公寓.

To use WQL from C++, you have to use COM. Using that from js-ctypes is not insignificant engineering task. You need to arrange for the DLL to be loaded and used from a ChromeWorker and sometimes I found that I specifically needed to make sure that JavaScript callback functions were being called from the correct Firefox thread, and that COM is not initialised in a multithreaded apartment.

Caption是驱动器号.似乎在弹出USB驱动器后,Size报告为零.

Caption is the drive letter. It would seem that once a USB drive is ejected Size reports as zero.

然后在ChromeWorker线程内的轮询循环中调用此代码,对已装入的卷进行更改建模,并在我的DOM窗口中引发合成的USB挂载/弹出/删除事件,相当简单.

It was then reasonably simple to call this in a polling loop inside the ChromeWorker thread, model the changes to the mounted volumes, and raise synthetic USB-Mounted/Ejected/Removed events in my DOM windows.

不幸的是,这有一个 巨大 问题.如果插入USB闪存驱动器,则Windows挂载通常需要2到30秒(取决于大小).在这段时间内(尤其是1秒左右),如果您运行上述WQL查询,它将阻止USB容量被操作系统安装(?!?)有效导致拒绝服务.

Unfortunately, there was one huge problem with this. If you insert a USB flash drive it generally takes between 2 and 30 seconds (depending on size) to be mounted by Windows. During that time (particularly after the 1st second or so), if you run the above WQL query, it will BLOCK THE USB VOLUME FROM BEING MOUNTED BY THE OPERATING SYSTEM (?!?) Effectively causing a denial of service.

无论如何,这引起了我的怀疑,在向我保证后,如果我使用asynchronous(而不是synchronoussemisynchronous)WQL查询,就不会发生拒绝服务的情况.

However much incredulity this caused me, after enquiring I was assured that if I used asynchronous (rather than synchronous or semisynchronous) WQL queries, that the denial of service would not occur.

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

如果__instanceoperationevent ISA __InstanceCreationEvent,则添加了该卷.如果__instanceoperationevent ISA __InstanceDeletionEvent,则该卷已删除.

If the __instanceoperationevent ISA __InstanceCreationEvent then the volume was added. If the __instanceoperationevent ISA __InstanceDeletionEvent then the volume was removed.

似乎似乎__instanceoperationevent ISA __InstanceModificationEvent时,该卷被弹出,但是我不清楚哪种其他操作可能导致此问题.由于该卷仍处于连接状态,因此使用第一个synchronous查询(上方)进行确定性查询Size可能是安全的.

It would seem that when __instanceoperationevent ISA __InstanceModificationEvent then the volume was ejected, but it's not clear to me what other kind of operation could cause this. As the volume is still connected at this point, it's probably safe to deterministically query its Size using the first synchronous query (above) to check.

asynchronous WQL查询似乎可以通过两种不同的方式调用,即temporarypermanent WMI事件使用者.差别不大,但是似乎建议使用permanent过滤器和消费者,而且似乎不影响WQL查询"quotas".

asynchronous WQL queries seem to callable in two different ways, as either temporary or permanent WMI event consumers. The difference isn't huge, but permanent filters+consumers seem to be recommended and don't seem to run afoul of WQL query "quotas".

无论哪种方式,都没有使用通过js-ctypes传递的JavaScript回调处理结果WMI事件的理智方法. :-(剩下的就是寻找一种方法来使用事件,然后将其传达回Firefox.

Either way, there's no sane way to handle the resulting WMI events using JavaScript callbacks passed in via js-ctypes. :-( That left looking for a way to consume the events and then communicate them back to Firefox.

我最终使用了基于 @Corion对 perlmonks问题的回答,请使用DBD :: WMI 每2秒异步轮询一次事件,然后使用 IO: :Socket :: INET 通过TCP套接字将结果发送给Firefox. (您可以用任何一种语言来完成此操作-我对Perl都很满意).

I ended up using a Strawberry perl script based on DBD::WMI per @Corion's answer to a perlmonks question to poll asynchronously for events every 2 seconds and then used IO::Socket::INET report the results to Firefox by sending them over a TCP socket. (You could do this in any language at all - I happen to be comfortable with Perl).

然后,我在插件中实现了nsIServerSocket,等待\n终止的行来解析所收集的输入,并执行与上述相同的建模和综合事件.

I then implemented nsIServerSocket from within my addon, waiting for \n terminated lines to parse the collected input and do the same modelling and synthetic events as described above.

这篇关于jsctypes-将SHChangeNotifyRegister用于MEDIA/DRIVE事件的问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆