none
クラスのインスタンス配列をクリップボードでやり取りしたい RRS feed

  • 質問

  • VS2010でMFCプログラムを作っています。

    アプリケーション間であるクラスのインスタンスデータ(複数)を渡し、受け取る必要が出来たので
    クリップボードを使う事を考えています。

    この場合、どのような形式でクリップボードにデータを入れれば良いでしょうか?
    (インスタンスをバイナリーでやり取りできればいいんですが、上手く行かなければ文字にする方法も検討しています)


    クラスのインスタンス配列(又は 構造体配列)をクリップボードでやり取りしているような例があれば大変助かります。

    2017年6月24日 6:22

回答

  • アプリケーションの独自データをクリップボードにコピーするには

    UINT uFormat = ::RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));

    というように、MY_CLASS_DATA の部分をアプリケーション独自の文字列として定義することで、任意のバイナリ値などをクリップボードに入れることが出来ます。例えば CMyClass というクラスのインスタンスが pMyClass であった場合、クリップボードに pMyClass をコピーするには下記のようにするとできると思います。

    UINT uFormat = RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));
    
    SIZE_T nSize = sizeof(CMyClass);
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, nSize);
    LPBYTE p = (LPBYTE)GlobalLock(hMem);
    CopyMemory(p, pMyClass, nSize);
    GlobalUnlock(hMem);
    
    if (::OpenClipboard(NULL))
    {
    	EmptyClipboard();
    	SetClipboardData(uFormat, hMem);
    	CloseClipboard();
    }

    取り出すには、

    UINT uFormat = RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));
    
    if (::OpenClipboard(NULL))
    {
    	HANDLE hMem = GetClipboardData(uFormat);
    	if (hMem)
    	{
    		SIZE_T nSize = GlobalSize(hMem);
    		LPBYTE p = (LPBYTE)GlobalLock(hMem);
    		pMyClass = new CMyClass;
    		CopyMemory(pMyClass, p, nSize);
    		GlobalUnlock(hMem);
    	}
    	CloseClipboard();
    }

    それとは別の実装で、OLE オブジェクトして ドラッグ & ドロップ と クリップボードを使った コピー & ペースト のサンプルプログラムが、下記のページの "Download sample" のリンクからダウンロードすることが出来るようです。(こちらの実装のほうが正攻法であると思いましたが、OLE 関連の知識が必要となり敷居が高いと感じました。)

    https://msdn.microsoft.com/ja-jp/library/windows/desktop/8c47csfz(v=vs.80).aspx

    参考サイト:
    https://msdn.microsoft.com/ja-jp/library/838a3whf.aspx

    2017年6月24日 9:38
  • 一応、MFC にもシリアル化(シリアライズ)はこうしましょうというドキュメントはあります。
    https://msdn.microsoft.com/ja-jp/library/6bz744w8.aspx
    (CArchive を使わず、自分で実装するのももちろんありです)

    CArchive によるシリアライズがすでに実装されている前提で、CSharedFile によるドラッグ&ドロップを実現するサンプルはここにありますね。
    https://www.codeproject.com/Articles/1115/The-Right-Way-To-Do-Object-Serialization

    この例はドラッグ&ドロップですが、それをやめて、kenjinote さんが示したサンプルを流用して SetClipboardData に持って行ければできそうな気はします。

    ただ、別の投稿でも述べましたが、シリアライズの実装について、クラス・構造体のメンバーに配列がある場合、CArray がある場合、std::vector がある場合など、単純に行かないケースもあります。
    単にアドレス・ポインターだけをコピーすれば良いというわけではない点に注意しましょう。

    2017年6月24日 10:16
    モデレータ

