MFC扩展DLL项目生成的DLL被调用出现的问题汇总

       在MFC DLL中有三种项目类型:使用共享MFC DLL的常规DLL、MFC扩展DLL、静态链接MFC的常规DLL。其中“MFC扩展DLL”主要针对MFC项目而提供DLL接口的,其作用可以生成普通的类,MFC包括界面的资源即对话框也可以被生成DLL文件供给主调用者程序使用。

MFC扩展DLL的使用步骤:

1.如果单纯建立MFC项目,就使用MFC扩展DLL项目就好。

2.接口使用:提供DLL头文件(包含要使用的函数等)、DLL文件、lib文件给主调用方。(2)若要整个程序调用dll函数都有作用,就在stdafx.h头文件添加“#include "xx.h"的dll头文件”、“#pragma comment(lib,"xx.lib")”。

3.而对于有资源的DLL项目,意思是包含对话框资源的MFC扩展DLL,开发者在其主调用方要使用的DLLL头文件不要include有资源ID的文件,比如DLL项目的对话框类,否则就会报出“某某ID未定义标识符”。

解决问题的方式有三种:

(1)开发者使用普通类(即没有资源并且不是MFC的类,指没有包含对话框类),不要在普通类头文件引用资源类文件,而应在普通类的源文件进行包含资源类,然后在源文件对资源类进行创建对象并初始化。

主调用方引用的代码示例:
 

//假设UserLoginDll.h是DLL项目自定义的普通类。
//在TestDlg源文件添加#include "UserLoginDll.h",表示引用UseLoginDll.h
//在TestDlg源文件添加全局变量:CUserLoginDll login;
void TestDlg::Onclick()
{
HINSTANCE exe_hInstance = GetModuleHandle(NULL);//用对象保存主调用者的句柄
HINSTANCE dll_hInstance = GetModuleHandle("LoginDLL.dll");//切换到DLL项目的资源ID文件查找
login.ShowLoginDlg();
AfxSetResourceHandle(exe_hInstance); //恢复回主调用者的资源ID文件进行查找
}

(2)开发者可以不用普通类的方式解决资源类引用的问题那么另一种方法就是在开发程序时,MFC扩展DLL项目必须和主调用方的项目都放在同一个解决方案中,开发者可以通过复制MFC扩展DLL项目的资源粘贴到主调用方的项目里,并在主调用方的Resouce.h文件添加粘贴过来的资源相迎的资源ID号。提示:这种方式并不好用,如果存在资源ID号是一致的,修改是相当麻烦的。

(3)开发者可以不使用普通类的方式,而是在主调用方进行调用时,使用显示加载的方式来解决资源ID相撞的问题。如果开发者仍然想要在主调用方直接调用DLL项目里的资源类,那么就应该在显示加载的地方进行切换项目的资源使用。

比如:在MFC扩展DLL项目编写了登录界面,然后通过主调用方的资源类(例如对话框类)的按钮一点击遍弹出登录界面。如果登录界面类的资源ID与主调用方的对话框类资源ID是一致的,就会报错。又很有可能就是主调用方程序文件查找不到DLL的登录界面类ID,就会报出登录界面类的资源ID“未定义标识符”.

MFC DLL项目的类:

引用的类头文件应该是这样声明的:

#pragma once
#include "KeyBoxDlg.h"//如果在这里包含登录界面类就需要注意引用的方式。
class AFX_EXT_CLASS  CShowKeyBoard//添加AFX_EXT_CLASS说明该类是导出的类,不过即便没有AFX_EXT_CLASS,也是可以被引用的。
{
public:
	CShowKeyBoard(CEdit *edit);
	~CShowKeyBoard(void);
	static CKeyBoxDlg *KeyBoxDlg;
	BOOL bSelectPlus;
	BOOL bOpenKeyBox;
	int disX,disY;
	CEdit *edit;
	void KeyBoardStatus(CEdit *edit2);
};
//注意:此类是楼主以前封装好的虚拟键盘类,懒得再新建一个工程,假设把它当作登录界面类来使用。

