# Delphi と最近の Windows のクリップボード事情 --- tags: Delphi programming Pascal embarcadero objectpascal created_at: 2021-04-21 updated_at: 2021-04-23 --- # はじめに Delphi で作られた古いプログラムを Windows 10 で動かしている時、クリップボードの機能を使うと `"クリップボードが開けません"` 的なエラーを拝むことがあります。 このプログラムを作った Windows XP 当時ではエラーが出た覚えがなく、そもそも頻繁にエラーが出るようだったら当時デバッグしているハズですので、変わったのは周囲の環境という事になります。 古い EXE は [互換性] タブで `Windows XP (Service Pack 2)` 辺りに設定しておけば、クリップボードの問題は出にくくなると思います。 ![image.png](./images/4459140d-db6b-ac94-f86c-b465441508c3.png) が、なんだかモヤっとしますよね。 # 原因 おおまかな原因としては - クリップボードの処理方法が Windows Vista 以降で変わった (多分)。 - Microsoft Office のクリップボード (履歴) が悪さしている。 こんな所じゃないかと思います。 ## 検証 **クリップボードを握った状態** は、Excel で 20x200 くらいのデータを作り、`〔Ctrl〕+〔A〕`, `〔Ctrl〕+〔C〕` すると作り出せます [^1]。 ![image.png](./images/a2b2b9d8-44cf-e9e6-ab3b-15971ebe8a8f.png) Excel で `〔Ctrl〕+〔A〕`, `〔Ctrl〕+〔C〕` した直後に、目的の EXE でクリップボードにデータを格納します。Delphi で書かれたこんなコードだと、 ```pascal procedure TForm1.Button1Click(Sender: TObject); begin Clipboard.AsText := 'abc'; end; ``` ボタンを押した時にエラーになります。結構長い時間、クリップボードがロックされている事が解ります。 ![image.png](./images/a7b907cb-4f10-7bd6-caf2-9e0cb44c9009.png) **「俺が作ったプログラムではエラーが出なかった!」**って安心する前に、目的の EXE でデータを本当にクリップボードに格納したかを確認してみましょう。Excel のデータが格納されていませんか?それはエラーを握りつぶしてるだけですよ? イロイロ調べてみましたが、根本的な解決方法としては、やっぱり **誰かが握っているクリップボードを離すまでリトライする**しかないんじゃないかと思います。`「昔の EXE を開いている時はなるべく Excel を起動しないでおく」` なんて、現実的な解決方法ではありませんしね。 ## コード Delphi でエラーになるのは `ClipBoard.Open()` した時か、`ClipBoard.AsText := ` でテキストをクリップボードに格納した時です。 次のコードは `ClipBoard.Open() / Close()` メソッドと `ClipBoard.AsText` プロパティの代替関数です。**Delphi 10.4 Sydney** 用のコードなので、それよりも前の Delphi では多少の修正が必要となります [^2]。 ```pascal interface uses ..., Vcl.Clipbrd; function OpenClipboard(RetryCnt: Cardinal = 10): Boolean; procedure CloseClipboard; function SetClipboardText(const s: string; RetryCnt: Cardinal = 10): Boolean; implementation function OpenClipboard(RetryCnt: Cardinal): Boolean; begin while RetryCnt > 0 do begin var BreakFlg := True; try Clipboard.Open; except on E: EClipboardException do begin Dec(RetryCnt); BreakFlg := False; end; end; if BreakFlg then Break; Sleep(250); end; result := RetryCnt <> 0; end; procedure CloseClipboard; begin Clipboard.Close; end; function SetClipboardText(const s: string; RetryCnt: Cardinal): Boolean; begin while RetryCnt > 0 do begin var BreakFlg := True; try Clipboard.AsText := s; except on E: EClipboardException do begin Dec(RetryCnt); BreakFlg := False; end; end; if BreakFlg then Break; Sleep(250); end; result := RetryCnt <> 0; end; ``` `OpenClipboard()` と `SetClipboardText()` はリトライ回数を超えた場合に False を返します。リトライ回数はデフォルトで 10 回に指定されているので、大抵は次のように置き換えるだけです。 ```pascal: //Clipboard.AsText := 'Hello, World.'; SetClipboardText('Hello, World.'); ``` もっと細かく制御したい場合には次のように記述します。 ```pascal: if not SetClipboardText('Hello, World.', 15) then begin // リトライ回数を超えた場合のエラー処理 end; ``` # おわりに 新しく作るアプリケーションでクリップボードを扱う際にはリトライも考慮したほうがいいかもですね、というお話でした。 なお、`ClipBoard.AsText` プロパティは Setter (SetAsText) の中で Open / Close を行っているので、 ```pascal: Clipboard.Open; try Clipboard.AsText := 'Hello, World.'; finally Clipboard.Close; end; ``` なんてやる必要はありません。 **See also:** - [Vcl.Clipbrd.Clipboard (DocWiki)](http://docwiki.embarcadero.com/Libraries/ja/Vcl.Clipbrd.Clipboard) - [How can I fix “Cannot open clipboard: Access Denied” errors? (StackOverflow)](https://stackoverflow.com/questions/1859102/how-can-i-fix-cannot-open-clipboard-access-denied-errors) [^1]: 検証用プログラム書いてもいいんですけど、**Office が握る**のを確認するにはこれがいいかと。 [^2]: よく訓練された Delphi 信者なら苦もなく修正してのけるでしょう。