나래온 툴 내부 구조 – 1. 물리 드라이브 목록 가져오기

들어가기에 앞서

이 시리즈는 나래온 툴의 내부 구조를 정리하여, 해당 프로그램의 일부 기능을 구현하는 사람들에게 도움을 줄 목적으로 작성되었다. 따라서 이 글은 그동안 질문 받았던 부분을 중심으로 쓰게 될 것이다. 더 궁금한 점이 있으면 메일로 문의하면 된다. 다만 코드 스니펫의 델파이 -> C++ 번역 요청은 받지 않는다.


필요성

디스크 툴이 처음 시작할 때 가장 먼저 하는 것은 디스크를 찾는 일이다. 여기에는 OS에서 제공하는 데이터베이스인 WMI를 이용하거나, 다른 API를 이용하는 방법이 있다. 나래온 툴에서는 어느 한 가지를 선택하지 않고, 두 가지 방법을 모두 순서대로 수행한다. 이처럼 나래온 툴에는 처음부터 어떤 방식을 써야 할 지 모르고 하나씩 시도해 보아야 할 것들이 많은데, 이것을 패턴으로 만든 것이 TAutoXXX 객체들이다.

어떤 스토리지를 가져올 것인가?

스토리지 목록을 가져오려면 가장 먼저 정의해야 할 것은, 어떤 드라이브를 가져올 지에 대한 계획이다. 로컬 드라이브인가? 네트워크 드라이브인가? 논리 파티션인가? 물리 드라이브인가? 이동식 디스크인가? 디스크를 분류하는 수많은 방법들이 있으며, 이것들은 한 API로 가져올 수 있는 게 아니라 흩어져 있다. 나래온 툴의 경우에는 물리 드라이브 (\\.\PhysicalDriveXX)를 가져온다.

이 부분을 확인하고 싶다면, TListChangeGetter의 GetPhysicalDriveList function에 중단점을 찍어보면 된다. 이 함수는 리스트가 달라지거나(USB 연결 등 PnP Signal) 프로그램을 켤 때 수행된다. 결과는 다음과 유사하게 나올 것이다. 당신의 PC에 달린 스토리지 갯수에 따라 리스트 길이는 달라질 수 있다. IPhysicalDrive 인터페이스와 TPhysicalDrive 객체에 대해서는 다음 편에 다룰 것이다.

두 가지 방법: WMI(TWMIPhysicalDriveListGetter), OS(TOSPhysicalDriveListGetter)

WMI는 Query 기반이므로 매우 간단하다. 이 부분은 TWMIPhysicalDriveListGetter 객체에 있다. 먼저 GetDiskDriveSearchResult에서 ‘Select * from Win32_DiskDrive’ 쿼리를 수행한다.

function TWMIPhysicalDriveListGetter.GetDiskDriveSearchResult
  (WMIObject: IDispatch): IEnumVARIANT;
const
  SelectAllDiskDrive = 'Select * from Win32_DiskDrive';
var
  ResultAsOleVariant: OleVariant;
begin
  ResultAsOleVariant :=
    OleVariant(WMIObject).ExecQuery(SelectAllDiskDrive);
  result :=
    IUnknown(ResultAsOleVariant._NewEnum) as IEnumVARIANT;
end;

그 후 TraverseResultAndAddFixedOrUSBDrive 내의 IfFixedOrUSBDriveAddToList에서 물리 디스크인지를 체크한다. 먼저 체크 과정에 오류가 없게 하기 위해서 해당 드라이브가 실재하고 각 항목이 Null이 아닌지를 확인해야 한다. IsCurrentDriveAvailable이 그 일을 하는데, 각 항목의 MediaLoaded가 True이고, MediaType이 Null이 아니고, DeviceID가 Null이 아닌 경우 Available이라고 판단한다.

function TWMIPhysicalDriveListGetter.IsDriveSetValidType: Boolean;
begin
  result := (not VarIsNull(CurrentDrive.MediaType));
end;

function TWMIPhysicalDriveListGetter.IsDriveLoaded: Boolean;
begin
  result := (CurrentDrive.MediaLoaded);
