• VC编程技术点滴(一)MFC编程基础


    一、传统的Windows编程--SDK编程

        SDK 就是 Software Development Kit 的缩写,即“软件开发工具包”。这是一个覆盖面相当广泛的名词,应该说,辅助开发某一类软件的相关文档、范例和工具的集合都可以叫做“SDK”,如Java SDK等,这里只讨论开发 Windows 平台下的应用程序所使用的SDK。

        上面只是说了一个 SDK 大概的概念而已,为了解释什么是 SDK 我们不得不引入 API、动态链接库、导入库等等概念。

        首先要接触的是“API”,也就是 Application Programming Interface,其实就是操作系统留给应用程序的一个调用接口,应用程序通过调用操作系统的 API 而使操作系统去执行应用程序的命令(动作)。其实早在 DOS 时代就有 API 的概念,只不过那个时候的 API 是以中断调用的形式(INT 21h)提供的,在 DOS 下跑的应用程序都直接或间接的通过中断调用来使用操作系统功能,比如将 AH 置为 30h 后调用 INT 21h 就可以得到 DOS 操作系统的版本号。而在 Windows 中,系统 API 是以函数调用的方式提供的。同样是取得操作系统的版本号,在 Windows 中你所要做的就是调用 GetVersionEx() 函数。可以这么说,DOS API 是“Thinking in 汇编语言”的,而 Windows API 则是“Thinking in 高级语言”的。DOS API 是系统程序的一部分,他们与系统一同被载入内存并且可以通过中断矢量表找到他们的入口,那么 Windows API 呢?要说明白这个问题就不得不引入我们下面要介绍得这个概念--DLL。

        DLL,即 Dynamic Link Library(动态链接库)。我们经常会看到一些 .dll 格式的文件,这些文件就是动态链接库文件,其实也是一种可执行文件格式。跟 .exe 文件不同的是,.dll 文件不能直接执行,他们通常由 .exe 在执行时装入,内含有一些资源以及可执行代码等。其实 Windows 的三大模块就是以 DLL 的形式提供的(Kernel32.dll,User32.dll,GDI32.dll),里面就含有了 API 函数的执行代码。为了使用 DLL 中的 API 函数,我们必须要有 API 函数的声明(.H)和其导入库(.LIB),函数的原型声明不难理解,那么导入库又是做什么用的呢?我们暂时先这样理解:导入库是为了在 DLL 中找到 API 的入口点而使用的。

        所以,为了使用 API 函数,我们就要有跟 API 所对应的 .H 和 .LIB 文件,而 SDK 正是提供了一整套开发 Windows 应用程序所需的相关文件、范例和工具的“工具包”。到此为止,我们才真正的解释清楚了 SDK 的含义。

        由于 SDK 包含了使用 API 的必需资料,所以人们也常把仅使用 API 来编写 Windows 应用程序的开发方式叫做“SDK 编程”。而 API 和 SDK 是开发 Windows 应用程序所必需的东西,所以其它编程框架和类库都是建立在它们之上的,比如 VCL 和 MFC,虽然他们比起“SDK 编程”来有着更高的抽象度,但这丝毫不妨碍它们在需要的时候随时直接调用 API 函数。 【1】

        *动态链接的概念:如果多个应用程序同时运行,而且都是用了某一静态库中的某一个函数,那么系统会出现该函数的多个代码,而动态链接库允许这些程序共享这个函数的单个代码,从而节省了内存。【2】

        *在Windows应用程序中,调用Windows API中函数的方法通常与调用C库函数的方法相同,主要区别是C库函数的目标代码直接放到程序目标代码中,而Windows API函数的目标代码则位于程序之外的某个动态链接库(DLL)中。

         *Windows的API是包含在Windows操作系统中的,另外还有单独的驱动程序开发包(DDK),用来开发Windows驱动程序。

         *Windows环境下的最佳编程语言就是C/C++语言,因为Windows API函数本身就是用C语言编写的,C和C++调用C语言的API函数是直接引用,而其他语言则多一道转换。C/C++编程可以很方便地利用计算机的底层资源,其程序运行速度远远高于其他语言。

    二、MFC编程【2】

        Visual C++的微软基础类库(Microsoft Foundation Class Library,MFC)封装了大部分API函数,并提供一个应用程序框架,简化了和标准化了Windows程序设计,所以用MFC编写的Windows 应用程序也称为标准Windows程序设计。MFC约有200个类,提供了Windows应用程序框架和创建应用程序的组件。其中只有5个核心类对应用程 序框架有影响:CWinApp,CDocument,CView,CFrameWnd和CDocTemplate。这5个类之中只有CWinApp是必不 可少的类,CWinApp的对象在应用程序中必须且只能有一个,并且是一个全局对象,它建立了应用程序执行的主线程。全局对象是在Windows操作系统 调用WinMain()之前建立的,它建立了程序的主线程。形象地说,它开通了程序执行的道路。

        在MFC编程中,入口函数WinMain()被封装在MFC的应用程序框架内,称为AfxWinMain(),已经不用也不可以再定义另一个WinMain()函数。

        MFC编程最好的办法是使用MFC的应用程序向导AppWizard。

        1、MFC基础类及其层次结构

        MFC类库的开发者是微软应用程序框架(Application Framework)开发小组(AFX,成立于1989年)。MFC类库的开发经历了一个曲折的过程,从最初的纯粹面向对象的理想模式,转而追求实用性,通过用类封装Windows API和Windows对象,并按照API的方式进行工作,从而建立起一个切实可行的类库,使得用户可以方便地调用API函数直接完成某些任务。

        通过这种MFC和Windows很好的连接,使Windows下的C语言程序设计,成为面向对象的MFC。这里没有追求纯粹,而是包容和实用!MFC采用 单一继承,从根类Cobject层层派生出绝大多数MFC中的类,其层次结构是最典型的。如下图示,该图表示一个分类的层次结构,并不严格对应派生结构。

    VC编程技术点滴(一)MFC编程基础

        下图给出了Cobject主要派生类的派生层次:

    VC编程技术点滴(一)MFC编程基础

        Cobject根类派生的最重要的类组成一个应用程序结构(Application Architecture)的集合,它是由CCmdTarget为基类派生出来的,其中主要部分如下图所示:

    VC编程技术点滴(一)MFC编程基础

        2、MFC定义的宏、全局函数及全局变量

      MFC和Windows程序一样,使用大量的宏,宏不带任何前缀,全由大写字母表示。

        (1)MFC提供的主要宏:
         •  消息映射宏: 声明消息映射表 (将于3.2.2节介绍)宏DECLARE_MESSAGE_MAP、定义消息映射表宏BEGIN_MESSAGE_MAP和 END_MESSAGE_MAP对、消息映射表入口宏ON_加消息名。MFC程序处理消息非常轻松,得益于其庞大的消息宏系统,使程序设计人员从记忆大量 的消息参数中解脱出来。
         •  动态 MFC对象宏: DECLARE_DYNCREATE和DECLARE_DYNAMIC, IMPLEMENT_DYNCREATE和IMPLEMENT_DYNAMIC
         •  运行时类宏: RUNTIME_CLASS
         •  序列化宏: DECLARE_SERIAL和IMPLEMENT_SERIAL
         •  诊断服务宏: ASSERT、VERIFY
         •  跟踪服务宏: TRACE
         •  异常处理宏: THROW
         (2)全局函数
      MFC提供一些不属于任何类的全局函数,函数名以Afx开头,可以被应用程序中的所有类和函数所调用。如AfxAbort,无条件终止一个应用程序的 执行;AfxGetAppName,返回指向应用程序名的字符串指针;AfxWinInit由WinMain调用,对MFC应用程序进行图形用户界面 (GUI)的初始化等等。

          (3)全局变量
           全局变量名以 afx开头,如afxTraceFlag、afxDump等,主要与异常处理有关。

    3、MFC对象和Windows对象的关系

        MFC对象是C++对象,而且是特指封装了Windows对象的C++对象,不是任意的C++对象。下图以最典型的窗口类(CWnd)为例,介绍CWnd类对象与Windows窗口对象的关系:

    VC编程技术点滴(一)MFC编程基础

        MFC的窗口对象wnd是C++类的实例,即CWnd类或其派生类的实例,是由CWnd类或其派生类的构造函数创建的,最终由CWnd类或其派生类的析构 函数撤消。hwnd是HWND句柄类型的实例,为它建立了一个Windows操作系统的对象。然后把这个句柄放入CWnd类对象wnd的成员数据 m_hwnd中。这样wnd就包含了一个Windows操作系统的窗口对象。程序段如下:

        CWnd wnd;//定义窗口类(CWnd)的对象wnd

        HWND hwnd;//定义窗口句柄hwnd

        hwnd=CreateWindow(......);//调用API函数CreateWindow(...)建立一个Windows窗口类实例

        wnd.Attach(hwd);//把Windows窗口实例的句柄链到CWnd对象hwnd上

        ......

        DestroyWindow(hwnd);//调用API函数撤消Windows窗口 

        MFC封装了Windows的几乎所有的API函数,所以上面的API函数,可以用CWnd的成员函数代替。成员函数Create()可以代替API函数 CreateWindows()并加上另一个成员函数Attach()的全部功能,建立一个Windows操作系统的窗口对象,并把句柄自动保存在MFC 窗口对象wnd的m_hwnd成员数据中。通常MFC程序员不需要通过Windows窗口对象的句柄去使用win32API函数,而只需直接使用CWnd 的成员函数。

           其它的MFC对象和对应Windows对象也有类似的关系。见下表:

     

    项目

    MFC类

    对象句柄

    应用程序

    CWinApp

    HINSTANCE  m_hInstance

    窗口

    CWnd

    HWND  m_hWnd

    菜单

    CMenu

    HMENU  m_hMenu

    设备环境

    CDC

    HDC   m_hDC

    矩形区域

    CRgn

    HRGN  m_hRgn

    画刷

    CBrush

    HBRUSH  m_hBrush

    画笔

    CPen

    HPEN  m_hPen

    字体

    CFont

    HFONT  m_hFont

    位图

    CBitmap

    HBITMAP  m_hBitmap

    调色板

    CPalatte

    HPALATTE  m_hPalatte

       

        Windows窗口句柄通常是全局量,动态建立的Windows窗口对象也是全局变量。所以一个进程或线程可以取得另一个进程或线程的窗口句柄,并给它发送消息。但一个线程只能使用本线程创建的MFC窗口对象,不能使用其它线程创建的MFC窗口对象。

        注意:

        (1) Windows对象是Windows操作系统的一个内部数据结构的实例,由一个Windows系统全局的窗口句柄来唯一标志,由API函数CreatWindow()创建,而由DestroyWindow()撤消。

        (2) 在MFC编程中,通常在MFC对象建立以后,调用Create()成员函数建立Windows对象,并链接在MFC对象上。下面的程序段用来建立应用程序的主框架窗口。

        CFrameWnd *pFrame=new CFrameWnd; //动态建立框架窗口对象

        pFrame->Create(NULL,”Ex2_xx”,ws_OVERLAPPENDWINDOW,rect,NULL,

             MAKEINTRESOURCE(IDR_MENUI),0,NULL);

        //建立与pFrame指向的动态框架窗口对象相连的Windows框架窗口

         

    三、使用向导工具创建MFC文档应用程序的基本文件

        使用Microsoft Visual Studio C++6.0开发工具,创建MFC多文档应用程序的步骤如下:

        1、创建基于MFC的工程文件:

    VC编程技术点滴(一)MFC编程基础

        2、选择创建多文档应用程序:

    VC编程技术点滴(一)MFC编程基础

        3、因为本文只是要分析和使用MFC基本程序文件,所以保持工具向导的默认设置,点击Next,一直到下图所示,点击Finish结束基本应用程序的自动创建:

    VC编程技术点滴(一)MFC编程基础

        对MFC基本程序文件的理解:

        按照上述步骤创建出的MFC文档应用程序主要由应用程序App、框架程序Frame、文档程序Doc和视图程序View等文件组成。各个程序文件分别定义 了对应的类及其成员函数,以实现不同的功能。其中应用程序负责程序的启动、窗口及控件等的消息捕获与发送等;框架类继承自CWnd(窗口类)子类,对应不 同的窗口,对于本例创建的多文档应用程序,其CMainFrame类对象对应着主窗口对象,CChildFrame类对象对应着子窗口对象(可以在子窗口 完成文档编辑、图形绘制等);文档类负责对文档的创建、打开以及多种数据的保存等;视图类负责在子窗口中显示用户输入或文档内容,可以根据用户动作在窗口 (子窗口)客户区绘制图形、文字等。

        文档与视图(视窗)分离带来的好处就是一个文档可以同时具有多个视窗,每个视窗只显示文档中的一部分数据,或者以特定的风格(文档模板)显示文档中的数据;文档与视窗分离的另一个好处就是在程序中可以处理多个文档,通过对不同视窗的处理达到分别处理不同文档的目的。

        对于消息的捕获和处理,不同的类负责各自不同的功能。应用程序类负责捕获键盘、鼠标等点击事件(消息)并确定消息来源于哪个窗口,将消息分发给对应的窗口 进程,窗口进程负责将消息传递给对应的事件处理函数进行处理。事件处理函数可以在框架(窗口)类、文档类、视图类等不同的类中。

        1、类CSysMenuApp

        类CSysMenuApp是应用程序运行的基础,是由MFC中的类CWINAPP派生来的。这个类的一个重要的成员函数就是INITINSTANCE,我 们知道,在WINDOWS环境下面可以运行同一程序的多个实例,函数INITINSTANCE的作用就是在生成一个新的实例的时候,完成一些初始化的工 作:

        BOOL CSysMenuApp::InitInstance()

      

          ......

          CMultiDocTemplate* pDocTemplate;
          pDocTemplate = new CMultiDocTemplate(

              //IDR_SYSMENTYPE为菜单资源ID(如下图所示,当前文档模板所使用的子框架窗口菜单),针对

              //不同的文档模板,同一子窗口可以使用不同的菜单。这里通过创建和添加模板,把一种模板格

              //式和特定的菜单资源、框架窗口类、文档类、视图类等联系起来。如果创建并添加了多个文档

              //模板,在新建文档时应用程序将提示用户选择文档将使用的模板格式。

              IDR_SYSMENTYPE,
              RUNTIME_CLASS(CSysMenuDoc),
              RUNTIME_CLASS(CChildFrame), // custom MDI child frame
              RUNTIME_CLASS(CSysMenuView));
          AddDocTemplate(pDocTemplate);

          ......

        }

    VC编程技术点滴(一)MFC编程基础

        注意以上INITINSTANCE 函数的部分代码,它定义了一个文档模板对象指针PDOCTEMPLATE,通过NEW操作符,系统动态生成了这个文档模板对象,然后使用 ADDDOCTEMPLATE函数把这个文档模板对象加入到应用程序所维护的文档模板链表当中,这个文档模板PDOCTEMPLATE的作用就是把程序用 到的框架窗口CChildFrame,文档CSysMenuDoc,视窗CSysMenuView与应用对象CSysMenuApp联系起来。
        CSysMenuApp类对象自动与WINDOWS系统建立联系,接收WINDOWS传送的消息,并交给程序中相应的对象去处理,免去了程序员许多的工作,使得开发C++的WINDOWS程序变得简单方便。

        2、类CMainFrame
        类CMainFrame是由MFC中的CFRAMEWND派生来的,所以是一个框架窗口。为便于理解,可以形象地把CMainFrame类看成是类 CSysMenuView的父类,也就是说CSysMenuView类的对象显示在主框架窗口的客户区中。在类CMainFrame中,系统已经从类 CFRAMEWND那里继承了处理窗口一般事件的WINDOWS消息,比如改变窗口大小(窗口最小化等)的成员函数,因此编程的时候程序员不需再关心此类 消息的处理,从而减轻了程序员的负担。当然,如果确实需要重新编写处理此类消息的成员函数,则需要对原有的成员函数进行重载(注释掉原成员函数并通过 ClassWizard重新添加)。
        在MFC程序中,我们并不需要经常对CMAINFRAME类进行操作,更多的是对视窗类进行操作,达到对程序中的数据进行编辑和修改的目的。
        最后要指出的是,在MFC方式的程序中,当程序的一个实例被运行的时候,系统根据前面在CSysMenuApp类中介绍的文档模板对象自动生成类 CChildFrame(对于单文档程序则是CMAINFRAME),CSysMenuView,CSysMenuDoc的对象,而不需要程序员主动地去 创建这些类的对象。

        3、类CSysMenuDoc与CSysMenuView

        之所以把CSysMenuDoc类和CSysMenuView类一起介绍是因为这两个类是密切相关的,下面的框图可以说明文档与视窗的关系:

    VC编程技术点滴(一)MFC编程基础

        在这个框图当中,文档是由文档模板对象生成的,并由应用程序对象管理;用户使用鼠标、键盘等输入工具,并通过与文档相联系的视窗对象来显示、编辑应用程序 的图形及数据;用户与文档之间的交互则是通过与文档相关联的视窗对象来进行的。通过在视窗对象成员函数(如消息处理函数)中操作文档对象(及其成员变量 等)获取、记录和保存文档数据。
        对于多文档应用程序,在生成一个新的文档对象的时候,MFC程序同时生成一个主框架窗口对象和子框架窗口对象(当打开多个文档时会生成对应的多个子框架对 象。而对于单文档程序则始终只有一个主框架窗口,所以一次只能打开一个文档),并且在子框架窗口的客户区中生成一个视窗对象,这个视窗对象以可视化的方式 表现文档中的内容。视窗的重要功能就是负责处理用户的鼠标、键盘等操作事件,从而在窗口客户区绘制图形、文字、数据等。而且,通过对视窗对象的处理(如保 存),可以达到处理文档对象(如把图形属性及数据等保存到文档)的目的。
        需要再次强调的是,WINDOWS应用程序分单文档界面SDI和多文档界面MDI两种,在单文档界面中,文档窗口与主框架窗口是同一概念。而这时的视窗对象则是显示在文档窗口(主框架窗口)的客户区当中。
        下面将以一个例子来说明这两个类之间的关系:
        前面已经提到,文档类是用来存放程序中的数据的,所以我们首先在文档类CSysMenuDoc中加入一个成员变量用来存放数据。
        如下图所示,在左边的工作区用右键单击CSysMenuDoc选项,在弹出的菜单中选中Add member variable菜单项,系统弹出Add Member Variable对话框。Variable Type一栏用来输入成员变量的类型。这里设置为CString,即字符串类型,Variable Name一栏用来输入变量的名字,这里不妨输入为myString,Access组合框用来设置成员变量的访问权限,缺省为Public,设好后单击OK按钮关闭对话框。

    VC编程技术点滴(一)MFC编程基础

     

        这时,在类视图的CSysMenuDoc类成员中以及类CMyDoc的头文件中可以发现其中已经自动加入了我们定义的公有变量myString。这个变量就可以作为我们的文档类的数据存储空间,因为myString是公有成员,它就可以被文档对应的视窗所处理了。
        如下图所示,在VIEW菜单中选择ClassWizard菜单项,系统打开MFC ClassWizard对话框,接下来我们要为视窗类添加处理键盘事件的成员函数。在Classname一栏中选中类CSysMenuView,然后在messages一栏中选中消息wm_char,单击add function按钮,系统就自动往CMyView类中添加了处理wm_char消息的成员函数OnChar的框架。单击edit code按钮,可以对OnChar这个成员函数进行编辑和修改。

    VC编程技术点滴(一)MFC编程基础


        // SysMenuView.cpp : implementation of the CSysMenuView class

        ......

        BEGIN_MESSAGE_MAP(CSysMenuView, CView)
           //{{AFX_MSG_MAP(CSysMenuView)
           ON_WM_CHAR()
           //}}AFX_MSG_MAP
           // Standard printing commands
           ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)
           ......
        END_MESSAGE_MAP()

        ......

        void CSysMenuView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
        {
            // TODO: Add your message handler code here and/or call default
     
            CView::OnChar(nChar, nRepCnt, nFlags);
        }

        ......

        可以看出系统已经自动在这个成员函数中添加了调用CSysMenuView的基类CView的WM_CHAR消息处理函数的语句。   

        注意由开发工具自动生成的以上代码,键盘消息处理被放在mfc的消息映射宏BEGIN_MESSAGE_MAP中,它的作用就是把windows系统发来 的WM_CHAR消息连接到CSysMenuView类的成员函数OnChar上,即把这个成员函数作为处理WM_CHAR消息的过程。接下来我们就往这 个成员函数中添加处理WM_CHAR消息的具体代码。
        首先在OnChar函数中添加如下的代码:

        void CSysMenuView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
        {
            // TODO: Add your message handler code here and/or call default
            CSysMenuDoc * pdoc;
            pdoc=GetDocument();
            CView::OnChar(nChar, nRepCnt, nFlags);
        }

        这段代码的作用是首先定义一个指向文档类对象的指针pdoc,然后利用CSysMenuView类的成员函数getdocument()获取指向当前视窗 类所对应的文档类对象的指针,并把这个指针赋给定义的文档类型指针pdoc,这样我们在后面就可以用“pdoc->myString”的方式访问文 档类中定义的公有数据成员myString了。
        接着往函数OnChar中添加如下的代码:

        pdoc->myString+=nChar;
        CClientDC mydc(this);
        mydc.TextOut(0,0,pdoc->myString,pdoc->myString.GetLength());

        这段代码中的第一行代码的作用是根据从消息WM_CHAR中传来的参数nChar,也就是键盘中输入的字符的ASCII码,把输入的字符添加到文档中的字符串对象myString中。

        在介绍第二行代码前要先介绍设备描述表的概念。设备描述表也称为设备上下文,在windows环境中,当需要对一个对象,如打印机,屏幕,窗口等进行输出 时,就必须先获取这个对象的设备描述表,然后通过这个设备描述表来进行输出。使用设备描述表带来的最大的好处就是输出格式的一致性,因为输出不再是直接针 对具体的设备,而是通过统一格式的设备描述表间接地实现。第二行代码的作用就是定义并生成了一个当前视窗的客户区的设备描述表对象MYDC,其中的参数 THIS是面向对象语言中的一个重要的关键字。指代成员函数所在类的当前对象的指针。在生成了视窗的客户区的设备描述表MYDC之后,我们可以利用它在视 窗的客户区中输出数据了。
        这段代码的第三行就是调用设备描述表MYDC的方法TEXTOUT,在视窗的客户区中输出文档中的字符串MYSTRING了。

        我们在前面曾经指出,一个文档可以对应多个视窗。如果用户通过某个视窗更改了文档中的数据,就比如上面的代码当中,我们通过视窗CSysMenuView 更改了文档中的字符串对象MYSTRING,那么系统又如何维护同一文档的不同的视窗显示的数据的一致性呢?我们接着在OnChar函数中输入如下的代 码:

        pdoc->UpdateAllViews(this,0L,0);

        这行代码的作用就是通知本视窗所在的文档的所有其他的视窗(多种视窗类对象或相同视窗类的多个对象),文档中的数据已经更新,这些视窗应该重新从文档中取 回数据用来显示,这样就维持了同一文档的所有视窗的数据的一致性。这一行是视窗类中对文档的数据作了修改以后需要加的一条典型语句。
        接下来运行这个程序,在BUILD菜单中选择REBUILD ALL菜单项来编译连接应用程序,然后单击BUILD菜单的EXECUTE菜单项运行程序,从键盘上输入一些字符,可以发现这些字符显示在视窗也就是子窗口的客户区当中。

        而这些字符的实际位置是存放在文档对象的成员变量MYSTRING这个字符串中。改变一下窗口的大小,可以发现显示的数据都没有了,这是因为我们在窗口尺 寸改变的时候没有把数据重新输出到窗口的客户区中。关闭应用程序,找到CSysMenuView类的成员函数ONDRAW,在其中添加如下的代码:
        void CSysMenuView::OnDraw(CDC* pDC)
        {
           CSysMenuDoc* pDoc = GetDocument();
           ASSERT_VALID(pDoc);//断言,如果文档对象指针无效将中断程序执行
     
           // TODO: add draw code for native data here
           pDC->TextOut(0,0,pDoc->myString); 
        }

        当窗口的大小改变的时候,应用程序会调用ONDRAW这个函数刷新视窗等,我们添加的这行代码的作用就是把字符串对象MYSTRING重新显示在窗口的客户区当中。
        再次编译运行这个程序,可以发现窗口大小的改变不再影响数据的显示了。 

    参考文献

    1】 “SDK编程是指什么样的编程方法”,http://zhidao.baidu.com/question/5830100.html

    【2】 MFC实践教程

          http://jwc.seu.edu.cn/jpkc/declare/2006Cpp/CPP/CPP_Web/Mfc/htms/kejian/07_1_1.htm

  • 相关阅读:
    程序的版式
    文件结构
    LIB和DLL的区别与使用
    静态链接库
    C++ Vector
    C++ Map
    C++ List
    快速实现十进制向二进制转换
    Fail2ban 运维管理 服务控制
    Fail2ban 配置详解 动作配置
  • 原文地址:https://www.cnblogs.com/luoshupeng/p/2146168.html
Copyright © 2020-2023  润新知