-1

内部で Windows のクリップボードがどのように機能するかについての記事や本を読みたいのですが、標準の API リファレンスや の使用例よりも詳しい記事が少なくとも 1 つ見つかりません。

標準の winapi で Windows クリップボードを使用していますが、奇妙な結果が得られます。

ケース 1: Unicode 文字列をクリップボードに書き込み、その文字列のアドレスを覚えています。次に、クリップボードを閉じて、次の手順を繰り返します: クリップボードを開き、Unicode 文字列のアドレスを取得し、クリップボードを閉じます。

クリップボードのコンテンツと同じアドレスを受け取る必要があると思いますが、そうではありません。なんで?

        //1.) Copying string to clipboard
        if (WinAPI.OpenClipboard(owner))
        {
            WinAPI.EmptyClipboard();

            IntPtr exampleStringPtr = Marshal.StringToHGlobalUni("Example");

            Console.WriteLine("setting address: {0}", exampleStringPtr.ToInt32());

            WinAPI.SetClipboardData(WinAPI.CF_UNICODETEXT, exampleStringPtr);

            WinAPI.CloseClipboard();
        }

        //2.) Getting string from clipboard
        for (int i = 0; i < 100; i++ )
            if (WinAPI.OpenClipboard(owner))
            {
                IntPtr exampleStringPtr = WinAPI.GetClipboardData(WinAPI.CF_UNICODETEXT);

                Console.WriteLine("getting address: {0}", exampleStringPtr.ToInt32());

                WinAPI.GlobalLock(exampleStringPtr);

                var s = Marshal.PtrToStringUni(exampleStringPtr);

                WinAPI.GlobalUnlock(exampleStringPtr);
                WinAPI.CloseClipboard();
            }

ケース 2: クリップボードに文字列を書き込み、クリップボードを閉じ、文字列を (アンマネージ メモリで) 変更し、クリップボードを再度開いてこの文字列を読み取ります。驚いたことに、同じ文字列アドレスと UNCHANGED 文字列を取得しました。

        //1.) Copying string to clipboard
        if (WinAPI.OpenClipboard(owner))
        {
            WinAPI.EmptyClipboard();

            IntPtr exampleStringPtr = Marshal.StringToHGlobalUni("Loooooooooooonng String Example");

            Console.WriteLine("setting address: {0}", exampleStringPtr.ToInt32());

            WinAPI.SetClipboardData(WinAPI.CF_UNICODETEXT, exampleStringPtr);

            WinAPI.CloseClipboard();

            //2.) Change string - replace first 10 characters on one any symbol 
            for (int i = 0; i < 10; i++)
            {
                Marshal.WriteByte(exampleStringPtr + i, 50);
            }

            //3.) Obtain string and make sure that string was changed
            Console.WriteLine("changed string: {0}", Marshal.PtrToStringUni(exampleStringPtr));
        }


            //2.) Getting string from clipboard
            for (int i = 0; i < 100; i++)
                if (WinAPI.OpenClipboard(owner))
                {
                    IntPtr exampleStringPtr = WinAPI.GetClipboardData(WinAPI.CF_UNICODETEXT);

                    Console.WriteLine("getting address: {0}", exampleStringPtr.ToInt32());

                    WinAPI.GlobalLock(exampleStringPtr);

                    var s = Marshal.PtrToStringUni(exampleStringPtr);

                    Console.WriteLine("obtained string: {0}", s);

                    WinAPI.CloseClipboard();
                }

今私は、クリップボードが SetClipboardData 内のすべてのメモリ ブロックを他のメモリにコピーし、ソース ブロックを数回コピーできると考えています。SetClipboardData の実行直後にアンマネージ メモリを文字列用に解放できないのはなぜですか?

多くの質問がありますが、いくつかの文献で明らかになると思います

アップデート:

