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

Posted muzizongheng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了WPF输入框Paste时出错,IDataObject的GetData抛出OutOfMemoryException相关的知识,希望对你有一定的参考价值。

现象:
客户在使用过程中,在输入框粘贴时,程序崩溃

分析:
从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中发现微软也是这样处理的, 如下:
技术分享

参考:



技术分享

以上是关于WPF输入框Paste时出错,IDataObject的GetData抛出OutOfMemoryException的主要内容,如果未能解决你的问题,请参考以下文章

VBA 帮助触发与输入框相关的事件

WPF限制文本框只能输入数字

WPF 提示框确认框确认输入框

Keras:ValueError:检查输入时出错

WPF C# 输入框

WPF开发登录窗口之——添加文本输入框用户控件