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:
- When explorer calls IInitializeWithStream.Initialize the STGM property is set to STGM_SHARE_DENY_WRITE.
- And at no point did explorer call IPropertyStore.SetValue or IPropertyStore.Commit.
- 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;
}
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.