Q:
我们需要在Dll中new一个class或者struct的实例,通过接口把这个实例传给了主程序(调用dll的程序),最后这个实例的释放(delete)是由主程序来执行的,这时我们的问题出现了,vs.net抛出一个debug模式下的异常,是在delete函数中。后来我试了另外一种情况,即在主程序中new的实例在dll中delete也会有相同的问题。哪位大侠知道为什么?
另外,还有一个小问题,就是在dll中的导出(dllexport)类中,如果有stl的变量存在如list<int>等,在编译的时候会有一个有关dll 接口不一致的warning,不知道怎么解决这个问题。
A:
不同的DLL中完全可能会维护不同的局部堆,这与编译器的实现有关。有些编译器可以选择使用进程共享的局部堆,但性能会受影响。
如果是不同的局部堆,当你在DLL中new时,是在DLL的堆中分配的;而当你在EXE中delete时,EXE会认为它是在EXE的局部堆中分配的,从而用EXE的堆信息去释放它,从而可能导致错误。具体情况与堆管理代码的实现有关。
最好为你的class或struct提供一个虚的free方法,在里面调用delete。不管是从EXE中分配的还是从DLL中分配的,都可以安全地用free方法释放,因为free是虚的,它的代码会和分配它的代码编译在同一个可执行体中。
我不赞成用引出类的方法提供功能,因为类成员的签名生成并没有一个标准,很难使用。
用DLL提供的类最好是只引出创建对象的函数,返回类型是一个“纯抽象类”指针,我说的“纯抽象类”指没有成员变量,没有非抽象方法的类。并且有一个公开的抽象的free方法负责释放。使用时调用创建函数创建一个(衍生类) 的对象并返回,然后调用对象的成员方法来完成操作。
----------------------------------------------------------------------------
Q:
这边还有一个小问题,如果为每一个导出的class都做一个wrap,如果导出的类很多不是开销很大?如果有些类是比较简单的,这样也需要为每个类做一个接口吗?
A:
这里面有一个模块划分的问题。对于需要跨模块调用的功能,最好是分处于不同层次的代码,相互之间不要有很多共用的对象。有时不同层次有相似的对象,但它们只是相似而已,并不是同一类型的对象,因为它们处于不同层次。而对于某一独立模块,引出的对象类型应该很少,用Facade模式封装起来是个不错的主意。
C++中的类要分成两种,一种是对基本数据类型的扩展,比如大家经常用到的std::string等,特点是值语义、无多态操作等,这种类没有必要跨模块共享;另一种是逻辑对象的分类,它们是否要共享要根据对象的职责来确定,有些是模块内部使用的,有些则是负责与其它模板交互的,只有后者才是应该共享的。
此外,共享使用的对象内部使用的对象是不需要这个接口的——如果在模块内部它本身就不需要的话。
------------------------------------------------------------------------------
Q:
因为我现在存在这样一种困惑,一般我们都会有一些平常比较常用的fucntion,class,这些都会代码都会跨项目的被使用到,所以我把这些代码做成dll,方便以后的需要。基于这样一个目的,我们去构架这个dll,会发现其中有很多简单明了的类,比如一个CVector3f,一个CMatrix44,或者一个类似std::string这样的类,那么这些类的导出该以怎样一种规则来制定呢?
A:
我觉得Dll不应该作为简单的代码重用手段,而应该作为逻辑重用手段。也就是说,用一个DLL维护一组相关的逻辑。事实上,与其说它是代码重用手段,还不如说是代码组织手段更合适。
象vector、matrix等这些只是用来封装类似于“基本数据类型扩展”的类的代码,不应该用DLL去重用,而应该是以源代码或是静态库方式重用。此外,如果你要重用的是模板,那么就只有源代码一种重用方法了,因为模板只有实例化之后才能产生可以加入到静态库或DLL中去的代码。
当你使用DLL调用进行重用时,你就不要去考虑多态的代价了,因为DLL调用本身就已经有代价了。
我们需要在Dll中new一个class或者struct的实例,通过接口把这个实例传给了主程序(调用dll的程序),最后这个实例的释放(delete)是由主程序来执行的,这时我们的问题出现了,vs.net抛出一个debug模式下的异常,是在delete函数中。后来我试了另外一种情况,即在主程序中new的实例在dll中delete也会有相同的问题。哪位大侠知道为什么?
另外,还有一个小问题,就是在dll中的导出(dllexport)类中,如果有stl的变量存在如list<int>等,在编译的时候会有一个有关dll 接口不一致的warning,不知道怎么解决这个问题。
A:
不同的DLL中完全可能会维护不同的局部堆,这与编译器的实现有关。有些编译器可以选择使用进程共享的局部堆,但性能会受影响。
如果是不同的局部堆,当你在DLL中new时,是在DLL的堆中分配的;而当你在EXE中delete时,EXE会认为它是在EXE的局部堆中分配的,从而用EXE的堆信息去释放它,从而可能导致错误。具体情况与堆管理代码的实现有关。
最好为你的class或struct提供一个虚的free方法,在里面调用delete。不管是从EXE中分配的还是从DLL中分配的,都可以安全地用free方法释放,因为free是虚的,它的代码会和分配它的代码编译在同一个可执行体中。
我不赞成用引出类的方法提供功能,因为类成员的签名生成并没有一个标准,很难使用。
用DLL提供的类最好是只引出创建对象的函数,返回类型是一个“纯抽象类”指针,我说的“纯抽象类”指没有成员变量,没有非抽象方法的类。并且有一个公开的抽象的free方法负责释放。使用时调用创建函数创建一个(衍生类) 的对象并返回,然后调用对象的成员方法来完成操作。
----------------------------------------------------------------------------
Q:
这边还有一个小问题,如果为每一个导出的class都做一个wrap,如果导出的类很多不是开销很大?如果有些类是比较简单的,这样也需要为每个类做一个接口吗?
A:
这里面有一个模块划分的问题。对于需要跨模块调用的功能,最好是分处于不同层次的代码,相互之间不要有很多共用的对象。有时不同层次有相似的对象,但它们只是相似而已,并不是同一类型的对象,因为它们处于不同层次。而对于某一独立模块,引出的对象类型应该很少,用Facade模式封装起来是个不错的主意。
C++中的类要分成两种,一种是对基本数据类型的扩展,比如大家经常用到的std::string等,特点是值语义、无多态操作等,这种类没有必要跨模块共享;另一种是逻辑对象的分类,它们是否要共享要根据对象的职责来确定,有些是模块内部使用的,有些则是负责与其它模板交互的,只有后者才是应该共享的。
此外,共享使用的对象内部使用的对象是不需要这个接口的——如果在模块内部它本身就不需要的话。
------------------------------------------------------------------------------
Q:
因为我现在存在这样一种困惑,一般我们都会有一些平常比较常用的fucntion,class,这些都会代码都会跨项目的被使用到,所以我把这些代码做成dll,方便以后的需要。基于这样一个目的,我们去构架这个dll,会发现其中有很多简单明了的类,比如一个CVector3f,一个CMatrix44,或者一个类似std::string这样的类,那么这些类的导出该以怎样一种规则来制定呢?
A:
我觉得Dll不应该作为简单的代码重用手段,而应该作为逻辑重用手段。也就是说,用一个DLL维护一组相关的逻辑。事实上,与其说它是代码重用手段,还不如说是代码组织手段更合适。
象vector、matrix等这些只是用来封装类似于“基本数据类型扩展”的类的代码,不应该用DLL去重用,而应该是以源代码或是静态库方式重用。此外,如果你要重用的是模板,那么就只有源代码一种重用方法了,因为模板只有实例化之后才能产生可以加入到静态库或DLL中去的代码。
当你使用DLL调用进行重用时,你就不要去考虑多态的代价了,因为DLL调用本身就已经有代价了。