jsctypes - 使用SHChangeNotifyRegister进行MEDIA / DRIVE事件的问题

时间:2014-01-28 07:52:10

标签: javascript winapi xul messages jsctypes

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

我首先修改了我在Alexandre Poirot博客上发现的一个例子:

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

看起来很简单,所以在对RegisterDeviceNotification vs SHChangeNotifyRegister的优点进行一些研究后,我试图通过{{1}来调整(工作!)示例以注册设备更新}。

代码位于bootstrapped(无重启)Firefox扩展(下面的代码)。

SHChangeNotifyRegister的实施效果很好,如原始示例所示。我的JavaScript回调记录了进来的Window消息(这个例子只是数字)。

的问题:

首先,似乎调用WindowProc会在扩展程序的DestroyWindow上崩溃Firefox(几乎总是)。我是否应该在“仅消息”窗口上处理一些Windows消息,以便正常处理shutdown()

其次,虽然它从控制台输出(下面)看起来我从DestryWindowSHGetSpecialFolderLocation的调用中获得了有意义的值(返回值不是错误, SHChangeNotifyRegister指针是一些真实地址)我没有在JavaScript回调中获取设备/驱动器消息。

另外,我尝试重现PIDLISTITEM结构无效(在调用PIDLISTITEM时无法让js-ctypes识别它们)并且在研究了其他非C ++示例之后,似乎大多数人只是使用SHChangeNotifyRegister - 我希望这是我误解的根源!

我已经通过类似的C++ sample project from Microsoft验证了long*成功后收到消息本身并生成USB媒体事件(ny插入和删除USB闪存介质)。

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


的install.rdf

SHChangeNotifyRegistration

bootstrap.js

<?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>

控制台输出:

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);
  }
}

1 个答案:

答案 0 :(得分:1)

对于任何想要这样做的人,我发布了一个令人讨厌的 hack 的解决方法。 (我不接受这个答案,希望最终会有人发布如何正确地做到这一点。)


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

select Caption, Size from win32_LogicalDisk where DriveType = 2

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

Caption是驱动器号。一旦USB驱动器弹出,Size报告为零。

然后在ChromeWorker线程内的轮询循环中调用它,模拟对已装入卷的更改,并在我的DOM窗口中引发合成的USB-Mounted / Ejected / Removed事件,这是相当简单的。


不幸的是,这有一个 巨大的 问题。如果插入USB闪存驱动器,则通常需要2至30秒(取决于大小)才能由Windows安装。在此期间(特别是在第一秒左右之后),如果您运行上述WQL查询,它将阻止USB音量被操作系统安装(?!?)有效地导致拒绝服务。

然而,这令我感到非常怀疑,在询问后我得到保证,如果我使用asynchronous(而不是synchronoussemisynchronous)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查询似乎可以通过两种不同方式调用,作为temporarypermanent WMI事件使用者。差异并不大,但似乎建议使用permanent过滤器+消费者,并且似乎不会与WQL查询“配额”相冲突。

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

我最终使用基于DBD::WMI的草莓perl脚本,根据@ Corion对a perlmonks question的回答,每隔2秒异步​​轮询一次事件,然后使用IO::Socket::INET将结果报告给Firefox通过TCP套接字发送它们。 (你可以用任何语言完成这个 - 我碰巧熟悉Perl)。

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