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

翻译:magictong(童磊)2013年5月

版权:microsoft

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

简介

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

有一个大家可能遇到过,相似的问题是,如果可执行程序在外面分配一块内存(显示的通过new或者malloc分配,或者通过调用strdup,strstreambuf::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.0和Windows 2000里面(譬如,美国英语版本,德国,法国和捷克的本地化版本),使用了一个代理模式的msvcrt40.dll(具体版本是4.20)。在这些环境里面,虽然DLL链接的是msvcrt40.dll,DLL的使用者(应用程序)链接的是msvcrt.dll,但是因为所有对msvcrt40.dll的调用都被转调用到了msvcrt.dll,因此这种情况二者仍然使用的是同一个CRT库。

然而,这个代理版本的msvcrt40.dll在Windows 95,Windows 98,Windows Me和其它一些本地化的Windows NT 4.0和Windows 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

http://blog.csdn.net/magictong/article/details/8927049

跨越DLL边界传递CRT对象潜在的错误的更多相关文章

  1. DLL中传递STL参数,vector对象作为dll参数传递等问题(转)

    STL跨平台调用会出现很多异常,你可以试试. STL使用模板生成,当我们使用模板的时候,每一个EXE,和DLL都在编译器产生了自己的代码,导致模板所使用的静态成员不同步,所以出现数据传递的各种问题,下 ...

  2. DLL中传递STL参数(如Vector或者list等)会遇到的问题[转载]

    最近的一个项目中遇到了调用别人的sdk接口(dll库)而传给我的是一个vector指针,用完之后还要我来删除的情况.这个过程中首先就是在我的exe中将其vector指针转为相应指针再获取vector中 ...

  3. Delphi 中的DLL 封装和调用对象技术(刘艺,有截图)

    Delphi 中的DLL 封装和调用对象技术本文刊登2003 年10 月份出版的Dr.Dobb's 软件研发第3 期刘 艺摘 要DLL 是一种应用最为广泛的动态链接技术但是由于在DLL 中封装和调用对 ...

  4. C# 调用Webservice并传递序列化对象

    原文:C# 调用Webservice并传递序列化对象 C#动态调用WebService注意要点 1.动态调用的url后面注意一定要加上?WSDL   例如:string _url = "ht ...

  5. C#在WinForm中使用WebKit传递js对象实现与网页交互的方法

    这篇文章主要介绍了C#在WinForm中使用WebKit传递js对象实现与网页交互的方法,涉及针对WebBroswer控件及WebKit控件的相关使用技巧,需要的朋友可以参考下 本文实例讲述了C#在W ...

  6. Android 通过 Intent 传递类对象或list对象

    (转:http://www.cnblogs.com/shaocm/archive/2013/01/08/2851248.html) Android中Intent传递类对象提供了两种方式一种是 通过实现 ...

  7. Intent.putExtra()传递Object对象或者ArrayList<Object> (转)

    Intent传递基本类型相信大家都十分熟悉,如何传递Object对象或者ArrayList<Object>对象呢? 可以通过: (1)public Intent putExtra (Str ...

  8. Android剪切板传递数据传递序列化对象数据

    一.剪切板的使用介绍 1. 剪切板对象的创建 使用剪切板会用到,ClipboardManager对象,这个对像的创建不可以使用构造方法,主要是由于没有提供public的构造函数(单例模式),需要使用A ...

  9. Android 通过 Intent 传递类对象

    Android中Intent传递类对象提供了两种方式一种是 通过实现Serializable接口传递对象,一种是通过实现Parcelable接口传递对象. 要求被传递的对象必须实现上述2种接口中的一种 ...

随机推荐

  1. spark rdd持久化的简单对比

    未使用rdd持久化 使用后 通过对比可以发现,未使用RDD持久化时,第一次计算比使用RDD持久化要快,但之后的计算显然要慢的多,差不多10倍的样子 代码 public class PersistRDD ...

  2. 常规容器下SpringBootServletInitializer如何实现web.xml作用解析

    在之前的<使用jsp作为视图模板&常规部署>章节有过一个实践,需要启动类继承自SpringBootServletInitializer方可正常部署至常规tomcat下,其主要能够起 ...

  3. 英文构词法 —— circum- 前缀

    1. - circum-:表示环绕,周围,圆周: circle:圆:循环: circumference:圆周,周长,胸围: circumstance:环境: circumnavigation:环球航行 ...

  4. hadoop编程技巧(8)---Unit Testing (单元测试)

    所需的环境: Hadoop相关jar包裹(下载版本的官方网站上可以): 下载junit包裹(新以及). 下载mockito包裹: 下载mrunit包裹: 下载powermock-mockito包裹: ...

  5. E: Could not get lock /var/lib/dpkg/lock(无法获得锁)

    出现这个问题可能是有另外一个程序正在运行,导致资源被锁不可用.而导致资源被锁的原因可能是上次运行安装或更新时没有正常完成,进而出现此状况,解决的办法其实很简单.有以下两种解决办法: 1. 强制解锁 执 ...

  6. 使用elasticsearch遇到的一些问题以及解决方法(不断更新)

    7.org.elasticsearch.transport.RemoteTransportException: Failed to deserialize exception response fro ...

  7. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

  8. UITextField设置leftView的Insets

    Insets就是css中的padding 我们给UITextField设置了leftView,目的是在文本输入框左側显示一个图标.可是在ios7里,这个图标会紧紧地挨着TextField的左边框,非常 ...

  9. 3D场景中的鼠标响应事件

    原文:3D场景中的鼠标响应事件 今天要讲的是3D场景中的鼠标响应事件的处理,首先Button的响应是大家熟知的,只要加上一个click事件,然后写一个响应的处理时间就行了.对于二维平面上的一些控件也很 ...

  10. 【Java】【Flume】Flume-NG阅读源代码AvroSink

    org.apache.flume.sink.AvroSink是用来通过网络来数据传输的.能够将event发送到RPCserver(比方AvroSource),使用AvroSink和AvroSource ...