Delphi Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
델파이 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
FreePascal/Lazarus
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
델마당
볼랜드포럼 광고 모집

델파이 팁&트릭
Delphi Programming Tip&Tricks
[3] [자료] CD 드라이브의 Open, Close 감지하기
박종민.BacTeria [bacteria] 5503 읽음    2002-02-27 20:21
안녕 하세요?

BacTeria 박종민 입니다.

이 곳 델파이 게시판이 많이 썰렁한것 같아서 앞으로 자료들을 하나씩 올리겠습니다.

델파이 만세!!

그럼 이만... 휘리릭~

-------------------------------------------------------------

notification message

When a compact disc is removed or inserted, then Windows 95 and Windows NT send a WM_DEVICECHANGE message to all top-level windows. This message contains an event (wParam) which describes the change and a structure (lParam) that provides detailed information about the change.

Here is the definition of WM_DEVICECHANGE: (from Win32 programmers reference)

[New - Windows 95]
Event = (UINT) wParam;
dwData = (DWORD) lParam;

The WM_DEVICECHANGE device message notifies an application or device driver of a change to the hardware configuration of a device or the computer.

Parameters

Event

Event type. Can be one of these values:

Value     Meaning
DBT_DEVICEARRIVAL   A device has been inserted and is now available.
DBT_DEVICEQUERYREMOVE  Permission to remove a device is requested.
                                                                   Any application can deny this request and cancel the removal.
DBT_DEVICEQUERYREMOVEFAILED Request to remove a device has been cancelled.
DBT_DEVICEREMOVEPENDING  Device is about to be removed. Can not be denied.
DBT_DEVICEREMOVECOMPLETE Device has been removed.
DBT_DEVICETYPESPECIFIC  Device-specific event.
DBT_CONFIGCHANGED  Current configuration has changed.


dwData

Address of a structure that contains event-specific data. Its meaning depends on the given event.

Return Value

Returns TRUE to complete a requested action, FALSE otherwise.

Remarks

For devices that offer software-controllable features, such as ejection and locking, the operating system typically sends a DBT_DEVICEREMOVEPENDING message to let applications and device drivers end their use of the device gracefully.
If the operating system forcefully removes of a device, it may not send a DBT_DEVICEQUERYREMOVE message before doing so.

As you can see in the definition, this message isn’t only send when a CD is inserted or removed. This message will be send whenever new devices or media are added and when existing devices or media are removed. But the purpose of this text is only to explain how to get notified when a compact disc is removed or inserted in the CD-ROM player.
Processing the wm_devicechange message (in case of a compact disc)

Now, when a new compact disc is inserted into a drive, a WM_DEVICECHANGE message is broadcasted. This message contains a DBT_DEVICEARRIVAL event.
When a compact disc is removed, the WM_DEVICECHANGE message contains the DBT_DEVICEREMOVECOMPLETE event. In this case (inserting and removing compact discs), we know that the device is of type volume (DBT_DEVTYP_VOLUME) and the event’s media flag is set (DBTF_MEDIA).

Getting to work

Great, intercepting WM_DEVICECHANGE and we’re done! Pity that all the needed constants and structures for interpreting this message aren’t declared in Delphi. So that’s the first thing we’ve got to do. Making some declarations which makes life easier. For that you can look at some libraries (in which these declarations are made) or have a look at the SDK kit of Microsoft. If you found them the only thing you still have to do is to translate them into something that Delphi understands. When you do this, always try to use the naming conventions. This makes live easier and less confusing for everybody.

Partial Translate of DBT.H

The first thing we make is a message-specific record for this windows message. We could use TMessage, but then we have to work with the ‘meaningless’ words like wParam and lParam.

TWMDeviceChange = record
  Msg : Cardinal;
  Event : UINT;
  dwData : Pointer;
  Result : LongInt;
end;

When we got a DBT_DEVICECHANGE or DBT_DEVICEREMOVECOMPLETE event, dwData contains an address of a DEV_BROADCAST_HDR structure identifying the device inserted. We also need a pointer so we can point to this structure.

PDEV_BROADCAST_HDR = ^TDEV_BROADCAST_HDR;
TDEV_BROADCAST_HDR = packed record
  dbch_size : DWORD;
  dbch_devicetype : DWORD;
  dbch_reserved : DWORD;
end;

When the device is of type volume, then we can get some device specific information, namely specific information about a logical volume.

PDEV_BROADCAST_VOLUME = ^TDEV_BROADCAST_VOLUME;
TDEV_BROADCAST_VOLUME = packed record
  dbcv_size : DWORD;
  dbcv_devicetype : DWORD;
  dbcv_reserved : DWORD;
  dbcv_unitmask : DWORD;
  dbcv_flags : WORD;
end;

Next to this structures, necessary constants have to be declared :

(* Events of WM_DEVICECHANGE (wParam) *)

DBT_DEVICEARRIVAL = $8000;            (* system detected a new device *)
DBT_DEVICEQUERYREMOVE = $8001;        (* wants to remove, may fail *)
DBT_DEVICEQUERYREMOVEFAILED = $8002;  (* removal aborted *)
DBT_DEVICEREMOVEPENDING = $8003;      (* about to remove, still avail *)
DBT_DEVICEREMOVECOMPLETE = $8004;     (* device is gone *)
DBT_DEVICETYPESPECIFIC = $8005;       (* type specific event *)
DBT_CONFIGCHANGED = $0018;

