首页 > 代码库 > Notepad++源码分析(1)(转载)
Notepad++源码分析(1)(转载)
在网上发现了一个哥们写了关于Notepad++源码的文章,不过就写了一就没有了,我就接着他的工作再说说吧!
大三了,也写了一点儿程序了,但是如果只是按照自己的思路写下去恐怕难以提高,于是准备开始阅读一些开源的代码,看看别人的代码,跟别人学习学习。
一上来就接触过于大型的项目怕是无力掌握,于是从小一点儿的开始。很早的时候我就准备读一读Notepad++这个开源项目的代码了,但是总是有别的事 情,一拖再拖,现在安静下来了,就读一读吧。一开始当然从V1.0开始读啦,之后再慢慢的更新,扩大。并且,边读边记一些笔记,有一些可能看起来非常幼 稚,不过确实是我所想所感的,于是记录下来,方便自己今后查阅,也方便与别的有需要的童鞋。
今天主要的任务是分析一下Notepad++启动是的动作,准备好源码(可以从SourceForge下载),设置好断点,准备开始吧!
下载完源码之后,可以看到,Notepad++的1.0版本一共有21个cpp文件,看他们的名字基本就可以知道他们的作用了。不要忘了今天的目的,就 是了解Notepad++启动时相关的动作,从开始执行分析到他的消息处理循环,今天的任务就完成了~所以,咱们果断打开winmain.cpp文件。
话说第一眼看到这个文件我正要感叹作者真是好心人,写这么多注释,这些可爽了~而是定睛一看,居然是版权说明!代码里面干净的要死。。。只好硬着头皮一点儿一点儿的看了。。。
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, int nCmdShow)
- {
- HWND hNotepad_plus = ::FindWindow(Notepad_plus::getClassName(), NULL);
- if (hNotepad_plus)
- {
- if (::IsIconic(hNotepad_plus))
- ::OpenIcon(hNotepad_plus);
- ::SetForegroundWindow(hNotepad_plus);
- if (lpszCmdLine[0])
- {
- COPYDATASTRUCT copyData;
- copyData.dwData = 0;//(ULONG_PTR);
- copyData.cbData = DWORD(strlen(lpszCmdLine) + 1);
- copyData.lpData = lpszCmdLine;
- ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)©Data);
- }
- return 0; // if there has been a opended window, do not open another one!
- }
- Notepad_plus notepad_plus_plus;
- MSG msg;
- msg.wParam = 0;
- try {
- char *pPathNames = NULL;
- if (lpszCmdLine[0])
- {
- pPathNames = lpszCmdLine;
- }
- notepad_plus_plus.init(hInstance, NULL, pPathNames);
- HACCEL hAccTable = ::LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_M30_ACCELERATORS));
- MSG msg;
- msg.wParam = 0;
- while (::GetMessage(&msg, NULL, 0, 0))
- {
- // if the message doesn‘t belong to the notepad_plus_plus‘s dialog
- if (!notepad_plus_plus.isDlgMsg(&msg))
- {
- if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), hAccTable, &msg) == 0)
- {
- ::TranslateMessage(&msg);
- ::DispatchMessage(&msg);
- }
- }
- }
- } catch(int i) {
- if (i == 106901)
- ::MessageBox(NULL, "Scintilla.init is failled!", "106901", MB_OK);
- else {
- char str[50] = "God Damn Exception : ";
- char code[10];
- itoa(i, code, 10);
- ::MessageBox(NULL, strcat(str, code), "int exception", MB_OK);
- }
- }
- catch(std::exception ex) {
- ::MessageBox(NULL, ex.what(), "Exception", MB_OK);
- }
- catch(...) {
- systemMessage("System Err");
- }
- return (UINT)msg.wParam;
- }
这个文件倒也直接了当,只有一个WinMain函数,消息循环这个文件里面也有了,看来搞定这个文件咱们今天就可以收工了~如果你对 WindowsAPI很熟悉的话,看这几行代码应该不成问题,可是我以前基本没用过(只用过很少几个),所以看代码也顺便学习一下API的使用了~由于这 里面大量使用了API,我不可能一一列出,所以打开MSDN或者Google准备好吧~
好,一行一行的看:
HWND hNotepad_plus = ::FindWindow(Notepad_plus::getClassName(), NULL);
这一行调用了FindWindow,这个API的目的是为了找到满足类名为第一个参数,而标题名为第二个参数的handle(句柄)。MSDN上面说第二个参数给NULL则匹配所有满足类名为第一个参数的句柄。
乍看起来,这个有些奇怪。作为主函数一开始不初始化,先获取句柄,这是安得什么心?各位看官向下看就能找到答案了~
- if (hNotepad_plus)
- {
- if (::IsIconic(hNotepad_plus))
- ::OpenIcon(hNotepad_plus);
- ::SetForegroundWindow(hNotepad_plus);
- if (lpszCmdLine[0])
- {
- COPYDATASTRUCT copyData;
- copyData.dwData = 0;//(ULONG_PTR);
- copyData.cbData = DWORD(strlen(lpszCmdLine) + 1);
- copyData.lpData = lpszCmdLine;
- ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)©Data);
- }
- return 0;
- }
这段代码是在结果不是NULL,也就是查找成功的时候执行的代码。认真看的话能看出一些名堂来了~对了,就是为了保证Notepad++只存在一个实 例,如果已经打开了一个Notepad++的实例,则hNotepad_plus这个句柄必然不是NULL,这个时候如果用户再次尝试打开,或者尝试拖拽 某个文件到Notepad++程序中,只会导致当前存在实例被最大化,或者开启一个新的tab来显示新打开的文件。(怎么实现的咱们之后再分析吧)各位可 以试一试,找到编译生成的Notepad++程序(在这里是debug版的),分别打开两次,把拖拽文件到程序图标打开试试,就会发现跟咱们想的一样,确 实只有一个Notepad++的实例存在~嗯嗯,还不赖~
好了,由于咱们现在分析的是第一次执行的时候的状况,所以可以不考虑这一段代码,如果你设置了断点,并且单步跟踪执行的话,也会发现这一段代码没有执行~
好,接下来进入第一次创建窗体时的部分~
这段代码是被try起来的,我们先不考虑异常处理部分,这段代码相对还是比较好懂的~
- char *pPathNames = NULL;
- if (lpszCmdLine[0])
- {
- pPathNames = lpszCmdLine;
- }
- notepad_plus_plus.init(hInstance, NULL, pPathNames);
- HACCEL hAccTable = ::LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_M30_ACCELERATORS));
- MSG msg;
- msg.wParam = 0;
- while (::GetMessage(&msg, NULL, 0, 0))
- {
- // if the message doesn‘t belong to the notepad_plus_plus‘s dialog
- if (!notepad_plus_plus.isDlgMsg(&msg))
- {
- if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), hAccTable, &msg) == 0)
- {
- ::TranslateMessage(&msg);
- ::DispatchMessage(&msg);
- }
- }
首先是,由于程序支持将文件拖拽到图标打开文件(马上就能看到怎么实现的),所以先去命令行参数的第一个参数,也就是要打开的文件名,然后调用notepad_plus_plus.init方法,这个方法值得一看,在init上面右键,转到定义!
- Window::init(hInst, parent);
- WNDCLASS MACOCS30EditorClass;
- MACOCS30EditorClass.style = 0;//CS_HREDRAW | CS_VREDRAW;
- MACOCS30EditorClass.lpfnWndProc = Notepad_plus_Proc;
- MACOCS30EditorClass.cbClsExtra = 0;
- MACOCS30EditorClass.cbWndExtra = 0;
- MACOCS30EditorClass.hInstance = _hInst;
- MACOCS30EditorClass.hIcon = ::LoadIcon(_hInst, MAKEINTRESOURCE(IDI_M30ICON));
- MACOCS30EditorClass.hCursor = NULL;
- MACOCS30EditorClass.hbrBackground = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));
- MACOCS30EditorClass.lpszMenuName = MAKEINTRESOURCE(IDR_M30_MENU);
- MACOCS30EditorClass.lpszClassName = _className;
- if (!::RegisterClass(&MACOCS30EditorClass))
- {
- systemMessage("System Err");
- throw int(98);
- }
- _hSelf = ::CreateWindowEx(
- WS_EX_ACCEPTFILES,/
- _className,/
- "MACOCS 30 IDE Demonstration",/
- WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,/
- CW_USEDEFAULT, CW_USEDEFAULT,/
- CW_USEDEFAULT, CW_USEDEFAULT,/
- _hParent,/
- NULL,/
- _hInst,/
- (LPVOID)this); // pass the ptr of this instantiated object
- // for retrive it in Notepad_plus_Proc from
- // the CREATESTRUCT.lpCreateParams afterward.
- if (!_hSelf)
- {
- systemMessage("System Err");
- throw int(777);
- }
- if (cmdLine)
- {
- FileNamStringSpliter fnss(cmdLine);
- char *pFn = NULL;
- for (int i = 0 ; i < fnss.size() ; i++)
- {
- pFn = (char *)fnss.getFileName(i);
- doOpen((const char *)pFn);
- }
- }
- setTitle(_className);
- display();
- checkDocState();
这个方法非常普通,首先调用父类Window的init方法,这个Window是自己实现的,之后咱们再分析里面有什么,为什么这样做~
然 后就是填充WNDCLASS的对象,MACOCS30EditorClass。WNDCLASS的各个属性各位可以查MSDN,需要各位注意的是 MACOCS30EditorClass.lpfnWndProc = Notepad_plus_Proc;这一行,这个注册的是消息处理的回调函数,至于消息驱动那一套东西我就不说了,大家可以自行 Google~Notepad_plus_Proc这个函数很关键,赶紧右键转到定义一下!
- LRESULT CALLBACK Notepad_plus::Notepad_plus_Proc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)
- {
- switch(Message)
- {
- case WM_NCCREATE : // First message we get the ptr of instantiated object
- // then stock it into GWL_USERDATA index in order to retrieve afterward
- {
- Notepad_plus *pM30ide = (Notepad_plus *)(((LPCREATESTRUCT)lParam)->lpCreateParams);
- pM30ide->_hSelf = hwnd;
- ::SetWindowLong(hwnd, GWL_USERDATA, (LONG)pM30ide);
- return TRUE;
- }
- default :
- {
- return ((Notepad_plus *)::GetWindowLong(hwnd, GWL_USERDATA))->runProc(hwnd, Message, wParam, lParam);
- }
- }
- }
这个方法里面特别处理了一个WM_NCCREATE消息,把其他的消息都送给了runProc这个方法来处理。那么WM_NCCREATE这个消息为什么要如此特别的处理一下呢?
这里干了一件事情,就是用SetWindowLong方法,把hwnd对应的GWL_USERDATA设置为Notepad_plus对象的指针。而以 后再处理消息的时候,则是用GWL_USERDATA对应的这个Notepad_plus对象指针调用runProc函数。这里我学到了一招,把 GWL_USERDATA作为数据存储的容器。尤其是消息处理函数给咱们的参数里面并没有Notepad_plus这种对象的指针,只有在 WM_NCCREATE消息的时候会通过CREATESTRUCT对象里面的lpCreateParameter传递过来,可是咱们的runProc是定 义在Notepad_plus这个类中的啊,得Notepad_plus这个对象的指针才能调用啊,所以就在第一次创建的时候把这个指针存到这个容器之 中,以后就可以随意使用啦!
好,从Notepad_plus_Proc回来,继续init之旅~接下来是用RegisterClass注册这个类的对象,然后 CreateWindowEx。注意咱们之前说的那个WM_NCCREATE消息也就是这个时候触发的,clear?CreateWindowEx的第一 个参数很有意思,WS_EX_ACCEPTFILES,去MSDN查一下就发现,正是因为这个属性,才使我们可以拖动文件到Notepad++里面去,好 了,以后如果咱自己写的应用也要有这种效果,咱们也这么干~
创建之后,会判断用户是否确实拖拽了一个文件以打开。这段代码也放到以后分析~
最后是setTitle设置标题,默认设置时类名,这个类名是由一个宏定义的常量决定的:
#define NOTEPAD_PP_CLASS_NAME "Notepad++"
在Notepad_plus.cpp中这样赋值:
const char Notepad_plus::_className[32] = NOTEPAD_PP_CLASS_NAME;
所以,这里的_className也就是Notepad++!然后显示,检查文档那个的状态等等,这些咱们都以后再分析!
之后是加载助记符~助记符的详细列表可以在资源文件的Notepad_plus.rc文件中找到,为了方便大家,贴到这里:
- IDR_M30_ACCELERATORS ACCELERATORS
- BEGIN
- //"A", IDM_EDIT_SELECTALL, VIRTKEY, CONTROL
- //"C", IDM_EDIT_COPY, VIRTKEY, CONTROL
- "N", IDM_FILE_NEW, VIRTKEY, CONTROL
- "O", IDM_FILE_OPEN, VIRTKEY, CONTROL
- "S", IDM_FILE_SAVE, VIRTKEY, CONTROL
- //"V", IDM_EDIT_PASTE, VIRTKEY, CONTROL
- //VK_DELETE, IDM_EDIT_DELETE, VIRTKEY
- //"X", IDM_EDIT_CUT, VIRTKEY, CONTROL
- //"Y", IDM_EDIT_REDO, VIRTKEY, CONTROL
- //"Z", IDM_EDIT_UNDO, VIRTKEY, CONTROL
- "F", IDM_EDIT_FIND, VIRTKEY, CONTROL
- "H", IDM_EDIT_REPLACE, VIRTKEY, CONTROL
- VK_F3, IDM_EDIT_FINDNEXT, VIRTKEY
- END
助记符加载完毕,就进入了主消息循环,看来胜利在望啊~~
首先GetMessage,第一件事是检测是不是对话框的消息,如果是的话就不继续处理了,对话框咱们也是以后再分析!继续向下看~
首先来了一个TranslateAccelerator,也就是判断是不是助记符,如果是的话,返回的结果不是0,也就不继续 TranslateMessage了,这个也是MSDN上面说的:an application should not call TranslateMessage if the TranslateAccelerator function returns a nonzero value.
最后就是消息循环啦~
好了,今天的任务也就到此为止了~~单击执行按钮,Notepad++的1.0版本也就展现在我们眼前了~
分析了这么多,好像感觉少了点儿什么~对!这些控件什么的怎么出来的?好,下一次就把这个作为切入点,分析一下WM_CREATE消息的处理中到底干了些什么~说不定可以顺藤摸瓜,看看都有Notepad++都“实现”了哪些控件~那么,下次见啦!