我正在尝试在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()
?
其次,虽然它从控制台输出(下面)看起来我从DestryWindow
和SHGetSpecialFolderLocation
的调用中获得了有意义的值(返回值不是错误, 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);
}
}
答案 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
(而不是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查询“配额”相冲突。
无论哪种方式,都没有理智的方法来处理使用通过js-ctypes
传递的JavaScript回调生成的WMI事件。 :-(这就是寻找一种方法来消费这些事件,然后将它们传回Firefox。
我最终使用基于DBD::WMI的草莓perl脚本,根据@ Corion对a perlmonks question的回答,每隔2秒异步轮询一次事件,然后使用IO::Socket::INET将结果报告给Firefox通过TCP套接字发送它们。 (你可以用任何语言完成这个 - 我碰巧熟悉Perl)。
然后我在我的插件中实现nsIServerSocket
,等待\n
终止的行解析收集的输入并执行与上述相同的建模和合成事件。