主调用引用DLL头文件的方式除了以上三种方式都是自己自定义的类被调用之外,以下还有两种方式是MFC扩展DLL项目自身设置的接口文件:

3.1 LoginDll.cpp(LoginDll是DLL项目工程名)

代码示例:

// LoginDLL.cpp : 定义 DLL 的初始化例程。
//

#include "stdafx.h"
#include "resource.h"
#include "LoginDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
//自己定义一个函数
void ShowLogin()
{
CLoginDlg dlg;
//....
}
//在主调用方项目添加DLL项目的LoginDll.cpp,主调用方直接在某YY函数里添加"ShowLogin();"这条语句
//就响应该YY函数可以弹出DLL的对话框。

3.2 LoginDll.def文件

; LoginDLL.def : 声明 DLL 的模块参数。

LIBRARY

EXPORTS
    ; 此处可以是显式导出
	ShowLoginDlg
    XX...
    ....
//该文件是列出的是导出的函数,导出的函数就是在LoginDll.cpp文件定义的函数。
//在主调用方引用这些函数时,就不必引入LoginDll.cpp了。如果链接动态链接库正确之后,直接显示调用即可。

那么以上两种方式在DLL项目设置好之后,就在主调用方项目进行显示加载:

代码示例:

void CTestDlg::OnClick()
{
HINSTANCE exe_hInstance = GetModuleHandle(NULL);
HINSTANCE dll_hInstance = GetModuleHandle("LoginDLL.dll");//切换到DLL项目的资源ID文件查找
ShowLoginDlg();
 AfxSetResourceHandle(exe_hInstance); //恢复状态,恢复为主调用方的资源ID查找
}
//CTestDlg是主调用方,OnClick()函数是某按钮的消息响应函数

最好的引用方式就是第一种。

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

自定义控件类的使用过程出现问题:

(1)

问题:应输入声明的解决方案

解决:工具-〉选项-〉文本编辑器-〉c/c++->高级-〉禁用自动更新改为:TRUE

(2)

问题:未使用定义

解决:#include导出的DLL头文件在被引用的地方,并且在主调用方的项目添加该导出的DLL头文件,正确加载lib。

(3)

问题:非法的成员初始化:“m_editt”不是基或成员

在主类里,构造函数那里把“, m_editt(_T(""))”去掉,目的是不能先让子类进行构造,楼主的这个问题才得以解决。其实真正原因就是假设m_editt成员变量原本就是CString类型,然后开发者把它设置为控件类型,即CEdit类型。之前m_editt(_T(""))”并没有自动删除掉,编译之后,该m_editt(_T(""))”在父类就无法通过了,因为父类识别的是CEdit类型,m_editt(_T(""))”在子类构造函数时,继承父类就会报错,m_editt(_T(""))”是CString类型的。

=======================================

 

(4)

问题:MFC DLL扩展项目,在调用者的项目里导入DLL后,调试程序时,一旦退出程序(关闭对话框)可能会出现中断的问题,大概提示“某对话框内存泄漏”。

原因:这种问题的提示描述信息比较模糊,一般导致这种问题的原因就是动态链接库某类里某对象new了在主调用者程序关闭之后,动态链接库要主动卸载却没有手动释放掉该内存块,所以导致内存泄漏的错误。不过这个问题在继承CEdit的类就没有出现过,现如今在继承CView类出现这种情况:

无法解析的外部符号 "__declspec(dllimport) public: virtual __thiscall ~CXX(),public: virtual __thiscall ~CYY()函数被引用。(也就是说在主调用着凡是要new动态链接库的对象,就会出现这种错误,而CEdit类并没有直接new,只是替换掉了主调着添加的Edit控件变量的类对象名)

解决:解决方法就是寻找创建内存块的地方,然后再该类的析构函数进行delete。

其他原因:

