WPF输入框Paste时出错,IDataObject的GetData抛出OutOfMemoryException

来源:互联网 发布:wps数据透视表教程视频 编辑:程序博客网 时间:2024/05/04 14:38
现象:
客户在使用过程中,在输入框粘贴时,程序崩溃

分析:
从dump和log看到是:
Insufficient memory to continue the execution of the program.
   at System.Runtime.InteropServices.ComTypes.IDataObject.GetData(FORMATETC& format, STGMEDIUM& medium)
   at System.Windows.DataObject.OleConverter.GetDataInner(FORMATETC& formatetc, STGMEDIUM& medium)
   at System.Windows.DataObject.OleConverter.GetDataFromOleHGLOBAL(String format, DVASPECT aspect, Int32 index)
   at System.Windows.DataObject.OleConverter.GetDataFromBoundOleDataObject(String format, DVASPECT aspect, Int32 index)
   at System.Windows.DataObject.OleConverter.GetData(String format, Boole     1448086091814

调查:
我们的TextBox实现了Behavior,其中需要对粘贴的东西进行校验, 因此注册了Paste事件
DataObject.AddPastingHandler (this. AssociatedObject, OnClipboardPaste );

OnClipboardPaste方法中获取剪贴板内容如下
  if ( e.SourceDataObject .GetDataPresent( DataFormats.UnicodeText , false))
                {
                    text = e .SourceDataObject. GetData(DataFormats .UnicodeText) as string ;
                }

查看各种资料,有人说是SetData的类没有加[Serializable]标记, 如:http://stackoverflow.com/questions/6999142/wpf-insufficient-memory-when-doing-copy-paste-vs-drag-drop-with-view-model-dat

但我们用的就是普通的TextBox,没有添加特性,只是把剪贴板的内容获取到转为字符串。 看来只能从别的地方着手。

刚才在Excel表中测试了下, 如果把excel中的某个表头Copy后, Paste到TextBox,报出同样的错误。 




下面是相关源码:
 [SecurityCritical]    void System.Runtime.InteropServices.ComTypes.IDataObject.GetData(ref FORMATETC formatetc, out STGMEDIUM medium)    {      if (this ._innerData is DataObject.OleConverter)      {        ((DataObject.OleConverter) this._innerData).OleDataObject.GetData(ref formatetc, out medium);      }      else      {        int num = -2147221399;        medium = new STGMEDIUM();        if (this .GetTymedUseable(formatetc.tymed))        {          if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) != TYMED.TYMED_NULL)          {            medium.tymed = TYMED.TYMED_HGLOBAL;            medium.unionmember = DataObject.Win32GlobalAlloc(8258, (IntPtr) 1);            num = this.OleGetDataUnrestricted(ref formatetc, ref medium, false);            if (MS.Win32.NativeMethods.Failed(num))              DataObject.Win32GlobalFree( new HandleRef((object ) this, medium.unionmember));          }          else if ((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_NULL)          {            if (SecurityHelper.CheckUnmanagedCodePermission())            {              medium.tymed = TYMED.TYMED_ISTREAM;              System.Runtime.InteropServices.ComTypes.IStream istream = (System.Runtime.InteropServices.ComTypes.IStream) null ;              num = DataObject.Win32CreateStreamOnHGlobal(IntPtr.Zero, true, ref istream);              if (MS.Win32.NativeMethods.Succeeded(num))              {                medium.unionmember = Marshal.GetComInterfaceForObject(( object) istream, typeof (System.Runtime.InteropServices.ComTypes.IStream));                Marshal.ReleaseComObject(( object) istream);                num = this.OleGetDataUnrestricted(ref formatetc, ref medium, false);                if (MS.Win32.NativeMethods.Failed(num))                  Marshal.Release(medium.unionmember);              }            }            else              num = -2147467259;          }          else          {            medium.tymed = formatetc.tymed;            num = this.OleGetDataUnrestricted(ref formatetc, ref medium, false);          }        }        if (!MS.Win32.NativeMethods.Failed(num))          return;        medium.unionmember = IntPtr.Zero;        Marshal.ThrowExceptionForHR(num);      }    } [SecurityCritical]      private void GetDataInner(ref FORMATETC formatetc, out STGMEDIUM medium)      {        new SecurityPermission(SecurityPermissionFlag.UnmanagedCode).Assert();        try        {          this._innerData.GetData(ref formatetc, out medium);        }        finally        {          CodeAccessPermission.RevertAssert();        }      } [SecurityCritical]      private object GetDataFromOleHGLOBAL(string format, DVASPECT aspect, int index)      {        FORMATETC formatetc = new FORMATETC();        formatetc.cfFormat = ( short) DataFormats.GetDataFormat(format).Id;        formatetc.dwAspect = aspect;        formatetc.lindex = index;        formatetc.tymed = TYMED.TYMED_HGLOBAL;        object obj = (object ) null;        if (this .QueryGetDataInner(ref formatetc) == 0)        {          STGMEDIUM medium;          this.GetDataInner(ref formatetc, out medium);          try          {            if (medium.unionmember != IntPtr.Zero)            {              if (medium.tymed == TYMED.TYMED_HGLOBAL)                obj = this.GetDataFromHGLOBAL(format, medium.unionmember);            }          }          finally          {            MS.Win32.UnsafeNativeMethods.ReleaseStgMedium( ref medium);          }        }        return obj;      }[SecurityCritical]    private int OleGetDataUnrestricted(ref FORMATETC formatetc, ref STGMEDIUM medium, bool doNotReallocate)    {      if (!(this ._innerData is DataObject.OleConverter))        return this .GetDataIntoOleStructs(ref formatetc, ref medium, doNotReallocate);      ((DataObject.OleConverter) this._innerData).OleDataObject.GetDataHere(ref formatetc, ref medium);      return 0;    } [SecurityCritical]    private int GetDataIntoOleStructs(ref FORMATETC formatetc, ref STGMEDIUM medium, bool doNotReallocate)    {      int num = -2147221399;      if (this .GetTymedUseable(formatetc.tymed) && this.GetTymedUseable(medium.tymed))      {        string name = DataFormats.GetDataFormat((int) formatetc.cfFormat).Name;        num = -2147221404;        if (this .GetDataPresent(name))        {          object data = this .GetData(name);          num = -2147221399;          if ((formatetc.tymed & TYMED.TYMED_HGLOBAL) != TYMED.TYMED_NULL)            num = this.GetDataIntoOleStructsByTypeMedimHGlobal(name, data, ref medium, doNotReallocate);          else if ((formatetc.tymed & TYMED.TYMED_GDI) != TYMED.TYMED_NULL)            num = this.GetDataIntoOleStructsByTypeMediumGDI(name, data, ref medium);          else if ((formatetc.tymed & TYMED.TYMED_ENHMF) != TYMED.TYMED_NULL)            num = this.GetDataIntoOleStructsByTypeMediumEnhancedMetaFile(name, data, ref medium);          else if ((formatetc.tymed & TYMED.TYMED_ISTREAM) != TYMED.TYMED_NULL)            num = this.GetDataIntoOleStructsByTypeMedimIStream(name, data, ref medium);        }      }      return num;    } [SecurityCritical]    private int GetDataIntoOleStructsByTypeMedimHGlobal(string format, object data, ref STGMEDIUM medium, bool doNotReallocate)    {      int num;      if (data is Stream)        num = this.SaveStreamToHandle(medium.unionmember, (Stream) data, doNotReallocate);      else if (DataObject.IsFormatEqual(format, DataFormats.Html) || DataObject.IsFormatEqual(format, DataFormats.Xaml))        num = this.SaveStringToHandleAsUtf8(medium.unionmember, data.ToString(), doNotReallocate);      else if (DataObject.IsFormatEqual(format, DataFormats.Text) || DataObject.IsFormatEqual(format, DataFormats.Rtf) || (DataObject.IsFormatEqual(format, DataFormats.OemText) || DataObject.IsFormatEqual(format, DataFormats.CommaSeparatedValue)))        num = this.SaveStringToHandle(medium.unionmember, data.ToString(), false, doNotReallocate);      else if (DataObject.IsFormatEqual(format, DataFormats.UnicodeText) || DataObject.IsFormatEqual(format, DataFormats.ApplicationTrust))        num = this.SaveStringToHandle(medium.unionmember, data.ToString(), true, doNotReallocate);      else if (DataObject.IsFormatEqual(format, DataFormats.FileDrop))        num = this.SaveFileListToHandle(medium.unionmember, (string[]) data, doNotReallocate);      else if (DataObject.IsFormatEqual(format, DataFormats.FileName))      {        string[] strArray = (string []) data;        num = this.SaveStringToHandle(medium.unionmember, strArray[0], false, doNotReallocate);      }      else if (DataObject.IsFormatEqual(format, DataFormats.FileNameW))      {        string[] strArray = (string []) data;        num = this.SaveStringToHandle(medium.unionmember, strArray[0], true, doNotReallocate);      }      else        num = !DataObject.IsFormatEqual(format, DataFormats.Dib) || !SystemDrawingHelper.IsImage(data) ? (!DataObject.IsFormatEqual(format, typeof (BitmapSource).FullName) ? (!DataObject.IsFormatEqual(format, "System.Drawing.Bitmap" ) ? (DataObject.IsFormatEqual(format, DataFormats.EnhancedMetafile) || SystemDrawingHelper.IsMetafile(data) ? -2147221399 : (DataObject.IsFormatEqual(format, DataFormats.Serializable) || data is ISerializable || data != null && data.GetType().IsSerializable ? this.SaveObjectToHandle(medium.unionmember, data, doNotReallocate) : -2147221399)) : this.SaveSystemDrawingBitmapToHandle(medium.unionmember, data, doNotReallocate)) : this.SaveSystemBitmapSourceToHandle(medium.unionmember, data, doNotReallocate)) : -2147221399;      if (num == 0)        medium.tymed = TYMED.TYMED_HGLOBAL;      return num;    }  [SecurityCritical]    private int SaveStringToHandle(IntPtr handle, string str, bool unicode, bool doNotReallocate)    {      if (handle == IntPtr.Zero)        return -2147024809;      if (unicode)      {        int minimumByteCount = str.Length * 2 + 2;        int hr = this .EnsureMemoryCapacity(ref handle, minimumByteCount, doNotReallocate);        if (MS.Win32.NativeMethods.Failed(hr))          return hr;        IntPtr pdst = DataObject.Win32GlobalLock( new HandleRef((object ) this, handle));        try        {          char[] psrc = str.ToCharArray(0, str.Length);          MS.Win32.UnsafeNativeMethods.CopyMemoryW(pdst, psrc, psrc.Length * 2);          Marshal.Copy( new char [1], 0, (IntPtr) ((long) pdst + ( long) psrc.Length * 2L), 1);        }        finally        {          DataObject.Win32GlobalUnlock( new HandleRef((object ) this, handle));        }      }      else      {        int cb = str.Length <= 0 ? 0 : DataObject.Win32WideCharToMultiByte(str, str.Length, (byte[]) null, 0);        byte[] numArray = new byte[cb];        if (cb > 0)          DataObject.Win32WideCharToMultiByte(str, str.Length, numArray, numArray.Length);        int hr = this .EnsureMemoryCapacity(ref handle, cb + 1, doNotReallocate);        if (MS.Win32.NativeMethods.Failed(hr))          return hr;        IntPtr pdst = DataObject.Win32GlobalLock( new HandleRef((object ) this, handle));        try        {          MS.Win32.UnsafeNativeMethods.CopyMemory(pdst, numArray, cb);          Marshal.Copy( new byte [1], 0, (IntPtr) ((long) pdst + ( long) cb), 1);        }        finally        {          DataObject.Win32GlobalUnlock( new HandleRef((object ) this, handle));        }      }      return 0;    } [SecurityCritical]    private int EnsureMemoryCapacity(ref IntPtr handle, int minimumByteCount, bool doNotReallocate)    {      int num = 0;      if (doNotReallocate)      {        if (MS.Win32.NativeMethods.IntPtrToInt32(DataObject.Win32GlobalSize(new HandleRef(( object) this , handle))) < minimumByteCount)        {          handle = IntPtr.Zero;          num = -2147286928;        }      }      else      {        handle = DataObject.Win32GlobalReAlloc( new HandleRef((object ) this, handle), (IntPtr) minimumByteCount, 8258);        if (handle == IntPtr.Zero)          num = -2147024882;      }      return num;    } [SecurityCritical]    internal static IntPtr Win32GlobalReAlloc(HandleRef handle, IntPtr bytes, int flags)    {      IntPtr num = MS.Win32.UnsafeNativeMethods.GlobalReAlloc(handle, bytes, flags);      int lastWin32Error = Marshal.GetLastWin32Error();      if (num == IntPtr.Zero)        throw new Win32Exception(lastWin32Error);      return num;    }




