使用Delphi 2007从虚拟文件夹加载文件

4

我正在尝试从Windows虚拟文件夹(例如相机或iPhone图片文件夹)加载文件内容。以下是我用来测试的一些示例代码:

procedure TfrmForm.ButtonClick(Sender: TObject);
Var
  Dialog: TAttachDialog;
  Enum: IEnumShellItems;
  Name: LPWSTR;
  Item: IShellItem;
  Strm: IStream;
  OStrm: TOLEStream;
  FStrm: TFileStream;
  Result: HRESULT;
  Buf: Array[0..99] Of Char;
  Read: LongInt;
begin
  Result := CoInitializeEx(Nil, COINIT_APARTMENTTHREADED Or
                                COINIT_DISABLE_OLE1DDE);
  If Succeeded(Result) Then
  Begin
    Dialog := TAttachDialog.Create(Self);
    Try
      Dialog.Options := [fdoAllowMultiSelect, fdoPathMustExist,
                         fdoFileMustExist];
      Dialog.Title := 'Select Attachments';

      If Dialog.Execute(Self.Handle) Then
      Begin
        If FAILED(Dialog.ShellItems.EnumItems(Enum)) Then
          Raise Exception.Create('Could not get the list of files selected.');

        While Enum.Next(1, Item, Nil) = S_OK Do
        Begin
          If (Item.GetDisplayName(SIGDN_NORMALDISPLAY, Name) = S_OK) Then
          Begin
            mResults.Lines.Add(Name);
            CoTaskMemFree(Name);
          End;

          If Item.BindToHandler(Nil, BHID_Stream, IID_IStream, Strm) = S_OK Then
          Begin
            OStrm := TOLEStream.Create(Strm);
            FStrm := TFileStream.Create('C:\Temp\Test.jpg', fmCreate);
            FStrm.CopyFrom(OStrm, OStrm.Size);
            FreeAndNil(OStrm);
            FreeAndNil(FStrm);
            Strm := Nil;
          End;

          Item := Nil;
        End;
      End;
    Finally
      FreeAndNil(Dialog);
    End;
    CoUninitialize;
  End;
end;

TAttachDialog是TCustomFileOpenDialog的后代,它公开了ShellItems属性。在我的实际应用程序中,我需要返回一个TStream对象。因此,在这个例子中,我使用TFileStream来复制源文件,以证明我已成功使用Delphi流访问文件。一切都正常工作,直到我尝试FStrm.CopyFrom时,此时我会收到一个“未实现”的错误。我做错了什么或者有没有更好的方法来完成我想要的功能?
1个回答

5
只有当32位或64位版本的Seek()方法都没有在后代类中重写(或其中一个错误地调用了inherited方法)时,TStream本身才会引发“未实现”错误。如果是这样,将引发EStreamError异常,显示“ClassName.Seek未实现”。
TOLEStream确实重写了32位版本的Seek()方法以调用IStream.Seek()。但是,它没有重写TStream.GetSize()属性getter。因此,在调用CopyFrom()之前读取OStrm.Size值时,它调用默认的TStream.GetSize()方法,该方法使用Seek()方法确定流大小-使用Seek()获取当前位置,然后再次使用Seek()到达流的末尾,保存结果,然后再次使用Seek()回到先前的位置。
因此,我猜测您获得的IStream可能不支持随机寻址,因此其Seek()方法返回E_NOTIMPL,TOLEStream.Seek()会检测并引发EOleSysError异常,显示“未实现”。
尝试调用IStream.Stat()以获取流大小(或从TOLEStream派生类并重写GetSize()方法以调用Stat()),然后如果> 0,则将返回的大小传递给CopyFrom()(如果将Count = 0传递给CopyFrom(),它将读取源流的Position和Size属性,从而导致相同的Seek()错误),例如:
var
  ...
  Stat: STATSTG;
begin
  ...
  if Item.BindToHandler(Nil, BHID_Stream, IID_IStream, Strm) = S_OK Then
  try
    OStrm := TOLEStream.Create(Strm);
    try
      FStrm := TFileStream.Create('C:\Temp\Test.jpg', fmCreate);
      try
        OleCheck(Strm.Stat(Stat, STATFLAG_NONAME));
        if Stat.cbSize.QuadPart > 0 then
          FStrm.CopyFrom(OStrm, Stat.cbSize.QuadPart);
      finally
        FreeAndNil(FStrm);
      end;
    finally
      FreeAndNil(OStrm);
    end;
  finally
    Strm := Nil;
  end;
  ...
end;

对于这种情况的替代方案是避免使用 TStream.CopyFrom(),并手动复制字节,通过分配本地缓冲区然后循环调用OStrm.Read() ,将每个读取的缓冲区写入FStrm,直到OStrm.Read()报告没有更多可读的字节为止:

var
  ...
  Buf: array[0..1023] of Byte;
  NumRead: Integer;
begin
  ...
  if Item.BindToHandler(Nil, BHID_Stream, IID_IStream, Strm) = S_OK Then
  try
    OStrm := TOLEStream.Create(Strm);
    try
      FStrm := TFileStream.Create('C:\Temp\Test.jpg', fmCreate);
      try
        repeat
          NumRead := OStrm.Read(Buf[0], SizeOf(Buf));
          if NumRead <= 0 then Break;
          FStrm.WriteBuffer(Buf[0], NumRead);
        until False;
      finally
        FreeAndNil(FStrm);
      end;
    finally
      FreeAndNil(OStrm);
    end;
  finally
    Strm := Nil;
  end;
  ...
end;

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接