jsctypes - 使用 SHChangeNotifyRegister 处理 MEDIA/DRIVE 事件时出现问题

2024-04-10

我正在尝试在 Firefox 中使用 js-ctypes 来接收 USB 媒体/驱动器通知,但我遇到了一些问题,我无法判断是否是因为我对 Win32 API 非常缺乏经验,或者对 js-ctypes 很糟糕(或两者!)

我首先改编了我在亚历山大·波洛的博客上找到的一个例子:

  • 博客条目 http://blog.techno-barje.fr//post/2010/08/24/jsctypes-unleashed/
  • 完整的 JS 源码 http://blog.techno-barje.fr/public/demo/jsctypes/example-jsctypes-full-power.txt

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

这看起来很简单,所以在对它的优点进行了一些研究之后注册设备通知 http://msdn.microsoft.com/en-us/library/windows/desktop/aa363431%28v=vs.85%29.aspx vs SHChangeNotify注册 http://msdn.microsoft.com/en-us/library/windows/desktop/bb762120%28v=vs.85%29.aspx,我正在尝试调整该(工作!)示例以通过以下方式注册设备更新SHChangeNotifyRegister.

该代码驻留在引导(无需重新启动)的 Firefox 扩展中(代码如下)。

实施WindowProc效果很好,就像原来的例子一样。我的 JavaScript 回调记录传入的 Window 消息(在本例中仅以数字形式表示)。

问题:

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

其次,尽管从控制台输出(如下)看来,我从调用中获得了有意义的值SHGetSpecialFolderLocation and SHChangeNotifyRegister (返回值不是错误并且PIDLISTITEM指针是一些真实的地址)我没有在 JavaScript 回调中收到设备/驱动器消息。

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

我已经通过类似的方式验证过来自 Microsoft 的 C++ 示例项目 http://msdn.microsoft.com/en-us/library/windows/desktop/dd940348%28v=vs.85%29.aspx当消息本身被接收时SHChangeNotifyRegistration成功,我生成 USB 媒体事件(插入和移除 USB 闪存媒体)。

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


安装.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] /cdn-cgi/l/email-protection</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解决方法。 (我不会接受这个答案,希望最终有人会发布如何正确地做到这一点)。


经过大量阅读后,我唯一推荐的枚举和/或确定 USB 卷状态的方法是使用 WMI。下列WQL查询成功了:

select Caption, Size from win32_LogicalDisk where DriveType = 2

要从 C++ 使用 WQL,您必须使用 COM。使用来自js-ctypes这并不是一项无关紧要的工程任务。您需要安排从某个位置加载和使用 DLLChromeWorker有时我发现我特别需要确保从正确的 Firefox 线程调用 JavaScript 回调函数,并且 COM 未在多线程单元中初始化。

Caption是驱动器盘符。似乎一旦 USB 驱动器弹出Size报告为零。

然后在轮询循环中调用它就相当简单了ChromeWorker线程,对已安装卷的更改进行建模,并在 DOM 窗口中引发合成 USB 安装/弹出/删除事件。


不幸的是,有一个huge这有问题。如果插入 USB 闪存驱动器,Windows 通常需要 2 到 30 秒(取决于大小)来安装。在此期间(特别是在第一秒左右),如果您运行上述命令WQL查询,就会阻止操作系统安装 USB 卷(?!?) 实际上导致拒绝服务。

不管这给我带来了多大的怀疑,在询问之后我确信如果我使用asynchronous(而不是synchronous or semisynchronous) WQL 查询,不会发生拒绝服务。

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

If the __instanceoperationevent ISA __InstanceCreationEvent然后添加体积。如果__instanceoperationevent ISA __InstanceDeletionEvent然后该卷被移除。

它会seem__instanceoperationevent ISA __InstanceModificationEvent然后该卷被弹出,但我不清楚其他类型的操作会导致这种情况。由于此时该卷仍处于连接状态,因此可以安全地确定性地查询其Size使用第一个synchronous查询(上面)来检查。

asynchronousWQL 查询似乎可以通过两种不同的方式调用,或者temporary or permanentWMI 事件消费者。差别并不大,但是permanent似乎推荐使用过滤器+消费者,并且似乎不会与 WQL 查询“配额”发生冲突。

无论哪种方式,都没有明智的方法来使用通过传入的 JavaScript 回调来处理生成的 WMI 事件js-ctypes。 :-( 这就需要寻找一种方法来使用事件,然后将它们传回 Firefox。

我最终使用了基于草莓 perl 脚本DBD::WMI http://search.cpan.org/~corion/DBD-WMI-0.06/lib/DBD/WMI.pm根据 @Corion 的回答佩尔蒙克斯的问题 http://www.perlmonks.org/bare/?node_id=827106每 2 秒异步轮询一次事件,然后使用IO::套接字::INET http://search.cpan.org/~gbarr/IO-1.25/lib/IO/Socket/INET.pm通过 TCP 套接字将结果发送给 Firefox。 (你可以用任何语言来做这件事 - 我碰巧对 Perl 很满意)。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

jsctypes - 使用 SHChangeNotifyRegister 处理 MEDIA/DRIVE 事件时出现问题 的相关文章

随机推荐