zoukankan      html  css  js  c++  java
  • 跨越DLL边界传递CRT对象潜在的错误

    跨越DLL边界传递CRT对象潜在的错误

     

    翻译:magictong(童磊)20135

    版权:microsoft 

    原文地址:http://msdn.microsoft.com/en-us/library/ms235460(v=vs.80).aspx


     

     

    简介

    当你把C运行时(CRT)对象(譬如文件句柄、语言环境和环境变量等等)传入传出DLL时(通过调用DLL里面暴露的一些函数),如果这个DLL加载了一份(与可执行文件)不同的CRT库,可能发现意向不到的事情。

    有一个大家可能遇到过相似的问题是,如果可执行程序在外面分配一块内存(显示的通过new或者malloc分配,或者通过调用strdupstrstreambuf::str等等函数隐式的分配),然后把分配得到的内存指针通过调用DLL暴露的函数传入DLL里面去释放,如果这个DLL加载了一份不同的CRT库,可能造成内存访问违例(AV)或者堆破坏。

    如果你正在调试程序,这个问题可能在调试输出窗口打印一条如下的错误信息:

    HEAP[]: Invalid Address specified to RtlValidateHeap(#,#)


     

    原因

    每个CRT库的副本都有一个单独并且独特的状态,因此,一些类似文件句柄,语言环境和环境变量这样的CRT对象,仅仅在分配它或者设置它的CRT库里面有效。当一个DLL和使用这个DLL的程序分别使用了两个不同的CRT库时,如果你把这些CRT对象在DLL和使用DLL的程序两边相互传递,并希望程序运行正常,那是不太可能的。

    另外,因为每个CRT库的副本都有一个自己的堆管理器,在一个CRT库里面分配的内存传递(通过调用DLL暴露的函数)到另外一个CRT库里面去释放可能导致堆破坏

    如果你想设计一个可以传递CRT对象的DLL,或者明确的在DLL内部分配内存,而在DLL的外部释放,那么你必须限制这个DLL的使用者使用和DLL一样的CRT库。而要想DLL的使用者使用和DLL一样的CRT库除非二者链接到相同版本的CRT动态链接库。如果应用程序和DLL的编译环境不一样,譬如应用程序使用Visual C++5.0编译而DLL使用Visual C++ 4.1编译或者更早版本,因为Visual C++ 4.1使用的CRT库是msvcrt40.dll,而Visual C++5.0使用的是msvcrt.dll,这可能会非常麻烦。你可能无法重新编译你的应用程序使得和DLL使用的CRT库一模一样。

    不过,有一个特殊情况。在一些特殊版本的Windows NT 4.0Windows 2000里面(譬如,美国英语版本,德国,法国和捷克的本地化版本),使用了一个代理模式的msvcrt40.dll(具体版本是4.20)。在这些环境里面,虽然DLL链接的是msvcrt40.dllDLL的使用者(应用程序)链接的是msvcrt.dll,但是因为所有对msvcrt40.dll的调用都被转调用到了msvcrt.dll,因此这种情况二者仍然使用的是同一个CRT库。

    然而,这个代理版本的msvcrt40.dllWindows 95,Windows 98,Windows Me和其它一些本地化的Windows NT 4.0Windows 2000(譬如日本,韩国和中国)版本里面是无效的。因此,如果你的应用程序的目标操作系统是这些环境,你需要获得一个不依赖msvcrt40.dll的升级版本的DLL,或者更改你的应用程序。如果你已经把这个DLL开发出来了,那么使用Visual C++ 4.2或者更高版本重新编译一下DLL,如果这个DLL是第三方提供的组件,你可能需要相关开发商提供一个升级。


     

    例子一

    描述

    这里是一个跨越DLL边界传递文件句柄的实例。

    假如DLL文件和.exe文件都是使用/MD来编译,那么它们使用同一份CRT库,这没有问题。

    如果你使用/MT来重新编译,使得它们使用不同的CRT库,然后运行test1Main.exe,马上就会出现访问违例(access violation)。

     

    代码

    // test1Dll.cpp

    // compile with: /MD /LD

    #include <stdio.h>

    __declspec(dllexport) void writeFile(FILE *stream)

    {

       char   s[] = "this is a string\n";

       fprintf( stream, "%s", s );

       fclose( stream );

    }

     

    代码

    // test1Main.cpp

    // compile with: /MD test1dll.lib

    #include <stdio.h>

    #include <process.h>

    void writeFile(FILE *stream);

     

    int main(void)

    {

       FILE  * stream;

       errno_t err = fopen_s( &stream, "fprintf.out", "w" );

       writeFile(stream);

       system( "type fprintf.out" );

    }

     

    输出

    this is a string


     

     

    例子二

    描述

    这个例子说明跨DLL传递环境变量的问题。

     

    代码

    // test2Dll.cpp

    // compile with: /MT /LD

    #include <stdio.h>

    #include <stdlib.h>

     

    __declspec(dllexport) void readEnv()

    {

       char *libvar;

       size_t libvarsize;

     

       /* Get the value of the MYLIB environment variable. */ 

       _dupenv_s( &libvar, &libvarsize, "MYLIB" );

     

       if( libvar != NULL )

          printf( "New MYLIB variable is: %s\n", libvar);

       else

          printf( "MYLIB has not been set.\n");

       free( libvar );

    }

     

    代码

    // test2Main.cpp

    // compile with: /MT /link test2dll.lib

    #include <stdlib.h>

    #include <stdio.h>

     

    void readEnv();

     

    int main( void )

    {

       _putenv( "MYLIB=c:\\mylib;c:\\yourlib" );

       readEnv();

    }

    如果DLL.EXE文件都使用/MD来编译,那么二者使用同一份CRT库,此时输出是New MYLIB variable is: c:\mylib;c:\yourlib,而如果二者都使用/MT编译,那么输出将是MYLIB has not been set.

    (注:以上两个实例,译者magictong并没有进行过测试,如果有问题,请告诉magictong


     

    参考文档

    C运行时库 http://msdn.microsoft.com/en-us/library/abx4dbyh(v=vs.100).aspx

     

  • 相关阅读:
    PHP文件下载
    win7的IE11降到IE8
    京东电话面试——PHP开发
    PHP异常处理
    php错误处理
    php练习7——类的运用(四则运算or面积计算[javascript小技巧——根据需求显示不同界面])
    php练习6——面向对象编程(打印乘法表)
    SICP阅读笔记(一)
    随笔(2015-18-19)
    MIT scheme入门使用
  • 原文地址:https://www.cnblogs.com/javawebsoa/p/3078635.html
Copyright © 2011-2022 走看看