すべての返信

  • 自分ではどこまでできますか?
    そのインスタンス配列をクリップボードではなく、たとえば、ファイルに保存・読み込みならできますか?

    基本的に、自分でシリアライズ(保存するための仕組み)を実装する必要があります。
    それができていれば、後は要素数と、自分たちのデータであることを識別するため符号をつければ良いと言うことにはなります。

    2017年6月24日 9:16
    モデレータ
  • アプリケーションの独自データをクリップボードにコピーするには

    UINT uFormat = ::RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));

    というように、MY_CLASS_DATA の部分をアプリケーション独自の文字列として定義することで、任意のバイナリ値などをクリップボードに入れることが出来ます。例えば CMyClass というクラスのインスタンスが pMyClass であった場合、クリップボードに pMyClass をコピーするには下記のようにするとできると思います。

    UINT uFormat = RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));
    
    SIZE_T nSize = sizeof(CMyClass);
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, nSize);
    LPBYTE p = (LPBYTE)GlobalLock(hMem);
    CopyMemory(p, pMyClass, nSize);
    GlobalUnlock(hMem);
    
    if (::OpenClipboard(NULL))
    {
    	EmptyClipboard();
    	SetClipboardData(uFormat, hMem);
    	CloseClipboard();
    }

    取り出すには、

    UINT uFormat = RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));
    
    if (::OpenClipboard(NULL))
    {
    	HANDLE hMem = GetClipboardData(uFormat);
    	if (hMem)
    	{
    		SIZE_T nSize = GlobalSize(hMem);
    		LPBYTE p = (LPBYTE)GlobalLock(hMem);
    		pMyClass = new CMyClass;
    		CopyMemory(pMyClass, p, nSize);
    		GlobalUnlock(hMem);
    	}
    	CloseClipboard();
    }

    それとは別の実装で、OLE オブジェクトして ドラッグ & ドロップ と クリップボードを使った コピー & ペースト のサンプルプログラムが、下記のページの "Download sample" のリンクからダウンロードすることが出来るようです。(こちらの実装のほうが正攻法であると思いましたが、OLE 関連の知識が必要となり敷居が高いと感じました。)

    https://msdn.microsoft.com/ja-jp/library/windows/desktop/8c47csfz(v=vs.80).aspx

    参考サイト:
    https://msdn.microsoft.com/ja-jp/library/838a3whf.aspx

    2017年6月24日 9:38
  • 自分ではどこまでできますか?
    そのインスタンス配列をクリップボードではなく、たとえば、ファイルに保存・読み込みならできますか?

    基本的に、自分でシリアライズ(保存するための仕組み)を実装する必要があります。
    それができていれば、後は要素数と、自分たちのデータであることを識別するため符号をつければ良いと言うことにはなります。


    返答ありがとうございます。
    クラスをファイルに保存する事もやったことがないので、シリアライズも含めて現在調べています。
    2017年6月24日 9:52
  • その方法はまずいのでは?
    今回の対象クラスが何かはわかりませんが、C++ のクラスには vtable が入っている領域がありますし、アプリケーション間で同じアドレスになる保証はないでしょうから。

    そのほか、T[], std::vector, CArray, のメンバーフィールドがあった場合、メモリ上のバイト列をコピーしても効果がないので、汎用的ではないという点には注意を促した方が良いかと思います。

    2017年6月24日 10:09
    モデレータ
  • 一応、MFC にもシリアル化(シリアライズ)はこうしましょうというドキュメントはあります。
    https://msdn.microsoft.com/ja-jp/library/6bz744w8.aspx
    (CArchive を使わず、自分で実装するのももちろんありです)

    CArchive によるシリアライズがすでに実装されている前提で、CSharedFile によるドラッグ&ドロップを実現するサンプルはここにありますね。
    https://www.codeproject.com/Articles/1115/The-Right-Way-To-Do-Object-Serialization

    この例はドラッグ&ドロップですが、それをやめて、kenjinote さんが示したサンプルを流用して SetClipboardData に持って行ければできそうな気はします。

    ただ、別の投稿でも述べましたが、シリアライズの実装について、クラス・構造体のメンバーに配列がある場合、CArray がある場合、std::vector がある場合など、単純に行かないケースもあります。
    単にアドレス・ポインターだけをコピーすれば良いというわけではない点に注意しましょう。

    2017年6月24日 10:16
    モデレータ
  • 一応、MFC にもシリアル化(シリアライズ)はこうしましょうというドキュメントはあります。
    https://msdn.microsoft.com/ja-jp/library/6bz744w8.aspx
    (CArchive を使わず、自分で実装するのももちろんありです)

    CArchive によるシリアライズがすでに実装されている前提で、CSharedFile によるドラッグ&ドロップを実現するサンプルはここにありますね。
    https://www.codeproject.com/Articles/1115/The-Right-Way-To-Do-Object-Serialization

    この例はドラッグ&ドロップですが、それをやめて、kenjinote さんが示したサンプルを流用して SetClipboardData に持って行ければできそうな気はします。

    ただ、別の投稿でも述べましたが、シリアライズの実装について、クラス・構造体のメンバーに配列がある場合、CArray がある場合、std::vector がある場合など、単純に行かないケースもあります。
    単にアドレス・ポインターだけをコピーすれば良いというわけではない点に注意しましょう。

    情報ありがとうございます。

    対象としているクラスにはArray類はありませんでした。

    頂いた情報を全部理解するまで少し時間がかかると思いますが、何か進みましたら情報を上げます。

    2017年6月24日 10:33
  • アプリケーションの独自データをクリップボードにコピーするには

    UINT uFormat = ::RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));

    というように、MY_CLASS_DATA の部分をアプリケーション独自の文字列として定義することで、任意のバイナリ値などをクリップボードに入れることが出来ます。例えば CMyClass というクラスのインスタンスが pMyClass であった場合、クリップボードに pMyClass をコピーするには下記のようにするとできると思います。

    UINT uFormat = RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));
    
    SIZE_T nSize = sizeof(CMyClass);
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, nSize);
    LPBYTE p = (LPBYTE)GlobalLock(hMem);
    CopyMemory(p, pMyClass, nSize);
    GlobalUnlock(hMem);
    
    if (::OpenClipboard(NULL))
    {
    	EmptyClipboard();
    	SetClipboardData(uFormat, hMem);
    	CloseClipboard();
    }

    取り出すには、

    UINT uFormat = RegisterClipboardFormat(TEXT("MY_CLASS_DATA"));
    
    if (::OpenClipboard(NULL))
    {
    	HANDLE hMem = GetClipboardData(uFormat);
    	if (hMem)
    	{
    		SIZE_T nSize = GlobalSize(hMem);
    		LPBYTE p = (LPBYTE)GlobalLock(hMem);
    		pMyClass = new CMyClass;
    		CopyMemory(pMyClass, p, nSize);
    		GlobalUnlock(hMem);
    	}
    	CloseClipboard();
    }

    それとは別の実装で、OLE オブジェクトして ドラッグ & ドロップ と クリップボードを使った コピー & ペースト のサンプルプログラムが、下記のページの "Download sample" のリンクからダウンロードすることが出来るようです。(こちらの実装のほうが正攻法であると思いましたが、OLE 関連の知識が必要となり敷居が高いと感じました。)

    https://msdn.microsoft.com/ja-jp/library/windows/desktop/8c47csfz(v=vs.80).aspx

    参考サイト:
    https://msdn.microsoft.com/ja-jp/library/838a3whf.aspx

    大変ありがとうございました。

    まずはサンプルを作って試してみます。  

    2017年6月24日 10:36
  • 長くなるので不用意に全文引用はしないようにしましょう。

    この方法(CopyMemory)でうまくいくのは、限定的な状況です。

    ・継承していない(正確には virtual キーワードをそのクラスおよび継承クラスが持たない=vtable を持たない)
    ・メンバーにクラスや配列に相当するものがない
    ・MFC マクロによる特殊なフィールドができていない

    int, long, double などのシンプルな型ばかりであれば、成功する可能性はあると思いますが、汎用的な方法ではないことも覚えておいてください。

    2017年6月24日 10:40
    モデレータ
  • 個々のインスタンスに含まれているのはvptrで、このvptrが指している先がvtbl(vtable)ですね。

    ともかく、vptrを含むポインター全般はシリアライズしても無効ですので、どのようなメンバーが含まれているかは確認する必要がありますね。またポインターだけでなくハンドル(ファイルハンドルなど)もプロセス内でしか有効でないものはコピーしても無効です。

    kenjinoteさんの書かれたコードはC言語を想定されているように見受けられます。C言語と異なり暗黙のメンバーを持ち得るC++言語でこのような行為は危険です。

    SIZE_T nSize = sizeof(CMyClass); の手前辺りで static_assert(std::is_pod<CMyClass>::value, "not serializable class."); とアサートを組み込んでおくのもいいかも?

    2017年6月24日 22:40
  • 上げて頂いたサンプルを試しています。

    インスタンスが1つだと問題無かったのですが、
    インスタンスが複数になると2つ目以降がうまく値を取れませんでした。

    なにかマズイ事がありますでしょうか ?

    // インスタンスが2個の場合の例
    
    SIZE_T nSize = sizeof(CMyClass);
    HGLOBAL hMem = GlobalAlloc(GMEM_MOVEABLE, nSize*2);
    LPBYTE p = (LPBYTE)GlobalLock(hMem);
    CopyMemory(p, pMyClass, nSize);
    CopyMemory(p+nSize, pMyClass2, nSize);
    
    // ここで 
    // CopyMemory(pMyClass, p+nSize, nSize); とやるとpMyClassに値が入っている
    
    GlobalUnlock(hMem);
    
    if (::OpenClipboard(NULL))
    {
    	EmptyClipboard();
    	SetClipboardData(uFormat, hMem);
    	CloseClipboard();
    }
    
    
    if (::OpenClipboard(NULL))
    {
    	HANDLE hMem = GetClipboardData(uFormat);
    	if (hMem)
    	{
    		SIZE_T nSize = GlobalSize(hMem);
    		LPBYTE p = (LPBYTE)GlobalLock(hMem);
    		pMyClass = new CMyClass;
    		CopyMemory(pMyClass, p, nSize);
    		pMyClass2 = new CMyClass;
    		CopyMemory(pMyClass2, p+nSize, nSize);// ここでは値が取れない
    		GlobalUnlock(hMem);
    	}
    	CloseClipboard();
    }

    2017年6月25日 10:37
  • クリップボードからデータを取り出すところで、

    SIZE_T nSize = GlobalSize(hMem);

    を行っていますが、こちらの nSize は複数個のインスタンスの合計サイズが入りますので、

    if (OpenClipboard(hWnd))
    {
    	HANDLE hMem = GetClipboardData(uFormat);
    	if (hMem)
    	{
    		SIZE_T nSize = GlobalSize(hMem); // クリップボードに格納したインスタンスが 2 つの場合は nSize は 2 * sizeof(CMyClass) となっているはず
    		LPBYTE p = (LPBYTE)GlobalLock(hMem);
    		pMyClass1 = new CMyClass;
    		CopyMemory(pMyClass1, p, sizeof(CMyClass));
    		pMyClass2 = new CMyClass;
    		CopyMemory(pMyClass2, p + sizeof(CMyClass), sizeof(CMyClass));
    	}
    	CloseClipboard();
    }

    上記のように、それぞれ sizeof(CMyClass) バイト分で CopyMemory する必要があります。
    他の方も、ご指摘されていますが、あくまでインスタンスのバイナリのコピーになりますので、プロセスをまたいで使用できないものデータ(ポインタなど)もあります。

    2017年6月26日 5:50
  • ご回答ありがとうございました。

    申し訳ないです。私の方の上げたソースが間違えてました。

    実際にはデータを取り出すところで、

    CopyMemory(pMyClass2, p + sizeof(CMyClass), sizeof(CMyClass));
    をやってまして、 それで
    pMyClass2に値が入らない現象です。

    pMyClass2の中はデバッカーで見ても全部無効の値でした。

    クリップボードから取り出さずに、SetClipboardData前のメモリで同じ事をやると、2個目もデータが正しく取得できました。

    2017年6月26日 6:51
  • こちらでも再現可能なソースをご提示することは可能でしょうか?
    2017年6月26日 7:19
  • こちらでも再現可能なソースをご提示することは可能でしょうか?

    ソース切り出しには少々編集が必要ですが、クラスの変数のCStringがあるのでこの方法は使えなそうに思って来ています。

    2017年6月26日 9:49
  • CString は可変長の文字リストでバイナリに展開するとわかるかと思いますが文字列へのポインタを含んでいますので、今回の手法は使えません。

    上の投稿で Azulean さんや私が提示したサンプルをもとに、実装されることをお勧めします。

    2017年6月26日 10:10
  • CString は可変長の文字リストでバイナリに展開するとわかるかと思いますが文字列へのポインタを含んでいますので、今回の手法は使えません。

    上の投稿で Azulean さんや私が提示したサンプルをもとに、実装されることをお勧めします。

    ご回答ありがとうございました。

    クラスにシリアライズを入れることを検討します。

    2017年6月26日 23:20