1

When I run the prog below, the result value of the stgOpenStorage is STG_E_SHAREVIOLATION. How should I close the IStorage to get it unlocked?

procedure TForm1.btnSaveClick(Sender: TObject);
var
  fileName : string;
  streamName : string;

  procedure storeTextIntoStorageStream( text_ : string );
  var
    documentStorage : IStorage;
    levelIStream : IStream;
    i, j : integer;
  begin
    if ( fileExists( fileName ) ) then
      deleteFile( fileName );
    stgCreateDocfile( @fileName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage );
    try
      documentStorage.CreateStream( @streamName[1], STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream );
      try
        i := length( text_ );
        levelIStream.write( @i, sizeOf( integer ), @j );
        levelIStream.write( @text_[1], i*sizeOf( char ), @j );
      finally
        levelIStream.Commit( 0 );
        levelIStream := NIL;
      end;
    finally
      documentStorage.Commit( 0 );
      documentStorage := NIL;
    end;
  end;

  function readTextFromStorageStream : string;
  var
    documentStorage : IStorage;
    levelIStream : IStream;
    i, j : integer;
  begin
    i := stgOpenStorage( @fileName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, NIL, 0, documentStorage );
    try
      documentStorage.OpenStream( @streamName[1], NIL, STGM_READ or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, levelIStream );
      try
        levelIStream.read( @i, sizeOf( integer ), @j );
        setLength( result, i );
        levelIStream.read( @result[1], i*sizeOf( char ), @j );
      finally
        levelIStream := NIL;
      end;
    finally
      documentStorage := NIL;
    end;
  end;

begin
  fileName := 'c:\temp\test.stg';
  streamName := 'Stream-0';
  storeTextIntoStorageStream( memo1.Lines.DelimitedText );
  memo1.Lines.DelimitedText := readTextFromStorageStream;
end;

And how could I set the IStorage/IStream default size / size step? Because my test 1.6K byte content stored in 16K.

SOLID Developper
  • 672
  • 4
  • 12

2 Answers2

1

There are two IStorage implementations in the Delphi source libraries. WinApi.OLE2 and WinApi.ActiveX. Which one do you use? In the WinApi.OLE2 unit, the IStorage and IStream are CLASSES, they are not INTERFACES. If you use this unit, the interface garbage collection and so the automatic closing does not work on the variables. If you use the WinApi.ActiveX unit, the example will work just fine.

The Bitman
  • 1,279
  • 1
  • 11
  • 25
0

The code looks fine 1, so I have a feeling that the problem is due to your use of STGM_SHARE_EXCLUSIVE. The file is in use at the time you are trying to open it, so I'm betting your OS/AV is the one keeping the file open (ie, to scan its content), not the interfaces in storeTextIntoStorageStream(), which are long gone by the time readTextFromStorageStream() is entered.

1: well, aside from the lack of adequate error handling. And the redundant nil'ing of interface variables. And, consider replacing your string indexes with PChar() casts instead.

In readTextFromStorageStream(), try replacing STGM_SHARE_EXCLUSIVE (which makes sense for a writer, but not a reader) with STGM_SHARE_DENY_WRITE instead and see if the error goes away:

