restructure projects

This commit is contained in:
2021-12-28 11:25:53 +08:00
parent 3a948f2063
commit 0f48dbe845
12 changed files with 267 additions and 135 deletions

View File

@ -0,0 +1,125 @@
using System;
namespace ShellExtensions.Interop.Common
{
/// <summary>
/// The STGM constants are flags that indicate
/// conditions for creating and deleting the object and access modes
/// for the object.
///
/// You can combine these flags, but you can only choose one flag
/// from each group of related flags. Typically one flag from each
/// of the access and sharing groups must be specified for all
/// functions and methods which use these constants.
/// </summary>
[Flags]
public enum AccessModes
{
/// <summary>
/// Indicates that, in direct mode, each change to a storage
/// or stream element is written as it occurs.
/// </summary>
Direct = 0x00000000,
/// <summary>
/// Indicates that, in transacted mode, changes are buffered
/// and written only if an explicit commit operation is called.
/// </summary>
Transacted = 0x00010000,
/// <summary>
/// Provides a faster implementation of a compound file
/// in a limited, but frequently used, case.
/// </summary>
Simple = 0x08000000,
/// <summary>
/// Indicates that the object is read-only,
/// meaning that modifications cannot be made.
/// </summary>
Read = 0x00000000,
/// <summary>
/// Enables you to save changes to the object,
/// but does not permit access to its data.
/// </summary>
Write = 0x00000001,
/// <summary>
/// Enables access and modification of object data.
/// </summary>
ReadWrite = 0x00000002,
/// <summary>
/// Specifies that subsequent openings of the object are
/// not denied read or write access.
/// </summary>
ShareDenyNone = 0x00000040,
/// <summary>
/// Prevents others from subsequently opening the object in Read mode.
/// </summary>
ShareDenyRead = 0x00000030,
/// <summary>
/// Prevents others from subsequently opening the object
/// for Write or ReadWrite access.
/// </summary>
ShareDenyWrite = 0x00000020,
/// <summary>
/// Prevents others from subsequently opening the object in any mode.
/// </summary>
ShareExclusive = 0x00000010,
/// <summary>
/// Opens the storage object with exclusive access to the most
/// recently committed version.
/// </summary>
Priority = 0x00040000,
/// <summary>
/// Indicates that the underlying file is to be automatically destroyed when the root
/// storage object is released. This feature is most useful for creating temporary files.
/// </summary>
DeleteOnRelease = 0x04000000,
/// <summary>
/// Indicates that, in transacted mode, a temporary scratch file is usually used
/// to save modifications until the Commit method is called.
/// Specifying NoScratch permits the unused portion of the original file
/// to be used as work space instead of creating a new file for that purpose.
/// </summary>
NoScratch = 0x00100000,
/// <summary>
/// Indicates that an existing storage object
/// or stream should be removed before the new object replaces it.
/// </summary>
Create = 0x00001000,
/// <summary>
/// Creates the new object while preserving existing data in a stream named "Contents".
/// </summary>
Convert = 0x00020000,
/// <summary>
/// Causes the create operation to fail if an existing object with the specified name exists.
/// </summary>
FailIfThere = 0x00000000,
/// <summary>
/// This flag is used when opening a storage object with Transacted
/// and without ShareExclusive or ShareDenyWrite.
/// In this case, specifying NoSnapshot prevents the system-provided
/// implementation from creating a snapshot copy of the file.
/// Instead, changes to the file are written to the end of the file.
/// </summary>
NoSnapshot = 0x00200000,
/// <summary>
/// Supports direct mode for single-writer, multireader file operations.
/// </summary>
DirectSingleWriterMultipleReader = 0x00400000
}
}

View File