end;

function TWMIPhysicalDriveListGetter.IsDriveSetValidID: Boolean;
begin
  result := (not VarIsNull(CurrentDrive.DeviceID));
end;

function TWMIPhysicalDriveListGetter.IsCurrentDriveAvailable: Boolean;
begin
  result :=
    IsDriveSetValidID and
    IsDriveLoaded and
    IsDriveSetValidType;
end;

그러면 CheckMediaTypeAndAddIfRequirementMet으로 넘어가 최종적으로 물리 디스크 여부를 체크한다. 이는 MediaType에 ‘hard’가 들어가고, InterfaceType이 ‘IDE’, ‘SCSI’, ‘USB’ 중 하나인 경우를 말한다. 이 모든 걸 만족하면 결과 리스트에 추가된다.

function TWMIPhysicalDriveListGetter.IsHarddrive(const MediaType: String):
  Boolean;
begin
  //Refer https://msdn.microsoft.com/en-us/library/aa394132%28v=vs.85%29.aspx
  result := Pos('hard', LowerCase(MediaType)) >= 0;
end;

function TWMIPhysicalDriveListGetter.IsDriveConnectedByKnownInterface: Boolean;
begin
  result :=
    IsDriveConnectedBy('IDE') or
    IsDriveConnectedBy('SCSI') or
    IsDriveConnectedBy('USB');
end;

procedure TWMIPhysicalDriveListGetter.IfDriveConnectedByKnownInterfaceAddToList;
begin
  if IsDriveConnectedByKnownInterface then
     AddDriveToDeviceIDList;
end;

procedure TWMIPhysicalDriveListGetter.CheckMediaTypeAndAddIfRequirementMet;
begin
  if IsHarddrive(CurrentDrive.MediaType) then
    IfDriveConnectedByKnownInterfaceAddToList;
end;

OS 방식은 QueryDosDevice API를 이용해 먼저 PhysicalDrive로 시작하는 경로명들을 얻어온다. 이 부분은 TOSPhysicalDriveGetter에 있다. TOSPhysicalDrivePathGetter에서 경로명들을 얻어온 뒤 파싱하는 작업을 한다. Null로 구분되어 있기 때문에 파싱은 매우 쉬운데, 델파이의 null-terminated string pointer(PChar)형이 해당 위치를 가리키게 하기만 하면 다음은 자동이다.

function TOSPhysicalDrivePathGetter.ParseVolumeNameIntoList(
  const AllDrives: PTVolumeNameBuffer): TDrivePathNumberList;
var
  CurrentCharIndex: Integer;
  CurrentName: PChar;
begin
  CurrentCharIndex := 0;
  result := TDrivePathNumberList.Create;
  while CurrentCharIndex < Length(AllDrives^) do
  begin
    CurrentName := @(AllDrives^)[CurrentCharIndex];
    if CurrentName = '' then
      break;
    if IsPhysicalDrive(CurrentName) then
      result.Add(ParseSeparatedName(CurrentName));
    Inc(CurrentCharIndex, Length(CurrentName) + 1);
  end;
end;

PhysicalDrive의 존재 여부는 IOCTL_STORAGE_CHECK_VERIFY ioctl code로 알아낼 수 있다. 자세한 내용은 다음 편인 PhysicalDrive에서 알아볼 예정이다.

나래온 툴 내의 패턴(?): TAutoXXX

나래온 툴 내에는 한 가지 설계 패턴이 있다. 어떤 방식으로 해야 할지 알 수 없을 때, TAuto로 시작하는 객체에 일을 시키면 알아서 결정해주는 방식이다. 여기서도 다른 모든 객체들은 TAutoPhysicalDriveListGetter를 사용해 리스트를 얻어온다. 여기서는 WMI를 먼저 시도해본 뒤, 안 되면 OS에 직접 요청하는 식으로 이루어진다. 전체적으로 명령어 셋이나(ATA, SAT, NVMe, etc.), 파티션 종류에서도(NTFS, FAT32) 이 방식을 사용한다. 이는 델파이의 메타클래스 기능을 이용한 것이다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다