47288


Windows
10でPowershellやC#でクリップボードにテキストを転送すると、`要求されたクリップボード操作に成功しませんでした。`例外が発生することがあります。  
例外は発生するもののテキストはクリップボードに転送されています。

この現象はどのような要因が発生原因と考えられるでしょうか。また、原因調査はどのように行うべきでしょうか。  
(空のcatchによる例外スキップ以外で)コーディングや設定による解決方法はあるでしょうか。

例外発生のトリガーになりそうな要因は不明です。  
クリップボードを占有するアプリケーションや、クリップボード履歴ソフトなどは使用していないはずです。  
PCを立ち上げて作業を続けていると突然発生することが多いです。  
1度例外が発生すると、それ以降は再起動するまで上記の例外が頻発します。  
長時間Excelを立ち上げていると発生率が高くなる気がしますが、統計は取っていません。

[本家SOの類似質問](https://stackoverflow.com/questions/6583642/determine-which-
process-is-locking-the-
clipboard)の回答を参考に、クリップボードを使用しているプロセス名を調べたところ、`Idle`と表示されました。

**Powershellサンプルコードと実行時出力**

[code]

    PS C:\> "hoge" | Set-Clipboard
    Set-Clipboard : 要求されたクリップボード操作に成功しませんでした。
    発生場所 行:1 文字:10
    + "hoge" | Set-Clipboard
    +          ~~~~~~~~~~~~~
        + CategoryInfo          : NotSpecified: (:) [Set-Clipboard], ExternalException
        + FullyQualifiedErrorId : System.Runtime.InteropServices.ExternalException,Microsoft.PowerShell.Commands.SetClipboardCommand
    
[/code]

**C#サンプルコード**

[code]

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace ConsoleApplication7
    {
        class Program
        {
            [STAThread]
            static void Main(string[] args)
            {
                var p = GetProcessLockingClipboard();
                if (p != null)
                {
                    Console.WriteLine(p.ProcessName); // "Idle"が出力される
                }
                try
                {
                    Clipboard.SetText("fuga");
                }catch(ExternalException e)
                {
                    Console.WriteLine(e);
                }
            }
    
            [DllImport("user32.dll", SetLastError = true)]
            static extern IntPtr GetOpenClipboardWindow();
    
            [DllImport("user32.dll", SetLastError = true)]
            static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
    
            private static Process GetProcessLockingClipboard()
            {
                int processId;
                GetWindowThreadProcessId(GetOpenClipboardWindow(), out processId);
    
                return Process.GetProcessById(processId);
            }
        }
    }
    
[/code]

**C#実行時出力**

[code]

    Idle
    System.Runtime.InteropServices.ExternalException (0x800401D0): 要求されたクリップボード操作に成功しませ んでした。
       場所 System.Windows.Forms.Clipboard.ThrowIfFailed(Int32 hr)
       場所 System.Windows.Forms.Clipboard.SetDataObject(Object data, Boolean copy, Int32 retryTimes, Int32 retryDelay)
       場所 System.Windows.Forms.Clipboard.SetText(String text, TextDataFormat format)
       場所 System.Windows.Forms.Clipboard.SetText(String text)
       場所 ConsoleApplication7.Program.Main(String[] args) 場所 c:\users\payaneco\documents\visual studio 2015\Projects\ConsoleApplication7\ConsoleApplication7\Program.cs:行 20
    
[/code]


CC BY-SA 4.0
2018-08-06T03:08:13.877
47269
2018-08-06T22:30:29.637
9820
question
c# powershell
PowershellやC#からクリップボード転送時にExternalExceptionが頻発する原因と対策


[System.Windows.Forms.Clipboard
のソースコード](https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Clipboard.cs)を確認してみると、  
[OleSetClipboard](https://docs.microsoft.com/en-
us/windows/desktop/api/ole2/nf-
ole2-olesetclipboard)と[OleFlushClipboard](https://docs.microsoft.com/en-
us/windows/desktop/api/ole2/nf-ole2-oleflushclipboard)というOle32.dllのWin32
APIを実行していることがわかります。

これらの関数で戻り値0x800401D0は、CLIPBRD_E_CANT_OPENが対応しています。  
これは内部で[OpenClipboard](https://msdn.microsoft.com/ja-
jp/library/cc430068.aspx)の実行に失敗したことを意味します。  
この関数が失敗するパターンは、他の誰かがクリップボードを開いているときです。

おそらく他のアプリケーションで、  
ちょうどクリップボードの確認をするような処理が発生したのだと思います。  
これは意外と頻繁に発生します。

そこで System.Windows.Forms.Clipboard では、  
裏でこっそりリトライしています。  
SetText()なら100ms間隔で10回リトライしています。

直接 SetDataObject() を呼び出せば、このリトライ回数を調整することができます。  
例えばリトライ回数を15回に増やすのであれば、

[code]

    Clipboard.SetDataObject("fuga", true, 15, 100);
    
[/code]

というような書き方が可能です。  
環境に合わせて色々と調整してみてはいかがでしょうか。


CC BY-SA 4.0
2018-08-06T13:51:26.617
47288
2018-08-06T22:30:29.637
2018-08-06T22:30:29.637
70
70
47269
answer