@ -0,0 +1,92 @@
using ShellExtensions.Interop.Common;
using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace ShellExtensions.Interop
{
internal static class HandlerNativeMethods
{
internal static readonly Guid IThumbnailProviderGuid = new Guid("e357fccd-a995-4576-b01f-234630154e96");
internal static readonly Guid IInitializeWithFileGuid = new Guid("b7d14566-0509-4cce-a71f-0a554233bd9b");
internal static readonly Guid IInitializeWithStreamGuid = new Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f");
//internal static readonly Guid IInitializeWithItemGuid = new Guid("7f73be3f-fb79-493c-a6c7-7ee14e245841");
internal static readonly Guid IMarshalGuid = new Guid("00000003-0000-0000-C000-000000000046");
}
#region Interfaces
/// <summary>
/// ComVisible interface for native IThumbnailProvider
/// </summary>
[ComImport]
[Guid("e357fccd-a995-4576-b01f-234630154e96")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IThumbnailProvider
{
/// <summary>
/// Gets a pointer to a bitmap to display as a thumbnail
/// </summary>
/// <param name="squareLength"></param>
/// <param name="bitmapHandle"></param>
/// <param name="bitmapType"></param>
void GetThumbnail(uint squareLength, [Out] out IntPtr bitmapHandle, [Out] out uint bitmapType);
}
/// <summary>
/// Provides means by which to initialize with a file.
/// </summary>
[ComImport]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
[Guid("b7d14566-0509-4cce-a71f-0a554233bd9b")]
interface IInitializeWithFile
{
/// <summary>
/// Initializes with a file.
/// </summary>
/// <param name="filePath"></param>
/// <param name="fileMode"></param>
void Initialize([MarshalAs(UnmanagedType.LPWStr)] string filePath, AccessModes fileMode);
}
/// <summary>
/// Provides means by which to initialize with a stream.
/// </summary>
[ComImport]
[Guid("b824b49d-22ac-4161-ac8a-9916e8fa3f7f")]
[InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
interface IInitializeWithStream
{
/// <summary>
/// Initializes with a stream.
/// </summary>
/// <param name="stream"></param>
/// <param name="fileMode"></param>
void Initialize(IStream stream, AccessModes fileMode);
}
#endregion
// <summary>
/// Thumbnail Alpha Types
/// </summary>
public enum ThumbnailAlphaType
{
/// <summary>
/// Let the system decide.
/// </summary>
Unknown = 0,
/// <summary>
/// No transparency
/// </summary>
NoAlphaChannel = 1,
/// <summary>
/// Has transparency
/// </summary>
HasAlphaChannel = 2,
}
}

View File

@ -0,0 +1,13 @@
namespace ShellExtensions.Resources
{
internal sealed class LocalizedMessages
{
public const string StorageStreamBufferOverflow = "The sum of offset and count must be less than or equal to the size of the buffer.";
public const string StorageStreamCountLessThanZero = "Count must be greater than or equal to zero.";
public const string StorageStreamIsReadonly = "The stream was initialized as read-only.";
public const string StorageStreamOffsetLessThanZero = "Offset must be greater than or equal to zero.";
public const string ThumbnailProviderDisabledProcessIsolation = "{0} does not implement IThumbnailFromStream and so requires DisableProcessIsolation set to true.";
public const string ThumbnailProviderInterfaceNotImplemented = "{0} must implement one or more of IThumbnailFromStream, IThumbnailFromShellObject or IThumbnailFromFile.";
}
}

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<MSBuildAllProjects Condition="'$(MSBuildVersion)' == '' Or '$(MSBuildVersion)' &lt; '16.0'">$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
<HasSharedItems>true</HasSharedItems>
<SharedGUID>50974517-2d52-4470-9f60-e37b30d1491e</SharedGUID>
</PropertyGroup>
<PropertyGroup Label="Configuration">
<Import_RootNamespace>ShellExtensions</Import_RootNamespace>
</PropertyGroup>
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)Interop\HandlerNativeMethods.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Interop\Common\ShellNativeStructs.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Resources\LocalizedMessages.cs" />
<Compile Include="$(MSBuildThisFileDirectory)StorageStream.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ThumbnailProviders\ManagedInitializationInterfaces.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ThumbnailProviders\ThumbnailProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)ThumbnailProviders\ThumbnailProviderAttribute.cs" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Label="Globals">
<ProjectGuid>50974517-2d52-4470-9f60-e37b30d1491e</ProjectGuid>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
<PropertyGroup />
<Import Project="ShellExtensions.projitems" Label="Shared" />
<Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
</Project>

View File

@ -0,0 +1,266 @@
using ShellExtensions.Resources;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
namespace ShellExtensions
{
/// <summary>
/// A wrapper for the native IStream object.
/// </summary>
public class StorageStream : Stream, IDisposable
{
private IStream _stream;
private readonly bool _isReadOnly;
internal StorageStream(IStream stream, bool readOnly)
{
_stream = stream ?? throw new ArgumentNullException(nameof(stream));
_isReadOnly = readOnly;
}
/// <summary>
/// Reads a single byte from the stream, moving the current position ahead by 1.
/// </summary>
/// <returns>A single byte from the stream, -1 if end of stream.</returns>
public override int ReadByte()
{
ThrowIfDisposed();
byte[] buffer = new byte[1];
if (Read(buffer, 0, 1) > 0)
{
return buffer[0];
}
return -1;
}
/// <summary>
/// Writes a single byte to the stream
/// </summary>
/// <param name="value">Byte to write to stream</param>
public override void WriteByte(byte value)
{
ThrowIfDisposed();
byte[] buffer = new byte[] { value };
Write(buffer, 0, 1);
}
/// <summary>
/// Gets whether the stream can be read from.
/// </summary>
public override bool CanRead => _stream != null;
/// <summary>
/// Gets whether seeking is supported by the stream.
/// </summary>
public override bool CanSeek => _stream != null;
/// <summary>
/// Gets whether the stream can be written to.
/// Always false.
/// </summary>
public override bool CanWrite => _stream != null && !_isReadOnly;
/// <summary>
/// Reads a buffer worth of bytes from the stream.
/// </summary>
/// <param name="buffer">Buffer to fill</param>
/// <param name="offset">Offset to start filling in the buffer</param>
/// <param name="count">Number of bytes to read from the stream</param>
/// <returns></returns>
public override int Read(byte[] buffer, int offset, int count)
{
ThrowIfDisposed();
if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); }
if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset), LocalizedMessages.StorageStreamOffsetLessThanZero); }
if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), LocalizedMessages.StorageStreamCountLessThanZero); }
if (offset + count > buffer.Length) { throw new ArgumentException(LocalizedMessages.StorageStreamBufferOverflow, nameof(count)); }
int bytesRead = 0;
if (count > 0)
{
IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(ulong));
try
{
if (offset == 0)
{
_stream.Read(buffer, count, ptr);
bytesRead = (int)Marshal.ReadInt64(ptr);
}
else
{
byte[] tempBuffer = new byte[count];
_stream.Read(tempBuffer, count, ptr);
bytesRead = (int)Marshal.ReadInt64(ptr);
if (bytesRead > 0)
{
Array.Copy(tempBuffer, 0, buffer, offset, bytesRead);
}
}
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
return bytesRead;
}
/// <summary>
/// Writes a buffer to the stream if able to do so.
/// </summary>
/// <param name="buffer">Buffer to write</param>
/// <param name="offset">Offset in buffer to start writing</param>
/// <param name="count">Number of bytes to write to the stream</param>
public override void Write(byte[] buffer, int offset, int count)
{
ThrowIfDisposed();
if (_isReadOnly) { throw new InvalidOperationException(LocalizedMessages.StorageStreamIsReadonly); }
if (buffer == null) { throw new ArgumentNullException(nameof(buffer)); }
if (offset < 0) { throw new ArgumentOutOfRangeException(nameof(offset), LocalizedMessages.StorageStreamOffsetLessThanZero); }
if (count < 0) { throw new ArgumentOutOfRangeException(nameof(count), LocalizedMessages.StorageStreamCountLessThanZero); }
if (offset + count > buffer.Length) { throw new ArgumentException(LocalizedMessages.StorageStreamBufferOverflow, nameof(count)); }
if (count > 0)
{
IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(ulong));
try
{
if (offset == 0)
{
_stream.Write(buffer, count, ptr);
}
else
{
byte[] tempBuffer = new byte[count];
Array.Copy(buffer, offset, tempBuffer, 0, count);
_stream.Write(tempBuffer, count, ptr);
}
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
}
/// <summary>
/// Gets the length of the IStream
/// </summary>
public override long Length
{
get
{
ThrowIfDisposed();
const int STATFLAG_NONAME = 1;
_stream.Stat(out System.Runtime.InteropServices.ComTypes.STATSTG stats, STATFLAG_NONAME);
return stats.cbSize;
}
}
/// <summary>
/// Gets or sets the current position within the underlying IStream.
/// </summary>
public override long Position
{
get
{
ThrowIfDisposed();
return Seek(0, SeekOrigin.Current);
}
set
{
ThrowIfDisposed();
Seek(value, SeekOrigin.Begin);
}
}
/// <summary>
/// Seeks within the underlying IStream.
/// </summary>
/// <param name="offset">Offset</param>
/// <param name="origin">Where to start seeking</param>
/// <returns></returns>
public override long Seek(long offset, SeekOrigin origin)
{
ThrowIfDisposed();
IntPtr ptr = Marshal.AllocCoTaskMem(sizeof(long));
try
{
_stream.Seek(offset, (int)origin, ptr);
return Marshal.ReadInt64(ptr);
}
finally
{
Marshal.FreeCoTaskMem(ptr);
}
}
/// <summary>
/// Sets the length of the stream
/// </summary>
/// <param name="value"></param>
public override void SetLength(long value)
{
ThrowIfDisposed();
_stream.SetSize(value);
}
/// <summary>
/// Commits data to be written to the stream if it is being cached.
/// </summary>
public override void Flush()
{
_stream.Commit((int)StorageStreamCommitOptions.None);
}
/// <summary>
/// Disposes the stream.
/// </summary>
/// <param name="disposing">True if called from Dispose(), false if called from finalizer.</param>
protected override void Dispose(bool disposing)
{
_stream = null;
base.Dispose(disposing);
}
private void ThrowIfDisposed() { if (_stream == null) throw new ObjectDisposedException(GetType().Name); }
}
/// <summary>
/// Options for commiting (flushing) an IStream storage stream
/// </summary>
[Flags]
internal enum StorageStreamCommitOptions
{
/// <summary>
/// Uses default options
/// </summary>
None = 0,
/// <summary>
/// Overwrite option
/// </summary>
Overwrite = 1,
/// <summary>
/// Only if current
/// </summary>
OnlyIfCurrent = 2,
/// <summary>
/// Commits to disk cache dangerously
/// </summary>
DangerouslyCommitMerelyToDiskCache = 4,
/// <summary>
/// Consolidate
/// </summary>
Consolidate = 8
}
}

