views:

76

answers:

2

Now that .NET CLR 4.0 supports side by side (SxS) operation it should now be possible to write shell extensions in managed code. I have attempted this and successfully coded a Property Handler that implements IPropertyStore, IInitializeWithStream and IPropertyStoreCapabilities.

The handler works fine and is called as expected when browsing files via the explorer. It also works fine in displaying the custom properties in the preview panel and the file properties "detail" panel.

However, when I attempt to edit a property in the preview panel, and then click "Save" I get a "File In Use" error saying that the file is open in Windows Explorer.

A few tidbits:

  1. When explorer calls IInitializeWithStream.Initialize the STGM property is set to STGM_SHARE_DENY_WRITE.
  2. And at no point did explorer call IPropertyStore.SetValue or IPropertyStore.Commit.
  3. I see repeated calls to my handler on different threads for the same file properties.

So what do I need to change (or set in the registery) to get the property save to work?

Update:

Thanks to Ben I've got it working. The "difficult part" (at least for me) was understanding that COM interop would never call Dispose or Finalize on my PropertyHandler. This was leaving the files I processed open till the GC ran.

Fortunately, the "property handler protocol" works such that when IInitializeWithSream.Initialize() is called for a ReadValue() the streamMode is ReadOnly, and when it is called for a SetValue() the streamMode is ReadWrite and Commit() will be called at the end.

int IInitializeWithStream.Initialize( IStream stream, uint grfMode )
{
    _stream = stream;
    _streamMode = (Stgm)grfMode;

    Load();

    // We release here cause if this is a read operation we won't get called back, 
    // and our finializer isn't called. 
    if ( ( _streamMode & Stgm.ReadWrite ) != Stgm.ReadWrite )
    {
        Marshal.ReleaseComObject( _stream );
        _stream = null;
    }
    return HResult.S_OK;
}

int IPropertyStore.Commit()
{
    bool result = false;

    if ( _stream != null )
    {
        result = WriteStream( _stream );
        Marshal.ReleaseComObject( _stream );
        _stream = null;
    }

    return result ? HResult.S_OK : HResult.E_FAIL;
}
+1  A: 

Hi David,

Explorer tries to ensure that it doesn't interfere with other applications that may have the file open. Could the file be legitimately in use by another application? Is there a preview handler open?

Sometimes, we see property handlers that keep their streams open longer than necessary (or file-based handlers that open the file with restrictive permissions). Can you verify if you are releasing the stream in a timely manner?

Finally, I don't think this is related to your immediate problem, but using .NET shell extensions is unsupported. We recommend that this not be incorporated into any product.

-Ben

Ben Karas
There is no preview handler, but the Windows Search service seems to load the handler occasionally. But it shouldn't lock the file? Further, I can make editing work by switching to using IInitializeWithFile, and my SaveValue() and Commit() get called as expected.Anyway, even when I stub out IInitializeWithStream so that it never uses the stream (and have GetValue() return default empty values) it still gives the same error when editing a property. Explorer never even calls SaveValue() or Commit().
David Lynch
Do I need to AddRef() the IStream that I get in Initialize()? And then Release() it after Commit()? I had assumed I didn't.As per your last issue I thought that using .NET 4.0 in side by side mode solved the CLR version issues with shell extensions. I've verified that the correct CLR is running my code.
David Lynch
+2  A: 

Yes, you have to AddRef() the stream to keep it open and to keep the reference alive correctly.

Note that the indexer will use your property handler to open the file as well. So if you leak the stream object, the file will remain open. You can use the sysinternals procexp to tell what process has the file open, or procmon to tell what calls and parameters it used.

Ben Karas