我在哪里可以获得有关剪贴板内部工作的信息?

时间:2013-09-06 16:46:31

标签: c# string winapi clipboard

我想阅读一些有关如何在其中使用Windows剪贴板的文章或书籍,但我找不到至少一篇比标准API参考或使用示例更深入的文章。

我正在使用Windows剪贴板上的标准winapi,我获得了一些奇怪的结果。

案例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:我向剪贴板写了一些字符串,关闭剪贴板,更改字符串(在非托管内存中)并再次打开剪贴板并读取此字符串。令我惊讶的是,我获得了SAME字符串地址和我的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执行后立即释放我的非托管内存字符串?

我有很多问题,我认为有些文献会说清楚

更新:

对雷蒙德·陈,乔纳森·波特,埃里克·布朗说:感谢您的回答,但我编辑了第二次测试,它会更正确,现在显示如下:  我更改源字符串BEFORE剪贴板关闭,我可能认为它是有效的操作,它删除了剪贴板关闭后我做的答案。然后我得到这个字符串,结果显示已更改,然后通过调用GetClipboardData得到此字符串,结果显示字符串已更改且指针相同。然后我关闭剪贴板再次打开它并再次读取字符串。我现在得到了什么?字符串地址与源字符串的地址相同,但字符串是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);

            //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

2 个答案:

答案 0 :(得分: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);

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

这称为未定义的行为。该语言没有定义在这种情况下发生的事情。当你释放内存时,你不再拥有它,你不能写它,也不能读它。如果它工作,或似乎工作,这是巧合,仅此而已。无法保证它始终有效。没有必要继续执行测试以试图证明它确实起作用 - 没有任何改变行为未定义的事实。它可能会起作用,也可能不起作用。不要这样做。

答案 1 :(得分:-1)

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