View File

@ -0,0 +1,54 @@
using System.Drawing;
using System.IO;
namespace ShellExtensions
{
/// <summary>
/// This interface exposes the <see cref="ConsructBitmap"/> function for initializing the
/// Thumbnail Provider with a <typeparamref name="Stream"/>.
/// If this interfaces is not used, then the handler must opt out of process isolation.
/// This interface can be used in conjunction with the other intialization interfaces,
/// but only 1 will be accessed according to the priorities preset by the Windows Shell:
/// <typeparamref name="IThumbnailFromStream"/>
/// <typeparamref name="IThumbnailFromShellObject"/>
/// <typeparamref name="IThumbnailFromFile"/>
/// </summary>
public interface IThumbnailFromStream
{
/// <summary>
/// Provides the <typeparamref name="Stream"/> to the item from which a thumbnail should be created.
/// <remarks>Only 32bpp bitmaps support adornments.
/// While 24bpp bitmaps will be displayed they will not display adornments.
/// Additional guidelines for developing thumbnails can be found at http://msdn.microsoft.com/en-us/library/cc144115(v=VS.85).aspx
/// </remarks>
/// </summary>
/// <param name="stream">Stream to initialize the thumbnail</param>
/// <param name="sideSize">Square side dimension in which the thumbnail should fit; the thumbnail will be scaled otherwise.</param>
/// <returns></returns>
Bitmap ConstructBitmap(Stream stream, int sideSize);
}
/// <summary>
/// This interface exposes the <see cref="ConsructBitmap"/> function for initializing the
/// Thumbnail Provider with file information.
/// This interface can be used in conjunction with the other intialization interfaces,
/// but only 1 will be accessed according to the priorities preset by the Windows Shell:
/// <typeparamref name="IThumbnailFromStream"/>
/// <typeparamref name="IThumbnailFromShellObject"/>
/// <typeparamref name="IThumbnailFromFile"/>
/// </summary>
public interface IThumbnailFromFile
{
/// <summary>
/// Provides the <typeparamref name="FileInfo"/> to the item from which a thumbnail should be created.
/// <remarks>Only 32bpp bitmaps support adornments.
/// While 24bpp bitmaps will be displayed they will not display adornments.
/// Additional guidelines for developing thumbnails can be found at http://msdn.microsoft.com/en-us/library/cc144115(v=VS.85).aspx
/// </remarks>
/// </summary>
/// <param name="info">FileInfo to initialize the thumbnail</param>
/// <param name="sideSize">Square side dimension in which the thumbnail should fit; the thumbnail will be scaled otherwise.</param>
/// <returns>Generated thumbnail</returns>
Bitmap ConstructBitmap(FileInfo info, int sideSize);
}
}

