BSOne.SFC/Tocsg.Lib/VCL/Tocsg.USB.pas

1004 lines
28 KiB
Plaintext

{*******************************************************}
{ }
{ Tocsg.USB }
{ }
{ Copyright (C) 2022 kku }
{ }
{*******************************************************}
unit Tocsg.USB;
interface
uses
Tocsg.Obj, System.Classes, System.SysUtils, Winapi.Windows,
System.Generics.Collections, Winapi.Messages;
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_DEVICEQUERYREMOVE = $8001;
DBT_DEVNODES_CHANGED = $0007;
DBTF_MEDIA = $0001;
DBT_DEVTYP_VOLUME = $0002;
DBT_DEVTYP_PORT = $0003;
DBT_DEVTYP_NET = $0004;
DBT_DEVTYP_DEVICEINTERFACE = $0005; // device interface class
DBT_DEVTYP_HANDLE = $0006;
type
PDevBroadcastHdr = ^DEV_BROADCAST_HDR;
DEV_BROADCAST_HDR = {$IFNDEF WIN64} packed {$ENDIF} record
dwSize : DWORD;
dwDevicetype : DWORD;
dwReserved : DWORD;
end;
PDevBroadcastDeviceInterface = ^DEV_BROADCAST_DEVICEINTERFACE;
DEV_BROADCAST_DEVICEINTERFACE = record
dwSize : DWORD;
dwDevicetype : DWORD;
dwReserved : DWORD;
ClassGUID : TGUID;
nName : Short;
end;
PDevBroadcastVolume = ^TDevBroadcastVolume;
TDevBroadcastVolume = {$IFNDEF WIN64} packed {$ENDIF} record
dwSize: DWORD;
dwDevicetype: DWORD;
dwReserved: DWORD;
dwUnitmask: DWORD;
wFlags: Word;
end;
PDevBroadcastHandle = ^TDevBroadcastHandle;
DEV_BROADCAST_HANDLE = record
dbch_size: DWORD;
dbch_devicetype: DWORD;
dbch_reserved: DWORD;
dbch_handle: THandle; { file handle used in call to RegisterDeviceNotification }
dbch_hdevnotify: HDEVNOTIFY; { HDEVNOTIFY returned from RegisterDeviceNotification }
{ The following 3 fields are only valid if wParam is DBT_CUSTOMEVENT. }
dbch_eventguid: TGUID;
dbch_nameoffset: LongInt; { offset (bytes) of variable-length string buffer (-1 if none)}
dbch_data: array[0..0] of BYTE; { variable-sized buffer, potentially containing binary and/or text data }
end;
TDevBroadcastHandle = DEV_BROADCAST_HANDLE;
TUSBChangeEvent = procedure(Sender: TObject; pInfo: PDevBroadcastVolume) of object;
TDevChangeEvent = procedure(Sender: TObject; pInfo: PDevBroadcastDeviceInterface) of object;
TUSBChangeQueryEvent = procedure(Sender: TObject; sDrive: String; var bAccept: Boolean) of object;
TTgUSBEventNotify = class(TTgObject)
private
hWindowHandle_ : HWND;
evUSBArrival_,
evUSBRemove_ : TUSBChangeEvent;
evDevArrival_,
evDevRemove_ : TDevChangeEvent;
evUSBQueryRemove_ : TUSBChangeQueryEvent;
DcQueryRemoveNotify_: TDictionary<HDEVNOTIFY,String>;
// 특정 환경에서 USB 연결/해제 시 DBT_DEVICEARRIVAL, DBT_DEVICEREMOVECOMPLETE 값을
// 제대로 받아오지 못하는 경우가 있다. 이 경우 기존 드라이브를 비교해서 연결/해제를 판단함.
dwLogicalDrvs_ : DWORD;
bDeviceChanging_: Boolean;
procedure ProcessWindowMessage(var msg: TMessage);
function RegisterDeviceChange(sPath: String): Boolean; overload;
function GetQueryRemovePath(hDev: HDEVNOTIFY): String;
procedure SetEventQueryRemove(evUSBChangeQueryEvent: TUSBChangeQueryEvent);
protected
procedure process_WM_DEVICECHANGE(var msg: TMessage);
public
constructor Create;
destructor Destroy; override;
function RegisterDeviceChange: Boolean; overload;// not use
published
property OnUSBArrival: TUSBChangeEvent write evUSBArrival_;
property OnUSBQueryRemove: TUSBChangeQueryEvent write SetEventQueryRemove;
property OnUSBRemove: TUSBChangeEvent write evUSBRemove_;
property OnDevArrival: TDevChangeEvent write evDevArrival_;
property OnDevRemove: TDevChangeEvent write evDevRemove_;
end;
const
REG_ENUM_USB = 'SYSTEM\CurrentControlSet\Enum\USB\';
REG_ENUM_USBSTOR = 'SYSTEM\CurrentControlSet\Enum\USBSTOR\';
REG_ENUM_USE_DISK_NUM = 'SYSTEM\CurrentControlSet\Services\disk\Enum\'; // 현재 인식된 물리디스크 넘버를 확인할수 있다.
REG_MOUNTED_DEVICES = 'SYSTEM\MountedDevices\';
REG_USB_LASTWRITE_CONTROL1 = 'SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\##?#USBSTOR#%s#%s#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\Control';
REG_USB_LASTWRITE_CONTROL2 = 'SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\##?#STORAGE#RemovableMedia#%s&RM#{53f5630d-b6bf-11d0-94f2-00a0c91efb8b}\Control';
REG_USB_LASTWRITE_CONTROL_VISTA = 'SYSTEM\CurrentControlSet\Control\DeviceClasses\{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\##?#USBSTOR#%s#%s#{53f56307-b6bf-11d0-94f2-00a0c91efb8b}\Control';
type
PUSBRec = ^TUSBRec;
TUSBRec = record
sDeviceName,
sDescription,
sDriveLetter,
sFriendlyName,
sSerial,
sParentIdPrefix,
sVID,
sPID,
sUsbstor,
sSerial2 : AnsiString;
dtCreate,
dtLastWrite: TDateTime;
nDiskNum : Integer; // 연결되어 있다면 값이, 없다면 -1
end;
TTgUSBStorInfo = class(TObject)
private
bIsVista_: Boolean;
protected
USBStorList_: TList<PUSBRec>;
procedure OnUSBStorNotify(Sender: TObject; const Item: PUSBRec;
Action: TCollectionNotification);
function GetCount: Integer;
function GetUSBInfoStep_1: Boolean;
function GetUSBInfoStep_2: Boolean;
function GetUSBInfoStep_3: Boolean;
function GetUSBInfoStep_4: Boolean;
function GetUSBInfoStep_5: Boolean;
public
Constructor Create;
Destructor Destroy; override;
function GetUSBInfoByLetter(cDrive: AnsiChar): PUSBRec;
function GetInfo(nIndex: Integer): PUSBRec;
procedure PutInfo(nIndex: Integer; const pData: PUSBRec);
function UpdateUSBStorInfo: Boolean;
// function RemoveUSBInfo(pData: PUSBRec) : Boolean;
property USBRecs[nIndex: Integer]: PUSBRec read GetInfo; default;
property Count: Integer read GetCount;
end;
implementation
uses
Tocsg.Disk, System.Win.Registry, Tocsg.Safe,
Tocsg.DateTime, Tocsg.Exception, EM.WinOSVersion,
Tocsg.Registry;
{ TTgUSBEventNotify }
// 이거 스레드에서 생성하면 오동작 할 가능성이 농후함.
// 내부적으로 다열로그를 만들어서 메세지를 받기 때문..
Constructor TTgUSBEventNotify.Create;
begin
{$IFDEF TRACE1} _Trace('Create()'); {$ENDIF}
inherited Create;
DcQueryRemoveNotify_ := TDictionary<HDEVNOTIFY,String>.Create;
dwLogicalDrvs_ := GetLogicalDrives;
bDeviceChanging_ := false;
hWindowHandle_ := AllocateHWnd(ProcessWindowMessage);
end;
Destructor TTgUSBEventNotify.Destroy;
begin
DeallocateHWnd(hWindowHandle_);
FreeAndNil(DcQueryRemoveNotify_);
inherited;
{$IFDEF TRACE1} _Trace('Destroy()'); {$ENDIF}
end;
procedure TTgUSBEventNotify.SetEventQueryRemove(evUSBChangeQueryEvent: TUSBChangeQueryEvent);
procedure register_notify;
var
sDrive: String;
nDrive: Integer;
begin
for nDrive := 2 to 31 do
begin
sDrive := Format('%s:\', [Char(Integer('A')+nDrive)]);
case GetDriveType(PChar(sDrive)) of
DRIVE_FIXED,
DRIVE_REMOVABLE :
if GetDriveSize(sDrive) <> 0 then
RegisterDeviceChange(sDrive);
end;
end;
end;
begin
DcQueryRemoveNotify_.Clear;
evUSBQueryRemove_ := evUSBChangeQueryEvent;
if Assigned(evUSBQueryRemove_) then
register_notify;
end;
function TTgUSBEventNotify.GetQueryRemovePath(hDev: HDEVNOTIFY): String;
begin
if DcQueryRemoveNotify_.ContainsKey(hDev) then
Result := DcQueryRemoveNotify_[hDev]
else
Result := '';
end;
procedure TTgUSBEventNotify.ProcessWindowMessage(var msg: TMessage);
begin
case Msg.Msg of
WM_DEVICECHANGE : process_WM_DEVICECHANGE(msg);
end;
Msg.Result := DefWindowProc(hWindowHandle_, msg.Msg, msg.wParam, msg.lParam);
end;
procedure TTgUSBEventNotify.process_WM_DEVICECHANGE(var msg : TMessage);
var
// nTime: Integer;
dwAddDrv,
dwDelDrv,
dwNewDrvs : DWORD;
pInfo: PDevBroadcastVolume;
bAccept: Boolean;
sDrive: String;
procedure compare_drives;
begin
dwAddDrv := dwLogicalDrvs_ xor dwNewDrvs;
dwDelDrv := dwAddDrv;
dwAddDrv := dwNewDrvs and dwAddDrv;
dwDelDrv := dwLogicalDrvs_ and dwDelDrv;
end;
procedure ProcessChangeDevice(dwDrvs: DWORD; bAdd: Boolean);
var
i: Integer;
m: DWORD;
begin
if dwDrvs = 0 then
exit;
for i := 0 to 31 do
begin
m := dwDrvs and (1 shl i);
if m <> 0 then
begin
// 일단 이것만 쓰니깐 이것만 채워준다
pInfo.dwUnitmask := m;
if bAdd and Assigned(evUSBArrival_) then
begin
evUSBArrival_(self, pInfo);
// 추가 22_0504 13:49:21 kku
if Assigned(evUSBQueryRemove_) then
begin
var sDrive: String := GetDriveFromMask(pInfo.dwUnitmask);
if GetDriveExtent(sDrive).liExtentLength.QuadPart <> 0 then
begin
if GetDriveSize(sDrive) <> 0 then
case Integer(GetDriveType(PChar(sDrive))) of
DRIVE_REMOVABLE,
DRIVE_FIXED : RegisterDeviceChange(sDrive);
end;
end;
end;
end else
if not bAdd and Assigned(evUSBRemove_) then
evUSBRemove_(self, pInfo);
end;
end;
end;
begin
// DBT_DEVICEQUERYREMOVE
// DBT_DEVICEQUERYREMOVEFAILED
// DBT_DEVICEREMOVECOMPLETE
// flags & DBTF_MEDIA = 1 -> 씨디룸
// flags & DBTF_MEDIA = 2 -> 네트워크 드라이브
// 반응속도가 참 느리네.. 비스타에서!
case msg.WParam of
{$IF true}
DBT_DEVICEARRIVAL :
begin
case PDevBroadcastHdr(msg.LParam).dwDevicetype of
DBT_DEVTYP_VOLUME :
begin
if Assigned(evUSBArrival_) then
begin
evUSBArrival_(self, PDevBroadcastVolume(msg.LParam));
// 추가 22_0504 13:49:21 kku
if Assigned(evUSBQueryRemove_) then
begin
sDrive := GetDriveFromMask(PDevBroadcastVolume(msg.LParam).dwUnitmask);
if GetDriveExtent(sDrive).liExtentLength.QuadPart <> 0 then
begin
if GetDriveSize(sDrive) <> 0 then
case Integer(GetDriveType(PChar(sDrive))) of
DRIVE_REMOVABLE,
DRIVE_FIXED : RegisterDeviceChange(sDrive);
end;
end;
end;
end;
sDrive := GetDriveFromMask(PDevBroadcastVolume(msg.LParam).dwUnitmask);
if Assigned(evUSBQueryRemove_) then
RegisterDeviceChange(sDrive);
// begin
// if register_deviceChange(sDrive) then
// OutputDebugString('register_deviceChange Succss~~~~~~~~~~~~~~~~~')
// else
// OutputDebugString('register_deviceChange Fail!!!!!!!!!!!!!!!!!!!!!');
// end;
end;
DBT_DEVTYP_DEVICEINTERFACE :
begin
if Assigned(evDevArrival_) then
begin
evDevArrival_(Self, PDevBroadcastDeviceInterface(msg.LParam));
_Trace('DevArrival = %s', [GUIDToString(PDevBroadcastDeviceInterface(msg.LParam).ClassGUID)]);
end;
end;
end;
end;
DBT_DEVICEQUERYREMOVE :
begin
if PDevBroadcastHdr(msg.LParam).dwDevicetype = DBT_DEVTYP_HANDLE then
if Assigned(evUSBQueryRemove_) then
begin
sDrive := GetQueryRemovePath(PDevBroadcastHandle(msg.LParam).dbch_hdevnotify);
if sDrive <> '' then
begin
bAccept := true;
evUSBQueryRemove_(self, sDrive, bAccept);
if not bAccept then // 거부임? 그럼 거부
msg.Result := BROADCAST_QUERY_DENY;
end;
end;
end;
DBT_DEVICEREMOVECOMPLETE :
begin
case PDevBroadcastHdr(msg.LParam).dwDevicetype of
DBT_DEVTYP_VOLUME :
begin
if Assigned(evUSBRemove_) then
evUSBRemove_(self, PDevBroadcastVolume(msg.LParam));
end;
DBT_DEVTYP_HANDLE :
begin
try
DcQueryRemoveNotify_.Remove(PDevBroadcastHandle(msg.LParam).dbch_hdevnotify);
except
// 건덕지 없을듯
end;
end;
DBT_DEVTYP_DEVICEINTERFACE :
begin
if Assigned(evDevRemove_) then
begin
evDevRemove_(Self, PDevBroadcastDeviceInterface(msg.LParam));
_Trace('DevRemove = %s', [GUIDToString(PDevBroadcastDeviceInterface(msg.LParam).ClassGUID)]);
end;
end;
end;
end;
{$ELSE}
// 이건 특정한 상황(?)에서 메세지가 이거 빼곤 다른게 안받아져서 구현한 거임..
// 급해서 이렇게 만들어서 썼는데 이거보단 위에껄 추천함
DBT_DEVNODES_CHANGED :
begin
if bDeviceChanging_ then
exit;
bDeviceChanging_ := true;
dwNewDrvs := GetLogicalDrives;
nTime := 0;
while nTime < 5000 do
begin
if dwLogicalDrvs_ <> dwNewDrvs then
begin
New(pInfo);
ZeroMemory(pInfo, SizeOf(TDevBroadcastVolume));
try
compare_drives;
dwLogicalDrvs_ := dwNewDrvs;
ProcessChangeDevice(dwAddDrv, true);
ProcessChangeDevice(dwDelDrv, false);
finally
Dispose(pInfo);
bDeviceChanging_ := false;
end;
break;
end;
dwNewDrvs := GetLogicalDrives;
WaitForTimer(10);
Inc(nTime);
end;
end;
{$IFEND}
end;
end;
// not use
function TTgUSBEventNotify.RegisterDeviceChange: Boolean;
var
dbi : DEV_BROADCAST_DEVICEINTERFACE;
nSize : Integer;
hDev : HDEVNOTIFY;
begin
Result := False;
nSize := SizeOf(DEV_BROADCAST_DEVICEINTERFACE);
ZeroMemory(@dbi, nSize);
dbi.dwSize := nSize;
dbi.dwDevicetype := DBT_DEVTYP_DEVICEINTERFACE;
dbi.ClassGUID := GUID_DEVINTERFACE_USB_DEVICE;
hDev := RegisterDeviceNotification(hWindowHandle_,
@dbi,
DEVICE_NOTIFY_WINDOW_HANDLE);
if hDev <> nil then
Result := True;
end;
function TTgUSBEventNotify.RegisterDeviceChange(sPath: String): Boolean;
var
hDev : HDEVNOTIFY;
hPath : THandle;
DevHandle : TDevBroadcastHandle;
begin
Result := false;
try
// 윈도우 xp에서 빈디스크 에러 메시지 걸러내기
if GetDriveExtent(sPath).liExtentLength.QuadPart = 0 then
exit;
if not DirectoryExists(sPath) then
exit;
hPath := CreateFile(PChar(sPath),
GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE,
nil,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS or FILE_ATTRIBUTE_NORMAL,
0);
if hPath = INVALID_HANDLE_VALUE then
exit;
try
ZeroMemory(@DevHandle, SizeOf(TDevBroadcastHandle));
DevHandle.dbch_size := SizeOf(TDevBroadcastHandle);
DevHandle.dbch_devicetype := DBT_DEVTYP_HANDLE;
DevHandle.dbch_handle := hPath;
hDev := RegisterDeviceNotification(hWindowHandle_,
@DevHandle,
DEVICE_NOTIFY_WINDOW_HANDLE);
if hDev <> nil then
begin
DcQueryRemoveNotify_.Add(hDev, sPath);
Result := true;
end;
finally
CloseHandle(hPath);
end;
except
on E: Exception do
ETgException.TraceException(Self, E);
end;
end;
procedure Get_VID_PID(sInfo: AnsiString; var sVid: AnsiString; var sPid: AnsiString);
var
nPos1, nPos2, nLen: Integer;
begin
sInfo := LowerCase(sInfo);
nPos1 := Pos('vid_', sInfo);
nPos2 := Pos('pid_', sInfo);
if (nPos1 = 0) or (nPos2 = 0) then exit;
nLen := Length(sInfo);
sVid := Copy(sInfo, nPos1+4, nLen-nPos2-3);
sPid := Copy(sInfo, nPos2+4, nLen-nPos2-3);
end;
{ TTgUSBStorInfo }
Constructor TTgUSBStorInfo.Create;
var
ver: TWinVerInfo;
begin
Inherited Create;
bIsVista_ := false;
ver := GetWinVersion;
// 비스타나 체크한다
if ver.Version.Major = 6 then
bIsVista_ := true;
USBStorList_ := TList<PUSBRec>.Create;
USBStorList_.OnNotify := OnUSBStorNotify;
end;
Destructor TTgUSBStorInfo.Destroy;
begin
FreeAndNil(USBStorList_);
Inherited;
end;
procedure TTgUSBStorInfo.OnUSBStorNotify(Sender: TObject; const Item: PUSBRec;
Action: TCollectionNotification);
begin
case Action of
cnAdded: ;
cnRemoved: Dispose(Item);
cnExtracted: ;
end;
end;
function TTgUSBStorInfo.GetUSBInfoByLetter(cDrive: AnsiChar): PUSBRec;
var
pData: PUSBRec;
i: Integer;
begin
Result := nil;
for i := 0 to USBStorList_.Count - 1 do
begin
pData := USBStorList_[i];
if (pData.sDriveLetter <> '') and
(pData.sDriveLetter[1] = cDrive) then
begin
Result := pData;
exit;
end;
end;
end;
function TTgUSBStorInfo.GetCount: Integer;
begin
Result := USBStorList_.Count;
end;
function TTgUSBStorInfo.GetInfo(nIndex: Integer): PUSBRec;
begin
if (nIndex >= 0) and (nIndex < Count) then
Result := USBStorList_[nIndex]
else
Result := nil;
end;
procedure TTgUSBStorInfo.PutInfo(nIndex: Integer; const pData: PUSBRec);
begin
if (nIndex >= 0) and (nIndex < Count) then
USBStorList_[nIndex] := pData;
end;
// USB 목록에서 Service가 USBSTOR인것을 추출하여
// 장치이름, 설명, 생성일자, 시리얼 넘버, vid, pid를 얻어온다.
function TTgUSBStorInfo.GetUSBInfoStep_1: Boolean;
var
pData : PUSBRec;
reg, regSub : TRegistry;
lstKey,
lstSubKey,
lstSubSubKey : TStringList;
i, j, n : Integer;
sKey : String;
dtCreate,
dtCompCreate : TDateTime;
regInfo : TRegKeyInfo;
begin
Result := false;
Guard(reg, TRegistry.Create);
reg.RootKey := HKEY_LOCAL_MACHINE;
if not reg.OpenKeyReadOnly(REG_ENUM_USB) then
begin
// ASSERT(false);
exit;
end;
Guard(regSub, TRegistry.Create);
regSub.RootKey := HKEY_LOCAL_MACHINE;
Guard(lstKey, TStringList.Create);
Guard(lstSubKey, TStringList.Create);
Guard(lstSubSubKey, TStringList.Create);
reg.GetKeyNames(lstKey);
for i := 0 to lstKey.Count - 1 do
begin
sKey := REG_ENUM_USB + lstKey[i] + '\';
lstSubKey.Clear;
reg.CloseKey;
if not reg.OpenKeyReadOnly(sKey) then
begin
// ASSERT(false);
exit;
end;
reg.GetKeyNames(lstSubKey);
for j := 0 to lstSubKey.Count - 1 do
begin
reg.CloseKey;
if not reg.OpenKeyReadOnly(sKey + lstSubKey[j]) then
begin
// ASSERT(false);
exit;
end;
// 이 시간이.. 하위 키값때문인지 바뀔수도 있다..
// 하위 키값이 더 오래된거면 그걸로 맞춘다.
if reg.GetKeyInfo(regInfo) then
begin
dtCreate := ConvFileTimeToDateTime_Local(regInfo.FileTime);
reg.GetKeyNames(lstSubSubKey);
for n := 0 to lstSubSubKey.Count - 1 do
if regSub.OpenKeyReadOnly(sKey + lstSubKey[j] + '\' + lstSubSubKey[n]) then
begin
if regSub.GetKeyInfo(regInfo) then
begin
dtCompCreate := ConvFileTimeToDateTime_Local(regInfo.FileTime);
if dtCompCreate < dtCreate then
dtCreate := dtCompCreate;
end;
regSub.CloseKey;
end;
end else
dtCreate := 0;
if LowerCase(reg.ReadString('Service')) = 'usbstor' then
begin
New(pData);
ZeroMemory(pData, SizeOf(TUSBRec));
pData.sDeviceName := reg.ReadString('LocationInformation');
pData.sDescription := reg.ReadString('DeviceDesc');
// 외장 USB하드는 ParentIdPrefix가 여기에 위치하는거 같다..
// 이건.. serial2로 넣도록하자
// USBSTOR에 또 ParentIdPrefix가 있는데 이건 볼륨명 찾을때 필요하다.
// 둘이 무슨차이가 있는거지..?
if reg.ValueExists('ParentIdPrefix') then
begin
pData.sSerial2 := reg.ReadString('ParentIdPrefix');
end else begin
pData.sSerial2 := lstSubKey[j];
if Length(pData.sSerial2) > 30 then
SetLength(pData.sSerial2, 30);
end;
// n := Pos('&', lstSubKey[j]);
// if n = 0 then pData.sSerial := lstSubKey[j];
pData.sSerial := lstSubKey[j];
if Length(pData.sSerial) > 30 then
SetLength(pData.sSerial, 30);
// pData.sSerial2 := lstSubKey[j];
Get_VID_PID(Ansistring(lstKey[i]), pData.sVid, pData.sPid);
pData.dtCreate := dtCreate;
pData.nDiskNum := -1;
USBStorList_.Add(pData);
end;
end;
end;
Result := true;
end;
// 드라이브 볼륨명을 얻기위해 ParentIdPrefix 값을 구하고
// USB의 FriendlyName을 구한다.
function TTgUSBStorInfo.GetUSBInfoStep_2: Boolean;
var
pData : PUSBRec;
reg : TRegistry;
lstKey, lstSubKey : TStringList;
sKey : String;
i, j, c : Integer;
begin
Result := false;
Guard(reg, TRegistry.Create);
reg.RootKey := HKEY_LOCAL_MACHINE;
if not reg.KeyExists(REG_ENUM_USBSTOR) then
begin
Result := True;
Exit;
end;
if not reg.OpenKeyReadOnly(REG_ENUM_USBSTOR) then
begin
// ASSERT(false);
exit;
end;
Guard(lstKey, TStringList.Create);
Guard(lstSubKey, TStringList.Create);
reg.GetKeyNames(lstKey);
for i := 0 to lstKey.Count - 1 do
begin
sKey := REG_ENUM_USBSTOR + lstKey[i] + '\';
lstSubKey.Clear;
reg.CloseKey;
if not reg.OpenKeyReadOnly(sKey) then
begin
// ASSERT(false);
exit;
end;
reg.GetKeyNames(lstSubKey);
for j := 0 to lstSubKey.Count - 1 do
begin
reg.CloseKey;
if not reg.OpenKeyReadOnly(sKey + lstSubKey[j]) then
begin
// ASSERT(false);
exit;
end;
for c := 0 to USBStorList_.Count - 1 do
begin
pData := USBStorList_[c];
if Pos(pData.sSerial, lstSubKey[j]) > 0 then
begin
if reg.ValueExists('ParentIdPrefix') then
pData.sParentIdPrefix := reg.ReadString('ParentIdPrefix');
if pData.sFriendlyName = '' then
pData.sFriendlyName := reg.ReadString('FriendlyName');
// vista의 경우 추가 정보획득..
// if bIsVista_ then
begin
pData.sUsbstor := lstKey[i];
pData.sSerial2 := lstSubKey[j];
end;
end else
if lstSubKey[j] = pData.sSerial2 then
begin
// 외장 USB하드의 경우를 위해 이렇게 처리
// 마지막 사용일자를 구하는 위치가 다른데.. 그 위치를 구하기 위한 준비
if reg.ValueExists('ParentIdPrefix') then
pData.sParentIdPrefix := reg.ReadString('ParentIdPrefix');
pData.sUsbstor := lstKey[i];
// pData.sSerial2 := lstSubKey[j];
end;
end;
end;
end;
Result := true;
end;
// 물리디스크 번호를 얻어온다. 0이상이면 연결된 상태이고 -1이면 없는상태이다
function TTgUSBStorInfo.GetUSBInfoStep_3: Boolean;
var
pData : PUSBRec;
reg : TRegistry;
// lstValue : TStringList;
i, c, n, nCnt : Integer;
sVal : String;
begin
Result := false;
Guard(reg, TRegistry.Create);
reg.RootKey := HKEY_LOCAL_MACHINE;
if not reg.OpenKeyReadOnly(REG_ENUM_USE_DISK_NUM) then
begin
// ASSERT(false);
exit;
end;
if not reg.ValueExists('Count') then
exit;
nCnt := reg.ReadInteger('Count');
for i := 0 to nCnt - 1 do
begin
sVal := IntToStr(i);
if not reg.ValueExists(sVal) then break;
sVal := reg.ReadString(sVal);
for c := 0 to USBStorList_.Count - 1 do
begin
pData := USBStorList_[c];
// vista의 경우 serial로 검사~
// if bIsVista_ then
// begin
// n := Pos(pData.sSerial2, sVal);
// end else
// n := Pos(pData.sParentIdPrefix, sVal);
n := Pos(pData.sSerial2, sVal); // 이건 xp나 비스타나 똑같네
if n <> 0 then
pData.nDiskNum := i;
end;
end;
Result := true;
end;
// 마운트 정보를 확인해서 드라이브 볼륨명을 구한다.
function TTgUSBStorInfo.GetUSBInfoStep_4: Boolean;
var
reg : TRegistry;
lstValue : TStringList;
i, c : Integer;
sLetter, sValue, sBuf : String;
pData : PUSBRec;
regInfo : TRegDataInfo;
buf : array of WideChar;
function ExtractDriveLetter(const str: String): String;
var
nPos: Integer;
begin
nPos := LastDelimiter('\', str);
if nPos = -1 then
begin
Result := '';
exit;
end;
Result := UpperCase(Copy(str, nPos+1, Length(str)-nPos));
end;
begin
Result := false;
Guard(reg, TRegistry.Create);
reg.RootKey := HKEY_LOCAL_MACHINE;
if not reg.OpenKeyReadOnly(REG_MOUNTED_DEVICES) then
begin
// ASSERT(false);
exit;
end;
Guard(lstValue, TStringList.Create);
reg.GetValueNames(lstValue);
for i := 0 to lstValue.Count - 1 do
begin
sValue := LowerCase(lstValue[i]);
if Pos('dosdevices', sValue) <> 0 then
begin
if not reg.GetDataInfo(sValue, regInfo) then
begin
// ASSERT(false);
exit;
end;
// 20보다 작으면 아니라고 보는게 좋다..
// 외장 하드 디스크의 경우엔 이렇게 나타나는데 다른방식으로 구해주도록 하자
if regInfo.DataSize < 20 then
begin
sLetter := ExtractDriveLetter(sValue);
for c := 0 to USBStorList_.Count - 1 do
begin
pData := USBStorList_[c];
if pData.nDiskNum < 0 then
continue;
if pData.nDiskNum = GetDriveExtent(sLetter).dwDiskNumber then
begin
pData.sDriveLetter := sLetter;
break;
end;
end;
continue;
end;
if regInfo.RegData = rdBinary then
begin
SetLength(buf, regInfo.DataSize+1);
ZeroMemory(buf, regInfo.DataSize+1);
reg.ReadBinaryData(sValue, buf[0], regInfo.DataSize);
sBuf := WideCharToString(@buf[0]);
for c := 0 to USBStorList_.Count - 1 do
begin
pData := USBStorList_[c];
if (Pos(pData.sParentIdPrefix, sBuf) <> 0) or
(Pos(pData.sSerial2, sBuf) <> 0) then
begin
pData.sDriveLetter := ExtractDriveLetter(sValue);
break;
end;
end;
end;
end;
end;
Result := true;
end;
// 가장 최근에 연결/해제한 날짜를 가져온다.
function TTgUSBStorInfo.GetUSBInfoStep_5: Boolean;
var
reg : TRegistry;
pData : PUSBRec;
i : Integer;
regInfo : TRegKeyInfo;
sKey : String;
begin
Result := true;
Guard(reg, TRegistry.Create);
reg.RootKey := HKEY_LOCAL_MACHINE;
for i := 0 to Count - 1 do
begin
pData := USBStorList_[i];
// if (bIsVista_ = false) and (pData.sParentIdPrefix = '') then continue;
// vista와 2000, xp는 다르다!!
if bIsVista_ then
sKey := Format(REG_USB_LASTWRITE_CONTROL_VISTA, [pData.sUsbstor, pData.sSerial2])
else begin
// 외장 USB하드는 serial이 정보에 포함되지 않기때문에 이렇게 구분
if (pData.sSerial = '') or (pData.sParentIdPrefix = '') then
sKey := Format(REG_USB_LASTWRITE_CONTROL1, [pData.sUsbstor, pData.sSerial2])
else
sKey := Format(REG_USB_LASTWRITE_CONTROL2, [pData.sParentIdPrefix]);
end;
reg.CloseKey;
if reg.OpenKeyReadOnly(sKey) then
begin
if reg.GetKeyInfo(regInfo) then
pData.dtLastWrite := ConvFileTimeToDateTime_Local(regInfo.FileTime);
end;
end;
end;
function TTgUSBStorInfo.UpdateUSBStorInfo: Boolean;
begin
USBStorList_.Clear;
Result := GetUSBInfoStep_1;
if not Result then exit;
Result := GetUSBInfoStep_2;
if not Result then exit;
Result := GetUSBInfoStep_3;
if not Result then exit;
Result := GetUSBInfoStep_4;
if not Result then exit;
Result := GetUSBInfoStep_5;
if not Result then exit;
end;
end.