🫢

クリップボードのデータってどうなっているの?

に公開

非Windows系プログラマ向けの簡単な記事です。

クリップボードにそれらしい名前を付けるとすると、 型付きのデータ転送用共通メモリ機構 です。
本記事では、現行の Windows 環境で実際に利用されている型付きのデータ転送用共通メモリ機構でのデータ管理方法を整理します。

Windows でのクリップボードの基本構造

クリップボードでは、アプリケーション間でデータを共有するためのメモリ領域をOSレベルで確保しています。Ctrl+Cを入力すると、そのメモリ領域内にユーザーの選択したデータが記録されます。
そのデータのプロパティは以下の通りです。

  • フォーマットID(UINT) — データの種類(テキスト、画像など)を識別
  • データハンドル(HGLOBAL) — 実際のデータを保持する共有メモリへのポインタ

注意点としては、1度のCtrl+Cで1組のフォーマットID/データハンドルではなく複数組のフォーマットID/データハンドルが同時に登録される可能性があります。
ユーザーから見ると複数のフォーマットが同時に登録されているようには見えませんが、内部では色々な場面での貼り付けに対応できるよう、複数のフォーマットが同時に登録されます。例えば、色付きの文字列・色なしの文字列の両方のデータが同時に登録されていたりします。この辺りは開発力のあるアプリケーションであるほど色々と対応されているはずです。

続いては、フォーマットIDの具体例を見ていきます。データハンドルについては、その実体はメモリに置かれたビット列にすぎず、クリップボード理解の本質から外れる気がするので、ここでお別れです。

フォーマットID(UINT)

テキスト系フォーマット

フォーマットID 実体 説明
CF_UNICODETEXT wchar_t[](UTF-16, NULL終端) 現行標準。多言語対応のテキスト。Word、メモ帳、ブラウザなどで使用され、UTF-16エンコーディングで保存される。
CF_TEXT char[](ANSIコード, NULL終端) 旧来の8ビット文字列。ローカルのコードページ(日本語OSではShift-JISなど)を使う。古いアプリやWin32 API互換用。
CF_LOCALE DWORD(LCID値) テキストがどの言語ロケールで作られたかを示す補助情報。
使われる場面: アプリが CF_TEXT を登録する際、同時に CF_LOCALE を登録して「このテキストはどの言語設定(例:日本語、英語)でエンコードされたか」を明示する。これにより、他のアプリが文字コード変換を行う際の判断材料になる。
(例:英語OSに日本語テキストを貼り付けた際、CF_LOCALE=0x0411 があるとShift-JISとして正しく処理できる)

関連する話題として、Microsoft PowerToysの例をご紹介します。Microsoft PowerToysでは「プレーンテキストとして直接貼り付ける」という機能が提供されています。

この機能では、貼り付け時にCF_UNICODETEXT以外のテキスト関係のフォーマットを削除しています。

実装からも確認できます。
EmptyClipboard()でクリップボードを空にしてから、SetClipboardData(CF_UNICODETEXT, h_clipboard_data)CF_UNICODETEXTを登録しています。順番前後していますが、wcscpy_s(pch_data, clipboard_text.length() + 1, clipboard_text.c_str())ではデータハンドルを登録しています。

引用元

            wcscpy_s(pch_data, clipboard_text.length() + 1, clipboard_text.c_str());

            EmptyClipboard();

            if (NULL == SetClipboardData(CF_UNICODETEXT, h_clipboard_data))
            {
                DWORD errorCode = GetLastError();
                auto errorMessage = get_last_error_message(errorCode);
                Logger::error(L"Couldn't set the clipboard data to the unformatted text. {}", errorMessage.has_value() ? errorMessage.value() : L"");
                GlobalUnlock(h_clipboard_data);
                GlobalFree(h_clipboard_data);
                CloseClipboard();
                Trace::AdvancedPaste_Error(errorCode, errorMessage.has_value() ? errorMessage.value() : L"", L"write.SetClipboardData");
                return;
            }

🖼️ 画像系フォーマット

フォーマットID 実体 説明
CF_BITMAP HBITMAP(GDIオブジェクト) Windows内部のビットマップ。描画API(GDI)で直接扱える形式。スクリーンキャプチャやペイントなどで使用。
CF_DIB BITMAPINFOHEADER + ピクセルデータ BMPファイルに近い構造で、ピクセル情報を直接持つ。デバイスに依存しない画像形式。画像編集ソフトや印刷アプリで使用。
CF_DIBV5 BITMAPV5HEADER + ピクセルデータ DIBの拡張版。αチャンネル(透明度)や色空間情報を保持でき、PNGに近い表現力を持つ。Office製品・ブラウザなどで広く使用。

📂 ファイル・オブジェクト系フォーマット

