c# - 将系统剪贴板作为流而不是字符串读取

标签 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;
    }

最佳答案

这会将系统剪贴板写出到文本文件,而无需先将其转换为字符串,从而允许写出非常大的剪贴板而不会遇到 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;
}

关于c# - 将系统剪贴板作为流而不是字符串读取,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44102918/

相关文章:

c# - Base64 图像未显示在 asp.net mvc View 中

c# - 使用包含相同 Entity MVC Core 的 1Entity 创建 Dbo

c - 使用 C 查询自定义 DNS 服务器

algorithm - 处理来自套接字的碎片化输入的一般方法

C++:32 与 64 位流操作

c# - Entity Framework 无法删除数据库,正在使用的数据库

c# - 枚举参数在 C# 中可以是可选的吗?

delphi - 为什么 EM_SETTEXTMODE 不起作用?

c++ - 处理 WinAPI 错误返回值

javascript - Node.js 将相同的可读流传输到多个(可写)目标