tjsmith
tjsmith

Reputation: 749

Read system clipboard as stream instead of string

If I have a massive amount of text on the system clipboard (e.g. 150MB text file), I'd like to be able to read the system clipboard from a stream as Unicode text so as to avoid an OutOfMemoryException. Is this at all possible by tweaking the pinvoke example below?

For these very large clipboards, Clipboard.GetText(TextDataFormat.UnicodeText) will return an empty string without throwing an exception.

Alternatively, if I use pinvoke like the example from here, I will get an 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;
    }

Upvotes: 3

Views: 1335

Answers (1)

tjsmith
tjsmith

Reputation: 749

This will write out the system clipboard to a text file without first converting it to a string, allowing very large clipboards to be written out without encountering an OutOfMemoryException. It requires that the Visual Studio project be built with the /unsafe flag.

[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(hGlobal);
        }
    }
    catch
    {
        return false;
    }
    finally
    {
        try
        {
            CloseClipboard();
        }
        catch
        {
            //ignore
        }
    }
    return true;
}

Upvotes: 5

Related Questions