-1

我想阅读一些关于 windows 剪贴板内部工作原理的文章或书籍,但我找不到至少一篇比标准 API 参考或使用 .

我正在使用标准 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:感谢您的回答,但我编辑了我的第二个测试,它会更正确,现在显示如下:我在剪贴板关闭之前更改源字符串,我可能认为这是有效的操作和它删除了我在剪贴板关闭后做出的答案。然后我得到这个字符串,结果显示它已更改,然后我通过调用 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.)我们在内存中有一个源对象的多个副本 2.)如果我在关闭剪贴板之前更改给 SetClipboardData 调用的源内存,在再次打开剪贴板后,即使在源地址上也可以恢复源对象。

不是吗?谁能解释一下,这些条款是否正确?

更新 2:好的.. 我正在重写我的第三个 C++ 测试。它在这里:

#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);

我希望你会看到这段代码是完全错误的。我们分配内存,写入它,释放它,然后,我们写入它并再次读取它。然而,如果你编译并运行这段代码,它很有可能会起作用。第二次分配也很有可能返回与第一次分配相同的地址。这是怎么回事?

这称为未定义行为。该语言没有定义在这种情况下会发生什么。当您释放内存时,您不再拥有它,并且您不能写入或读取它。如果它有效,或者看起来有效,那只是巧合,仅此而已。不能保证它会一直有效。持续进行测试以试图证明它确实有效是没有意义的——没有什么能改变行为未定义的事实。它可能有效,也可能无效。不要这样做。

于 2013-09-06T21:07:14.377 回答
-1

SetClipboardData复制给定全局句柄中的数据。关闭剪贴板后,应该释放全局句柄(而不是之前)。 GetClipboardData返回(内部)内存句柄;您应该将此句柄视为只读缓冲区。

于 2013-09-06T17:41:16.680 回答