带界面的MFC DLL例子
模块化编程不可避免,尤其是一些稳的模块就不需要每次都重新生成什么了的。但是查了一下,C++下的DLL调用文章一大堆,反而MFC DLL的反反复复就是两三篇,而且好多都是
复制粘贴转载
的,唉,看得心塞,实在看不下去了。实战了一把,已经通过测试。需要demo的可以去下载。(本文涉及的工程都是MFC工程)
1、环境;
2、工程Quotes;
3、工程Main;
4、工程讲解(重点,思路在此);
1、环境:
本次例子环境为Windows10 + VS2015。
2、工程Quotes:
为MFC DLL工程。多字节。
2.1、新建工程:
2.2、一路next,到这里的时候注意选择Regular的共享MFC DLL。DLL type这里除非必要,不然基本都是共享dll的。至于区别,可以去百度一下,这里就不提及了。
2.3、增加对话框。
2.4、为该对话框增加类,命名为CQuotesDlg:
2.5、增加接口文件,为了快速我直接增加一个class,实际上我只是需要h文件和cpp而已。等下会将里面的内容删除的。
2.6、将生成的h文件和cpp内容清空,实际代码如下:
先大概过一下这些代码,都是很简单的代码,你那里会报错,那因为你还没有写CQuotesDlg的对应代码。没关系,先让它报错。最重要是理解了思路,这些都是小事情。如果你实在看不过去,那就将函数实现都先清空了,迟点回来复制代码也没关系。
PS:
IQuotes_CreateObj
函数将会放在少后面点讲解,涉及点是传参到底是指针还是句柄,还是其他。
2.6.1、IQuotes.h(这里使用__stdcall来进行函数导出,这种方法对应的就是*.def文件)
#pragma once void* __stdcall IQuotes_CreateObj(); bool __stdcall IQuotes_InitObj(const void* pHwnd, unsigned char *pEngine); bool __stdcall IQuotes_ReleaseObj(); bool __stdcall IQuotes_Start(const void* pHwnd); bool __stdcall IQuotes_Stop(const void* pHwnd); bool __stdcall IQuotes_SetBk(const void* pHwnd, const unsigned long clrBk);
2.6.2、IQuotes.cpp
这里要特别描述一下:
- 作为MFC的DLL,导出函数加上
AFX_MANAGE_STATE(AfxGetStaticModuleState());
- 不加上面那句代码的话断点都进不来的。界面也出不来。微软就是这么霸道。
- 特别注意pDlg在new成功了后指针是有内容的,但是它的句柄却是空的,因为还没有创建。
pDlg->Create(参数1,参数2);
中:
1.参数1的2000实际上就是IDD_DIALOG_BASE的ID号。我为了简便所以直接在这里写2000了,因为这里它并不认识IDD_DIALOG_BASE。
2.参数2需要一个CWnd*,如果你指定了child(实际上我们前面就是指定了child)属性,那么这里不可填写NULL,所以你可以看到我直接取桌面的窗口来临时用一下。实际上你可以将这个接口函数增加一个父窗口句柄作为参数传递进来即可。所以这里要特别注意如果IQuotes_CreateObj返回是空,那么可能是pDlg已经new成功,但是
Create失败了
的情况。#include "stdafx.h" #include "IQuotes.h" #include "QutoesDlg.h" void * __stdcall IQuotes_CreateObj() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CQutoesDlg *pDlg = new CQutoesDlg(); pDlg->Create(2000, CWnd::FromHandle(GetDesktopWindow()));//IDD_DIALOG_BASE - 2000 return pDlg->GetSafeHwnd(); } bool __stdcall IQuotes_InitObj(const void* pHwnd, unsigned char *pEngine) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CQutoesDlg *pDlg = (CQutoesDlg*)CWnd::FromHandle((HWND)pHwnd); return pDlg->Init(pEngine); } bool __stdcall IQuotes_ReleaseObj() { AFX_MANAGE_STATE(AfxGetStaticModuleState()); //AfxMessageBox("IQuotes_ReleaseObj"); return true; } bool __stdcall IQuotes_Start(const void *pHwnd) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CQutoesDlg *pDlg = (CQutoesDlg*)CWnd::FromHandle((HWND)pHwnd); return pDlg->Start(); } bool __stdcall IQuotes_Stop(const void *pHwnd) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CQutoesDlg *pDlg = (CQutoesDlg*)CWnd::FromHandle((HWND)pHwnd); return pDlg->Stop(); } bool __stdcall IQuotes_SetBk(const void * pHwnd, const unsigned long clrBk) { AFX_MANAGE_STATE(AfxGetStaticModuleState()); CQutoesDlg *pDlg = (CQutoesDlg*)CWnd::FromHandle((HWND)pHwnd); return pDlg->SetBk(clrBk); }
2.7、编写导出def文件:
打开Quotes.def(看到这个文件名我突然觉得这个工程名称应该叫做
IQuotes
才对…)文件,输入以下内容:LIBRARY EXPORTS IQuotes_CreateObj IQuotes_InitObj IQuotes_ReleaseObj IQuotes_Start IQuotes_Stop IQuotes_SetBk
关于这个文件的格式和意义可以直接百度一下,内容知识不少的。简单来说,导出的函数要被别的模块认识,需要这个文件来进行说明。这个函数就是
LIBRARY
和
EXPORTS
后接要导出的函数名即可。
2.8、编写CQuotesDlg代码。
这里直接贴出代码,其实也没加什么代码,就是做了三个接口Init、Start、Stop。然后为了凸显子窗口,所以类向导增加了一个OnPaint函数,为了动态改下颜色,又增加了SetBk接口,仅此而已。
2.8.1、CQuotesDlg.h
#pragma once // CQutoesDlg dialog class CQutoesDlg : public CDialogEx { DECLARE_DYNAMIC(CQutoesDlg) public: CQutoesDlg(CWnd* pParent = NULL); // standard constructor virtual ~CQutoesDlg(); public: bool Init(BYTE* pEngine) { m_pIUnknow = pEngine; return true; }; bool Start() { AfxMessageBox("Start Function"); return true; }; bool Stop() { AfxMessageBox("Stop Function"); return true; }; bool SetBk(const COLORREF clrBk) { m_clrBk = clrBk; Invalidate(); return true; } // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_DIALOG_BASE }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support afx_msg void OnPaint(); DECLARE_MESSAGE_MAP() private: BYTE *m_pIUnknow = nullptr; COLORREF m_clrBk; };
2.8.2、CQuotesDlg.cpp
// QutoesDlg.cpp : implementation file // #include "stdafx.h" #include "Quotes.h" #include "QutoesDlg.h" #include "afxdialogex.h" // CQutoesDlg dialog IMPLEMENT_DYNAMIC(CQutoesDlg, CDialogEx) CQutoesDlg::CQutoesDlg(CWnd* pParent /*=NULL*/) : CDialogEx(IDD_DIALOG_BASE, pParent) { } CQutoesDlg::~CQutoesDlg() { } void CQutoesDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CQutoesDlg, CDialogEx) ON_WM_PAINT() END_MESSAGE_MAP() // CQutoesDlg message handlers void CQutoesDlg::OnPaint() { CPaintDC dc(this); // device context for painting // TODO: Add your message handler code here // Do not call CDialogEx::OnPaint() for painting messages CRect rt; GetClientRect(&rt); dc.FillSolidRect(rt, m_clrBk); dc.SetTextColor(RGB(200, 200, 0)); dc.DrawText("我是DLL 1", rt, DT_CENTER | DT_VCENTER | DT_SINGLELINE); }
至此已经完成了Quotes工程。不要忘记将工程属性设置为
多字节
。因为我打算在exe那边也是用多字节的。
3、工程Main;
这边的工程建立就简单了,新建个对话框,工程名字叫做Main,然后设置属性为
多字节
工程。这里就不上很详细的图文讲解了,挑重要的讲。毕竟利用VS新建个对话框是最基本的技能。
3.1、新建Main工程,类型为对话框,属性为多字节。
3.2、文件复制:在Quotes工程中找到4个文件:
IQuotes.h
Quotes.def
Quotes.dll
Quotes.lib
,其中IQuotes.h、Quotes.def以及Quotes.lib放到Main工程目录下,并且在工程中右键添加前两个文件进来如下图。另外Quotes.dll放在和exe同目录(也就是Main/Debug下)。
请注意:一旦Quotes工程代码有改动,如果你并不清楚,那么以上4个文件都要更新过来。
实际上是: 接口改变,4个文件都要更新。
非接口改变,则更新dll和lib即可。
如果你突然发现前两分钟还好好的然后突然莫名其妙断点打不上了之类的问题,大多数都是没有更新文件导致的。
3.3、编写Main工程对应的代码:
MainDlg.h文件内容:// MainDlg.h : header file // #pragma once // CMainDlg dialog class CMainDlg : public CDialogEx { // Construction public: CMainDlg(CWnd* pParent = NULL); // standard constructor ~CMainDlg(); // Dialog Data #ifdef AFX_DESIGN_TIME enum { IDD = IDD_MAIN_DIALOG }; #endif protected: virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support // Implementation protected: HICON m_hIcon; // Generated message map functions virtual BOOL OnInitDialog(); afx_msg void OnPaint(); afx_msg void OnSize(UINT nType, int cx, int cy); afx_msg void OnBnClickedBtnQuotesStart(); afx_msg void OnBnClickedBtnQuotesStop(); afx_msg void OnBnClickedBtnSetcolor(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() private: //保存Quotes的对话框,对话框实际上也是窗口,所以用CWnd*来保存。 CWnd *m_pQuotesDlg = nullptr; unsigned char *m_pEngine = nullptr; unsigned char m_data = 'a'; int init(); };
MainDlg.cpp文件内容:
要记得:
#include “IQuotes.h”
#pragma comment(lib,“Quotes”)// MainDlg.cpp : implementation file // #include "stdafx.h" #include "Main.h" #include "MainDlg.h" #include "afxdialogex.h" #include "IQuotes.h" #pragma comment(lib,"Quotes") #ifdef _DEBUG #define new DEBUG_NEW #endif // CMainDlg dialog CMainDlg::CMainDlg(CWnd* pParent /*=NULL*/) : CDialogEx(IDD_MAIN_DIALOG, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); } CMainDlg::~CMainDlg() { IQuotes_ReleaseObj(); } void CMainDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CMainDlg, CDialogEx) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_WM_SIZE() ON_BN_CLICKED(IDC_BTN_QUOTES_START, &CMainDlg::OnBnClickedBtnQuotesStart) ON_BN_CLICKED(IDC_BTN_QUOTES_STOP, &CMainDlg::OnBnClickedBtnQuotesStop) ON_BN_CLICKED(IDC_BTN_SETCOLOR, &CMainDlg::OnBnClickedBtnSetcolor) END_MESSAGE_MAP() // CMainDlg message handlers BOOL CMainDlg::OnInitDialog() { CDialogEx::OnInitDialog(); // Set the icon for this dialog. The framework does this automatically // when the application's main window is not a dialog SetIcon(m_hIcon, TRUE); // Set big icon SetIcon(m_hIcon, FALSE); // Set small icon // TODO: Add extra initialization here init(); return TRUE; // return TRUE unless you set the focus to a control } // If you add a minimize button to your dialog, you will need the code below // to draw the icon. For MFC applications using the document/view model, // this is automatically done for you by the framework. void CMainDlg::OnPaint() { if (IsIconic()) { CPaintDC dc(this); // device context for painting SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0); // Center icon in client rectangle int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1) / 2; int y = (rect.Height() - cyIcon + 1) / 2; // Draw the icon dc.DrawIcon(x, y, m_hIcon); } else { CDialogEx::OnPaint(); } } // The system calls this function to obtain the cursor to display while the user drags // the minimized window. HCURSOR CMainDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); } int CMainDlg::init() { CRect rt; GetClientRect(&rt); HWND hQuotesDlg = (HWND)IQuotes_CreateObj(); m_pQuotesDlg = CWnd::FromHandle(hQuotesDlg); if (m_pQuotesDlg) { if (!m_pEngine) m_pEngine = &m_data; m_pQuotesDlg->SetParent(this); m_pQuotesDlg->ShowWindow(SW_SHOW); m_pQuotesDlg->MoveWindow(0, 0, rt.Width() - 80, rt.Height() - 80); IQuotes_InitObj(hQuotesDlg, m_pEngine); } return 0; } void CMainDlg::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); CRect rt; GetClientRect(&rt); if (m_pQuotesDlg) { m_pQuotesDlg->MoveWindow(0, 0, rt.Width() - 80, rt.Height() - 80); } // TODO: Add your message handler code here } void CMainDlg::OnBnClickedBtnQuotesStart() { // TODO: Add your control notification handler code here if (!m_pQuotesDlg) return; HWND h = m_pQuotesDlg->GetSafeHwnd(); IQuotes_Start(h); } void CMainDlg::OnBnClickedBtnQuotesStop() { // TODO: Add your control notification handler code here if (!m_pQuotesDlg) return; HWND h = m_pQuotesDlg->GetSafeHwnd(); IQuotes_Stop(h); } //CString切割函数 void SplitCString(const CString &str, const CString &split, CArray<CString,CString&> &ary) { ary.RemoveAll(); CString data = str; int nPos = 0; while (nPos >= 0) { nPos = data.Find(split); if (nPos >= 0) { ary.Add(data.Left(nPos)); data = data.Right(data.GetLength() - nPos - 1); } else { if (data.GetLength() > 0) ary.Add(data); } } } void CMainDlg::OnBnClickedBtnSetcolor() { // TODO: Add your control notification handler code here CEdit *pEdit = (CEdit*)GetDlgItem(IDC_EDIT_COLOR); if (pEdit) { CString text,temp; pEdit->GetWindowText(text); CArray<CString,CString&> ary; SplitCString(text, ",", ary); if (m_pQuotesDlg && ary.GetCount() == 3) { int r = atoi(ary.GetAt(0)); int g = atoi(ary.GetAt(1)); int b = atoi(ary.GetAt(2)); HWND h = m_pQuotesDlg->GetSafeHwnd(); COLORREF clrBk = RGB(r, g, b); IQuotes_SetBk(h, clrBk); } } }
3.4、MainDlg的内容大同小异,多余的就不要看了,请看重点:
3.4.1、MainDlg.h中:
1.关于Quotes的模块对象保存:
可以看到CMainDlg中定义了一个CWnd*类指针来保存:CWnd *m_pQuotesDlg = nullptr;
为什么是CWnd* 指针呢?明明在
IQuotes_CreateObj
函数中是返回句柄。其实句柄也就是HWND,可以看到这个是微软封装了的一个结构,实际上就是void*,可以F12跟踪它发现它是这样的:
如果定义一个CWnd* 进行传递,不知道会发生多少坑。有兴趣的可以自行查询。一般来说,跨DLL传递内容,能基础类型的就基础类型,CWnd是一个集大成者,包含的内容太多,会容易出问题的。可是网上文章也少,资料也无从查起。所以我宁愿传递一个void* 类型,到了需要用的时候再转换即可。而对话框本身就是一个窗口,所以定义CWnd*没有问题。
3.4.2、MainDlg.cpp中:
MainDlg在初始化的时候调用了我定义的函数init(),init()中代码如下:
看到下面代码,应该没有什么疑问了。就是直接调用接口,想办法保存起来对象即可。
前面在Quotes工程中的接口
IQuotes_CreateObj
提及了可以直接将父窗口的句柄传递进去。我因为没有传递,所以添加了一句
m_pQuotesDlg->SetParent(this);
来给它个爹。int CMainDlg::init() { CRect rt; GetClientRect(&rt); HWND hQuotesDlg = (HWND)IQuotes_CreateObj(); m_pQuotesDlg = CWnd::FromHandle(hQuotesDlg); if (m_pQuotesDlg) { if (!m_pEngine) m_pEngine = &m_data; m_pQuotesDlg->SetParent(this); m_pQuotesDlg->ShowWindow(SW_SHOW); m_pQuotesDlg->MoveWindow(0, 0, rt.Width() - 80, rt.Height() - 80); IQuotes_InitObj(hQuotesDlg, m_pEngine); } return 0; }
至此,大功告成,没啥问题了。
4、工程讲解(重点,思路在此):
思路:
1、建立MFC的dll工程Quotes,选择MFC DLL,属性为多字节。
2、增加接口文件,编写接口代码,使用__stdcall模式即可,配合def文件。
3、建立MFC主工程Main,选择MFC工程,属性为多字节。
4、在主工程中增加4个文件:Quotes.h,Quotes.def,Quotes.lib,Quotes.dll。其中Quotes.h,Quotes.def增加到工程树目录中,Quotes.h,Quotes.def,Quotes.lib放在Main工程主目录下,Quotes.dll放在Main工程的Debug目录下。
5、编写对应代码调用即可。
写到这里好像没什么好写了,毕竟认真看下来,什么问题都没了,足够详细了。写一下思考和注意点吧:
思考
1、所有的运算都放在dll中,界面不要放dll,放主窗口程序工程中,实现完全的界面和运算剥离。这个想法不知道实现起来如何。并非突发奇想,有可行性的。这样子做的好处是,dll的兼容性更加强了,并非局限于MFC DLL了。
2、如果说传递句柄只是为了对窗口做基础的动作如设置父子窗口、调整大小等,那么可不可以更加纯粹点想办法绕过传递句柄这个步骤,也就是通过其他的方法拿到句柄,这样子整个接口文件就保留下来的都是基础类型,而不会有其他类型了。如果了解过跨DLL不要传递STL做法的原因,那么应该能明白这里这么思考的初衷了。
注意点:
1、接口传参基本类型为主,窗口的用句柄,不要直接用窗口本身。
2、创建对话框的接口函数记得加上
AFX_MANAGE_STATE(AfxGetStaticModuleState());
代码,不然对话框甚至接口都是没法玩的。好像每个导出函数都可以加上这一句,没深究。
3、对话框属性如果是child,那么Create的时候要给一个父窗口,不然它会创建失败,就会出现pDlg指针有内容,实际上pDlg->GetSafeHwnd()为空的情况。
4、没了。
结束语
终于写完了,网上的文章能不能有点质量的再转载,唉。以后不要不懂写MFC DLL了。
PS:如果写的有问题的,欢迎提出来,我会修正的。
附上工程下载地址:
跳转到工程下载页面
d
O - O
b
```
J
——2020.12.30