Нашел на просторах рунета код для отлова подключения к усб порту
unit usb_utils;
//http://www.swissdelphicenter.ch/en/tipsindex.php
interface
uses
Windows, Messages, SysUtils, Classes, Forms;
type
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = packed record
dbch_size: DWORD;
dbch_devicetype: DWORD;
dbch_reserved: DWORD;
end;
PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE;
DEV_BROADCAST_DEVICEINTERFACE = record
dbcc_size: DWORD;
dbcc_devicetype: DWORD;
dbcc_reserved: DWORD;
dbcc_classguid: TGUID;
dbcc_name: short;
end;
const
GUID_DEVINTERFACE_USB_DEVICE: TGUID = '{A5DCBF10-6530-11D2-901F-00C04FB951ED}';
DBT_DEVICEARRIVAL = $8000; // system detected a new device
DBT_DEVICEREMOVECOMPLETE = $8004; // device is gone
DBT_DEVTYP_DEVICEINTERFACE = $00000005; // device interface class
type
TComponentUSB = class(TComponent)
private
FWindowHandle: HWND;
FOnUSBArrival: TNotifyEvent;
FOnUSBRemove: TNotifyEvent;
procedure WndProc(var Msg: TMessage);
function USBRegister: Boolean;
protected
procedure WMDeviceChange(var Msg: TMessage); dynamic;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property OnUSBArrival: TNotifyEvent read FOnUSBArrival write FOnUSBArrival;
property OnUSBRemove: TNotifyEvent read FOnUSBRemove write FOnUSBRemove;
end;
implementation
constructor TComponentUSB.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWindowHandle := AllocateHWnd(WndProc);
USBRegister;
end;
destructor TComponentUSB.Destroy;
begin
DeallocateHWnd(FWindowHandle);
inherited Destroy;
end;
procedure TComponentUSB.WndProc(var Msg: TMessage);
begin
if (Msg.Msg = WM_DEVICECHANGE) then
begin
try
WMDeviceChange(Msg);
except
Application.HandleException(Self);
end;
end
else
Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;
procedure TComponentUSB.WMDeviceChange(var Msg: TMessage);
var
devType: Integer;
Datos: PDevBroadcastHdr;
begin
if (Msg.wParam = DBT_DEVICEARRIVAL) or (Msg.wParam = DBT_DEVICEREMOVECOMPLETE) then
begin
Datos := PDevBroadcastHdr(Msg.lParam);
devType := Datos^.dbch_devicetype;
if devType = DBT_DEVTYP_DEVICEINTERFACE then
begin // USB Device
if Msg.wParam = DBT_DEVICEARRIVAL then
begin
if Assigned(FOnUSBArrival) then
FOnUSBArrival(Self);
end
else
begin
if Assigned(FOnUSBRemove) then
FOnUSBRemove(Self);
end;
end;
end;
end;
function TComponentUSB.USBRegister: Boolean;
var
dbi: DEV_BROADCAST_DEVICEINTERFACE;
Size: Integer;
r: Pointer;
begin
Result := False;
Size := SizeOf(DEV_BROADCAST_DEVICEINTERFACE);
ZeroMemory(@dbi, Size);
dbi.dbcc_size := Size;
dbi.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
dbi.dbcc_reserved := 0;
dbi.dbcc_classguid := GUID_DEVINTERFACE_USB_DEVICE;
dbi.dbcc_name := 0;
r := RegisterDeviceNotification(FWindowHandle, @dbi,
DEVICE_NOTIFY_WINDOW_HANDLE
);
if Assigned(r) then Result := True;
end;
end.
Все бы хорошо, но вот не особо у меня получается его совместить с dll (хочу потом заинжектить длл в процесс). Тобищь, нужно чтоб длл отлавливала подключения флешки, как я использовал код выше
library dll;
uses
SysUtils,
Classes,windows,dialogs,messages;
type
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = packed record
dbch_size: DWORD;
dbch_devicetype: DWORD;
dbch_reserved: DWORD;
end;
PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE;
DEV_BROADCAST_DEVICEINTERFACE = record
dbcc_size: DWORD;
dbcc_devicetype: DWORD;
dbcc_reserved: DWORD;
dbcc_classguid: TGUID;
dbcc_name: short;
end;
const
GUID_DEVINTERFACE_USB_DEVICE: TGUID = '{A5DCBF10-6530-11D2-901F-00C04FB951ED}';
DBT_DEVICEARRIVAL = $8000; // system detected a new device
DBT_DEVICEREMOVECOMPLETE = $8004; // device is gone
DBT_DEVTYP_DEVICEINTERFACE = $00000005; // device interface class
type
TComponentUSB = class(TComponent)
private
FWindowHandle: HWND;
FOnUSBArrival: TNotifyEvent;
FOnUSBRemove: TNotifyEvent;
procedure WndProc(var Msg: TMessage);
function USBRegister: Boolean;
protected
procedure WMDeviceChange(var Msg: TMessage); dynamic;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property OnUSBArrival: TNotifyEvent read FOnUSBArrival write FOnUSBArrival;
property OnUSBRemove: TNotifyEvent read FOnUSBRemove write FOnUSBRemove;
end;
{$R *.res}
constructor TComponentUSB.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWindowHandle := AllocateHWnd(WndProc);
USBRegister;
end;
destructor TComponentUSB.Destroy;
begin
DeallocateHWnd(FWindowHandle);
inherited Destroy;
end;
procedure TComponentUSB.WndProc(var Msg: TMessage);
begin
if (Msg.Msg = WM_DEVICECHANGE) then
begin
try
WMDeviceChange(Msg);
except
showmessage('error');
// Application.HandleException(Self);
end;
end
else
Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;
procedure TComponentUSB.WMDeviceChange(var Msg: TMessage);
var
devType: Integer;
Datos: PDevBroadcastHdr;
begin
if (Msg.wParam = DBT_DEVICEARRIVAL) or (Msg.wParam = DBT_DEVICEREMOVECOMPLETE) then
begin
Datos := PDevBroadcastHdr(Msg.lParam);
devType := Datos^.dbch_devicetype;
if devType = DBT_DEVTYP_DEVICEINTERFACE then
begin // USB Device
if Msg.wParam = DBT_DEVICEARRIVAL then
begin
showmessage('tada!');
if Assigned(FOnUSBArrival) then
FOnUSBArrival(Self);
end
else
begin
if Assigned(FOnUSBRemove) then
FOnUSBRemove(Self);
end;
end;
end;
end;
function TComponentUSB.USBRegister: Boolean;
var
dbi: DEV_BROADCAST_DEVICEINTERFACE;
Size: Integer;
r: Pointer;
begin
Result := False;
Size := SizeOf(DEV_BROADCAST_DEVICEINTERFACE);
ZeroMemory(@dbi, Size);
dbi.dbcc_size := Size;
dbi.dbcc_devicetype := DBT_DEVTYP_DEVICEINTERFACE;
dbi.dbcc_reserved := 0;
dbi.dbcc_classguid := GUID_DEVINTERFACE_USB_DEVICE;
dbi.dbcc_name := 0;
r := RegisterDeviceNotification(FWindowHandle, @dbi,
DEVICE_NOTIFY_WINDOW_HANDLE
);
if Assigned(r) then Result := True
else
showmessage('bad');
end;
begin
showmessage('good');
TComponentUSB.Create(nil);
while true do
sleep(100);
end.
Инжектю длл, выходит сообщение good, значит заинжектилось норм, но вот при вставке флэшки не выходит сообщение tada!, хотя на форме все работает хорошо. Я думаю, что проблема в регистрации (USBRegister) мб хэндл не тот, можете мне подсказать ?
Ответ
Представленный в вопросе код содержит ошибки:
При инициализации dll (между begin и end, в секции initialization
используемых модулей) крайне не рекомендуется использовать что-то
"тяжелое". Объяснение легко тянет на отдельную статью (например -
статья Gunsmoker-а )
Уведомления о подключении устройств к компьютеру используют штатный механизм сообщений Windows , т.е. код, реализующий этот функционал, должен выполняться в потоке, который реализует цикл выборки сообщений (Message loop).
Собственно, код из вопроса не работает "благодаря" пункту 2.
С учетом обоих замечаний, код должен выполнять следующие действия:
Максимально быстро завершить работу между "главными" begin и end кода dll.
После "отложенной" инициализации нужно организовать цикл выборки сообщений, который должен работать до момента выгрузки dll (или любых других условий, на ваш выбор).
По завершению работы цикла выборки сообщений - провести деинициализацию.
Пункт 1 будем выполнять при помощи потока. Т.е. при инициализации dll создаем свой поток и уже в нем проводим основную работу. Этот метод тоже не очень хорош, посему - если есть возможность, то при внедрении dll лучше вызвать отдельную функцию, экспортируемую этой dll и уже в ней производить указанные манипуляции.
Итого, общий код dll будет выглядеть примерно так:
var
DelayedThread: TDelayedThread;
procedure LibProc(Reason: integer);
begin
case Reason of
DLL_PROCESS_ATTACH: // нашу dll загрузили
begin
DelayedThread:=TDelayedThread.Create; // создаем отдельный поток
// внутри которого и будем осуществлять основные "тяжелые" действия
DelayedThread.FreeOnTerminate:=True; // мы будем завершать поток при выгрузке dll
// и из-за этого не будем ждать, пока поток завершится. Пусть он уничтожится после завершения самостоятельно.
end;
DLL_PROCESS_DETACH: // нашу dll выгружают
begin
// Завершаем поток.
DelayedThread.Terminate;
PostThreadMessage(DelayedThread.ThreadID, WM_QUIT, 0, 0); // отправляем нашему потоку сообщение
// чтобы он "проснулся", если в данный момент он "спит" внутри GetMessage (см. код ниже).
// и после пробуждения - вышел из цикла.
end;
end;
end;
begin
DLLProc := LibProc;
LibProc(DLL_PROCESS_ATTACH);
end.
Этот код выполняет действия №1 (не делать ничего "тяжелого" при инициализации dll, максимально быстро отдать управление) и №3 (деинициализация).
Теперь нужно реализовать основное действие №2. Объявление класса потока оставим на ваше усмотрение, здесь же остановимся именно на реализации. Код организации выборки сообщений для работы всех окон (созданных в этом потоке) может выглядеть так:
procedure TDelayedThread.Execute;
var
msg: TMSG;
ComponentUSB: TComponentUSB;
begin
ComponentUSB:=TComponentUSB.Create(nil); // создание компонента, в котором есть необходимость получения сообщений
// создаем именно внутри метода Execute, а не в конструкторе.
// чтобы созданное внутри компонента окно принадлежало именно этому потоку.
// Иначе дальнейшие действия окажутся бессмысленными.
try
PeekMessage(msg, 0, 0, 0, PM_NOREMOVE); // "говорим" ОС, что этот поток будет выбирать сообщения
while not Terminated and GetMessage(msg, 0, 0, 0) do // и входим в цикл выборки сообщений
// пока этот поток не прервут.
begin
TranslateMessage(msg);
DispatchMessage(msg); // этот (стандартный) метод "отдает" принятое сообщение нужной оконной функции.
// в данном случае - той, которая использовалась в AllocateHWND в конструкторе TComponentUSB
end;
finally
ComponentUSB.Free;
end;
end;
В главном потоке обычного VCL-приложения выборкой сообщений (используя подобный цикл) занимается объект Application: TApplication, а конкретнее - его метод ProcessMessages (лирическое отступление: если вам приходится принудительно вызывать этот метод у себя в приложении - скорее всего, вы что-то делаете неправильно).