5

興味深い問題があります。私たちのクライアントは、通話からの音声会話を録音しましたが、録音に与えられたファイル名は無効でした. これはファイル名の例です123:123.wmv

信じてください、Windows Media エンコーダーがファイルを作成し、すべての情報がファイルに含まれていますが、Windows は明らかにファイル名を認識せず、フォルダーにのみ表示し123、ファイルは 0KB です。

さて、ここから編集します。Keith Miller のおかげで、ファイルからストリーム名を抽出して使用する関数を書くことができました。

2 つのデータ ストリームをファイルに作成し、ストリーム名を読み取り、各ストリームからデータを読み取る方法の作業コピーを含めました。これは本当に素晴らしいので、他の人にも使ってもらいたいです。私のコードはメイン ストリームを無視します。メイン ストリームにデータを書き込む場合は、無視しないのが最善です。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, uGeneralStuff;

type
  _FILE_STREAM_INFORMATION = record
    NextEntryOffset: cardinal;
    StreamNameLength: cardinal;
    StreamSize: int64;
    StreamAllocationSize: int64;
    StreamName: array[0..MAX_PATH] of WideChar;
  end;

  PFILE_STREAM_INFORMATION = ^_FILE_STREAM_INFORMATION;

  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    InfoBlock: _FILE_STREAM_INFORMATION;
    StatusBlock : record
      Status: Cardinal;
      Information: PDWORD;
    end;

    procedure CreateFile(FileName, Info: String);
    function ReadFile(FileName: String): String;
    function ReadStreams(filename: String): TStringList;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

function NtQueryInformationFile(FileHandle : Cardinal;
                                  IoStatusBlock : Pointer;
                                  FileInformation : Pointer;
                                  FileInformationLength : Cardinal;
                                  FileInformationClass : Cardinal
                                  ): Cardinal; stdcall; external 'ntdll.dll';
implementation

uses Math, StrUtils;
{$R *.dfm}

function TForm1.ReadStreams(filename: String): TStringList;
var
  iFH1: Integer;
  aFileName: array[0..MAX_PATH] of WideChar;
  aStreamName: String;
begin
Result := TStringList.Create;
iFH1 := FileOpen(filename, GENERIC_READ);
NtQueryInformationFile(iFH1, @StatusBlock, @InfoBlock, SizeOf(InfoBlock), 22);  // 22 Means FileStreamInformation
FileClose(iFH1);
while (1=1) do
  begin
  if InfoBlock.StreamNameLength = 0 then
    break;
  CopyMemory(@aFileName, @InfoBlock.StreamName, InfoBlock.StreamNameLength);
  aStreamName := Copy(aFileName, 1, PosEx(':', aFileName, 2) - 1);
  if aStreamName <> ':' then   //Ignore main stream, because I know I didn't write data in there
    Result.Add(aStreamName);
  if (InfoBlock.NextEntryOffset = 0) then
    break;
  InfoBlock := PFILE_STREAM_INFORMATION(PByte(@InfoBlock) + InfoBlock.NextEntryOffset)^;
  end;
end;


procedure TForm1.Button2Click(Sender: TObject);
var
  aStreams: TStringList;
  I: Integer;
begin
aStreams := ReadStreams('C:\Temp\123');
for I := 0 to aStreams.Count - 1 do
  begin
  ShowMessage(ReadFile('C:\Temp\123' + aStreams[I]));
  end;
end;

procedure TForm1.CreateFile(FileName, Info: String);
var
  iFH1: Integer;
  Buffer: PAnsiString;
