当前位置:C++技术网 > 资讯 > MFC的资源冲突是怎么回事?如何防止资源冲突?

MFC的资源冲突是怎么回事?如何防止资源冲突?

更新时间:2015-10-17 20:53:18浏览次数:1+次

    在研究这个问题时,我感慨很多,然后就写了一篇学习方法的文章《由一个技术问题引发了对练就大神的深度剖析》。
    这个问题曾经在以前的公司工作时出现过。我们的项目分为模块,不同的人负责不同的模块,每一个模块都以dll形式提供,最后整合到主程序中。当然我们项目是用MFC做的,自然就会有资源冲突的问题。
    为了防止资源冲突带来的软件Bug,我们每次正式升级发布软件时,在打包软件前,都要做资源冲突检测。不过,这个具体怎么检测,很惭愧,我没有在意,所以也不太清楚他们怎么整的。然后因为这个事情,我听说了资源冲突的事情,听他们解释说是多个Dll的资源在调用时可能出现。
    之前就了解到这些,没有深入了解,就是这么一个状态。然后群里一个群友问了资源冲突是怎么回事,我一时语塞了,因为我一时竟然说不清了。我发现是我没有真正搞懂,确实没有搞懂。所以,我去请教原来的公司的同事,他是大牛!在请教之前,我是查了资料的,也更了解了。然而,还是不够,这是请教了之后才知道的。
    我还一直有一个问题,就是RC资源定义,一个资源ID对应多种类型的资源,会不会冲突?这个问题我在《RC资源文件定义使用分析:RC资源定义中不存在资源冲突》里已经讲清楚了。
    现在来讲一下资源冲突是怎么回事。这里的资源就是资源文件中定义的这些东西,比如对话框,字符串等。我们的资源文件即RC文件,编写好后,会用资源编译器编译成二进制的资源文件数据,对于图片图标这些,将会被保存到二进制资源文件中,这个文件扩展名为res,如下图所示:

    编译的资源文件res
    在链接的时候,链接器会将的二进制obj代码文件、二进制的res资源文件和二进制的库文件等都整合放在一起,创建一个exe文件。
    当然,我们有时候需要将一些好的资源放在一个dll里,这样,可以给很多的程序使用,这就是常见的界面库的做法。当我们的exe程序运行后,如果需要使用dll,就会加载dll。然后,我们可以使用exe里的资源,也可以使用dll的资源。exe和dll就相当于容器一样,存储了程序运行时需要的东西。
    API函数中的加载资源的第一个参数就是实例句柄,可以允许我们在不同的实例中加载资源。但是MFC把实例句柄封装成了一个类的成员变量,简化了操作,包装了API函数,因此,不需要每次都填写实例句柄这个参数,开发变得简单。
    而要加载不同模块的资源,MFC提供了一个机制,就是有一个当前(默认)资源模块的机制。程序会从当前资源模块中查找资源,而默认当前资源的自然是exe模块。如果你不指明调用哪个模块,在MFC中,就默认在exe中找这个资源了。如果找不到指定类型的资源ID的资源,那就会搜索其他模块的资源,比如在exe中搜索一个ICON图标资源,调用的函数是LoacIcon,然后没有找到,然后就会去搜索其他模块比如dll的资源,如果也找不到,那就宣告资源不存在,如果找到就用找到的资源而不再继续搜索了。这里有个搜索顺序,从设定的当前资源模块开始搜索,然后是其他的模块。所以,如果你的EXE中的资源ID和dll里的资源ID重复,那么默认情况下,先搜索exe模块,因为默认的资源模块是exe,如果你想使用dll的资源,就用不了了。因为在搜索过程中,先在exe中找到了,就不继续找了。
    在exe中调用Dll的资源,先设置一下默认的资源模块,代码如下:

HINSTANCE hRes = NULL;
hRes= LoadLibrary("ResourceD.dll");
if(hRes)
    AfxSetResourceHandle(hRes);
     这里是在exe中执行的代码。首先加载的是dll,然后设置好当前的资源实例句柄,这样再调用加载资源的函数,就会从dll优先搜索,这样,即使exe和dll的相同的资源ID重复,也是使用dll的。
    还有一种情况,就是你在dll中调用函数LoadIcon,你以为默认的资源模块是dll,但是实际上,默认的还是exe模块。代码如下:
LoadIcon(IDR_TEST);// - dll中的代码
     所以,你一定要显示的切换模块,调用的函数为AfxSetResourceHandle。dll中的代码如下:
HINSTANCE save_hInstance = AfxGetResourceHandle();// - 获取当前的资源的实例
AfxSetResourceHandle(theApp.m_hInstance);// - 设置dll为资源实例
LoadIcon(IDR_TEST);// - dll中的代码
AfxSetResourceHandle(save_hInstance); // - 恢复原先的资源实例
     以上解释了在exe中使用exe中的资源,就什么也不用管,直接用就行。如果要在exe中使用dll的资源,最好是将当前资源模块设置为dll的,这样就可以优先使用dll的资源。而在dll中如果只是简单的直接调用dll的资源是不行的,因为它会默认先从exe中找。所以最好先将dll设置为当前资源模块。
    而我们所说的资源冲突,就是不同的模块的相同类型的资源有相同ID,可能使用的时候找错了模块导致调用错误。
    当然,如果在exe和dll中不存在相同的资源ID,就不存在这个问题。只要定义过,始终都会搜索到的。如果你不幸遇到这个问题,那么要么将资源ID做调整,让他们不重复,或者在调用时提前设置好资源模块句柄即可。这个在API中是不可能出现的,因为API函数都需要指定实例句柄,这个是MFC的封装后带来的一种现象。