View File

@ -0,0 +1,282 @@
using Microsoft.Win32;
using ShellExtensions.Interop;
using ShellExtensions.Interop.Common;
using ShellExtensions.Resources;
using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
namespace ShellExtensions
{
public abstract class ThumbnailProvider : IThumbnailProvider, ICustomQueryInterface, IDisposable, IInitializeWithStream, IInitializeWithFile
{
private Bitmap GetBitmap(int sideLength)
{
if (_stream != null && this is IThumbnailFromStream stream)
{
return stream.ConstructBitmap(_stream, sideLength);
}
if (_info != null && this is IThumbnailFromFile file)
{
return file.ConstructBitmap(_info, sideLength);
}
throw new InvalidOperationException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.ThumbnailProviderInterfaceNotImplemented,
GetType().Name));
}
public virtual ThumbnailAlphaType ThumbnailAlphaType => ThumbnailAlphaType.Unknown;
private StorageStream _stream;
private FileInfo _info;
#region IThumbnailProvider Members
void IThumbnailProvider.GetThumbnail(uint sideLength, out IntPtr hBitmap, out uint alphaType)
{
using (Bitmap map = GetBitmap((int)sideLength))
{
hBitmap = map.GetHbitmap();
}
alphaType = (uint)ThumbnailAlphaType;
}
#endregion
#region ICustomQueryInterface Members
CustomQueryInterfaceResult ICustomQueryInterface.GetInterface(ref Guid iid, out IntPtr ppv)
{
ppv = IntPtr.Zero;
// Forces COM to not use the managed (free threaded) marshaler
if (iid == HandlerNativeMethods.IMarshalGuid)
{
return CustomQueryInterfaceResult.Failed;
}
if ((iid == HandlerNativeMethods.IInitializeWithStreamGuid && !(this is IThumbnailFromStream)) ||
(iid == HandlerNativeMethods.IInitializeWithFileGuid && !(this is IThumbnailFromFile)))
{
return CustomQueryInterfaceResult.Failed;
}
return CustomQueryInterfaceResult.NotHandled;
}
#endregion
#region COM Registration
/// <summary>
/// Called when the assembly is registered via RegAsm.
/// </summary>
/// <param name="registerType">Type to be registered.</param>
[ComRegisterFunction]
private static void Register(Type registerType)
{
if (registerType != null && registerType.IsSubclassOf(typeof(ThumbnailProvider)))
{
object[] attributes = registerType.GetCustomAttributes(typeof(ThumbnailProviderAttribute), true);
if (attributes != null && attributes.Length == 1)
{
ThumbnailProviderAttribute attribute = attributes[0] as ThumbnailProviderAttribute;
ThrowIfInvalid(registerType, attribute);
RegisterThumbnailHandler(registerType.GUID.ToString("B"), attribute);
}
}
}
private static void RegisterThumbnailHandler(string guid, ThumbnailProviderAttribute attribute)
{
// set process isolation
using (RegistryKey clsidKey = Registry.ClassesRoot.OpenSubKey("CLSID"))
using (RegistryKey guidKey = clsidKey.OpenSubKey(guid, true))
{
guidKey.SetValue("DisableProcessIsolation", attribute.DisableProcessIsolation ? 1 : 0, RegistryValueKind.DWord);
using (RegistryKey inproc = guidKey.OpenSubKey("InprocServer32", true))
{
inproc.SetValue("ThreadingModel", "Apartment", RegistryValueKind.String);
}
}
// register file as an approved extension
using (RegistryKey approvedShellExtensions = Registry.LocalMachine.OpenSubKey(
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", true))
{
approvedShellExtensions.SetValue(guid, attribute.Name, RegistryValueKind.String);
}
// register extension with each extension in the list
string[] extensions = attribute.Extensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string extension in extensions)
{
using (RegistryKey extensionKey = Registry.ClassesRoot.CreateSubKey(extension)) // Create makes it writable
using (RegistryKey shellExKey = extensionKey.CreateSubKey("shellex"))
using (RegistryKey providerKey = shellExKey.CreateSubKey(HandlerNativeMethods.IThumbnailProviderGuid.ToString("B")))
{
providerKey.SetValue(null, guid, RegistryValueKind.String);
if (attribute.ThumbnailCutoff == ThumbnailCutoffSize.Square20)
{
extensionKey.DeleteValue("ThumbnailCutoff", false);
}
else
{
extensionKey.SetValue("ThumbnailCutoff", (int)attribute.ThumbnailCutoff, RegistryValueKind.DWord);
}
if (attribute.TypeOverlay != null)
{
extensionKey.SetValue("TypeOverlay", attribute.TypeOverlay, RegistryValueKind.String);
}
if (attribute.ThumbnailAdornment == ThumbnailAdornment.Default)
{
extensionKey.DeleteValue("Treatment", false);
}
else
{
extensionKey.SetValue("Treatment", (int)attribute.ThumbnailAdornment, RegistryValueKind.DWord);
}
}
}
}
/// <summary>
/// Called when the assembly is registered via RegAsm.
/// </summary>
/// <param name="registerType">Type to register.</param>
[ComUnregisterFunction]
private static void Unregister(Type registerType)
{
if (registerType != null && registerType.IsSubclassOf(typeof(ThumbnailProvider)))
{
object[] attributes = registerType.GetCustomAttributes(typeof(ThumbnailProviderAttribute), true);
if (attributes != null && attributes.Length == 1)
{
ThumbnailProviderAttribute attribute = attributes[0] as ThumbnailProviderAttribute;
UnregisterThumbnailHandler(registerType.GUID.ToString("B"), attribute);
}
}
}
private static void UnregisterThumbnailHandler(string guid, ThumbnailProviderAttribute attribute)
{
string[] extensions = attribute.Extensions.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (string extension in extensions)
{
using (RegistryKey extKey = Registry.ClassesRoot.OpenSubKey(extension, true))
using (RegistryKey shellexKey = extKey.OpenSubKey("shellex", true))
{
shellexKey.DeleteSubKey(HandlerNativeMethods.IThumbnailProviderGuid.ToString("B"), false);
extKey.DeleteValue("ThumbnailCutoff", false);
extKey.DeleteValue("TypeOverlay", false);
extKey.DeleteValue("Treatment", false); // Thumbnail adornment
}
}
using (RegistryKey approvedShellExtensions = Registry.LocalMachine.OpenSubKey(
@"SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved", true))
{
approvedShellExtensions.DeleteValue(guid, false);
}
}
private static void ThrowIfInvalid(Type type, ThumbnailProviderAttribute attribute)
{
if (attribute is null)
{
throw new ArgumentNullException(nameof(attribute));
}
var interfaces = type.GetInterfaces();
bool interfaced = interfaces.Any(x => x is IThumbnailFromStream);
if (interfaces.Any(x => x is IThumbnailFromFile))
{
// According to MSDN (http://msdn.microsoft.com/en-us/library/cc144114(v=VS.85).aspx)
// A thumbnail provider that does not implement IInitializeWithStream must opt out of
// running in the isolated process. The default behavior of the indexer opts in
// to process isolation regardless of which interfaces are implemented.
if (!interfaced && !attribute.DisableProcessIsolation)
{
throw new InvalidOperationException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.ThumbnailProviderDisabledProcessIsolation,
type.Name));
}
interfaced = true;
}
if (!interfaced)
{
throw new InvalidOperationException(
string.Format(System.Globalization.CultureInfo.InvariantCulture,
LocalizedMessages.ThumbnailProviderInterfaceNotImplemented,
type.Name));
}
}
#endregion
#region IInitializeWithStream Members
void IInitializeWithStream.Initialize(System.Runtime.InteropServices.ComTypes.IStream stream, AccessModes fileMode)
{
_stream = new StorageStream(stream, fileMode != AccessModes.ReadWrite);
}
#endregion
#region IInitializeWithFile Members
void IInitializeWithFile.Initialize(string filePath, AccessModes fileMode)
{
_info = new FileInfo(filePath);
}
#endregion
#region IDisposable Members
/// <summary>
/// Finalizer for the thumbnail provider.
/// </summary>
~ThumbnailProvider()
{
Dispose(false);
}
/// <summary>
/// Disposes the thumbnail provider.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
/// <summary>
/// Disploses the thumbnail provider.
/// </summary>
/// <param name="disposing"></param>
protected virtual void Dispose(bool disposing)
{
if (disposing && _stream != null)
{
_stream.Dispose();
}
}
#endregion
}
}

