在 Delphi XE6 中调用 datasnap 服务器方法后删除文件

File delete after datasnap server method call in Delphi XE6

我有一个用 Delphi XE6 编写的 Datasnap 客户端服务器应用程序。我正在调用一个服务器方法,该方法使用 TFileStream 创建临时文件,用我的 TStream 格式的报告从我的 http post 填充它,然后 returns 到我的客户端。这一切都按需要工作。然而,在该方法的最后,我调用 deleteFile 方法从服务器中删除临时文件,但它永远不会被删除。我做错了什么?

var
  r,f: String;
  SS: TStringStream;
  Uid: TGuid;
begin
   CreateGuid(Uid);
   f:= ChangeFileExt(GuidToString(Uid),'.rpt');
   result:= TFileStream.Create(f, fmCreate or fmOpenWrite);

  r := getRunReportJSON(ARunReportObj);
  SS := TStringStream.Create(r, TEncoding.ASCII);
  try
    try      
      ServerContainer1.idHttp1.Post(gUrl, SS, result);
      Result.Position:= 0;
    except
    end;
  finally
    SS.Free;
    if FileExists(f) then
      DeleteFile(f)
  end;
end;

在销毁文件流对象之前,您不能删除您创建的文件流。文件流对象包裹了一个文件句柄,当文件句柄存在时,其后面的文件对象不能被删除。

您需要销毁文件流对象才能删除它。

您的代码有一些相当奇怪的异常处理。它在函数中间不加区别地吞下异常。你真的想要这么残忍吗?而在try/except块之外,任何异常都会导致文件流对象被泄露。

在删除文件之前测试文件是否存在并没有多大意义。如果您成功创建了文件流,那么您可以确信该文件存在。

正如 David 所说,只有先销毁 TFileStream 才能删除文件,这样它才能关闭文件的句柄。如果您希望文件在服务器使用完后自动删除,您可以:

  1. 直接用Win32CreateFile()函数打开文件所以可以指定FILE_FLAG_DELETE_ON_CLOSE标志,然后用THandleStream包裹得到的句柄class.

    type
      TMyHandleStream = class(THandleStream)
      public
        destructor Destroy; override;
      end;
    
    destructor TMyHandleStream.Destroy;
    begin
      inherited;
      CloseHandle(Handle);
    end;
    
    var
      h: THandle;
      r, f: String;
      SS: TStringStream;
      Uid: TGuid;
    begin
      CreateGuid(Uid);
      f := 'C:\some folder\' + GuidToString(Uid) + '.rpt';
      h := Windows.CreateFile(PChar(f), GENERIC_READ or GENERIC_WRITE, FILE_SHARE_READ, nil, CREATE_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, 0);
      if h = INVALID_HANDLE_VALUE then RaiseLastOSError;
      Result := TMyHandleStream.Create(h);
      try
        r := getRunReportJSON(ARunReportObj);
        SS := TStringStream.Create(r, TEncoding.ASCII);
        try
          ServerContainer1.IdHTTP1.Post(gUrl, SS, Result);
        finally
          SS.Free;
        end;
        Result.Position := 0;
      except
        Result.Free;
        raise;
      end;
    end;
    
  2. TFileStream 派生一个新的 class 并覆盖其析构函数以删除文件:

    type
      TMyFileStream = class(TFileStream)
      public
        destructor Destroy; override;
      end;
    
    destructor TMyFileStream.Destroy;
    begin
      inherited;
      DeleteFile(Self.FileName);
    end;
    

    var
      r, f: String;
      SS: TStringStream;
      Uid: TGuid;
    begin
      CreateGuid(Uid);
      f := 'C:\some folder\' + GuidToString(Uid) + '.rpt';
      Result := TMyFileStream.Create(f, fmCreate or fmOpenReadWrite);
      try
        r := getRunReportJSON(ARunReportObj);
        SS := TStringStream.Create(r, TEncoding.ASCII);
        try
          ServerContainer1.IdHTTP1.Post(gUrl, SS, Result);
        finally
          SS.Free;
        end;
        Result.Position := 0;
      except
        Result.Free;
        raise;
      end;
    end;
    
  3. 使用 TFileStreamEx class this forum answer:

    type
      TFileStreamEx = class(THandleStream)
      public
        constructor Create(const FileName: string; Mode: Word; flags: DWORD = FILE_ATTRIBUTE_NORMAL);
        destructor Destroy; override;
      end;
    
    constructor TFileStreamEx.Create(const FileName: string; Mode: Word; flags: DWORD = FILE_ATTRIBUTE_NORMAL);
    const
      AccessMode: array[0..2] of LongWord = (
        GENERIC_READ,
        GENERIC_WRITE,
        GENERIC_READ or GENERIC_WRITE);
      ShareMode: array[0..4] of LongWord = (
        0,
        0,
        FILE_SHARE_READ,
        FILE_SHARE_WRITE,
        FILE_SHARE_READ or FILE_SHARE_WRITE);
    begin
      if Mode = fmCreate then
      begin
        inherited Create(
          CreateFile(
            PChar(FileName),
            GENERIC_READ or GENERIC_WRITE,
            0,
            nil,
            CREATE_ALWAYS,
            flags,
            0
          )
        );
        if Handle = INVALID_HANDLE then
          raise EFCreateError.CreateFmt(SFCreateError, [FileName]);
      end
      else
      begin
        inherited Create(
          CreateFile(
            PChar(FileName),
            AccessMode[Mode and 3],
            ShareMode[(Mode and $F0) shr 4],
            nil,
            OPEN_EXISTING,
            flags,
            0
          )
        );
        if Handle = INVALID_HANDLE then
          raise EFOpenError.CreateFmt(SFOpenError, [FileName]);
      end;
    end;
    
    destructor TFileStreamEx.Destroy;
    begin
      if Handle <> INVALID_HANDLE then CloseHandle(Handle);
    end;
    

    var
      r, f: String;
      SS: TStringStream;
      Uid: TGuid;
    begin
      CreateGuid(Uid);
      f := 'C:\some folder\' + GuidToString(Uid) + '.rpt';
      Result := TFileStreamEx.Create(f, fmCreate or fmOpenReadWrite, FILE_FLAG_DELETE_ON_CLOSE);
      try
        r := getRunReportJSON(ARunReportObj);
        SS := TStringStream.Create(r, TEncoding.ASCII);
        try
          ServerContainer1.IdHTTP1.Post(gUrl, SS, Result);
        finally
          SS.Free;
        end;
        Result.Position := 0;
      except
        Result.Free;
        raise;
      end;
    end;