#if THUMBNAIL 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 /// /// Called when the assembly is registered via RegAsm. /// /// Type to be registered. [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); } } } } /// /// Called when the assembly is registered via RegAsm. /// /// Type to register. [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 == typeof(IThumbnailFromStream)); if (interfaces.Any(x => x == typeof(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 /// /// Finalizer for the thumbnail provider. /// ~ThumbnailProvider() { Dispose(false); } /// /// Disposes the thumbnail provider. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// Disploses the thumbnail provider. /// /// protected virtual void Dispose(bool disposing) { if (disposing && _stream != null) { _stream.Dispose(); } } #endregion } } #endif