Notepad++源码分析(1)(转载)

在网上发现了一个哥们写了关于Notepad++源码的文章,不过就写了一就没有了,我就接着他的工作再说说吧!微笑

 

  大三了,也写了一点儿程序了,但是如果只是按照自己的思路写下去恐怕难以提高,于是准备开始阅读一些开源的代码,看看别人的代码,跟别人学习学习。

    一上来就接触过于大型的项目怕是无力掌握,于是从小一点儿的开始。很早的时候我就准备读一读Notepad++这个开源项目的代码了,但是总是有别的事 情,一拖再拖,现在安静下来了,就读一读吧。一开始当然从V1.0开始读啦,之后再慢慢的更新,扩大。并且,边读边记一些笔记,有一些可能看起来非常幼 稚,不过确实是我所想所感的,于是记录下来,方便自己今后查阅,也方便与别的有需要的童鞋。

今天主要的任务是分析一下Notepad++启动是的动作,准备好源码(可以从SourceForge下载),设置好断点,准备开始吧!

    下载完源码之后,可以看到,Notepad++的1.0版本一共有21个cpp文件,看他们的名字基本就可以知道他们的作用了。不要忘了今天的目的,就 是了解Notepad++启动时相关的动作,从开始执行分析到他的消息处理循环,今天的任务就完成了~所以,咱们果断打开winmain.cpp文件。

    话说第一眼看到这个文件我正要感叹作者真是好心人,写这么多注释,这些可爽了~而是定睛一看,居然是版权说明!代码里面干净的要死。。。只好硬着头皮一点儿一点儿的看了。。。

  1. int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpszCmdLine, int nCmdShow)  
  2. {  
  3.       
  4.     HWND hNotepad_plus = ::FindWindow(Notepad_plus::getClassName(), NULL);   
  5.     if (hNotepad_plus)   
  6.     {  
  7.         if (::IsIconic(hNotepad_plus))   
  8.             ::OpenIcon(hNotepad_plus);   
  9.         ::SetForegroundWindow(hNotepad_plus);   
  10.         if (lpszCmdLine[0])    
  11.         {  
  12.             COPYDATASTRUCT copyData;  
  13.             copyData.dwData = 0;//(ULONG_PTR);  
  14.             copyData.cbData = DWORD(strlen(lpszCmdLine) + 1);   
  15.             copyData.lpData = lpszCmdLine;   
  16.               
  17.             ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&copyData);   
  18.         }  
  19.         return 0; // if there has been a opended window, do not open another one!  
  20.     }  
  21.   
  22.     Notepad_plus notepad_plus_plus;  
  23.     MSG msg;  
  24.     msg.wParam = 0;  
  25.     try {  
  26.         char *pPathNames = NULL;  
  27.         if (lpszCmdLine[0])  
  28.         {  
  29.             pPathNames = lpszCmdLine;  
  30.         }  
  31.         notepad_plus_plus.init(hInstance, NULL, pPathNames);  
  32.         HACCEL hAccTable = ::LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_M30_ACCELERATORS));  
  33.         MSG msg;  
  34.         msg.wParam = 0;  
  35.         while (::GetMessage(&msg, NULL, 0, 0))  
  36.         {  
  37.             // if the message doesn't belong to the notepad_plus_plus's dialog  
  38.             if (!notepad_plus_plus.isDlgMsg(&msg))  
  39.             {   
  40.                 if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), hAccTable, &msg) == 0)   
  41.                 {  
  42.                     ::TranslateMessage(&msg);  
  43.                     ::DispatchMessage(&msg);  
  44.                 }  
  45.             }  
  46.         }  
  47.     } catch(int i) {  
  48.         if (i == 106901)  
  49.             ::MessageBox(NULL, "Scintilla.init is failled!", "106901", MB_OK);  
  50.         else {  
  51.             char str[50] = "God Damn Exception : ";  
  52.             char code[10];  
  53.             itoa(i, code, 10);  
  54.             ::MessageBox(NULL, strcat(str, code), "int exception", MB_OK);  
  55.         }  
  56.     }  
  57.       
  58.     catch(std::exception ex) {  
  59.         ::MessageBox(NULL, ex.what(), "Exception", MB_OK);  
  60.     }  
  61.     catch(...) {  
  62.         systemMessage("System Err");  
  63.     }  
  64.     return (UINT)msg.wParam;  
  65. }  

 

    这个文件倒也直接了当,只有一个WinMain函数,消息循环这个文件里面也有了,看来搞定这个文件咱们今天就可以收工了~如果你对 WindowsAPI很熟悉的话,看这几行代码应该不成问题,可是我以前基本没用过(只用过很少几个),所以看代码也顺便学习一下API的使用了~由于这 里面大量使用了API,我不可能一一列出,所以打开MSDN或者Google准备好吧~

    好,一行一行的看:

    HWND hNotepad_plus = ::FindWindow(Notepad_plus::getClassName(), NULL);

    这一行调用了FindWindow,这个API的目的是为了找到满足类名为第一个参数,而标题名为第二个参数的handle(句柄)。MSDN上面说第二个参数给NULL则匹配所有满足类名为第一个参数的句柄。

    乍看起来,这个有些奇怪。作为主函数一开始不初始化,先获取句柄,这是安得什么心?各位看官向下看就能找到答案了~

  1. if (hNotepad_plus)   
  2.     {  
  3.         if (::IsIconic(hNotepad_plus))   
  4.             ::OpenIcon(hNotepad_plus);   
  5.         ::SetForegroundWindow(hNotepad_plus);   
  6.         if (lpszCmdLine[0])    
  7.         {  
  8.             COPYDATASTRUCT copyData;  
  9.             copyData.dwData = 0;//(ULONG_PTR);  
  10.             copyData.cbData = DWORD(strlen(lpszCmdLine) + 1);   
  11.             copyData.lpData = lpszCmdLine;   
  12.               
  13.             ::SendMessage(hNotepad_plus, WM_COPYDATA, (WPARAM)hInstance, (LPARAM)&copyData);   
  14.         }  
  15.         return 0;   
  16.     }  

 

    这段代码是在结果不是NULL,也就是查找成功的时候执行的代码。认真看的话能看出一些名堂来了~对了,就是为了保证Notepad++只存在一个实 例,如果已经打开了一个Notepad++的实例,则hNotepad_plus这个句柄必然不是NULL,这个时候如果用户再次尝试打开,或者尝试拖拽 某个文件到Notepad++程序中,只会导致当前存在实例被最大化,或者开启一个新的tab来显示新打开的文件。(怎么实现的咱们之后再分析吧)各位可 以试一试,找到编译生成的Notepad++程序(在这里是debug版的),分别打开两次,把拖拽文件到程序图标打开试试,就会发现跟咱们想的一样,确 实只有一个Notepad++的实例存在~嗯嗯,还不赖~

    好了,由于咱们现在分析的是第一次执行的时候的状况,所以可以不考虑这一段代码,如果你设置了断点,并且单步跟踪执行的话,也会发现这一段代码没有执行~

    好,接下来进入第一次创建窗体时的部分~

    这段代码是被try起来的,我们先不考虑异常处理部分,这段代码相对还是比较好懂的~

  1. char *pPathNames = NULL;  
  2.         if (lpszCmdLine[0])  
  3.         {  
  4.             pPathNames = lpszCmdLine;  
  5.         }  
  6.         notepad_plus_plus.init(hInstance, NULL, pPathNames);  
  7.         HACCEL hAccTable = ::LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_M30_ACCELERATORS));  
  8.         MSG msg;  
  9.         msg.wParam = 0;  
  10.         while (::GetMessage(&msg, NULL, 0, 0))  
  11.         {  
  12.             // if the message doesn't belong to the notepad_plus_plus's dialog  
  13.             if (!notepad_plus_plus.isDlgMsg(&msg))  
  14.             {   
  15.                 if (::TranslateAccelerator(notepad_plus_plus.getHSelf(), hAccTable, &msg) == 0)   
  16.                 {  
  17.                     ::TranslateMessage(&msg);  
  18.                     ::DispatchMessage(&msg);  
  19.                 }  
  20.             }  

 

    首先是,由于程序支持将文件拖拽到图标打开文件(马上就能看到怎么实现的),所以先去命令行参数的第一个参数,也就是要打开的文件名,然后调用notepad_plus_plus.init方法,这个方法值得一看,在init上面右键,转到定义!

  1. Window::init(hInst, parent);  
  2.       
  3.     WNDCLASS MACOCS30EditorClass;  
  4.     MACOCS30EditorClass.style = 0;//CS_HREDRAW | CS_VREDRAW;  
  5.     MACOCS30EditorClass.lpfnWndProc = Notepad_plus_Proc;  
  6.     MACOCS30EditorClass.cbClsExtra = 0;  
  7.     MACOCS30EditorClass.cbWndExtra = 0;  
  8.     MACOCS30EditorClass.hInstance = _hInst;  
  9.     MACOCS30EditorClass.hIcon = ::LoadIcon(_hInst, MAKEINTRESOURCE(IDI_M30ICON));  
  10.     MACOCS30EditorClass.hCursor = NULL;  
  11.     MACOCS30EditorClass.hbrBackground = ::CreateSolidBrush(::GetSysColor(COLOR_MENU));  
  12.     MACOCS30EditorClass.lpszMenuName = MAKEINTRESOURCE(IDR_M30_MENU);  
  13.     MACOCS30EditorClass.lpszClassName = _className;  
  14.     if (!::RegisterClass(&MACOCS30EditorClass))  
  15.     {  
  16.         systemMessage("System Err");  
  17.         throw int(98);  
  18.     }  
  19.     _hSelf = ::CreateWindowEx(  
  20.                     WS_EX_ACCEPTFILES,/  
  21.                     _className,/  
  22.                     "MACOCS 30 IDE Demonstration",/  
  23.                     WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,/  
  24.                     CW_USEDEFAULT, CW_USEDEFAULT,/  
  25.                     CW_USEDEFAULT, CW_USEDEFAULT,/  
  26.                     _hParent,/  
  27.                     NULL,/  
  28.                     _hInst,/  
  29.                     (LPVOID)this); // pass the ptr of this instantiated object  
  30.                                    // for retrive it in Notepad_plus_Proc from   
  31.                                    // the CREATESTRUCT.lpCreateParams afterward.  
  32.     
  33.     if (!_hSelf)  
  34.     {  
  35.         systemMessage("System Err");  
  36.         throw int(777);  
  37.     }  
  38.       
  39.     if (cmdLine)  
  40.     {  
  41.         FileNamStringSpliter fnss(cmdLine);  
  42.         char *pFn = NULL;  
  43.         for (int i = 0 ; i < fnss.size() ; i++)  
  44.         {  
  45.             pFn = (char *)fnss.getFileName(i);  
  46.             doOpen((const char *)pFn);  
  47.         }  
  48.     }  
  49.     setTitle(_className);  
  50.     display();  
  51.     checkDocState();  

 

    这个方法非常普通,首先调用父类Window的init方法,这个Window是自己实现的,之后咱们再分析里面有什么,为什么这样做~

然 后就是填充WNDCLASS的对象,MACOCS30EditorClass。WNDCLASS的各个属性各位可以查MSDN,需要各位注意的是 MACOCS30EditorClass.lpfnWndProc = Notepad_plus_Proc;这一行,这个注册的是消息处理的回调函数,至于消息驱动那一套东西我就不说了,大家可以自行 Google~Notepad_plus_Proc这个函数很关键,赶紧右键转到定义一下!

  1. LRESULT CALLBACK Notepad_plus::Notepad_plus_Proc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.   switch(Message)  
  4.   {  
  5.     case WM_NCCREATE : // First message we get the ptr of instantiated object  
  6.                        // then stock it into GWL_USERDATA index in order to retrieve afterward  
  7.     {  
  8.         Notepad_plus *pM30ide = (Notepad_plus *)(((LPCREATESTRUCT)lParam)->lpCreateParams);  
  9.         pM30ide->_hSelf = hwnd;  
  10.         ::SetWindowLong(hwnd, GWL_USERDATA, (LONG)pM30ide);  
  11.         return TRUE;  
  12.     }  
  13.     default :  
  14.     {  
  15.       return ((Notepad_plus *)::GetWindowLong(hwnd, GWL_USERDATA))->runProc(hwnd, Message, wParam, lParam);  
  16.     }  
  17.   }  
  18. }  

 

    这个方法里面特别处理了一个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文件中找到,为了方便大家,贴到这里:

  1. IDR_M30_ACCELERATORS ACCELERATORS   
  2. BEGIN  
  3.     //"A",            IDM_EDIT_SELECTALL,     VIRTKEY, CONTROL  
  4.     //"C",            IDM_EDIT_COPY,          VIRTKEY, CONTROL  
  5.     "N",            IDM_FILE_NEW,           VIRTKEY, CONTROL  
  6.     "O",            IDM_FILE_OPEN,          VIRTKEY, CONTROL  
  7.     "S",            IDM_FILE_SAVE,          VIRTKEY, CONTROL  
  8.     //"V",            IDM_EDIT_PASTE,         VIRTKEY, CONTROL  
  9.     //VK_DELETE,      IDM_EDIT_DELETE,        VIRTKEY   
  10.     //"X",            IDM_EDIT_CUT,           VIRTKEY, CONTROL  
  11.     //"Y",            IDM_EDIT_REDO,          VIRTKEY, CONTROL  
  12.     //"Z",            IDM_EDIT_UNDO,          VIRTKEY, CONTROL  
  13. "F",            IDM_EDIT_FIND,          VIRTKEY, CONTROL  
  14. "H",            IDM_EDIT_REPLACE,      VIRTKEY, CONTROL  
  15. VK_F3,        IDM_EDIT_FINDNEXT,    VIRTKEY  
  16. 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++都“实现”了哪些控件~那么,下次见啦!

posted @ 2014-07-17 16:56  hxb316  阅读(310)  评论(0编辑  收藏  举报