begin
  iFH1 := FileCreate(FileName);
  Buffer := PAnsiString(AnsiString(Info) + #0);
  FileWrite(iFH1, Buffer^, Length(Info));
  FileClose(iFH1);
end;

function TForm1.ReadFile(FileName: String): String;
var
  iFH1: Integer;
  Buffer: PAnsiChar;
  iFL: Integer;
  iBR, iCurPos, iReadSize: Integer;
begin
  iFH1 := FileOpen(FileName, GENERIC_READ);
  iFL := FileSeek(iFH1, 0, 2);
  FileSeek(iFH1, 0, 0);
  iReadSize := Min(iFL, 1024);
  Buffer := AllocMem(iReadSize + 1);
  iCurPos := 0;
  Result := '';
  while iCurPos < iFL do
    begin
    iBR := FileRead(iFH1, Buffer^, iReadSize);
    if iBR = -1 then
      break;
    Result := Result + Buffer;
    Inc(iCurPos, iBR);
    end;
  FileClose(iFH1);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  CreateFile('C:\Temp\123:123.txt', 'This is TestFile 1');
  CreateFile('C:\Temp\123:345.txt', 'This is TestFile 2');
  ShowMessage(ReadFile('C:\Temp\123:123.txt'));
  ShowMessage(ReadFile('C:\Temp\123:345.txt'));
end;

end.
4

3 に答える 3

1

@KeithMillerが答えた'C:\Temp\123'ように、空のメイン ストリームと 2 つの代替ストリームでファイルを作成しています。

記事に基づいて、Delphi XE (so - Unicode!) コードでストリーム名を表示する私の簡単な試み:

type
  NTSTATUS = Cardinal;
  TFileInformationClass = (
    FileDirectoryInformation                  = 1,
    FileFullDirectoryInformation,
    FileBothDirectoryInformation,
    FileBasicInformation,
    FileStandardInformation,
    FileInternalInformation,
    FileEaInformation,
    FileAccessInformation,
    FileNameInformation,
    FileRenameInformation,
    FileLinkInformation,
    FileNamesInformation,
    FileDispositionInformation,
    FilePositionInformation,
    FileFullEaInformation,
    FileModeInformation,
    FileAlignmentInformation,
    FileAllInformation,
    FileAllocationInformation,
    FileEndOfFileInformation,
    FileAlternateNameInformation,
    FileStreamInformation,
    FilePipeInformation,
    FilePipeLocalInformation,
    FilePipeRemoteInformation,
    FileMailslotQueryInformation,
    FileMailslotSetInformation,
    FileCompressionInformation,
    FileObjectIdInformation,
    FileCompletionInformation,
    FileMoveClusterInformation,
    FileQuotaInformation,
    FileReparsePointInformation,
    FileNetworkOpenInformation,
    FileAttributeTagInformation,
    FileTrackingInformation,
    FileIdBothDirectoryInformation,
    FileIdFullDirectoryInformation,
    FileValidDataLengthInformation,
    FileShortNameInformation,
    FileIoCompletionNotificationInformation,
    FileIoStatusBlockRangeInformation,
    FileIoPriorityHintInformation,
    FileSfioReserveInformation,
    FileSfioVolumeInformation,
    FileHardLinkInformation,
    FileProcessIdsUsingFileInformation,
    FileNormalizedNameInformation,
    FileNetworkPhysicalNameInformation,
    FileIdGlobalTxDirectoryInformation,
    FileIsRemoteDeviceInformation,
    FileAttributeCacheInformation,
    FileNumaNodeInformation,
    FileStandardLinkInformation,
    FileRemoteProtocolInformation,
    FileMaximumInformation
  );
  PIOStatusBlock = ^TIOStatusBlock;
  TIOStatusBlock = packed record
    case Boolean of
      False: (Status: NTSTATUS; P: Pointer;);
      True: (Information:  ULONG_PTR);
  end;
  PFileStreamInformation = ^TFileStreamInformation;
  TFileStreamInformation = packed record
    NextEntryOffset: ULONG;
    StreamNameLength: ULONG;
    StreamSize: LARGE_INTEGER;
    StreamAllocationSize: LARGE_INTEGER;
    StreamName: array[0..0] of Char;
  end;

type
  TNtQueryInformationFile = function(FileHandle: THandle; IoStatusBlock: PIOStatusBlock;
  FileInformation: Pointer; Length: ULONG; FileInformationClass: TFileInformationClass): NTSTATUS; stdcall;

procedure GetAlternateFileStreamNames(const FileName: string; StreamNames: TStrings);
var
  hNT, hFile: THandle;
  NtQueryInformationFile: TNtQueryInformationFile;
  Buffer: array[Word] of Byte;
  ioStatus: TIOStatusBlock;
  P: PFileStreamInformation;
  S: string;
  L: Integer;
begin
  hNT := GetModuleHandle('ntdll.dll');
  if hNT = 0 then
    Exit;
  NtQueryInformationFile := GetProcAddress(hNT, 'NtQueryInformationFile');
  if @NtQueryInformationFile = nil then
    Exit;

  FillChar(Buffer, SizeOf(Buffer), 0);
  hFile := CreateFile(PChar(FileName), 0, FILE_SHARE_READ or FILE_SHARE_WRITE, nil, OPEN_EXISTING, 0, 0);
  try
    if NtQueryInformationFile(hFile, @ioStatus, @Buffer[0], SizeOf(Buffer), FileStreamInformation) = 0 then
    begin
      StreamNames.BeginUpdate;
      try
        StreamNames.Clear;
        P := @Buffer[0];
        while Assigned(P) do
        begin
          SetString(S, P^.StreamName, P^.StreamNameLength div SizeOf(Char));
          // strip trailing :$DATA
          L := Length(S);
          if (L >= 6) and (StrComp(@S[L - 5], ':$DATA') = 0) then
            Delete(S, L - 5, L);
          StreamNames.Add(S);

          if P^.NextEntryOffset = 0 then
            P := nil
          else
            P := Pointer(Integer(P) + P^.NextEntryOffset); //@Buffer[P^.NextEntryOffset];
        end;
      finally
        StreamNames.EndUpdate;
      end;
    end;
  finally
    CloseHandle(hFile);
  end;
end;


procedure TForm1.Button2Click(Sender: TObject);
var
  StreamNames: TStringList;
begin
  StreamNames := TStringList.Create;
  try
    GetAlternateFileStreamNames('C:\Temp\123', StreamNames);
    ShowMessage(StreamNames.Text);
  finally
    StreamNames.Free;
  end;
end;

質問に投稿されたコードによって作成されたファイルの場合、次のエントリが表示されます。

  1. ':'- 名前のないメイン ストリームだと思います
  2. ':123.txt'- 最初の代替ストリーム
  3. ':345.txt'- 2 番目の代替ストリーム

完全にテストされておらず、奇妙です。また、D2007 以前では修正が必要です。

于 2012-05-10T15:46:54.550 に答える