Raymond Chen さん、Jonathan Potter さん、Eric Brown さんへ: 回答ありがとうございます。しかし、2 番目のテストを編集して、より正確に修正したところ、次のように表示されました。クリップボードを閉じた後に作成した回答が削除されます。次に、この文字列を取得すると、結果が変更されたことが示されます。次に、GetClipboardData を呼び出してこの文字列を取得すると、文字列が変更され、ポインターが同じであることを結果が示します。次に、クリップボードを閉じて再度開き、文字列を再度読み取ります。私は今何を得ますか?文字列アドレスはソース文字列のアドレスと同じですが、文字列は変更されていません。このコードは次のとおりです。

        //1.) Copying string to clipboard
        if (WinAPI.OpenClipboard(owner))
        {
            WinAPI.EmptyClipboard();

            IntPtr exampleStringPtr = Marshal.StringToHGlobalUni("Loooooooooooonng String Example");

            Console.WriteLine("setting address: {0}", exampleStringPtr.ToInt32());

            WinAPI.SetClipboardData(WinAPI.CF_UNICODETEXT, exampleStringPtr);

            //2.) Change string while clipboard isn't closed - replace first 10 characters on one any symbol 
            for (int i = 0; i < 10; i++)
            {
                Marshal.WriteByte(exampleStringPtr + i, 50);
            }

            //3.) Obtain string and make sure that string was changed
            Console.WriteLine("changed string: {0}", Marshal.PtrToStringUni(exampleStringPtr));

            //4.) Get this string from clipboard and make sure that clipboard was changed
            exampleStringPtr = WinAPI.GetClipboardData(WinAPI.CF_UNICODETEXT);

            Console.WriteLine("getting address of changed string: {0}", exampleStringPtr.ToInt32());

            var lockedPtr = WinAPI.GlobalLock(exampleStringPtr);

            var s = Marshal.PtrToStringUni(exampleStringPtr);
            WinAPI.GlobalUnlock(lockedPtr);

            Console.WriteLine("obtained string: {0}", s);

            WinAPI.CloseClipboard();

        }
            Console.WriteLine("\n-------Close and open clipboard------------------\n");

            //5.) Getting string from clipboard
            for (int i = 0; i < 100; i++)
                if (WinAPI.OpenClipboard(owner))
                {
                    IntPtr exampleStringPtr = WinAPI.GetClipboardData(WinAPI.CF_UNICODETEXT);

                    Console.WriteLine("getting address: {0}", exampleStringPtr.ToInt32());

                    var lockedPtr = WinAPI.GlobalLock(exampleStringPtr);

                    var s = Marshal.PtrToStringUni(lockedPtr);

                    WinAPI.GlobalUnlock(lockedPtr);

                    Console.WriteLine("obtained string: {0}", s);

                    WinAPI.CloseClipboard();
                }

プログラムを実行して一時停止し、WinDbg でメモリを分析しました。次に、結果のスクリーンショットを作成して提供します。http://postimg.org/image/are6um7yv/したがって、私のテストとスクリーンショットは次のことを示しています。

1.) メモリ内に 1 つのソース オブジェクトの複数のコピーがあります 2.) クリップボードを閉じる前に SetClipboardData 呼び出しに指定されたソース メモリを変更すると、クリップボードを再度開いた後、ソース アドレスでもソース オブジェクトが復元されます。

そうじゃない?これらの条項が真実かどうか、誰か説明できますか?

更新 2: OK.. C++ で 3 番目のテストを書き直していました。それはここにあります:

#include "stdafx.h"
#include "windows.h"
#include "conio.h"

int main()
{
HWND owner = GetConsoleWindow();

//1.) Copying string to clipboard
if (OpenClipboard(owner))
{
    EmptyClipboard();

    //WCHAR *str = L"Loooong string example";
    char *str = "Loooooooong string Example";

    int cch = strlen(str);

    char* strptr = (char*)GlobalAlloc(GMEM_MOVEABLE, (cch + 1));

    printf("setting (segment??) address: %X \n", strptr);

    LPVOID lockedPtr = GlobalLock(strptr);
    printf("locked setting address: %X \n", lockedPtr);

    // copy
    memcpy(lockedPtr, str, cch);

    GlobalUnlock(strptr);

    // set to clipboard
    SetClipboardData(CF_TEXT, strptr);

    //2.) Change string while clipboard isn't closed - replace first 10 characters on one any symbol 
    lockedPtr = GlobalLock(strptr);
    for (int i = 0; i < 10; i++)
    {
        ((char*)lockedPtr)[i] = 50;
    }
    GlobalUnlock(strptr);

    //3.) Obtain string and make sure that string was changed
    lockedPtr = GlobalLock(strptr);
    printf("changed string: %s \n", lockedPtr);
    GlobalUnlock(strptr);

    //4.) Get this string from clipboard and make sure that clipboard was changed
    strptr = (char*)GetClipboardData(CF_TEXT);

    printf("getting address: %X \n", strptr);

    lockedPtr = GlobalLock(strptr);

    printf("locked getting address: %X \n", lockedPtr);

    printf("obtained string: %s \n", (char*)lockedPtr );

    GlobalUnlock(strptr);

    CloseClipboard();

}

printf("\n-------Close and open clipboard------------------\n");

//5.) Getting string from clipboard
for (int i = 0; i < 10; i++)
{
    //Sleep(1000);
    if (OpenClipboard(owner))
    {
        HANDLE exampleStringPtr = GetClipboardData(CF_TEXT);

        printf("getting address: %X \n", exampleStringPtr);

        char* lockedPtr = (char*)GlobalLock(exampleStringPtr);

        printf("locked getting address: %X \n", lockedPtr);
        printf("obtained string: %s \n", lockedPtr);

        GlobalUnlock(exampleStringPtr);

        CloseClipboard();
    }
}

getch();
return 0;
} 

