user1009073
user1009073

Reputation: 3238

Delphi TStreamReader - How to read in a shareable mode?

I have written a routine in Delphi Tokyo which takes multiple files (such as CSV) and merges them together, giving the user the option to ignore the first line on all files except the first one (as CSV files often have header lines/column name lines, when merging the files, I only want one copy of the header). The issue I am having is that even though I am only reading the various input files, if the file is open in another process, (specifically Excel), my app gives an error: "Cannot open file . The process cannot access the file because it is being used by another process."

I am using TStreamReader. How do I tell TStreamReader that it should open the file in read-only...and continue even if the file is open elsewhere?

Code below:

procedure glib_MergeTextFiles(const InFileNames: array of string; const OutFileName: string;
          HasHeader: Boolean = True;
          KeepHeader: Boolean = True);
var
  I: Integer;
  InStream: TStreamReader;
  OutStream: TStreamWriter;
  Line: string;
  IsFirstLine: Boolean;
begin
  // Create our output stream
  OutStream := TStreamWriter.Create(OutFileName, False, TEncoding.UTF8);
  try
    for I := 0 to high(InFileNames) do
    begin
      InStream := TStreamReader.Create(InFileNames[I], TEncoding.UTF8);
      IsFirstLine := True;
      try
        while not InStream.EndOfStream do
        begin
          Line := InStream.ReadLine;

          if IsFirstLine then { First Line }
          begin
            if HasHeader = False then
            begin
              OutStream.WriteLine(Line);
            end
            else
            begin
              // Is First Line, Has Header
              if I = 0 then  {is first file}
                OutStream.WriteLine(Line);
            end;
          end
          else
          begin
            OutStream.WriteLine(Line);
          end;

          IsFirstLine := False;
        end;

      finally
        InStream.Free;
      end;

    end;
  finally
    OutStream.Free;
  end;
end;

Upvotes: 2

Views: 2934

Answers (1)

Sertac Akyuz
Sertac Akyuz

Reputation: 54832

The problem is with the sharing mode. By default, the stream reader creates a file stream for reading only, but specifies no sharing mode, so it opens the file for exclusive access. However, to open a file for reading when it is already opened elsewhere, the file must have been previously opened to share reading access using the FILE_SHARE_READ flag:

FILE_SHARE_READ
0x00000001

Enables subsequent open operations on a file or device to request read access.

Otherwise, other processes cannot open the file or device if they request read access.

If this flag is not specified, but the file or device has been opened for read access, the function fails.

You can pass your own file stream to the stream reader, opened with the mode you like:

var
  I: Integer;
  FileStream: TFileStream;
  InStream: TStreamReader;
  ..
begin
...
  FileStream := TFileStream.Create(InFileNames[I], fmOpenRead or fmShareDenyNone);
  try
    InStream := TStreamReader.Create(FileStream, TEncoding.UTF8);
    try
      ..

Again, this requires Excel doing the same while opening the file, but with my simple test it looks like it does.

Upvotes: 7

Related Questions