天天看点

解读各种内存分配函数的差别

今天我们来讲讲几种容易混淆的内存分配函数的区别,它们分别是:SHGetMalloc, SHAlloc, CoGetMalloc, 和CoTaskMemAlloc。

让我们先从简单的开始吧。

首先,CoTaskMemAlloc实际上等效于:CoGetMalloc(MEMCTX_TASK) + IMalloc::Alloc。

CoTaskMemFree等效于CoGetMalloc(MEMCTX_TASK) + IMalloc::Free。

CoTaskMemAlloc和CoTaskMemFree(还有更少使用的CoTaskMemRealloc)只是一种包装函数,这样开发者调用的时候就不需要涉及CoGetMalloc,这样就可以避免一些错误和麻烦。

所以,你可以安全地使用CoGetMalloc(MEMCTX_TASK) + IMalloc::Alloc来进行内存分配,然后使用CoTaskMemFree来释放它,效果都是一样的。

类似地,SHAlloc和SHFree是对SHGetMalloc的一个封装,它们主要通过外壳任务内存分配器来分配和释放内存。使用SHGetMalloc + IMalloc::Alloc分配的内存可以被SHFree释放掉。

我们将上述文字表述为下图:

解读各种内存分配函数的差别

有些人可能会注意到,上图中有两个问号,这个是什么意思呢?

如果你查看一下shlobj.h中的注释,你应该会得到一些提示。

解读各种内存分配函数的差别

小插曲:没错,你会上图中看到一些错误的拼写,如”guild”和”Windowso”,这些是真实存在的,早在1995年它们就已经在那里了。

下面我们详细解析一下。

在开发Windows 95的时候,计算机一般只有4MB的内存(某些人可能会有一台8MB的机器)。但是桌面资源管理器非常依赖外壳扩展COM架构,而将OLE32.DLL这个组件加载到内存是十分消耗内存的行为。在当时的机器配置上,即使是消耗4KB控件也是一个十分巨大的消耗。

开发人员给出的解决方案是:”OLE Chicken”。

桌面资源管理器最终还是没有过多地使用COM:它支持的唯一对象是一个没有列集的进程内套间线程对象。因此,开发团队编写了一个”mini-COM”,用来支持这些操作,而不是执行真实的操作。(很幸运的是,当时资源管理器开发人员中就有一个很精通COM的专家。)

所以,资源管理器有它自己的简化版的任务分配器,它自己的绑定器和它自己的拖放循环,其他任何使用OLE32的应用程序都不能执行它,除了资源管理器自己。

一旦一些其他的使用了OLE32的应用程序开始运行,你就会碰到这样一个问题:系统里有两个单独分开的OLE版本:一个是真实的版本,一个是资源管理器中虚拟的版本。除非做某些特殊的操作,否则你无法在真实的COM和虚拟COM中进行互操作。举个例子,你不可以在资源管理器(使用虚拟COM)和一个使用了真实COM的应用程序之间进行数据拖放。

解决方案

通过系统其它部件的协调和帮助,资源管理器会在任何人加载OLE32.DLL时检测到”有人在使用真实的COM版本”,然后它会将所有的信息转发给真实的COM。当它这样实现之后,所有的虚拟COM代码也会同时切换真实的COM代码。举个例子,一旦OLE32.DLL完成加载,则对资源管理器的虚拟任务分配器的调用会被转发给其对应的正式版本。

但是,上面所说的”OLE Chicken”是什么东西?

在”OLE Chicken”中,每一个应用程序会尽可能地避免加载OLE32.DLL,这样它就不会因此受到指责(首次加载OLE32需要花费很长的时间,毕竟当时的操作系统上分配32KB,是非常消耗内存空间的事情)。

好了,让我们再来看看上面代码的注释部分。

在本文的开头,我们提到,一个外壳扩展自身不会链接到OLE32.DLL。

选项(1)讨论了确实使用OLE32的外壳扩展,在这种情况下,它应该使用官方的OLE函数(例如CoGetMalloc)。 但是选项(2)讨论了不使用OLE32的外壳扩展。这些外壳扩展旨在使用外壳的虚拟COM函数(例如SHGetMalloc),而不是真实版本的COM函数,因此不会创建对OLE32的新依赖关系。 因此,如果尚未加载OLE32,则加载这些外壳程序扩展名也不会导致OLE32加载,从而节省了加载和初始化OLE32.DLL的成本。

因此,适用于Windows 95年代的图表如下图所示:

解读各种内存分配函数的差别

最后的”注释”提示了外壳打算去的方向。最终,加载OLE32.DLL不会像在Windows 95中那样痛苦,并且外壳程序可以放弃其虚拟COM,而只使用真实的东西。 此时,要求外壳程序任务分配器将与要求COM任务分配器相同。

那个时间实际上是很久以前到的。4MB机器的时代如今已成为传奇。 该外壳放弃了其虚拟COM,现在仅在任何地方都使用真实的COM。

因此,在Windows XP及更高版本中,所有这四个功能都是可以互换的。

如果要在较旧的系统上运行怎么办? 好吧,始终可以使用

CoTaskMemAlloc/CoTaskMemFree。 为什么? 你可以从逻辑上解决这个问题。 由于这些函数是从OLE32.DLL导出的,因此你正在使用它们的事实意味着已加载OLE32.DLL,此时,上面带有等号的“ After”图将启动,一切都会按照预期般运作良好。

你需要注意的情况是,你的DLL没有链接到OLE32.DLL。 在那种情况下,你不知道自己是处于“之前”还是“之后”情况,因此必须谨慎行事,并使用Shell任务分配器处理记录为使用Shell任务分配器的内容。

总结

最后