结论:
源码中很清晰的看出
1. 执行DataObject.Win32GlobalReAlloc失败,抛出了 -2147024882即内存不足的异常。
2.而它失败的原因就是minimumByteCount太长,minimumByteCount是data.ToString()转换后的长度计算出来的
SaveStringToHandle(medium.unionmember, data.ToString()true, doNotReallocate);
  int minimumByteCount = str.Length * 2 + 2;
3.剪贴板中的data这个object转为string的length太长,导致申请堆失败,抛出OutOfMemoryException

(win32中用剪贴板复制一般都会用到GlobalAlloc,SetClipboardData,GlobalFree等api,有兴趣的自己去了解。)

解决方法:
从源码中看,目前只能在调用GetData方法时try-catch,捕获OutOfMemoryException异常。

我在PresentationFramework.dll中的System.Windows.Documents.TextEditorCopyPaste中发现微软也是这样处理的, 如下:


参考:
 https://msdn.microsoft.com/en-us/library/windows/desktop/aa366590(v=vs.85).aspx
 https://msdn.microsoft.com/en-us/library/windows/desktop/aa366574(v=vs.85).aspx
 https://msdn.microsoft.com/zh-cn/library/system.windows.dataobject(v=vs.110).aspx
 http://referencesource.microsoft.com/#PresentationFramework/src/Framework/System/windows/Documents/TextEditorCopyPaste.cs,956





2 0