VS2005环境下的DLL应用

作者:一点一滴的Beer http://beer.cnblogs.com/

    以前写过一篇题为《VC++的DLL应用(含Demo演示)》的文章,当时是刚开始接触DLL,而且所讲到的一些DLL的应用都是比较浅层次的数据传递,基本不具备很强的实用性,而且所选用的开发环境是VC6.0,这次因为做做WinCE开发的过程中需要用到这个技术,所以进行了比较深入的研究,而且此次用的是VS2005开发环境,对比VC6.0的一些操作略有不同,所以重新进行了整理。

    关于DLL的好处,我就不多说了,只需要记住几条:

1) 可以实现代码集成封装。

2) 实现生成的应用程序以文件为载体实现模块化。在升级程序版本的时候,不用重新对应用程序进行重新编译,则只需要将相应的DLL文件进行替换就行了。

3) 可以实现跨语言调用。对于一些用C#作为主要开发语言的程序,需要C++进行接近硬件的底层操作时,可以通过DLL技术,实现语言的“混合”编程,C#具有开发高效性的特点,C++具有运行高效性和对底层的良好操作性的优点,DLL技术可以实现两种语言优点的结合。

     注:这些技术在WinXp和WinCe上都测试过,如果没有特别说明,在两种平台下都可以使用的,微软的产品还是具有一定的通用性的。

 

1. VS2005建立基于C++的DLL项目

    本文主要是讲基于C/C++的DLL,因为这种基于C++的DLL不像C#建立的DLL那样依赖于.NET环境,移植性比较好。

    【文件】->【新建】->【项目】。选择C++语言里面的Win32控制台应用程序

clip_image002

    然后点击“确定”,再到后面的向导出进行设置

clip_image004

    “应用程序类型”选择“DLL”,可以选择公共头文件支持“ATL”或者“MFC”,一般都选择“MFC”。然后点击“完成”,那么VS2005就自动创建了一个基于C++的DLL模板了。

    生成项目,然后在对应的目录下面看到相应的DLL文件了,但是此时里面还没有任何功能,用户需要根据实际需求为DLL编写导出函数,然后供其它应用执行程序调用。

 

2. 为DLL添加自定义导出函数

    主要的函数类型有下面三种或者三种的任意组合:

1) 带传入参数无返回值函数。

2) 有返回值函数。

3) 带传出参数函数。

    前面的两种类型都比较简单,所以在下面也只作一些简单的介绍和代码演示。主要是第三种类型,在实现跨语言应用DLL的时候的作用最大,也是难度最高(反正自己是这么认为的)的一种高级应用吧,所以要进行详细介绍。

2.1带传入参数无返回值函数

    在以前的那篇关于DLL的文章中提到过,在此不再赘述了。

2.2有返回值函数

    一般只返回整数或者少量的字符串,这个应用也比较简单,用户到网上可以查到相关资料,所以也不再详细介绍了。

2.3带传出参数函数

    通过上面提到的两种类型的函数,可以实现简单的基本数据类型的传入的传出。比如,传入两个整数a,b到一个表示加法的导出函数中,然后返回两者的和。这个是可以做到的,实现起来也比较容易,所以在此不详细说明。两个来对两种稍微高级点的数据传递进行说明:“特殊数据结构”和“大量数据集合”,这个时候如果还用那种简单的形参传入,返回值传出就无法解决问题了。还有,如果你熟练地掌握了传出参数的使用方法,那么你完全可以用此类形的方法实现返回值函数的数据传出功能。不过,关于传出参数,要想熟练应用,还需对指针、地址等概念有比较好的掌握。

2.3.1特殊数据的传递

    对于大量数据的传出,返回值的方法是行不通的。比如,我曾经在写一个图像数据处理的函数的时候,需要DLL函数返回处理完后的图像数据,这个数据有150K,当时的想法是声明一个150K的数组,然后返回。但是后来遇到了一个问题:每次程序运行到这个函数的时候,都会出现问题“Microsfot Visual Studio正忙”然后就是Visual Studio假死没有反应。

clip_image006

    当时都不知道这是什么原因,到后来对数据的存储空间进行了一些学习后才逐渐有点明白了。可以参考文章:《堆栈,堆栈,堆和栈的区别》:里面一段关于“申请大小的限制 ”的说明:

  • 栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
  • 堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。

