C++编写可供C#调用的算法模块,算法有实时性要求,而且涉及大量的处理数据(摄像头采集的多帧大分辨率图像)
需要学习如何编写规范的、零缺陷的、低耦合的dll代码
符合规范的dll
别人的代码,不同的开发语言(C#、java等)可以通过标准的接口调用dll
零缺陷的dll
开发过程不要留下后遗症
注意细节,如动态内存的管理,不要产生野指针
低耦合的dll
封装性,内部变量、与调用功能无关的变量不要暴露给调用方,有利于后期维护
调用方永远是上帝,dll接口只需要——简单易用不出错
针对C#调用dll
dll中的内存管理——可以解决耦合、异常等问题,调用过程所涉及的所有数据交互,调用方只需要简单的传递int、float等变量,不出现任何指针变量
大量数据交给dll管理下的内存池,所有接口函数都通过该内存池读写数据,内存池在程序开始运行时建立,结束运行时回收
内存池:以dll中的一个全局指针变量代表,调用方访问内存池数据需要通过dll内的相应接口函数,这样做有利于代码的规范化
参考:
DLL动态链接库是WIN32环境下代码共享的一种机制,只在磁盘上保存一份二进制代码,可以被多个程序加载、调用。
既然二进制代码在磁盘上只保留有一份,那么被调用加载到内存中运行,这时候在内存中的情况是怎样的呢?下面我们将来探讨这个问题。
一般的DLL动态链接库被不同的进程加载到虚拟内存中后分成PEheader、.text段、.rdata段、.data段、.reloc段,各个段映射到进程不同的虚拟地址空间。在不同进程的虚拟地址空间,同一个DLL动态链接库的数据段都有自己独立的拷贝,所以在不同的进程中,各个DLL的数据是独立的。
我们再来看看.text代码段的情况,当有两个不同的进程加载同一个DLL,DLL映射到各自进程中的虚拟地址相同,如地址0x10000000,则.text代码在物理内存中只存在一份,由内存管理机制映射到不同进程的虚拟地址空间中,非常符合DLL的设计初衷:代码共享。
但如果情况稍稍变化一下,两个进程加载同一个DLL到不同的虚拟地址空间,会出现什么情况呢?这里涉及到DLL的重定位机制,EXE被系统调用,通常会加载到0x00400000的虚拟地址空间,DLL会由系统自动加载到合适的虚拟地址空间,由于DLL被进程加载后的虚拟地址是不定的,则DLL中相关的代码就需要根据加载的地址进行重定位,如一条赋值指令在DLL加载到0x10000000时,指令如下:mov dword [10006030], eax,指令二进制代码为a330600010,如DLL加载到0x30000000地址空间,则经过重定位的指令如下:mov dword[30006030],eax,指令二进制代码为a330600030。这样同一个DLL加载到不同的进程中,由于加载的虚拟地址不同,经过重定位后,实际的二进制代码是不相同的,其必然不可能存放在相同的物理内存中,即:相同的DLL被不同的进程加载到不同的虚拟地址空间后,由于DLL重定位的原因,DLL的代码段在物理内存中也会有各自不同的拷贝。
根据以上讨论,可以得出,在通常情况下,不同进程加载DLL的数据段和代码段都会在物理内存中有独立的拷贝,但WIN32系统的DLL被广泛使用,如果每个进程都使用独立的拷贝,会极大的占用物理内存空间,微软使用了一种特殊的处理方式,让WIN32系统的DLL不再进行重定位,总是映射到虚拟内存的高地址空间中,这样,在不同的进程中,WIN32系统DLL代码段都使用物理地址的同一份拷贝,以达到代码共享和节省物理内存空间的效果。
参考:DLL 内存管理
在Win32中,DLL文件按照片段(sections)进行组织。每个片段有它自己的属性,如可写或是只读、可执行(代码)或者不可执行(数据)等等。
DLL代码段通常被使用这个DLL的进程所共享;也就是说它们在物理内存中占据一个地方,并且不会出现在页面文件中。如果代码段所占据的物理内存被收回,它的内容就会被放弃,后面如果需要的话就直接从DLL文件重新加载。
与代码段不同,DLL的数据段通常是私有的;也就是说,每个使用DLL的进程都有自己的DLL数据副本。作为选择,数据段可以设置为共享,允许通过这个共享内存区域进行进程间通信。但是,因为用户权限不能应用到这个共享DLL内存,这将产生一个安全漏洞;也就是一个进程能够破坏共享数据,这将导致其它的共享进程异常。例如,一个使用访客账号的进程将可能通过这种方式破坏其它运行在特权账号的进程。这是在DLL中避免使用共享片段的一个重要原因。
当DLL被如UPX这样一个可执行的packer压缩时,它的所有代码段都标记为可以读写并且是非共享的。可以读写的代码段,类似于私有数据段,是每个进程私有的并且被页面文件备份。这样,压缩DLL将同时增加内存和磁盘空间消耗,所以共享DLL应当避免使用压缩DLL。
DLL的内存管理问题:跨模块释放内存
场景:主程序调用了一个DLL的函数,分配了一段空间。当不需要这段空间的时候在主程序中释放掉它,怎样释放?
DLL被制作成自身释放内存的就要DLL才能释放,因为DLL的调用是有被计数的,为了防止还有调用程序正在引用DLL,不应该在程序中释放内存.
参考:关于Cross-Dll问题(在不同的模块之间申请和释放内存)
所谓Cross_Dll问题,就是在一个dll中申请了一段内存空间,在外部程序调用完该dll提供的功能后,为了不造成内存泄露,要释放掉在dll内部申请的空间。但是这种操作会引起程序崩溃。
原因分析:
模块之间分配和释放内存有一些不安全因素,因为不同模块(特别是不同语言开发的模块)可能使用了不同的内存管理机制,这种情况是不是跨模块释放的,所以VC的Debug版中对内存释放做了检查,如果发现不是本模块分配的就会报错。
造成失败的原因是分配和释放内存不是由相同的堆管理程序完成的,例如动态链接库中的堆在默认情况下是由msvcrt.dll中的堆管理程序管理的 (以动态链接的方式),而exe程序的堆在默认情况下是由程序自己的代码管理(以静态链接的方式),由于它们的堆管理程序不同,当动态链接库分配的内存在 exe程序中释放时就会出错,因为exe程序所在的堆并没有分配这块内存,而你却要求它释放这块内存。
解决方法(三种解决办法):
1、将程序中所有的模块都链接到C/C++运行期库Multithreaded DLL,修改后所有分配和释放堆上内存的操作都由同一个堆管理程序管理,这样便解决了问题。
2、 在DLL中增加一个导出函数来释放内存。谁负责分配,谁就必须负责释放!
3、使用智能指针,如std::shared_ptr。这样不用自己释放内存,智能指针在发现引用计数为1的时候,会自动释放内存。
DLL中申请的内存,是否会在其它地方被修改?
DLL中申请的内存也在主程序的内存空间中,如果是 new出来的,并且没有释放的话,和在主程序在申请的效果是一样的。
原理:windows内存管理机制、CRT内存管理、new/delete原理
————————未完