次のコードで「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のせいだと思います。
クリップボードにデータを設定するには、まずそれを開く必要があります。一度に 1 つのプロセスだけがクリップボードを開くことができます。そのため、チェックすると、別のプロセスが何らかの理由でクリップボードを開いている場合、それを開こうとすると失敗します。
たまたまターミナル サービスがクリップボードを追跡しており、古いバージョンの 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);
}
この場合、SetDataObject は常に true で呼び出されることに注意してください。
内部的には、win32 API への 2 つの呼び出しがトリガーされます。1 つはデータを設定するため、もう 1 つはアプリからデータをフラッシュするためで、アプリを閉じた後に使用できるようにします。
クリップボード イベントをリッスンするいくつかのアプリ (Chrome プラグインとダウンロード マネージャー) を見てきました。最初の呼び出しがヒットするとすぐに、アプリはクリップボードを開いてデータを調べ、2 回目のフラッシュ呼び出しは失敗します。
win32 API を直接使用する独自のクリップボード クラスを作成するか、setDataObject を false で直接呼び出して、アプリの終了後にデータを保持する以外に、適切な解決策が見つかりませんでした。
ネイティブの Win32 関数 (OpenClipboard()、CloseClipboard()、および SetClipboardData()) を使用して、自分のアプリでこの問題を解決しました。
私が作ったラッパークラスの下。誰でもレビューして、それが正しいかどうか教えてください。特にマネージ コードが x64 アプリとして実行されている場合 (私はプロジェクト オプションで任意の 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;
}
}
WinForms バージョンを使用します (はい、WPF アプリケーションで WinForms を使用しても害はありません)。必要なものはすべて処理されます。
System.Windows.Forms.SetDataObject(yourText, true, 10, 100);
これは yourText をクリップボードにコピーしようとします。コピーはアプリが存在した後も残り、最大 10 回試行され、試行ごとに 100 ミリ秒待機します。