为什么以下代码有时会导致内容为"CLIPBRD_E_CANT_OPEN"的异常:
Clipboard.SetText(str);
这通常发生在第一次在应用程序中使用剪贴板而不是之后.
这是由终端服务剪贴板中的错误/功能(以及可能的其他内容)和剪贴板的.NET实现引起的.打开剪贴板的延迟会导致错误,错误通常会在几毫秒内完成.
解决方案是在循环内多次尝试并在两者之间休眠.
for (int i = 0; i < 10; i++) { try { Clipboard.SetText(str); return; } catch { } System.Threading.Thread.Sleep(10); }
实际上,我认为这是Win32 API的错误.
要在剪贴板中设置数据,您必须先打开它.只有一个进程可以一次打开剪贴板.因此,当您检查时,如果另一个进程因任何原因打开了剪贴板,则尝试打开它将失败.
它恰好是终端服务跟踪剪贴板,在旧版本的Windows(Vista之前),你必须打开剪贴板,看看里面是什么......最终会阻止你.唯一的解决方案是等到终端服务关闭剪贴板并再试一次.
重要的是要意识到这不是特定于终端服务,但它可以发生任何事情.在Win32中使用剪贴板是一个巨大的竞争条件.但是,由于设计你只应该用剪贴板来回应用户输入,这通常不会出现问题.
我知道这个问题很老,但问题仍然存在.如前所述,当系统剪贴板被另一个进程阻止时,会发生此异常.不幸的是,有许多剪切工具,截图程序和文件复制工具可以阻止Windows剪贴板.因此,每当您尝试使用Clipboard.SetText(str)
PC上安装此类工具时,您将获得异常.
解:
从不使用
Clipboard.SetText(str);
改用
Clipboard.SetDataObject(str);
实际上可能还有另一个问题.框架调用(WPF和winform风格)到这样的代码(代码来自反射器):
private static void SetDataInternal(string format, object data) { bool flag; if (IsDataFormatAutoConvert(format)) { flag = true; } else { flag = false; } IDataObject obj2 = new DataObject(); obj2.SetData(format, data, flag); SetDataObject(obj2, true); }
请注意,在这种情况下,始终使用true调用SetDataObject.
在内部触发对win32 api的两次调用,一次设置数据,一次从应用程序中刷新,以便在应用程序关闭后可用.
我见过几个听取剪贴板事件的应用程序(一些chrome插件和一个下载管理器).一旦第一次呼叫命中,应用程序将打开剪贴板查看数据,第二次调用将失败.
除了编写我自己的使用直接win32 API的剪贴板类或直接使用false调用setDataObject以在应用程序关闭后保留数据之外,还没有找到一个好的解决方案.
我使用本机Win32函数为我自己的应用程序解决了这个问题:OpenClipboard(),CloseClipboard()和SetClipboardData().
在我制作的包装类之下.任何人都可以请审查,并判断它是否正确.特别是当托管代码作为x64 app运行时(我在项目选项中使用Any CPU).当我从x64应用程序链接到x86库时会发生什么?
谢谢!
这是代码:
public static class ClipboardNative { [DllImport("user32.dll")] private static extern bool OpenClipboard(IntPtr hWndNewOwner); [DllImport("user32.dll")] private static extern bool CloseClipboard(); [DllImport("user32.dll")] private static extern bool SetClipboardData(uint uFormat, IntPtr data); private const uint CF_UNICODETEXT = 13; public static bool CopyTextToClipboard(string text) { if (!OpenClipboard(IntPtr.Zero)){ return false; } var global = Marshal.StringToHGlobalUni(text); SetClipboardData(CF_UNICODETEXT, global); CloseClipboard(); //------------------------------------------- // Not sure, but it looks like we do not need // to free HGLOBAL because Clipboard is now // responsible for the copied data. (?) // // Otherwise the second call will crash // the app with a Win32 exception // inside OpenClipboard() function //------------------------------------------- // Marshal.FreeHGlobal(global); return true; } }