What is the correct way to implement a Managed Property Handler Shell Extension?
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.
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
精彩评论