我在使用剪切板时,发现通用的都是使用GlobalAlloc来分配内存,我就想不是说在Win32中GlobalAlloc和LocalAlloc是一样的那为什么不用LocalAlloc呢,原谅我的好奇心吧,对此我研究了一番,如果你也有此疑问,可以看一看。

先看一看关于GlobalAlloc和LocalAlloc的由来,这里有前人做了比较好的解释,这里为了保证博客连贯性,直接截取过来(原文链接)


16位windows用一个全局堆和局部堆来管理内存,每一个应用程序或dll装入内存时,代码段被装入全局堆,而系统又为每个实例从全局堆中分配了一个64kb的数据段作为该实例的局部堆,用来存放应用程序的堆栈和所有全局或静态变量。而LocalAlloc/GlobalAlloc就是分别用于在局部堆或全局堆中分配内存。

由于每个进程的局部堆很小,所以在局部堆中分配内存会受到空间的限制。但这个堆是每个进程私有的,相对而言分配数据较安全,数据访问出错不至于影响到整个系统。  而在全局堆中分配的内存是为各个进程共享的,每个进程只要拥有这个内存块的句柄都可以访问这块内存,但是每个全局内存空间需要额外的内存开销,造成分配浪费。而且一旦发生严重错误,可能会影响到整个系统的稳定。

不过在Win32中,每个进程都只拥有一个缺省的私有堆,它只能被当前进程访问。应用程序也不可能直接访问系统内存。所以在Win32中全局堆和局部堆都指向进程的省缺堆。用LocalAlloc/GlobalAlloc分配内存没有任何区别。甚至LocalAlloc分配的内存可以被GlobalFree释放掉。所以在Win32下编程,无需注意Local和Global的区别。


结合上面的解释可知道,在Windows早期中的跨进程共享数据是通过将数据内存分配在共享内存(全局堆)中,然后使Clipboard拥有它来保证跨进程通信的。

那么刚刚说道GlobalAlloc和LocalAlloc分配的内存都是在进程的堆上,那么他们是如何来完成进程间共享数据的呢。这个只能依靠Clipboard函数了。事实上这个涉及到虚拟内存的内容(如果你对虚拟内存不了解的话,可以看一下我的这篇博文中的第一部分内容),在《Windows核心编程》书中讲到进程的地址空间划分,其中提到进程的内核模式分区是操作系统代码的驻地,其中的所有东西为所有进程所有,再结合MSDN中SetClipboardData中描述"AfterSetClipboardData is called, the system owns the object identified by the hMem parameter. The application can read the data, but must not free the handle or leave it locked.If the hMem parameter identifies a memory object, the object must have been allocated using the GlobalAlloc function with the GMEM_MOVEABLE and GMEM_DDESHARE flags. "那么我们很自然会想到当调用SetClipboardData后,系统在虚拟内存中移动要共享的内存到进程的内核模式分区中托管,这也是为什么一定要指明GMEM_MOVABLE的原因。

聪明的小伙伴你一定会问那为什么一定要指定GMEM_DDESHARE 参数呢,我们查看MSDN中GlobalAlloc中关于GMEM_SHARE和GMEM_DDESHARE参数的描述,“This flag is provided primarily for compatibility with 16-bit Windows. However, this flag may be used by some applications to enhance the performance of DDE operations and therefore can be specified if the memory is to be used for DDE. .”显然这里提供GMEM_SHARE和GMEM_DDESHARE参数只是为了兼容以前的程序编译,事实上你尽管把SHARE参数去掉吧,没什么问题,一切运行如初。

那么,既然这样了,使用LocalAlloc应该没有什么问题吧。

事实上在如下的程序中测试GlobalAlloc和LocalAlloc完全正常