実際、GetClipboardData を呼び出すと、データへの同じポインタが常に取得されます。しかし、クリップボードに入れた最初の文字列のロックされたポインターとは異なる場合がありました。

しかし、私はこのテストを C++ で書いていますが、以前に書いていたのと同じ効果がまだあります。

SetClipboardData を呼び出した後にソース メモリ ブロックを変更してから GetClipboardData を呼び出そうとすると、変更されたメモリ ブロックが取得されます。しかし、このクリップボードを閉じてから再度開くと、変更されたメモリ ブロックが何らかの情報で上書きされていましたが、わかりません。GetClipboardData を呼び出すと、そのメモリ ブロックは、変更していないかのように初期状態に復元されました。 .

コピーがなく、ソースブロックを変更した場合、クリップボードがこのブロックを復元する方法を「知っている」場所から?

記憶が回復した瞬間を示す小さなスクリーンキャストを記録しましたhttp://screencast.com/t/5t3wc9LS

4

2 に答える 2

1

のドキュメントは、提供したデータをコピーしないSetClipboardData()ことを明確に述べています-代わりに、クリップボードがデータハンドルを所有しており、クリップボードが閉じられるまでデータハンドルから読み取ることはできますが、呼び出し後にデータに書き込んだり解放したりしないでください。成功しました。SetClipboardData()

クリップボードを閉じると、クリップボードの所有者ではなくなり、別のプロセスによってクリップボードの内容が変更された可能性があるため、データ オブジェクトを読み取るために使用することはまったく安全ではありません。クリップボードを閉じた後にデータを変更する際のテストは、想定されているためではなく、運によって機能しました。

SetClipboardData が成功すると、システムは hMem パラメーターで識別されるオブジェクトを所有します。所有権がシステムに転送されると、アプリケーションはデータの書き込みや解放を行うことはできませんが、CloseClipboard 関数が呼び出されるまではデータをロックして読み取ることができます。(クリップボードを閉じる前にメモリのロックを解除する必要があります。)

編集: リソースの所有権と未定義の動作の概念に問題があるように思われるため、この類推が役立つかもしれません。

// allocate 1 byte of memory
char* ptr = malloc(sizeof(char));
// set the byte to the letter A
*ptr = 'A'; 
// free the memory
free(ptr);
// set the byte to B
*ptr = 'B';
// verify that the byte is set to B
printf("byte contains %c\n", *ptr);
// allocate another byte of memory
char* ptr2 = malloc(sizeof(char));
// are they the same byte? maybe
printf("byte contains %c - first was %lx, second is %lx\n", *ptr2, ptr, ptr2);

このコードが完全に間違っていることがわかると思います。メモリを割り当て、書き込み、解放し、その後、書き込みと読み取りを繰り返します。それでも、このコードをコンパイルして実行すると、うまくいく可能性が高くなります。2 番目の割り当てが最初の割り当てと同じアドレスを返す可能性も十分にあります。どうしたの?

これは未定義の動作と呼ばれます。言語は、このような状況で何が起こるかを定義しません。メモリを解放すると、そのメモリの所有者ではなくなるため、書き込みや読み取りを行ってはなりません。それが機能する、または機能するように見える場合、それは偶然であり、それ以上のものではありません. 常に機能するという保証はありません。動作が未定義であるという事実を変更するものはありません。うまくいくかもしれないし、うまくいかないかもしれない。やらないでください。

于 2013-09-06T21:07:14.377 に答える
-1

SetClipboardData指定されたグローバル ハンドルのデータをコピーします。クリップボードが閉じられたら、グローバル ハンドルを解放する必要があります (前ではなく)。 GetClipboardData(内部) メモリ ハンドルを返します。このハンドルは読み取り専用バッファーとして扱う必要があります。

于 2013-09-06T17:41:16.680 に答える