Caynadian
Caynadian

Reputation: 757

Load File From Virtual Folder Using Delphi 2007

I am trying to load the contents of a file from one of the Windows virtual folders (for example, a camera or iPhone picture folder). Below is some sample code that I am using to play around with this:

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 is just a descendant of TCustomFileOpenDialog that exposes the ShellItems property. In my actual application, I need a TStream object returned. So, in this example, I am using a TFileStream top copy the source file as proof of concept that I have successfully accessed the file using a Delphi stream. Everything works Ok until I try the FStrm.CopyFrom at which point I get a "Not Implemented" error. What am I doing wrong with this or is there a better way entirely to do what I want?

Upvotes: 4

Views: 483

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595897

The only time TStream itself raises a "not implemented" error is if neither the 32bit or 64bit version of Seek() are overridden in a descendant class (or one of them erroneously called the inherited method). If that were true, an EStreamError exception is raised saying "ClassName.Seek not implemented".

TOLEStream does override the 32bit version of Seek() to call IStream.Seek(). However, it does not override the TStream.GetSize() property getter. So when you are reading the OStrm.Size value before calling CopyFrom(), it calls the default TStream.GetSize() method, which uses Seek() to determine the stream size - Seek() to get the current position, then Seek() again to the end of the stream, saving the result, then Seek() again to go back to the previous position.

So, my guess would be that the IStream you have obtained likely does not support random seeking so its Seek() method is returning E_NOTIMPL, which TOLEStream.Seek() would detect and raise an EOleSysError exception saying "Not implemented".

Try calling IStream.Stat() to get the stream size (or derive a class from TOLEStream and override the GetSize() method to call Stat()), and then pass the returned size to CopyFrom() if > 0 (if you pass Count=0 to CopyFrom(), it will read the source stream's Position and Size properties, thus causing the same Seek() error), eg:

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;

The alternative would be to simply avoid TStream.CopyFrom() and manually copy the bytes yourself, by allocating a local buffer and then calling OStrm.Read() in a loop, writing each read buffer to FStrm, until OStrm.Read() reports that there is no more bytes to 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;

Upvotes: 5

Related Questions