procedure TForm1.btnSaveClick(Sender: TObject);
var
  fileName : string;
  streamName : string;

  procedure storeTextIntoStorageStream( const text_ : string );
  var
    documentStorage : IStorage;
    levelIStream : IStream;
    i, j : integer;
  begin
    if ( FileExists( fileName ) ) then
      DeleteFile( fileName );
    OleCheck( StgCreateDocFile( PChar(fileName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT or STGM_CREATE, 0, documentStorage ));
    try
      OleCheck( documentStorage.CreateStream( PChar(streamName), STGM_WRITE or STGM_SHARE_EXCLUSIVE or STGM_DIRECT, 0, 0, levelIStream ) );
      try
        i := Length( text_ );
        OleCheck( levelIStream.Write( @i, SizeOf( i ), @j ) );
        if ( i > 0 ) then
          OleCheck( levelIStream.Write( PChar(text_), i * SizeOf( Char ), @j ) );
      finally
        levelIStream.Commit( 0 );
      end;
    finally
      documentStorage.Commit( 0 );
    end;
  end;

  function readTextFromStorageStream : string;
  var
    documentStorage : IStorage;
    levelIStream : IStream;
    i, j : integer;
  begin
    Result := '';
    OleCheck( StgOpenStorage( PChar(fileName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, NIL, 0, documentStorage ) );
    OleCheck( documentStorage.OpenStream( PChar(streamName), nil, STGM_READ or STGM_SHARE_DENY_WRITE or STGM_DIRECT, 0, levelIStream ) );
    OleCheck( levelIStream.Read( @i, SizeOf( i ), @j ) );
    if ( i > 0 ) then
    begin
      SetLength( Result, i );
      OleCheck( levelIStream.Read( PChar(Result), i * SizeOf( Char ), @j ) );
    end;
  end;

begin
  fileName := 'c:\temp\test.stg';
  streamName := 'Stream-0';
  storeTextIntoStorageStream( Memo1.Lines.DelimitedText );
  Memo1.Lines.DelimitedText := readTextFromStorageStream;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • If I call a manual Release, the code works fine (the written content read back). So the Ref counting is the problem. It is not a timing problem. Because the file stick in locked state until I exit the prog. (If I press again the save button, the same error message appear for the save operation) On the next run it can delete and recreate the file. The save is fast. The data is just 1.6K in my test case. Your code runs the same way. – SOLID Developper Jul 08 '22 at 21:33
  • "*So the Ref counting is the problem*" - if that were true, the Stg API would be seriously flawed. Your code only has 1 ref to the `IStorage`, and 1 ref to the `IStream`. Those refs are cleared *automatically* by the Delphi compiler when they go out of scope, as well as when you assign `nil` to them. But calling `Release()` directly would lead to *double*-releasing the interfaces, which violates COM refcount rules, and is a big no-no. Look online, no example of `StgCreateDocFile()`/`StgOpenStorage()` will call `IStorage.Release()` twice, or `IStream.Release()` twice, without extra `AddRef()`s. – Remy Lebeau Jul 08 '22 at 22:11
  • I know that very well, but it solves the problem in this case. – SOLID Developper Jul 08 '22 at 22:38
  • I don't see how it can solve anything. What you are proposing is a blatant violation of COM, it *CAN'T* work the way you have described. In any case, I tried the code you originally provided, as well as the code I presented, and they both work just fine for me, no errors, the file is created and read correctly as expected in both cases. So, whatever is going on with your setup must be an issue with your particular environment. You are just going to have to debug it for yourself. – Remy Lebeau Jul 08 '22 at 23:33
  • There are some negotiation about the same problem. But it is in a C# solution and I cant understand even a word. It is about Marshal and the stats record... https://stackoverflow.com/questions/1661905/avoid-or-remove-lock-from-stgopenstorage – SOLID Developper Jul 09 '22 at 00:20
  • Now you are just confusing the matter without irrelevant stuff. The code in that "solution" is the C# equivalent of the Delphi code you presented here (calling `Commit()` before `Release()`). Are you referring to the double `GC.Collect()` calls? That has NO EFFECT in Delphi, which has no garbage collector at all. Even so, those `GC.Collect()` calls are only about memory management, it has nothing to do with unlocking the `IStorage`/`IStream` locks, or `Release()`'ing the interfaces. – Remy Lebeau Jul 09 '22 at 00:21
  • I run the same (ANSI version) program from D7 and the result is the same. Sharing violation. What kind of environment settings could cause this? Folder access/file creation rights in the OS? I don't insist on the exclusive flag. But if I don't include it, the result : invalid flag error. – SOLID Developper Sep 13 '22 at 08:37
  • I have just solved the meaning of your comment : AV. I don't use any anti virus stuff. Win defender AV feature turned off as well. – SOLID Developper Sep 13 '22 at 08:51