1.主要原因就是复制的DLL和lib文件重复了,该项目首先会检查自身的文件,然后才去外部访问。因为当我在调用者的文件里,已经对该类的对象进行创建并初始化了,再把dll和lib文件删除掉,就没有出现过问题。

2.当我在调用者的类向导添加控件变量时,再把dll的控件类进行声明,可还是报出原来的错信息。这下我才明白,其实出现这种问题的原因就是控件类在调用者的头文件声明之后,并没有在调用者的源文件进行定义。于是在调用者的初始化函数OnInitDialog函数里面对控件变量进行初始化,果然没有报错。(这可是在正常的项目里(即没有涉及到dll项目)是允许我们不用亲自初始化的,因为系统主动把我们完成这个工作,可是做dll项目就不同了。)

 

(5)

问题:

MFC DLL扩展项目,在调用者的文件当中导入,报错的信息如下:

1>c:\users\chen\documents\visualstudio2010\projects\cworkingimage\cimagetest\cimagetestdlg.h(37): warning C4624: “CCImageTestDlg”: 未能生成析构函数,因为基类析构函数不可访问

1>c:\users\chen\documents\visual studio 2010\projects\cworkingimage\cimagetest\cimagetestdlg.h(37):errorC2248: “CWorkView::~CWorkView”: 无法访问 protected 成员(在“CWorkView”类中声明)

1>c:\users\chen\documents\visual studio 2010\projects\cworkingimage\cworkingimage\workview.h(16) : 参见“CWorkView::~CWorkView”的声明

原因:DLL类的头文件里的构造函数和析构函数不能设置成protected:,要改为public:。

(6)

之前在项目调用自定义的继承MFC类从来就没有出现过如此错误,甚至百度也无法找到解决的办法,现如今采用MFC DLL扩展项目进行编译通过,再导入另外可执行的项目也正常编译运行,可是进行调试成功运行并关闭对话框时,等了许久才会弹出如下问题的信息:

CImageTest.exe 中的 0x606c27f7 (CWorkingImage.dll) 处最可能的异常: 0xC00000FD: Stack overflow

CImageTest.exe 中的 0x606c27f7 (CWorkingImage.dll) 处有未经处理的异常: 0xC000041D: 用户回调期间遇到未经处理的异常。

(注:CImageTest是调用DLL的项目,CWorkingImage是生成DLL的工程)

MFC扩展DLL项目生成的DLL被调用出现的问题汇总

出现该问题,一般的原因就是某些在DLL项目里new出来的对象执行到DLL卸载时并没有被释放掉,因此退出程序之后,导致堆栈溢出,DLL无法提前终止。但是这个问题并非是楼主所想的那么简单,而且百度搜索出来的基本上没有这个问题的答案,同时在项目DLL里也无法找到遗漏的对象,甚至特意在析构函数尽量企图把自己所申请的内存全部释放掉,可仍旧没法解决这个问题。

于是楼主继续调试运行到viewcode源文件(系统自带的文件),箭头指向了PostNcDestory函数,这才断定有些对象真的没有被释放掉。可是到底有哪些对象没有被释放掉呢?真想不通!

于是楼主继续调试再中断,然后查看调用堆栈窗口和自动窗口,发现this指针下的pDocument的地址是0x000000,运行到的语句就在析构函数,然后楼主在析构函数尝试添加两条语句:

CWorkView::~CWorkView()

{

         delete m_pDocument;

         m_pDocument=NULL;

}

而在OnDraw函数注释掉了这条语句:

 //CDocument* pDoc = GetDocument(); 其实与这条语句一点关系都没有,可有可无

再次调试并运行,关闭程序之后却成功正常退出。

于是楼主再次把析构函数里的那两条语句都删除掉,并且把onDraw函数里的那条已经注释掉的语句复原回来,然后再执行,调试仍然正常退出。

解决的原因:虽然解决了这个问题,但是楼主依旧迷惑,暂时还不太清楚其核心的原因。额,估计就是pDocument对象的问题吧,可是复原回来之后回到正常退出的状态,又为何呢?