在C#中,声明一个150K甚至是1M的数组都是完全没有问题的,觉得可能是因为C#的数组在声明的时候本来就是用的new,也就是说本来就放在“堆空间”上的,然后最后用完后,由系统自动回收。但是C++中却不行,C++函数中声明的临时变量都是放在“栈空间”里面,大小是有限制的,如果太大会出现上面那种处理不过来的问题。所以,对于比较大的数据,C++中一般采用动态申请内存(malloc/free,new/delete),在“堆空间”上开辟存储空间,这个里面的上限就是剩余的内存大小,所以可以进行大量数据的缓存。

   

    不同语言之间的数据类型实现兼容。如果是同语言之间的调用,数据类型的兼容性就不会是问题了。如果是不同语言之间的调用,比如C#里面有很多高级的数据类型是C++里面没有的,这个时候就要考虑数据的兼容性了。在网上可以找到一些数据兼容的文章可以参考一下,例如:《C#调用C++的DLL搜集整理的所有数据类型转换方式》,如果有兴趣可以自己试试。其中有几个比较重要的:

  • C++的取地址符号对应C#中的ref引用关键字,可以用来传出整形等基本数据类型
  • C++里面的字节数组BYTE数组也直接对应着C#中的BYTE数组(事先指明了大小的)
  • C++里面的指针对应着C#中的IntPtr(可以用于动态分配内存的场合)

    虽然里面还有,C++中的字符串和C#中的StringBuilder对应,但是这个时候涉及到C#中在引用DLL的导出函数的时候,要设置编码格式,否则DLL传出的参数会出现乱码,XP下可以设置,WinCE下却没有。所以,笔者认为,这种方法不具有一般性和通用性。后来在编程学习的过程中,对数据的硬件存储有了一定的概念后,终于搞明白了一点了,其实任何复杂的数据类型在硬盘的存储形态都是01二进制编码的,用稍微高级点的眼光来看,就是以8位为一字节来存储和描述的,比如:不管是什么文件,实际上都是二进制流;复杂点的数据如字符串,也可以用一个整数数组来描述;结构体,实际上也是一系列数据的存储介质上按字节来排列存储的。所以,任何的数据类型都可以转成一个BYTE(unsigned char)数组进行表示,同样,这个BYTE数组也可以还原成原先定义的那种复杂的数据类型。

    对于一些大小事先就能确定的数组,可以直接用数组作为C++语言的DLL和C#的EXE之间的共同数据通道。对于一些大小不确定的(需要在DLL程序中动态申请的内存块),可以用指针来作为共同的数据通道,在C#中有个IntPtr,从DLL中传出内存块的地址和数据区域的大小后,C#的EXE程序就可以通过相应的接口函数将这些内存块中的数据拷贝出来到一个BYTE数组中。

    C#中的new的数据类型,就相当于C++中的malloc一样,动态分配了内存,只是在C#的EXE程序中不需要由程序员自己去释放,所以C#中new的数据,C++的DLL中可以直接把它看成malloc后的数据,同时在C#使用数据完毕后,不用自己手动释放的(现在还不知道这个猜测是不是对的),DLL中malloc得到的动态内存空间传到C#的EXE程序中后,不知道C#中是否需要手动编写代码进行释放?。

    注:本来是想重点对传出参数进行介绍,而且还附演示代码,但是后来想想都是一些细节,写下来太繁琐,而且源代码一直没有时间去完善,使它能包含上面所有的函数示例,所以,就主要把自己的心得体会还有疑问写下来和大家分享下,如果今后有时间的话,再对这一部分进行完善吧。今后可以会专门写一个关于DLL跨语言传递动态申请空间的数据的总结的。

 

3. DLL的调用

3.1 C++程序的调用

3.2 C#程序的调用

这部分在以前一篇文章中已经进行了详细介绍,在此不再重复了。VS2005和VC6.0在这个步骤上的操作一样。详情请见《VC++的DLL应用(含Demo演示)》。

 

4. DLL调试

    以前写的一篇关于DLL的文章,里面用的是VC6.0,当时还不知道其实一个“工作区”可以包含多个“项目”,所以,就可以直接实现C++的DLL和EXE源码的联调的,但是C#应用程序的话,还是不行。在VS2005中,这点就比较好的解决了。在VS2005的“解决方案资源管理器”中,一个“解决方案”里面可以建立多个“项目”,这些项目可以是不同的语言项目。所以,VS2005中的跨语言调试比VC6.0中更方便一些。

    首先,在VS2005的同一个解决方案中建立三个项目,一个DLL项目(用来生成DLL文件),一个C++项目和一个C#项目(用来调用DLL并进行测试)。

对DLL项目编写相关源码,实现相应的导出函数,然后生成DLL文件,对DLL的项目属性进行参数设置,调试选项中的“命令”项设置成对应的EXE程序。将DLL文件放到相应的EXE程序的目录下面,然后就可以通过右键相应的项目选择【调试】对相应的项目进行调试了。如果是C++的EXE项目,在调试的时候,遇到DLL的导出函数,然后单步执行,可以进入到本解决方案下的DLL项目的源码中,实现两个项目的代码的联调。对于C#执行程序,也可以进行联调,但是要在DLL项目属性中对“调试属性”进行设置,调试器类型选择“混合”模式,就可以实现不同语言的两项目的源码联调了。

clip_image008

    上面的调试方法讲的都是WinXp下的,在WinCE系统中,目前根据笔者的经历,好像跨语言的时候是不能实现源码级的联调的,只能要么对EXE进行调试,要么对DLL进行调试,分开调试,其实起到的效果一样,只是调试启动的项目不同而已。对于同语言项目的调用,比如:从DLL项目启动调试,调用EXE,在DLL和EXE项目中可以同时断点成功。但是从EXE项目启动的话,就无法断到DLL源文件中(XP环境下可以)。

    注:本文所讲的内容,除了特别指出地区别外,其余的技术在WinXP和WinCE平台上都是通用的,经过了实地测试的。

 

参考文章:

堆栈,堆栈,堆和栈的区别》:

http://www.cppblog.com/oosky/archive/2006/01/21/2958.html

VC++的DLL应用(含Demo演示)

http://www.cnblogs.com/beer/archive/2010/08/19/1803560.html

C#调用C++的DLL搜集整理的所有数据类型转换方式

http://blog.csdn.net/xqf222/archive/2010/09/11/5877795.aspx

------------------------------------------------------------------

Author:一点一滴的Beer

Email /Gtalk:dreamzsm@gmail.com

From:http://www.cnblogs.com/beer

Notes:欢迎转贴,但请在页面中加个链接注明出处

Time:2010-11-20 in Whu

posted on 2018-03-11 14:24  xmj  阅读(280)  评论(0编辑  收藏  举报