/* **************************************************************************** * * Copyright (c) Microsoft Corporation. * * This source code is subject to terms and conditions of the Apache License, Version 2.0. A * copy of the license can be found in the License.html file at the root of this distribution. If * you cannot locate the Apache License, Version 2.0, please send an email to * vspython@microsoft.com. By using this source code in any fashion, you are agreeing to be bound * by the terms of the Apache License, Version 2.0. * * You must not remove this notice, or any other, from this software. * * ***************************************************************************/ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Runtime.InteropServices; using System.Security.Permissions; using Microsoft.VisualStudio; using Microsoft.VisualStudio.OLE.Interop; using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.Project { public enum tagDVASPECT { DVASPECT_CONTENT = 1, DVASPECT_THUMBNAIL = 2, DVASPECT_ICON = 4, DVASPECT_DOCPRINT = 8 } public enum tagTYMED { TYMED_HGLOBAL = 1, TYMED_FILE = 2, TYMED_ISTREAM = 4, TYMED_ISTORAGE = 8, TYMED_GDI = 16, TYMED_MFPICT = 32, TYMED_ENHMF = 64, TYMED_NULL = 0 } public sealed class DataCacheEntry : IDisposable { #region fields /// /// Defines an object that will be a mutex for this object for synchronizing thread calls. /// private static volatile object Mutex = new object(); private FORMATETC format; private long data; private DATADIR dataDir; private bool isDisposed; #endregion #region properties public FORMATETC Format { get { return this.format; } } public long Data { get { return this.data; } } [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] public DATADIR DataDir { get { return this.dataDir; } } #endregion /// /// The IntPtr is data allocated that should be removed. It is allocated by the ProcessSelectionData method. /// public DataCacheEntry(FORMATETC fmt, IntPtr data, DATADIR dir) { this.format = fmt; this.data = (long)data; this.dataDir = dir; } #region Dispose ~DataCacheEntry() { Dispose(false); } /// /// The IDispose interface Dispose method for disposing the object determinastically. /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } /// /// The method that does the cleanup. /// /// private void Dispose(bool disposing) { // Everybody can go here. if (!this.isDisposed) { // Synchronize calls to the Dispose simulteniously. lock (Mutex) { if (disposing && this.data != 0) { Marshal.FreeHGlobal((IntPtr)this.data); this.data = 0; } this.isDisposed = true; } } } #endregion } /// /// Unfortunately System.Windows.Forms.IDataObject and /// Microsoft.VisualStudio.OLE.Interop.IDataObject are different... /// public sealed class DataObject : IDataObject { #region fields public const int DATA_S_SAMEFORMATETC = 0x00040130; EventSinkCollection map; ArrayList entries; #endregion public DataObject() { this.map = new EventSinkCollection(); this.entries = new ArrayList(); } public void SetData(FORMATETC format, IntPtr data) { this.entries.Add(new DataCacheEntry(format, data, DATADIR.DATADIR_SET)); } #region IDataObject methods int IDataObject.DAdvise(FORMATETC[] e, uint adv, IAdviseSink sink, out uint cookie) { VsUtilities.ArgumentNotNull("e", e); STATDATA sdata = new STATDATA(); sdata.ADVF = adv; sdata.FORMATETC = e[0]; sdata.pAdvSink = sink; cookie = this.map.Add(sdata); sdata.dwConnection = cookie; return 0; } void IDataObject.DUnadvise(uint cookie) { this.map.RemoveAt(cookie); } int IDataObject.EnumDAdvise(out IEnumSTATDATA e) { e = new EnumSTATDATA((IEnumerable)this.map); return 0; //?? } int IDataObject.EnumFormatEtc(uint direction, out IEnumFORMATETC penum) { penum = new EnumFORMATETC((DATADIR)direction, (IEnumerable)this.entries); return 0; } int IDataObject.GetCanonicalFormatEtc(FORMATETC[] format, FORMATETC[] fmt) { throw new System.Runtime.InteropServices.COMException("", DATA_S_SAMEFORMATETC); } void IDataObject.GetData(FORMATETC[] fmt, STGMEDIUM[] m) { STGMEDIUM retMedium = new STGMEDIUM(); if (fmt == null || fmt.Length < 1) return; foreach (DataCacheEntry e in this.entries) { if (e.Format.cfFormat == fmt[0].cfFormat /*|| fmt[0].cfFormat == publicNativeMethods.CF_HDROP*/) { retMedium.tymed = e.Format.tymed; // Caller must delete the memory. retMedium.unionmember = DragDropHelper.CopyHGlobal(new IntPtr(e.Data)); break; } } if (m != null && m.Length > 0) m[0] = retMedium; } void IDataObject.GetDataHere(FORMATETC[] fmt, STGMEDIUM[] m) { } int IDataObject.QueryGetData(FORMATETC[] fmt) { if (fmt == null || fmt.Length < 1) return VSConstants.S_FALSE; foreach (DataCacheEntry e in this.entries) { if (e.Format.cfFormat == fmt[0].cfFormat /*|| fmt[0].cfFormat == publicNativeMethods.CF_HDROP*/) return VSConstants.S_OK; } return VSConstants.S_FALSE; } void IDataObject.SetData(FORMATETC[] fmt, STGMEDIUM[] m, int fRelease) { } #endregion } [SecurityPermissionAttribute(SecurityAction.Demand, Flags = SecurityPermissionFlag.UnmanagedCode)] public static class DragDropHelper { #pragma warning disable 414 public static readonly ushort CF_VSREFPROJECTITEMS; public static readonly ushort CF_VSSTGPROJECTITEMS; public static readonly ushort CF_VSPROJECTCLIPDESCRIPTOR; #pragma warning restore 414 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline")] static DragDropHelper() { CF_VSREFPROJECTITEMS = UnsafeNativeMethods.RegisterClipboardFormat("CF_VSREFPROJECTITEMS"); CF_VSSTGPROJECTITEMS = UnsafeNativeMethods.RegisterClipboardFormat("CF_VSSTGPROJECTITEMS"); CF_VSPROJECTCLIPDESCRIPTOR = UnsafeNativeMethods.RegisterClipboardFormat("CF_PROJECTCLIPBOARDDESCRIPTOR"); } public static FORMATETC CreateFormatEtc(ushort iFormat) { FORMATETC fmt = new FORMATETC(); fmt.cfFormat = iFormat; fmt.ptd = IntPtr.Zero; fmt.dwAspect = (uint)DVASPECT.DVASPECT_CONTENT; fmt.lindex = -1; fmt.tymed = (uint)TYMED.TYMED_HGLOBAL; return fmt; } public static int QueryGetData(Microsoft.VisualStudio.OLE.Interop.IDataObject pDataObject, ref FORMATETC fmtetc) { FORMATETC[] af = new FORMATETC[1]; af[0] = fmtetc; int result = pDataObject.QueryGetData(af); if (result == VSConstants.S_OK) { fmtetc = af[0]; return VSConstants.S_OK; } return result; } public static STGMEDIUM GetData(Microsoft.VisualStudio.OLE.Interop.IDataObject pDataObject, ref FORMATETC fmtetc) { FORMATETC[] af = new FORMATETC[1]; af[0] = fmtetc; STGMEDIUM[] sm = new STGMEDIUM[1]; pDataObject.GetData(af, sm); fmtetc = af[0]; return sm[0]; } /// /// Retrives data from a VS format. /// public static List GetDroppedFiles(ushort format, Microsoft.VisualStudio.OLE.Interop.IDataObject dataObject, out DropDataType ddt) { ddt = DropDataType.None; List droppedFiles = new List(); // try HDROP FORMATETC fmtetc = CreateFormatEtc(format); if (QueryGetData(dataObject, ref fmtetc) == VSConstants.S_OK) { STGMEDIUM stgmedium = DragDropHelper.GetData(dataObject, ref fmtetc); if (stgmedium.tymed == (uint)TYMED.TYMED_HGLOBAL) { // We are releasing the cloned hglobal here. IntPtr dropInfoHandle = stgmedium.unionmember; if (dropInfoHandle != IntPtr.Zero) { ddt = DropDataType.Shell; try { uint numFiles = UnsafeNativeMethods.DragQueryFile(dropInfoHandle, 0xFFFFFFFF, null, 0); // We are a directory based project thus a projref string is placed on the clipboard. // We assign the maximum length of a projref string. // The format of a projref is : || uint lenght = (uint)Guid.Empty.ToString().Length + 2 * NativeMethods.MAX_PATH + 2; char[] moniker = new char[lenght + 1]; for (uint fileIndex = 0; fileIndex < numFiles; fileIndex++) { uint queryFileLength = UnsafeNativeMethods.DragQueryFile(dropInfoHandle, fileIndex, moniker, lenght); string filename = new String(moniker, 0, (int)queryFileLength); droppedFiles.Add(filename); } } finally { Marshal.FreeHGlobal(dropInfoHandle); } } } } return droppedFiles; } public static string GetSourceProjectPath(Microsoft.VisualStudio.OLE.Interop.IDataObject dataObject) { string projectPath = null; FORMATETC fmtetc = CreateFormatEtc(CF_VSPROJECTCLIPDESCRIPTOR); if (QueryGetData(dataObject, ref fmtetc) == VSConstants.S_OK) { STGMEDIUM stgmedium = DragDropHelper.GetData(dataObject, ref fmtetc); if (stgmedium.tymed == (uint)TYMED.TYMED_HGLOBAL) { // We are releasing the cloned hglobal here. IntPtr dropInfoHandle = stgmedium.unionmember; if (dropInfoHandle != IntPtr.Zero) { try { string path = GetData(dropInfoHandle); // Clone the path that we can release our memory. if (!String.IsNullOrEmpty(path)) { projectPath = String.Copy(path); } } finally { Marshal.FreeHGlobal(dropInfoHandle); } } } } return projectPath; } /// /// Returns the data packed after the DROPFILES structure. /// /// /// public static string GetData(IntPtr dropHandle) { IntPtr data = UnsafeNativeMethods.GlobalLock(dropHandle); try { _DROPFILES df = (_DROPFILES)Marshal.PtrToStructure(data, typeof(_DROPFILES)); if (df.fWide != 0) { IntPtr pdata = new IntPtr((long)data + df.pFiles); return Marshal.PtrToStringUni(pdata); } } finally { if (data != null) { UnsafeNativeMethods.GlobalUnLock(data); } } return null; } public static IntPtr CopyHGlobal(IntPtr data) { IntPtr src = UnsafeNativeMethods.GlobalLock(data); int size = UnsafeNativeMethods.GlobalSize(data); IntPtr ptr = Marshal.AllocHGlobal(size); IntPtr buffer = UnsafeNativeMethods.GlobalLock(ptr); try { for (int i = 0; i < size; i++) { byte val = Marshal.ReadByte(new IntPtr((long)src + i)); Marshal.WriteByte(new IntPtr((long)buffer + i), val); } } finally { if (buffer != IntPtr.Zero) { UnsafeNativeMethods.GlobalUnLock(buffer); } if (src != IntPtr.Zero) { UnsafeNativeMethods.GlobalUnLock(src); } } return ptr; } public static void CopyStringToHGlobal(string s, IntPtr data, int bufferSize) { Int16 nullTerminator = 0; int dwSize = Marshal.SizeOf(nullTerminator); if ((s.Length + 1) * Marshal.SizeOf(s[0]) > bufferSize) throw new System.IO.InternalBufferOverflowException(); // IntPtr memory already locked... for (int i = 0, len = s.Length; i < len; i++) { Marshal.WriteInt16(data, i * dwSize, s[i]); } // NULL terminate it Marshal.WriteInt16(new IntPtr((long)data + (s.Length * dwSize)), nullTerminator); } } // end of dragdrophelper public class EnumSTATDATA : IEnumSTATDATA { IEnumerable i; IEnumerator e; public EnumSTATDATA(IEnumerable i) { this.i = i; this.e = i.GetEnumerator(); } void IEnumSTATDATA.Clone(out IEnumSTATDATA clone) { clone = new EnumSTATDATA(i); } int IEnumSTATDATA.Next(uint celt, STATDATA[] d, out uint fetched) { uint rc = 0; //uint size = (fetched != null) ? fetched[0] : 0; for (uint i = 0; i < celt; i++) { if (e.MoveNext()) { STATDATA sdata = (STATDATA)e.Current; rc++; if (d != null && d.Length > i) { d[i] = sdata; } } } fetched = rc; return 0; } int IEnumSTATDATA.Reset() { e.Reset(); return 0; } int IEnumSTATDATA.Skip(uint celt) { for (uint i = 0; i < celt; i++) { e.MoveNext(); } return 0; } } public class EnumFORMATETC : IEnumFORMATETC { IEnumerable cache; // of DataCacheEntrys. DATADIR dir; IEnumerator e; public EnumFORMATETC(DATADIR dir, IEnumerable cache) { this.cache = cache; this.dir = dir; e = cache.GetEnumerator(); } void IEnumFORMATETC.Clone(out IEnumFORMATETC clone) { clone = new EnumFORMATETC(dir, cache); } int IEnumFORMATETC.Next(uint celt, FORMATETC[] d, uint[] fetched) { uint rc = 0; //uint size = (fetched != null) ? fetched[0] : 0; for (uint i = 0; i < celt; i++) { if (e.MoveNext()) { DataCacheEntry entry = (DataCacheEntry)e.Current; rc++; if (d != null && d.Length > i) { d[i] = entry.Format; } } else { return VSConstants.S_FALSE; } } if (fetched != null && fetched.Length > 0) fetched[0] = rc; return VSConstants.S_OK; } int IEnumFORMATETC.Reset() { e.Reset(); return 0; } int IEnumFORMATETC.Skip(uint celt) { for (uint i = 0; i < celt; i++) { e.MoveNext(); } return 0; } } }