(* type of device in DEV_BROADCAST_HDR *)
DBT_DEVTYP_OEM = $00000000;           (* OEM- or IHV-defined *)
DBT_DEVTYP_DEVNODE = $00000001;       (* Devnode number *)
DBT_DEVTYP_VOLUME = $00000002;        (* Logical volume *)
DBT_DEVTYP_PORT = $00000003;          (* Port (serial or parallel *)
DBT_DEVTYP_NET = $00000004;           (* Network resource *)

(* media types in DBT_DEVTYP_VOLUME *)
DBTF_MEDIA = $0001;                (* change affects media in drive *)
DBTF_NET = $0002;                     (* logical volume is network volume *)

Making the component

Our component needs to intercept messages. For that we’ve to create an invisible window, which is able to intercept these messages. This is done with the AllocateHWnd function. With it we’ve to send the window-procedure and in return we get the handle to this invisible window.  We may not forget to free this window when we don’t need it anymore! This can be done with the DeAllocateHWnd procedure.  The correct place for creating and destroying this invisible window would be in the respective Create and Destroy procedures of our component. For this we’ve to override these methods in the public declarations sector :

constructor TCDEvents.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FWindowHandle := AllocateHWnd(WndProc);
end;

destructor TCDEvents.Destroy;
begin
DeallocateHWnd(FWindowHandle);
inherited Destroy;
end;

Offcourse we must also declare a variable for storing the handle of the invisible window and our window-procedure in the private sector. This procedure will intercept all the messages, so here we can check if the message is of  WM_DEVICECHANGE. To be sure that any other will be processed, we call the DefWindowProc function if our message is different from WM_DEVICECHANGE.

procedure TCDEvents.WndProc(var Msg: TMessage);
begin
    if (Msg.Msg = WM_DEVICECHANGE) then
     try
       WMDeviceChange(TWMDeviceChange(Msg));
     except
       Application.HandleException(Self);
     end
   else
     Msg.Result := DefWindowProc(FWindowHandle, Msg.Msg, Msg.wParam, Msg.lParam);
end;

As you can see, we check if we received a WM_DEVICECHANGE message. If not, the procedure send the message to the default window procedure. Otherwise we call another procedure (WMDeviceChange) for further processing of the message. While sending to WMDeviceChange the Msg of type TMessage will be transformed to our self defined tWMDeviceChange type.

procedure TCDEvents.WMDeviceChange(var Msg : TWMDeviceChange);
var lpdb : PDEV_BROADCAST_HDR;
      lpdbv : PDEV_BROADCAST_VOLUME;
begin
(* received a wm_devicechange message *)
lpdb := PDEV_BROADCAST_HDR(Msg.dwData);
(* look at the event send together with the wm_devicechange message *)
  case Msg.Event of
   DBT_DEVICEARRIVAL : begin
     if lpdb^.dbch_devicetype = DBT_DEVTYP_VOLUME then begin
      lpdbv := PDEV_BROADCAST_VOLUME(Msg.dwData);
      if (lpdbv^.dbcv_flags and DBTF_MEDIA) = 1 then
       if Assigned(fAfterArrival) then
        fAfterArrival(Self, GetFirstDriveLetter(lpdbv^.dbcv_unitmask));
     end;
    end;
   DBT_DEVICEREMOVECOMPLETE : begin
     if lpdb^.dbch_devicetype = DBT_DEVTYP_VOLUME then begin
      lpdbv := PDEV_BROADCAST_VOLUME(Msg.dwData);
      if (lpdbv^.dbcv_flags and DBTF_MEDIA) = 1 then
       if Assigned(fAfterArrival) then
        fAfterRemove(Self, GetFirstDriveLetter(lpdbv^.dbcv_unitmask));
     end;
    end;
  end;
end;

In this procedure we check the message thoroughly. We first look at the event send with the WM_DEVICECHANGE message. Next, we must check if the message is send by a CD-ROM drive. For this we check if the sending device is of type volume (DBT_DEVTYP_VOLUME). This specific information we can find in the record to which the address (dwData) points stored in the message record (TWMDeviceChange).

Then we’ve to look from which device the message is send, this we do by looking at the record through the ‘eyes’ of the TDEV_BROADCAST_HDR type. The dbch_devicetype field defines the type. In our case, we only want to be notified when a CD is removed or ejected from the CD-ROM player. So the device has to be a logical volume (DBT_DEVTYP_VOLUME).

Now, we know from which device the message comes and we can look at the record with the correct ‘glasses’, namely TDEV_BROADCAST_VOLUME. This record especially defined for logical volumes, gives us extra information about this sort of device.
If the device change message comes from  a logical volume, we still have to check if the change was owing to the CD inside the CD-ROM drive or the CD-ROM drive itself. For knowing this, we’ve to check if the media flag is set in the dbcv_flags field. If so, the message is owed by a change in CD.

With the dbcv_unitmask field we can find the valid drive letters, from which the message comes. The function below, returns the first valid drive letter. A valid drive letter is defined when the corresponding bit is set to 1 in the mask of drive letters, namely dbcv_unitmask field.

function TCDEvents.GetFirstDriveLetter(unitmask : longint):char;
var DriveLetter : shortint;
begin
DriveLetter := Ord('A');
while (unitmask and 1)=0  do begin
unitmask := unitmask shr 1;
inc(DriveLetter);
end;
Result := Char(DriveLetter);
end;

Well,  if the message cames from a CD, we can send the events. And that’s it, not much as you can see and it isn’t really that hard to accomplish.
I hope that you liked this article and also I hope you give me some comments about it. Just send it to my e-mail address. You can also look at the source code, included with this article. Please, read the header in the .pas file. That’s all that I ask from you, I don’t think this is much asked.

+ -

관련 글 리스트
3 [자료] CD 드라이브의 Open, Close 감지하기 박종민.BacTeria 5503 2002/02/27
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.