剪切和复制

  1. case IDM_EDIT_CUT:
  2. case IDM_EDIT_COPY:
  3. if (!pText)
  4. {
  5. return (0);
  6. }
  7. #ifdef GLOBALFUNC
  8. //将Text数据复制到Global分配的数据区
  9. hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, (lstrlen(pText) + 1) * sizeof(TCHAR));
  10. pGlobal = GlobalLock(hGlobal);
  11. lstrcpy(pGlobal, pText);
  12. GlobalUnlock(hGlobal);
  13. //将Global数据设为剪切板所有以实现跨进程使用
  14. OpenClipboard(hwnd);
  15. EmptyClipboard();
  16. SetClipboardData(CF_TCHAR, hGlobal);
  17. CloseClipboard();
  18. #else
  19. //将Text数据复制到Global分配的数据区
  20. hGlobal = LocalAlloc(LMEM_MOVEABLE | LMEM_ZEROINIT, (lstrlen(pText) + 1) * sizeof(TCHAR));
  21. pGlobal = LocalLock(hGlobal);
  22. lstrcpy(pGlobal, pText);
  23. LocalUnlock(hGlobal);
  24. //将Global数据设为剪切板所有以实现跨进程使用
  25. OpenClipboard(hwnd);
  26. EmptyClipboard();
  27. SetClipboardData(CF_TCHAR, hGlobal);
  28. CloseClipboard();
  29. #endif
  30. if(LOWORD(wParam) == IDM_EDIT_COPY)
  31. {
  32. return (0);
  33. }
  34. //对于剪切还要向下执行以清除Text数据
  35. case IDM_EDIT_CLEAR:
  36. if (pText)
  37. {
  38. free(pText);
  39. pText = NULL;
  40. }
  41. InvalidateRect(hwnd, NULL, TRUE);
  42. return (0);

粘贴

  1. case IDM_EDIT_PASTE:
  2. fdef GLOBALFUNC
  3. OpenClipboard(hwnd);
  4. if (hGlobal = GetClipboardData(CF_TCHAR))
  5. {
  6. pGlobal = GlobalLock(hGlobal);
  7. if (pText)
  8. {
  9. free(pText);
  10. pText = NULL;
  11. }
  12. pText = malloc(GlobalSize(hGlobal));
  13. lstrcpy(pText, pGlobal);
  14. InvalidateRect(hwnd, NULL, TRUE);
  15. GlobalUnlock(hGlobal);
  16. }
  17. CloseClipboard();
  18. lse
  19. OpenClipboard(hwnd);
  20. if (hGlobal = GetClipboardData(CF_TCHAR))
  21. {
  22. pGlobal = LocalLock(hGlobal);
  23. if (pText)
  24. {
  25. free(pText);
  26. pText = NULL;
  27. }
  28. pText = malloc(LocalSize(hGlobal));
  29. lstrcpy(pText, pGlobal);
  30. InvalidateRect(hwnd, NULL, TRUE);
  31. LocalUnlock(hGlobal);
  32. }
  33. CloseClipboard();
  34. ndif
  35. return (0);

那么,很明显这个问题的答案令人感到可笑,所谓剪切板必须使用GlobalAlloc的说法不过是前人遗留的写法而已,在现在的操作系统(至少的得是XP了吧)中使用GlobalAlloc和LocalAlloc什么差别也没有。

如果你问我要使用哪个函数,那还是使用GlobalAlloc吧,毕竟你拿不准是否有人还会在年代久远的windows系统上编译你的程序呢。但是有一点需要说明的是,千万不要再说必须使用GlobalAlloc了。

其实再大胆一点不用GMEM_MOVABLE而使用GMEM_FIXED参数分配内存,你会发现一切还是正常的,原来SetClipboardData对这个分配的内存类型都没有了检查,直接强制性的移动了。尽管是个古老的东西,在现在的Windows操作系统中剪切板还是非常的重要,但是微软将它的标准放的这么低来保证程序的兼容性,说不定这个里面藏有好几个漏洞呢,这个黑客们慢慢探讨吧。

博客测试源代码下载

原创,转载请注明来自http://blog.csdn.net/wenzhou1219

http://blog.csdn.net/wenzhou1219/article/details/17693241

05-28 18:04