终于发布了第一个程序《Mini记事本》,阶段性的成果。不过没有高兴,因为看了看整个程序还是那么的简单,没有深入的东西,还要继续努力,努力,努力。
在看SDK文档时看了关于COM方面的,但一直都未学习如何使用。在DELPHI开发时COM是相当的简单,而在C++中却不知道会如何,今天在扩展《Mini记事本》的时候顺手写了一个用于选择文件的对话框,目前这个类还只实现了选择一份文件一个方法,如果要继续扩展其实道理都差不多了,于是就写一篇文章与大家分享心得。
学习COM从用开始,因为发现魅族把一些核心的应用都用COM实现了,所以学习如何使用可以省去很多事情。正好新的sdk中也带了com的开发例子,而且是IFileBrowser的,拿过来用就是啦。今天在写一个打开文件的功能,需要在运行时选择一份文件,于是就有了封装一个文件对话框的想法。先贴出.h部分的代码:
- #include <mzfc_inc.h>
- class CMiniFileDialog : public CMzWndEx
- {
- private:
- CMzString m_DefaultDir;
- CMzString m_ExtFilter;
- public:
- CMiniFileDialog(void);
- ~CMiniFileDialog(void);
- //设置默认目录
- void SetDefaultDir(TCHAR *defalutDir);
- //设置文件后缀过滤
- void SetExtFilter(TCHAR *extfilter);
- //选择一份文件
- bool SelectFile(TCHAR **filename);
- };
复制代码这是个窗体类,按照IFileBrowser的例子中提醒要SetParent,需要一个窗口句柄,而这个类又是个独立的功能,所以就继承了窗口以提供完整的功能。从上面的代码中可以发现主要就三个方法:
SetDefaultDir(TCHAR *defalutDir);这个是用于设置打开选择文件对话框时显示的默认目录,比如:\\Disk\\Program Files\\
SetExtFilter(TCHAR *extfilter);这个是用于设置在对话框中列举的文件类型,比如只列出mp3文件就传入:*.mp3;
SelectFile(TCHAR **filename);这是本类中实现的第一功能性的方法:选择一份文件。 就是在磁盘中选择文件,将选择的文件完整路径返回给调用者。
接下来看实现部分的代码:
- #include "StdAfx.h"
- #include "MiniFileDialog.h"
- #include <InitGuid.h>
- #include <IMzUnknown.h>
- #include <IMzUnknown_IID.h>
- #include <IFileBrowser.h>
- #include <IFileBrowser_GUID.h>
- #include "CPubFunction.h"
- CMiniFileDialog::CMiniFileDialog(void)
- {
- CoInitializeEx(NULL, COINIT_MULTITHREADED);
- m_DefaultDir = NULL;
- m_ExtFilter = L"*.*;";
- }
- CMiniFileDialog::~CMiniFileDialog(void)
- {
- CoUninitialize();
- }
- void CMiniFileDialog::SetDefaultDir(TCHAR *defaultDir)
- {
- m_DefaultDir = defaultDir;
- }
- void CMiniFileDialog::SetExtFilter(TCHAR *extfilter)
- {
- m_ExtFilter = extfilter;
- }
- bool CMiniFileDialog::SelectFile(TCHAR **filename)
- {
- IFileBrowser *pFileBrow;
- IMzSelect *pSelect = NULL;
- if ( SUCCEEDED( CoCreateInstance( CLSID_FileBrowser, NULL,CLSCTX_INPROC_SERVER ,IID_MZ_FileBrowser,(void **)&pFileBrow ) ) )
- {
- if( SUCCEEDED( pFileBrow->QueryInterface( IID_MZ_Select, (void**)&pSelect ) ) )
- {
- TCHAR file[ MAX_PATH ] = { 0 };
- pFileBrow->SetParentWnd( m_hWnd );
- if (!m_DefaultDir.IsEmpty())
- pFileBrow->SetOpenDirectoryPath( m_DefaultDir );
- pFileBrow->SetExtFilter( m_ExtFilter );//过滤后缀
- pFileBrow->SetOpenDocumentType(DOCUMENT_SELECT_SINGLE_FILE); //应用根据需求进行文档打开方式的设置
- if( pSelect->Invoke() )
- {
- //各应用根据自己需求获取文档的返回值
- _tcscpy( file, pFileBrow->GetSelectedFileName());
- *filename = file;
- }
- pSelect->Release();
- }
- pFileBrow->Release();
- }
- else
- return false;
- ::InvalidateRect( m_hWnd, NULL, FALSE );
- ::UpdateWindow( m_hWnd );
- return FileExists(*filename);
- }
复制代码在类的构造函数中调用了CoInitializeEx来为当前线程初始化COM,这一句是必须的。另外还处理了两个成员的初始值:
m_DefaultDir = NULL;这一句初始化默认目录为空。
m_ExtFilter = L"*.*;"; *.*表示为所有文件
SetDefaultDir, SetExtFilter这两个函数的目的在说明头文件时已经讲明,实现也很简单不再说明。接着看SelectFile的实现。在SelectFile的实现比较复杂,开始阅读代码:
在开始部分申明了两个指针:
IFileBrowser *pFileBrow;
IMzSelect *pSelect = NULL;
作为COM最重要的一个概念就是接口,因为COM实际是二进制文件,所以调用COM就必须有一个约定,通常在编程中将一些调用约定都称为接口。在COM中为接口定义了一种特殊点的东西:interface。
接口与类一样都有方法,这些方法就和类的头文件里的申明一样,只不过接口并不实现这些方法仅仅是个申明而已,这样做有什么用呢?其实在COM中还是需要有具体的类来继承接口,从而实现接口中申明的所有方法,这样一来接口即拥有了实现。这其中所涉及到的很多技术我也不是很懂,像虚方法表等等等,有兴趣的朋友看看COM原理吧。使用接口的好处就是在调用者和实现者之间制定了一个约定,大家都按个来,这样只要接口不变调用者不用关心实现者是怎么做出的。
IFileBrowser 和IMzSelect 便是两个接口,一些文件浏览需要用到一些基础属性都在IFileBrowser中定义,如前面提到的设置默认目录和设置过滤文件类型和一会在说明其他代码时会提及。
有了接口后如何才能调用呢?首先我们要创建COM并从COM中得到具体的实现:
CoCreateInstance( CLSID_FileBrowser, NULL,CLSCTX_INPROC_SERVER ,IID_MZ_FileBrowser,(void **)&pFileBrow )
这个CoCreateInstance就是干这活的,从函数名就可以理解,用于创建COM实例。看下几个主要的参数:
CLSID_FileBrowser:就是COM对象的GUID。IID_MZ_FileBrowser:这个是接口的GUID。(void **)&pFileBrow:这个主是返回的接口指针啦。返回成功的话会执行pFileBrow->QueryInterface( IID_MZ_Select, (void**)&pSelect ),这个用于查询com接口的,通过QueryInterface就可以查询出COM还实现了那些接口,比如本例中需要选择文件,通过QueryInterface查询IMzSelect接口来实现选择文件功能,当然这些你得看文档,除了选择外还有IMzSave保存文件等。
如果QueryInterface成功返回,接下来可以干正事了:
//申明一个用于获取返回的文档路径的字符串变量TCHAR file[ MAX_PATH ] = { 0 };
//用于设置父窗体pFileBrow->SetParentWnd( m_hWnd );
//下面的代码是设置打开的默认目录,之所以要检查m_DefaultDir是否为空就不调用SetOpenDirectoryPath函数,这样打开后就是默认为根目录啦。if (!m_DefaultDir.IsEmpty())
pFileBrow->SetOpenDirectoryPath( m_DefaultDir );
//设置过滤后缀pFileBrow->SetExtFilter( m_ExtFilter );
//应用根据需求进行文档打开方式的设置,这里使用单文件模式pFileBrow->SetOpenDocumentType(DOCUMENT_SELECT_SINGLE_FILE);
好了,这样准备工作都好了,接下就是调用了。
pSelect->Invoke()
我们查看IMzSelect继承的接口IMzUnknown,就实现了一个方法Invoke(),意思是调用也不知道魅族都干了些啥事情。总之就一个方法就调呗。实际的运行结果也是调用Invoke后就打开了文件浏览窗了。
选择了文件后就要处理返回值,看代码:
_tcscpy( file, pFileBrow->GetSelectedFileName());
*filename = file;
调用Invoke后COM会将选择的文件路径丢回给IFileBrowser里,于是就用下面的代码来将获取的文件路径写入到前面申明的字符串变量file中。然后将file交给函数参数,这样就可将文件路径返回给调用者咯。
到此正事干完了,开始善后咯,这个善后很重要哦。COM是通过引用计数来控制生命周期的,也就是说当一个COM每被一个调用者引用就会增加一次引用计数,当引用计数为0时COM对象会自动释放。看看代码都干了什么:
pSelect->Release();
pFileBrow->Release();
两个接口指针对调用了Release()方法,这个方法会使COM的引用计数减1,也就是减少一个引用计数。在前面引用接口时引用计数加过1了,所以在这里减1自然就抵消了。如果这时候COM没有被其他地方引用的话,引用计数就会回到0,即会自动释放。
最后的几句代码就是刷新一下窗口,并返回函数返回值。
::InvalidateRect( m_hWnd, NULL, FALSE );
::UpdateWindow( m_hWnd );
return FileExists(*filename);
函数返回时检查一下获取的文件是否存在,如果存在返回true,否则为false。
这样就完成了文件对话框类就完成了,最后放出调用的代码:
- void CM8NoteMainWnd::OpenFile()
- {
- CMiniFileDialog *openFile = NULL;
- openFile = new CMiniFileDialog();
- openFile->SetDefaultDir(L"\\Program Files\\");
- openFile->SetExtFilter(L"*.txt;*.ini;");
- TCHAR *thefile;
- if (openFile->SelectFile(&thefile))
- {
- CMzString mzstr = thefile;
- MzMessageBoxEx(m_hWnd, mzstr , L"哈哈");
- }
- delete openFile;
- }
复制代码