将系统剪贴板作为流而不是字符串

时间:2017-05-21 23:58:19

标签: c# winapi stream pinvoke clipboard

如果我在系统剪贴板上有大量文本(例如150MB文本文件),我希望能够从流中读取系统剪贴板作为Unicode文本,以避免OutOfMemoryException。这可以通过调整下面的pinvoke示例来实现吗?

对于这些非常大的剪贴板,Clipboard.GetText(TextDataFormat.UnicodeText)将返回一个空字符串而不会抛出异常。

或者,如果我像这里的示例一样使用pinvoke,我将得到一个OutOfMemoryException http://komalmangal.blogspot.ca/2016/04/how-to-get-clipboard-data-and-its-size.html

    [DllImport("user32.dll")]
    static extern IntPtr GetClipboardData(uint uFormat);
    [DllImport("user32.dll")]
    static extern bool IsClipboardFormatAvailable(uint format);
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool OpenClipboard(IntPtr hWndNewOwner);
    [DllImport("user32.dll", SetLastError = true)]
    static extern bool CloseClipboard();
    [DllImport("kernel32.dll")]
    static extern IntPtr GlobalLock(IntPtr hMem);
    [DllImport("kernel32.dll")]
    static extern bool GlobalUnlock(IntPtr hMem);

    const uint CF_UNICODETEXT = 13;
    public static string GetText()
    {
        if (!IsClipboardFormatAvailable(CF_UNICODETEXT))
            return null;
        if (!OpenClipboard(IntPtr.Zero))
            return null;

        string data = null;
        var hGlobal = GetClipboardData(CF_UNICODETEXT);
        if (hGlobal != IntPtr.Zero)
        {
            var lpwcstr = GlobalLock(hGlobal);
            if (lpwcstr != IntPtr.Zero)
            {
                data = Marshal.PtrToStringUni(lpwcstr);
                GlobalUnlock(lpwcstr);
            }
        }
        CloseClipboard();

        return data;
    }

1 个答案:

答案 0 :(得分:5)

这会将系统剪贴板写入文本文件,而无需先将其转换为字符串,从而可以在不遇到OutOfMemoryException的情况下写出非常大的剪贴板。它要求使用 / unsafe 标志构建Visual Studio项目。

[DllImport("user32.dll")]
private static extern IntPtr GetClipboardData(uint uFormat);
[DllImport("user32.dll")]
private static extern bool IsClipboardFormatAvailable(uint format);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool OpenClipboard(IntPtr hWndNewOwner);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool CloseClipboard();
[DllImport("kernel32.dll")]
private static extern IntPtr GlobalLock(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern bool GlobalUnlock(IntPtr hMem);
[DllImport("kernel32.dll")]
private static extern UIntPtr GlobalSize(IntPtr hMem);
private const uint CF_UNICODETEXT = 13;

//Write the clipboard to a text file without having to first convert it to a string.
//This avoids OutOfMemoryException for large clipboards and is faster than other methods
public static bool WriteClipboardTextToFile(string filename)
{
    try
    {
        if (!IsClipboardFormatAvailable(CF_UNICODETEXT) || !OpenClipboard(IntPtr.Zero))
            return false;
    }
    catch
    {
        return false;
    }

    try
    {
        var hGlobal = GetClipboardData(CF_UNICODETEXT);
        if (hGlobal == IntPtr.Zero)
            return false;

        var lpwcstr = GlobalLock(hGlobal);
        if (lpwcstr == IntPtr.Zero)
            return false;

        try
        {
            long length = (long)GlobalSize(lpwcstr);
            Stream stream;
            unsafe
            {
                stream = new UnmanagedMemoryStream((byte*)lpwcstr, length);
            }

            const int bufSize = 4096;
            var buffer = new char[bufSize];
            using (var sw = new StreamWriter(new FileStream(filename, FileMode.Create, FileAccess.Write), Encoding.UTF8))
            {
                //Clipboard text is in Encoding.Unicode == UTF-16LE
                using (var sr = new StreamReader(stream, Encoding.Unicode))
                {
                    int charCount;
                    while (!sr.EndOfStream && (charCount = sr.ReadBlock(buffer, 0, bufSize)) > 0)
                    {
                        if (sr.EndOfStream && buffer[charCount - 1] == '\0')
                            sw.Write(buffer, 0, charCount - 1); //don't write out null terminator
                        else
                            sw.Write(buffer, 0, charCount);
                    }
                }
            }
        }
        finally
        {
            GlobalUnlock(lpwcstr);
        }
    }
    catch
    {
        return false;
    }
    finally
    {
        try
        {
            CloseClipboard();
        }
        catch
        {
            //ignore
        }
    }
    return true;
}