フォーマット 実体 説明
CF_HDROP DROPFILES 構造体 + ファイルパス群 エクスプローラでファイルをコピーやドラッグ&ドロップしたときに使用。
ファイルパスがNULL区切りで並ぶ(例:C:\test1.txt\0C:\test2.png\0\0)。
実際のファイル内容は含まず、パスのリストのみを渡す。
CF_OWNERDISPLAY 遅延レンダリング(Lazy Rendering)用トリガ SetClipboardData(format, NULL) を使って登録し、実データを即時には渡さず、「貼り付け要求が来た時点」で生成する。
ExcelやPhotoshopなどで大量データをコピーする際、処理を遅延させてレスポンスを改善するために使われる。
OSは貼り付け要求時に WM_RENDERFORMAT メッセージを送信し、アプリがその場で実データを返す。

🔊 メディア系フォーマット

フォーマットID 実体 説明
CF_WAVE WAVファイル形式(RIFF構造) 音声データ。録音ソフトやメディアエディタ間でのコピー・貼り付けに使用される。

📄 図形・レイアウト系(ベクター)

フォーマットID 実体 説明
CF_ENHMETAFILE HENHMETAFILE(拡張メタファイル) ベクター形式の図形データ(線・円・文字などの描画命令列)。解像度に依存せず、WordやPowerPoint間の図形貼り付けで使用される。

PowerPointでポスター印刷をするときとかに意識するやつです。

カスタムフォーマット(RegisterClipboardFormat)

アプリケーション独自のフォーマットは RegisterClipboardFormat() により登録されます。
登録名を文字列で指定し、同じ名前を使うアプリ間では同一フォーマットIDが共有されます。

  • ID割り当て範囲:
    0xC000(=49152)〜0xFFFF はアプリケーション定義領域であり、OS標準フォーマットと衝突しない。

カスタムフォーマット例: Microsoft Word の "HTML Format"

WordやOutlookがコピー時に登録する代表的カスタムフォーマットが"HTML Format"です。
書式付きテキストをHTMLとして転送するために使われます。

  • フォーマット名: "HTML Format"
  • データ内容: HTML断片+オフセット情報ヘッダ
    Version:1.0\nStartHTML:...EndFragment:...
  • 利用アプリ: Word, Outlook, Edge, Chrome
  • 目的:
    プレーンテキストだけでなく、リッチな装飾(色、フォント、リンク)を含むテキストを
    他アプリへ貼り付け可能にする。

実際のクリップボード内のフォーマットを見てみる。

最後に実際のクリップボード内のフォーマットを見てみます。

VSCodeでコードをコピーしてクリップボードに記録されたフォーマットを見てみると、いろいろな情報が入っています。ID: 13はこちらの通り、CF_UNICODETEXTです。それ以外にも色々なフォーマットが記録されおり、例えばHTML Formatでは色情報なども記録されます。このお陰でVSCodeでコピーテキストをPower Pointに貼り付けるといった場面でも、元の文字色が反映されるのです。

ID: 49416 / 名前: HTML Format
ID: 13 / 名前: (標準フォーマット)
ID: 50013 / 名前: Chromium Web Custom MIME Data Format
ID: 50150 / 名前: Chromium internal source RFH token
ID: 50151 / 名前: Chromium internal source URL
ID: 16 / 名前: (標準フォーマット)
ID: 1 / 名前: (標準フォーマット)
ID: 7 / 名前: (標準フォーマット)

ちなみに、Microsoft PowerToysの「プレーンテキストとして直接貼り付ける」を使うと、以下のフォーマットに書き換わります。HTML Formatが削除され、プレーンテキストの13と管理用フォーマットのみが残っています。

ID: 13 / 名前: (標準フォーマット)
ID: 50001 / 名前: ExcludeClipboardContentFromMonitorProcessing
ID: 16 / 名前: (標準フォーマット)
ID: 1 / 名前: (標準フォーマット)
ID: 7 / 名前: (標準フォーマット)

クリップボード内のフォーマットは以下のスクリプトで確認しています。

import win32clipboard

win32clipboard.OpenClipboard()
format = 0
print("=== クリップボードフォーマット一覧 ===")
while True:
    format = win32clipboard.EnumClipboardFormats(format)
    if not format:
        break
    try:
        name = win32clipboard.GetClipboardFormatName(format)
    except:
        name = "(標準フォーマット)"
    print(f"ID: {format} / 名前: {name}")
win32clipboard.CloseClipboard()

おわり

以上です!
今回は省略しましたが、実際にクリップボードを操作するときにはデータハンドルをゴニョゴニョする感じです。
OSレイヤの機能って身近な割にはあまり実装まで意識しないので、深掘りすると面白いですよね。

またWin32のコードベースを見るたびにWindows系プログラマって凄いなーと。。

ヘッドウォータース

Discussion