View File

@ -0,0 +1,136 @@
using System;
namespace ShellExtensions
{
/// <summary>
/// This class attribute is applied to a Thumbnail Provider to specify registration parameters
/// and aesthetic attributes.
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
public sealed class ThumbnailProviderAttribute : Attribute
{
/// <summary>
/// Creates a new instance of the attribute.
/// </summary>
/// <param name="name">Name of the provider</param>
/// <param name="extensions">Semi-colon-separated list of extensions supported by this provider.</param>
public ThumbnailProviderAttribute(string name, string extensions)
{
Name = name ?? throw new ArgumentNullException(nameof(name));
Extensions = extensions ?? throw new ArgumentNullException(nameof(extensions));
DisableProcessIsolation = false;
ThumbnailCutoff = ThumbnailCutoffSize.Square20;
TypeOverlay = null;
ThumbnailAdornment = ThumbnailAdornment.Default;
}
/// <summary>
/// Gets the name of the provider
/// </summary>
public string Name { get; private set; }
/// <summary>
/// Gets the semi-colon-separated list of extensions supported by the provider.
/// </summary>
public string Extensions { get; private set; }
// optional parameters below.
/// <summary>
/// Opts-out of running within the surrogate process DllHost.exe.
/// This will reduce robustness and security.
/// This value should be true if the provider does not implement <typeparamref name="IThumbnailFromStream"/>.
/// </summary>
// Note: The msdn documentation and property name are contradicting.
// http://msdn.microsoft.com/en-us/library/cc144118(VS.85).aspx
public bool DisableProcessIsolation { get; set; } // If true: Makes it run IN PROCESS.
/// <summary>
/// Below this size thumbnail images will not be generated - file icons will be used instead.
/// </summary>
public ThumbnailCutoffSize ThumbnailCutoff { get; set; }
/// <summary>
/// A resource reference string pointing to the icon to be used as an overlay on the bottom right of the thumbnail.
/// ex. ISVComponent.dll@,-155
/// ex. C:\Windows\System32\SampleIcon.ico
/// If an empty string is provided, no overlay will be used.
/// If the property is set to null, the default icon for the associated icon will be used as an overlay.
/// </summary>
public string TypeOverlay { get; set; }
/// <summary>
/// Specifies the <typeparamref name="ThumbnailAdornment"/> for the thumbnail.
/// <remarks>
/// Only 32bpp bitmaps support adornments.
/// While 24bpp bitmaps will be displayed, their adornments will not.
/// If an adornment is specified by the file-type's associated application,
/// the applications adornment will override the value specified in this registration.</remarks>
/// </summary>
public ThumbnailAdornment ThumbnailAdornment { get; set; }
}
/// <summary>
/// Defines the minimum thumbnail size for which thumbnails will be generated.
/// </summary>
public enum ThumbnailCutoffSize
{
/// <summary>
/// Default size of 20x20
/// </summary>
Square20 = -1, //For 20x20, you do not add any key in the registry
/// <summary>
/// Size of 32x32
/// </summary>
Square32 = 0,
/// <summary>
/// Size of 16x16
/// </summary>
Square16 = 1,
/// <summary>
/// Size of 48x48
/// </summary>
Square48 = 2,
/// <summary>
/// Size of 16x16. An alternative to Square16.
/// </summary>
Square16B = 3
}
/// <summary>
/// Adornment applied to thumbnails.
/// </summary>
public enum ThumbnailAdornment
{
/// <summary>
/// This will use the associated application's default icon as the adornment.
/// </summary>
Default = -1, // Default behaviour for no value added in registry
/// <summary>
/// No adornment
/// </summary>
None = 0,
/// <summary>
/// Drop shadow adornment
/// </summary>
DropShadow = 1,
/// <summary>
/// Photo border adornment
/// </summary>
PhotoBorder = 2,
/// <summary>
/// Video sprocket adornment
/// </summary>
VideoSprockets = 3
}
}