Visual C++动态链接库编程(三)

  第4节我们对非MFC DLL进行了介绍,这一节将详细地讲述MFC规则DLL的创建与使用技巧。

  另外,自从本文开始连载后,收到了一些读者的e-mail。有的读者提出了一些问题,笔者将在本文的最后一次连载中选取其中的典型问题进行解答。由于时间的关系,对于读者朋友的来信,笔者暂时不能一一回复,还望海涵!由于笔者的水平有限,文中难免有错误和纰漏,也热诚欢迎读者朋友不吝指正!

  5. MFC规则DLL

  5.1 概述

  MFC规则DLL的概念体现在两方面:

  (1) 它是MFC的      “是MFC的”意味着可以在这种DLL的内部使用MFC;

  (2) 它是规则的

  “是规则的”意味着它不同于MFC扩展DLL,在MFC规则DLL的内部虽然可以使用MFC,但是其与应用程序的接口不能是MFC。而MFC扩展DLL与应用程序的接口可以是MFC,可以从MFC扩展DLL中导出一个MFC类的派生类。

  Regular DLL能够被所有支持DLL技术的语言所编写的应用程序调用,当然也包括使用MFC的应用程序。在这种动态连接库中,包含一个从CWinApp继承下来的类,DllMain函数则由MFC自动提供。

  Regular DLL分为两类:

  (1)静态链接到MFC 的规则DLL

  静态链接到MFC的规则DLL与MFC库(包括MFC扩展 DLL)静态链接,将MFC库的代码直接生成在.dll文件中。在调用这种DLL的接口时,MFC使用DLL的资源。因此,在静态链接到MFC 的规则DLL中不需要进行模块状态的切换。

  使用这种方法生成的规则DLL其程序较大,也可能包含重复的代码。

  (2)动态链接到MFC 的规则DLL

  动态链接到MFC 的规则DLL 可以和使用它的可执行文件同时动态链接到 MFC DLL 和任何MFC扩展 DLL。在使用了MFC共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模板。这样,当DLL和应用程序中存在相同ID的资源时(即所谓的资源重复问题),系统可能不能获得正确的资源。因此,对于共享MFC DLL的规则DLL,我们必须进行模块切换以使得MFC能够找到正确的资源模板。

  我们可以在Visual C++中设置MFC规则DLL是静态链接到MFC DLL还是动态链接到MFC DLL。如图8,依次选择Visual C++的project -> Settings -> General菜单或选项,在Microsoft Foundation Classes中进行设置。

图8 设置动态/静态链接MFC DLL

  5.2 MFC规则DLL的创建

  我们来一步步讲述使用MFC向导创建MFC规则DLL的过程,首先新建一个project,如图9,选择project的类型为MFC AppWizard(dll)。点击OK进入如图10所示的对话框。

图9 MFC DLL工程的创建

图10所示对话框中的1区选择MFC DLL的类别。

  2区选择是否支持automation(自动化)技术, automation 允许用户在一个应用程序中操纵另外一个应用程序或组件。例如,我们可以在应用程序中利用 Microsoft Word 或Microsoft Excel的工具,而这种使用对用户而言是透明的。自动化技术可以大大简化和加快应用程序的开发。

  3区选择是否支持Windows Sockets,当选择此项目时,应用程序能在 TCP/IP 网络上进行通信。 CWinApp派生类的InitInstance成员函数会初始化通讯端的支持,同时工程中的StdAfx.h文件会自动include <AfxSock.h>头文件。

  添加socket通讯支持后的InitInstance成员函数如下:

BOOL CRegularDllSocketApp::InitInstance() {  if (!AfxSocketInit())  {   AfxMessageBox(IDP_SOCKETS_INIT_FAILED);   return FALSE;  }  return TRUE; }

  4区选择是否由MFC向导自动在源代码中添加注释,一般我们选择“Yes,please”。

图10 MFC DLL的创建选项

  5.3 一个简单的MFC规则DLL

  这个DLL的例子(属于静态链接到MFC 的规则DLL)中提供了一个如图11所示的对话框。

图11 MFC规则DLL例子

  在DLL中添加对话框的方式与在MFC应用程序中是一样的。 在图11所示DLL中的对话框的Hello按钮上点击时将MessageBox一个“Hello,pconline的网友”对话框,下面是相关的文件及源代码,其中删除了MFC向导自动生成的绝大多数注释(下载本工程):

  第一组文件:CWinApp继承类的声明与实现

// RegularDll.h : main header file for the REGULARDLL DLL#if !defined(AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_) #define AFX_REGULARDLL_H__3E9CB22B_588B_4388_B778_B3416ADB79B3__INCLUDED_#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #ifndef __AFXWIN_H__ #error include ’stdafx.h’ before including this file for PCH #endif #include “resource.h” // main symbols class CRegularDllApp : public CWinApp {  public:   CRegularDllApp();   DECLARE_MESSAGE_MAP() }; #endif // RegularDll.cpp : Defines the initialization routines for the DLL. #include “stdafx.h” #include “RegularDll.h” #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif BEGIN_MESSAGE_MAP(CRegularDllApp, CWinApp) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CRegularDllApp construction CRegularDllApp::CRegularDllApp() { } ///////////////////////////////////////////////////////////////////////////// // The one and only CRegularDllApp object CRegularDllApp theApp;

  分析:

  在这一组文件中定义了一个继承自CWinApp的类CRegularDllApp,并同时定义了其的一个实例theApp。乍一看,您会以为它是一个MFC应用程序,因为MFC应用程序也包含这样的在工程名后添加“App”组成类名的类(并继承自CWinApp类),也定义了这个类的一个全局实例theApp。

  我们知道,在MFC应用程序中CWinApp取代了SDK程序中WinMain的地位,SDK程序WinMain所完成的工作由CWinApp的三个函数完成:

virtual BOOL InitApplication( );virtual BOOL InitInstance( );virtual BOOL Run( ); //传说中MFC程序的“活水源头”

  但是MFC规则DLL并不是MFC应用程序,它所继承自CWinApp的类不包含消息循环。这是因为,MFC规则DLL不包含CWinApp::Run机制,主消息泵仍然由应用程序拥有。如果DLL 生成无模式对话框或有自己的主框架窗口,则应用程序的主消息泵必须调用从DLL 导出的函数来调用PreTranslateMessage成员函数。

  另外,MFC规则DLL与MFC 应用程序中一样,需要将所有 DLL中元素的初始化放到InitInstance 成员函数中。

  第二组文件 自定义对话框类声明及实现

#if !defined(AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_) #define AFX_DLLDIALOG_H__CEA4C6AF_245D_48A6_B11A_A5521EAD7C4E__INCLUDED_#if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 // DllDialog.h : header file ///////////////////////////////////////////////////////////////////////////// // CDllDialog dialogclass CDllDialog : public CDialog {  // Construction  public:   CDllDialog(CWnd* pParent = NULL); // standard constructor   enum { IDD = IDD_DLL_DIALOG };  protected:   virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support   // Implementation  protected:   afx_msg void OnHelloButton();   DECLARE_MESSAGE_MAP() }; #endif // DllDialog.cpp : implementation file #include “stdafx.h” #include “RegularDll.h” #include “DllDialog.h” #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // CDllDialog dialog CDllDialog::CDllDialog(CWnd* pParent /*=NULL*/) : CDialog(CDllDialog::IDD, pParent) {} void CDllDialog::DoDataExchange(CDataExchange* pDX) {  CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CDllDialog, CDialog)  ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton) END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // CDllDialog message handlers void CDllDialog::OnHelloButton() {  MessageBox(“Hello,pconline的网友”,”pconline”); }

  分析:

  这一部分的编程与一般的应用程序根本没有什么不同,我们照样可以利用MFC类向导来自动为对话框上的控件添加事件。MFC类向导照样会生成类似ON_BN_CLICKED(IDC_HELLO_BUTTON, OnHelloButton)的消息映射宏。

  第三组文件 DLL中的资源文件

//// Microsoft Developer Studio generated include file.// Used by RegularDll.rc // #define IDD_DLL_DIALOG 1000 #define IDC_HELLO_BUTTON 1000

  分析:

  在MFC规则DLL中使用资源也与在MFC应用程序中使用资源没有什么不同,我们照样可以用Visual C++的资源编辑工具进行资源的添加、删除和属性的更改。

  第四组文件 MFC规则DLL接口函数

#include “StdAfx.h” #include “DllDialog.h”extern “C” __declspec(dllexport) void ShowDlg(void) {  CDllDialog dllDialog;  dllDialog.DoModal(); }

  分析:

  这个接口并不使用MFC,但是在其中却可以调用MFC扩展类CdllDialog的函数,这体现了“规则”的概类。

  与非MFC DLL完全相同,我们可以使用__declspec(dllexport)声明或在.def中引出的方式导出MFC规则DLL中的接口。

  5.4 MFC规则DLL的调用

  笔者编写了如图12的对话框MFC程序(下载本工程)来调用5.3节的MFC规则DLL,在这个程序的对话框上点击“调用DLL”按钮时弹出5.3节MFC规则DLL中的对话框。

图12 MFC规则DLL的调用例子

  下面是“调用DLL”按钮单击事件的消息处理函数:

void CRegularDllCallDlg::OnCalldllButton() {  typedef void (*lpFun)(void);  HINSTANCE hDll; //DLL句柄  hDll = LoadLibrary(“RegularDll.dll”);  if (NULL==hDll)  {   MessageBox(“DLL加载失败”);  } lpFun addFun; //函数指针  lpFun pShowDlg = (lpFun)GetProcAddress(hDll,”ShowDlg”);  if (NULL==pShowDlg)  {   MessageBox(“DLL中函数寻找失败”);  }  pShowDlg(); }

  上述例子中给出的是显示调用的方式,可以看出,其调用方式与第4节中非MFC DLL的调用方式没有什么不同。

  我们照样可以在EXE程序中隐式调用MFC规则DLL,只需要将DLL工程生成的.lib文件和.dll文件拷入当前工程所在的目录,并在RegularDllCallDlg.cpp文件(图12所示对话框类的实现文件)的顶部添加:

#pragma comment(lib,”RegularDll.lib”) void ShowDlg(void);

  并将void CRegularDllCallDlg::OnCalldllButton() 改为:

void CRegularDllCallDlg::OnCalldllButton() {  ShowDlg(); }

  5.5 共享MFC DLL的规则DLL的模块切换

  应用程序进程本身及其调用的每个DLL模块都具有一个全局唯一的HINSTANCE句柄,它们代表了DLL或EXE模块在进程虚拟空间中的起始地址。进程本身的模块句柄一般为0x400000,而DLL模块的缺省句柄为0x10000000。如果程序同时加载了多个DLL,则每个DLL模块都会有不同的HINSTANCE。应用程序在加载DLL时对其进行了重定位。

  共享MFC DLL(或MFC扩展DLL)的规则DLL涉及到HINSTANCE句柄问题,HINSTANCE句柄对于加载资源特别重要。EXE和DLL都有其自己的资源,而且这些资源的ID可能重复,应用程序需要通过资源模块的切换来找到正确的资源。如果应用程序需要来自于DLL的资源,就应将资源模块句柄指定为DLL的模块句柄;如果需要EXE文件中包含的资源,就应将资源模块句柄指定为EXE的模块句柄。

  这次我们创建一个动态链接到MFC DLL的规则DLL(下载本工程),在其中包含如图13的对话框。

图13 DLL中的对话框

  另外,在与这个DLL相同的工作区中生成一个基于对话框的MFC程序,其对话框与图12完全一样。但是在此工程中我们另外添加了一个如图14的对话框。

图14 EXE中的对话框

  图13和图14中的对话框除了caption不同(以示区别)以外,其它的都相同。

  尤其值得特别注意,在DLL和EXE中我们对图13和图14的对话框使用了相同的资源ID=2000,在DLL和EXE工程的resource.h中分别有如下的宏:

//DLL中对话框的ID#define IDD_DLL_DIALOG 2000//EXE中对话框的ID #define IDD_EXE_DIALOG 2000

  与5.3节静态链接MFC DLL的规则DLL相同,我们还是在规则DLL中定义接口函数ShowDlg,原型如下:

#include “StdAfx.h” #include “SharedDll.h”void ShowDlg(void) {  CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框  dlg.DoModal(); }

  而为应用工程主对话框的“调用DLL”的单击事件添加如下消息处理函数:

void CSharedDllCallDlg::OnCalldllButton() {  ShowDlg(); }

  我们以为单击“调用DLL”会弹出如图13所示DLL中的对话框,可是可怕的事情发生了,我们看到是图14所示EXE中的对话框!

  惊讶?

  产生这个问题的根源在于应用程序与MFC规则DLL共享MFC DLL(或MFC扩展DLL)的程序总是默认使用EXE的资源,我们必须进行资源模块句柄的切换,其实现方法有三:

  方法一 在DLL接口函数中使用:

AFX_MANAGE_STATE(AfxGetStaticModuleState());

  我们将DLL中的接口函数ShowDlg改为:

void ShowDlg(void) {  //方法1:在函数开始处变更,在函数结束时恢复  //将AFX_MANAGE_STATE(AfxGetStaticModuleState());作为接口函数的第一//条语句进行模块状态切换 AFX_MANAGE_STATE(AfxGetStaticModuleState());  CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框  dlg.DoModal(); }

  这次我们再点击EXE程序中的“调用DLL”按钮,弹出的是DLL中的如图13的对话框!嘿嘿,弹出了正确的对话框资源。

  AfxGetStaticModuleState是一个函数,其原型为:

AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );

  该函数的功能是在栈上(这意味着其作用域是局部的)创建一个AFX_MODULE_STATE类(模块全局数据也就是模块状态)的实例,对其进行设置,并将其指针pModuleState返回。

  AFX_MODULE_STATE类的原型如下:

// AFX_MODULE_STATE (global data for a module)class AFX_MODULE_STATE : public CNoTrackObject {  public:   #ifdef _AFXDLL    AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion);    AFX_MODULE_STATE(BOOL bDLL, WNDPROC pfnAfxWndProc, DWORD dwVersion,BOOL bSystem);   #else    AFX_MODULE_STATE(BOOL bDLL);   #endif   ~AFX_MODULE_STATE();  CWinApp* m_pCurrentWinApp;   HINSTANCE m_hCurrentInstanceHandle;   HINSTANCE m_hCurrentResourceHandle;   LPCTSTR m_lpszCurrentAppName;   … //省略后面的部分 }

  AFX_MODULE_STATE类利用其构造函数和析构函数进行存储模块状态现场及恢复现场的工作,类似汇编中call指令对pc指针和sp寄存器的保存与恢复、中断服务程序的中断现场压栈与恢复以及操作系统线程调度的任务控制块保存与恢复。

  许多看似不着边际的知识点居然有惊人的相似!

  AFX_MANAGE_STATE是一个宏,其原型为:

AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )

  该宏用于将pModuleState设置为当前的有效模块状态。当离开该宏的作用域时(也就离开了pModuleState所指向栈上对象的作用域),先前的模块状态将由AFX_MODULE_STATE的析构函数恢复。

  方法二 在DLL接口函数中使用:

AfxGetResourceHandle();AfxSetResourceHandle(HINSTANCE xxx);

  AfxGetResourceHandle用于获取当前资源模块句柄,而AfxSetResourceHandle则用于设置程序目前要使用的资源模块句柄。

  我们将DLL中的接口函数ShowDlg改为:

void ShowDlg(void) {  //方法2的状态变更  HINSTANCE save_hInstance = AfxGetResourceHandle();  AfxSetResourceHandle(theApp.m_hInstance);  CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框  dlg.DoModal(); //方法2的状态还原  AfxSetResourceHandle(save_hInstance); }

  通过AfxGetResourceHandle和AfxSetResourceHandle的合理变更,我们能够灵活地设置程序的资源模块句柄,而方法一则只能在DLL接口函数退出的时候才会恢复模块句柄。方法二则不同,如果将ShowDlg改为:

extern CSharedDllApp theApp; //需要声明theApp外部全局变量void ShowDlg(void) {  //方法2的状态变更  HINSTANCE save_hInstance = AfxGetResourceHandle();  AfxSetResourceHandle(theApp.m_hInstance); CDialog dlg(IDD_DLL_DIALOG);//打开ID为2000的对话框  dlg.DoModal();  //方法2的状态还原  AfxSetResourceHandle(save_hInstance);  //使用方法2后在此处再进行操作针对的将是应用程序的资源  CDialog dlg1(IDD_DLL_DIALOG); //打开ID为2000的对话框  dlg1.DoModal(); }

  在应用程序主对话框的“调用DLL”按钮上点击,将看到两个对话框,相继为DLL中的对话框(图13)和EXE中的对话框(图14)。

  方法三 由应用程序自身切换

  资源模块的切换除了可以由DLL接口函数完成以外,由应用程序自身也能完成(下载本工程)。

  现在我们把DLL中的接口函数改为最简单的:

void ShowDlg(void) {  CDialog dlg(IDD_DLL_DIALOG); //打开ID为2000的对话框  dlg.DoModal(); }

  而将应用程序的OnCalldllButton函数改为:

void CSharedDllCallDlg::OnCalldllButton() {  //方法3:由应用程序本身进行状态切换  //获取EXE模块句柄 HINSTANCE exe_hInstance = GetModuleHandle(NULL); //或者HINSTANCE exe_hInstance = AfxGetResourceHandle();  //获取DLL模块句柄  HINSTANCE dll_hInstance = GetModuleHandle(“SharedDll.dll”);  AfxSetResourceHandle(dll_hInstance); //切换状态  ShowDlg(); //此时显示的是DLL的对话框  AfxSetResourceHandle(exe_hInstance); //恢复状态  //资源模块恢复后再调用ShowDlg  ShowDlg(); //此时显示的是EXE的对话框 }

  方法三中的Win32函数GetModuleHandle可以根据DLL的文件名获取DLL的模块句柄。如果需要得到EXE模块的句柄,则应调用带有Null参数的GetModuleHandle。

  方法三与方法二的不同在于方法三是在应用程序中利用AfxGetResourceHandle和AfxSetResourceHandle进行资源模块句柄切换的。同样地,在应用程序主对话框的“调用DLL”按钮上点击,也将看到两个对话框,相继为DLL中的对话框(图13)和EXE中的对话框(图14)。

  在下一节我们将对MFC扩展DLL进行详细分析和实例讲解,欢迎您继续关注本系列连载。

Comments

Visual C++动态链接库编程(二)

  4.1一个简单的DLL

  第2节给出了以静态链接库方式提供add函数接口的方法,接下来我们来看看怎样用动态链接库实现一个同样功能的add函数。

  如图6,在VC++中new一个Win32 Dynamic-Link Library工程dllTest(单击此处下载本工程)。注意不要选择MFC AppWizard(dll),因为用MFC AppWizard(dll)建立的将是第5、6节要讲述的MFC 动态链接库。

图6 建立一个非MFC DLL

  在建立的工程中添加lib.h及lib.cpp文件,源代码如下:

/ 文件名:lib.h /

ifndef LIB_H

define LIB_H

extern “C” int __declspec(dllexport)add(int x, int y);

endif

/ 文件名:lib.cpp /

include “lib.h”

int add(int x, int y) {  return x + y; }

  与第2节对静态链接库的调用相似,我们也建立一个与DLL工程处于同一工作区的应用工程dllCall,它调用DLL中的函数add,其源代码如下:

#include <stdio.h>

include <windows.h>

typedef int(lpAddFun)(int, int); //宏定义函数指针类型 int main(int argc, char argv[]) {  HINSTANCE hDll; //DLL句柄  lpAddFun addFun; //函数指针  hDll = LoadLibrary(“..\Debug\dllTest.dll”);  if (hDll != NULL)  {   addFun = (lpAddFun)GetProcAddress(hDll, “add”);   if (addFun != NULL)   {    int result = addFun(2, 3);    printf(“%d”, result);   }   FreeLibrary(hDll);  }  return 0; }

  分析上述代码,dllTest工程中的lib.cpp文件与第2节静态链接库版本完全相同,不同在于lib.h对函数add的声明前面添加了__declspec(dllexport)语句。这个语句的含义是声明函数add为DLL的导出函数。DLL内的函数分为两种:

  (1)DLL导出函数,可供应用程序调用;

  (2) DLL内部函数,只能在DLL程序使用,应用程序无法调用它们。

  而应用程序对本DLL的调用和对第2节静态链接库的调用却有较大差异,下面我们来逐一分析。

  首先,语句typedef int ( * lpAddFun)(int,int)定义了一个与add函数接受参数类型和返回值均相同的函数指针类型。随后,在main函数中定义了lpAddFun的实例addFun;

  其次,在函数main中定义了一个DLL HINSTANCE句柄实例hDll,通过Win32 Api函数LoadLibrary动态加载了DLL模块并将DLL模块句柄赋给了hDll;

  再次,在函数main中通过Win32 Api函数GetProcAddress得到了所加载DLL模块中函数add的地址并赋给了addFun。经由函数指针addFun进行了对DLL中add函数的调用;

  最后,应用工程使用完DLL后,在函数main中通过Win32 Api函数FreeLibrary释放了已经加载的DLL模块。

  通过这个简单的例子,我们获知DLL定义和调用的一般概念:

  (1)DLL中需以某种特定的方式声明导出函数(或变量、类);

  (2)应用工程需以某种特定的方式调用DLL的导出函数(或变量、类)。

  下面我们来对“特定的方式进行”阐述。

  4.2 声明导出函数

  DLL中导出函数的声明有两种方式:一种为4.1节例子中给出的在函数声明中加上__declspec(dllexport),这里不再举例说明;另外一种方式是采用模块定义(.def) 文件声明,.def文件为链接器提供了有关被链接程序的导出、属性及其他方面的信息。

  下面的代码演示了怎样同.def文件将函数add声明为DLL导出函数(需在dllTest工程中添加lib.def文件):

; lib.def : 导出DLL函数

LIBRARY dllTest

EXPORTS

add @ 1

  .def文件的规则为:

  (1)LIBRARY语句说明.def文件相应的DLL;

  (2)EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号将发挥其作用);

  (3).def 文件中的注释由每个注释行开始处的分号 (;) 指定,且注释不能与语句共享一行。

  由此可以看出,例子中lib.def文件的含义为生成名为“dllTest”的动态链接库,导出其中的add函数,并指定add函数的序号为1。

  4.3 DLL的调用方式

  在4.1节的例子中我们看到了由“LoadLibrary-GetProcAddress-FreeLibrary”系统Api提供的三位一体“DLL加载-DLL函数地址获取-DLL释放”方式,这种调用方式称为DLL的动态调用。

  动态调用方式的特点是完全由编程者用 API 函数加载和卸载 DLL,程序员可以决定 DLL 文件何时加载或不加载,显式链接在运行时决定加载哪个 DLL 文件。

  与动态调用方式相对应的就是静态调用方式,“有动必有静”,这来源于物质世界的对立统一。“动与静”,其对立与统一竟无数次在技术领域里得到验证,譬如静态IP与DHCP、静态路由与动态路由等。从前文我们已经知道,库也分为静态库与动态库DLL,而想不到,深入到DLL内部,其调用方式也分为静态与动态。“动与静”,无处不在。《周易》已认识到有动必有静的动静平衡观,《易.系辞》曰:“动静有常,刚柔断矣”。哲学意味着一种普遍的真理,因此,我们经常可以在枯燥的技术领域看到哲学的影子。

  静态调用方式的特点是由编译系统完成对DLL的加载和应用程序结束时 DLL 的卸载。当调用某DLL的应用程序结束时,若系统中还有其它程序使用该 DLL,则Windows对DLL的应用记录减1,直到所有使用该DLL的程序都结束时才释放它。静态调用方式简单实用,但不如动态调用方式灵活。

  下面我们来看看静态调用的例子(单击此处下载本工程),将编译dllTest工程所生成的.lib和.dll文件拷入dllCall工程所在的路径,dllCall执行下列代码:

#pragma comment(lib,“dllTest.lib”)

//.lib文件中仅仅是关于其对应DLL文件中函数的重定位信息

extern “C” __declspec(dllimport) add(int x,int y);

int main(int argc, char* argv[]) {  int result = add(2,3);  printf(“%d”,result);  return 0; }

  由上述代码可以看出,静态调用方式的顺利进行需要完成两个动作:

  (1)告诉编译器与DLL相对应的.lib文件所在的路径及文件名,#pragma comment(lib,“dllTest.lib”)就是起这个作用。

  程序员在建立一个DLL文件时,连接器会自动为其生成一个对应的.lib文件,该文件包含了DLL 导出函数的符号名及序号(并不含有实际的代码)。在应用程序里,.lib文件将作为DLL的替代文件参与编译。

  (2)声明导入函数,extern “C” declspec(dllimport) add(int x,int y)语句中的declspec(dllimport)发挥这个作用。

  静态调用方式不再需要使用系统API来加载、卸载DLL以及获取DLL中导出函数的地址。这是因为,当程序员通过静态链接方式编译生成应用程序时,应用程序中调用的与.lib文件中导出符号相匹配的函数符号将进入到生成的EXE 文件中,.lib文件中所包含的与之对应的DLL文件的文件名也被编译器存储在 EXE文件内部。当应用程序运行过程中需要加载DLL文件时,Windows将根据这些信息发现并加载DLL,然后通过符号名实现对DLL 函数的动态链接。这样,EXE将能直接通过函数名调用DLL的输出函数,就象调用程序内部的其他函数一样。

  4.4 DllMain函数

  Windows在加载DLL的时候,需要一个入口函数,就如同控制台或DOS程序需要main函数、WIN32程序需要WinMain函数一样。在前面的例子中,DLL并没有提供DllMain函数,应用工程也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的缺省DllMain函数版本,并不意味着DLL可以放弃DllMain函数。

  根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。

  我们来看一个DllMain函数的例子(单击此处下载本工程)。

BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {  switch (ul_reason_for_call)  {   case DLL_PROCESS_ATTACH:    printf(“\nprocess attach of dll”);    break;   case DLL_THREAD_ATTACH:    printf(“\nthread attach of dll”);    break;   case DLL_THREAD_DETACH:    printf(“\nthread detach of dll”);    break;   case DLL_PROCESS_DETACH:    printf(“\nprocess detach of dll”);    break;  }  return TRUE; }
  DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DLLMain函数也被调用,ul_reason_for_call指明了被调用的原因。原因共有4种,即PROCESS_ATTACH、PROCESS_DETACH、THREAD_ATTACH和THREAD_DETACH,以switch语句列出。

  来仔细解读一下DllMain的函数头BOOL APIENTRY DllMain( HANDLE hModule, WORD ul_reason_for_call, LPVOID lpReserved )。

  APIENTRY被定义为__stdcall,它意味着这个函数以标准Pascal的方式进行调用,也就是WINAPI方式;

  进程中的每个DLL模块被全局唯一的32字节的HINSTANCE句柄标识,只有在特定的进程内部有效,句柄代表了DLL模块在进程虚拟空间中的起始地址。在Win32中,HINSTANCE和HMODULE的值是相同的,这两种类型可以替换使用,这就是函数参数hModule的来历。

  执行下列代码:

hDll = LoadLibrary(“..\Debug\dllTest.dll”); if (hDll != NULL) {  addFun = (lpAddFun)GetProcAddress(hDll, MAKEINTRESOURCE(1));  //MAKEINTRESOURCE直接使用导出文件中的序号  if (addFun != NULL)  {   int result = addFun(2, 3);   printf(“\ncall add in dll:%d”, result);  }  FreeLibrary(hDll); }
  我们看到输出顺序为:
process attach of dll call add in dll:5 process detach of dll
  这一输出顺序验证了DllMain被调用的时机。

  代码中的GetProcAddress ( hDll, MAKEINTRESOURCE ( 1 ) )值得留意,它直接通过.def文件中为add函数指定的顺序号访问add函数,具体体现在MAKEINTRESOURCE ( 1 ),MAKEINTRESOURCE是一个通过序号获取函数名的宏,定义为(节选自winuser.h):

#define MAKEINTRESOURCEA(i) (LPSTR)((DWORD)((WORD)(i)))

define MAKEINTRESOURCEW(i) (LPWSTR)((DWORD)((WORD)(i)))

ifdef UNICODE

define MAKEINTRESOURCE MAKEINTRESOURCEW

else

define MAKEINTRESOURCE MAKEINTRESOURCEA

  4.5 __stdcall约定

  如果通过VC++编写的DLL欲被其他语言编写的程序调用,应将函数的调用方式声明为stdcall方式,WINAPI都采用这种方式,而C/C++缺省的调用方式却为cdecl。stdcall方式与cdecl对函数名最终生成符号的方式不同。若采用C编译方式(在C++中需将函数声明为extern “C”),stdcall调用约定在输出函数名前面加下划线,后面加“@”符号和参数的字节数,形如_functionname@number;而cdecl调用约定仅在输出函数名前面加下划线,形如_functionname。

  Windows编程中常见的几种函数类型声明宏都是与stdcall和cdecl有关的(节选自windef.h):

#define CALLBACK __stdcall //这就是传说中的回调函数

define WINAPI __stdcall //这就是传说中的WINAPI

define WINAPIV __cdecl

define APIENTRY WINAPI //DllMain的入口就在这里

define APIPRIVATE __stdcall

define PASCAL __stdcall

  在lib.h中,应这样声明add函数:
int stdcall add(int x, int y);
  在应用工程中函数指针类型应定义为:
typedef int(stdcall lpAddFun)(int, int);
  若在lib.h中将函数声明为__stdcall调用,而应用工程中仍使用typedef int ( lpAddFun)(int,int),运行时将发生错误(因为类型不匹配,在应用工程中仍然是缺省的__cdecl调用),弹出如图7所示的对话框。

图7 调用约定不匹配时的运行错误

  图8中的那段话实际上已经给出了错误的原因,即“This is usually a result of …”。

  单击此处下载__stdcall调用例子工程源代码

  4.6 DLL导出变量

  DLL定义的全局变量可以被调用进程访问;DLL也可以访问调用进程的全局数据,我们来看看在应用工程中引用DLL中变量的例子(单击此处下载本工程)。

/ 文件名:lib.h /

ifndef LIB_H

define LIB_H

extern int dllGlobalVar;

endif

/ 文件名:lib.cpp /

include “lib.h”

include <windows.h>

int dllGlobalVar;

BOOL APIENTRY DllMain(HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {  switch (ul_reason_for_call)  {   case DLL_PROCESS_ATTACH:    dllGlobalVar = 100; //在dll被加载时,赋全局变量为100    break;   case DLL_THREAD_ATTACH:   case DLL_THREAD_DETACH:   case DLL_PROCESS_DETACH:    break;  }  return TRUE; }

  ;文件名:lib.def

  ;在DLL中导出变量

LIBRARY “dllTest”

EXPORTS

dllGlobalVar CONSTANT

;或dllGlobalVar DATA

GetGlobalVar

  从lib.h和lib.cpp中可以看出,全局变量在DLL中的定义和使用方法与一般的程序设计是一样的。若要导出某全局变量,我们需要在.def文件的EXPORTS后添加:

  变量名 CONSTANT   //过时的方法

  或

  变量名 DATA     //VC++提示的新方法

  在主函数中引用DLL中定义的全局变量:

#include <stdio.h>

pragma comment(lib,“dllTest.lib”)

extern int dllGlobalVar;

int main(int argc, char argv[]) {  printf(“%d ”, (int)dllGlobalVar);  (int)dllGlobalVar = 1;  printf(“%d ”, (int)dllGlobalVar);  return 0; }

  特别要注意的是用extern int dllGlobalVar声明所导入的并不是DLL中全局变量本身,而是其地址,应用程序必须通过强制指针转换来使用DLL中的全局变量。这一点,从(int*)dllGlobalVar可以看出。因此在采用这种方式引用DLL全局变量时,千万不要进行这样的赋值操作:
dllGlobalVar = 1;
  其结果是dllGlobalVar指针的内容发生变化,程序中以后再也引用不到DLL中的全局变量了。

  在应用工程中引用DLL中全局变量的一个更好方法是:

#include <stdio.h>

pragma comment(lib,“dllTest.lib”)

extern int declspec(dllimport) dllGlobalVar; //用declspec(dllimport)导入 int main(int argc, char *argv[]) {  printf(“%d ”, dllGlobalVar);  dllGlobalVar = 1; //这里就可以直接使用, 无须进行强制指针转换  printf(“%d ”, dllGlobalVar);  return 0; }

  通过_declspec(dllimport)方式导入的就是DLL中全局变量本身而不再是其地址了,笔者建议在一切可能的情况下都使用这种方式。

  4.7 DLL导出类

  DLL中定义的类可以在应用工程中使用。

  下面的例子里,我们在DLL中定义了point和circle两个类,并在应用工程中引用了它们(单击此处下载本工程)。

//文件名:point.h,point类的声明

ifndef POINT_H

define POINT_H

ifdef DLL_FILE

 class _declspec(dllexport) point //导出类point

else

 class _declspec(dllimport) point //导入类point

endif

{  public:   float y;   float x;   point();   point(float x_coordinate, float y_coordinate); };

endif

//文件名:point.cpp,point类的实现

ifndef DLL_FILE

 #define DLL_FILE

endif

include “point.h”

//类point的缺省构造函数

point::point() {  x = 0.0;  y = 0.0; }

//类point的构造函数

point::point(float x_coordinate, float y_coordinate) {  x = x_coordinate;  y = y_coordinate; }

//文件名:circle.h,circle类的声明

ifndef CIRCLE_H

define CIRCLE_H

include “point.h”

ifdef DLL_FILE

class _declspec(dllexport)circle //导出类circle

else

class _declspec(dllimport)circle //导入类circle

endif

{  public:   void SetCentre(const point &centrePoint);   void SetRadius(float r);   float GetGirth();   float GetArea();   circle();  private:   float radius;   point centre; };

endif

//文件名:circle.cpp,circle类的实现

ifndef DLL_FILE

define DLL_FILE

endif

include “circle.h”

define PI 3.1415926

//circle类的构造函数

circle::circle() {  centre = point(0, 0);  radius = 0; }

//得到圆的面积

float circle::GetArea() {  return PI *radius * radius; }

//得到圆的周长

float circle::GetGirth() {  return 2 *PI * radius; }

//设置圆心坐标

void circle::SetCentre(const point &centrePoint) {  centre = centrePoint; }

//设置圆的半径

void circle::SetRadius(float r) {  radius = r; }

  类的引用:
#include “..\circle.h”  //包含类声明头文件

pragma comment(lib,“dllTest.lib”);

int main(int argc, char *argv[]) {  circle c;  point p(2.0, 2.0);  c.SetCentre(p);  c.SetRadius(1.0);  printf(“area:%f girth:%f”, c.GetArea(), c.GetGirth());  return 0; }

  从上述源代码可以看出,由于在DLL的类实现代码中定义了宏DLL_FILE,故在DLL的实现中所包含的类声明实际上为:
class declspec(dllexport) point //导出类point {  … }
  和
class declspec(dllexport) circle //导出类circle {  … }
  而在应用工程中没有定义DLL_FILE,故其包含point.h和circle.h后引入的类声明为:
class declspec(dllimport) point //导入类point {  … }
  和
class declspec(dllimport) circle //导入类circle {  … }
  不错,正是通过DLL中的
class declspec(dllexport) class_name //导出类circle  {  … }
  与应用程序中的
class declspec(dllimport) class_name //导入类 {  … }
  匹对来完成类的导出和导入的!

  我们往往通过在类的声明头文件中用一个宏来决定使其编译为class declspec(dllexport) class_name还是class declspec(dllimport) class_name版本,这样就不再需要两个头文件。本程序中使用的是:

#ifdef DLL_FILE  class _declspec(dllexport) class_name //导出类

else

 class _declspec(dllimport) class_name //导入类

endif

  实际上,在MFC DLL的讲解中,您将看到比这更简便的方法,而此处仅仅是为了说明declspec(dllexport)与declspec(dllimport)匹对的问题。

  由此可见,应用工程中几乎可以看到DLL中的一切,包括函数、变量以及类,这就是DLL所要提供的强大能力。只要DLL释放这些接口,应用程序使用它就将如同使用本工程中的程序一样!

  本章虽以VC++为平台讲解非MFC DLL,但是这些普遍的概念在其它语言及开发环境中也是相同的,其思维方式可以直接过渡。 接下来,我们将要研究MFC规则DLL。

Comments

Visual C++动态链接库编程(一)

 最近在网上闲逛,一是为了巩固和拓展一下对C/C++的研习,二是大致感受下windows编程原理。当然最近被汇编也搞的比较晕乎,找机会出来透点新鲜空气。

 要说我接触VC的时间很短,仅凭一两个工程实例和一本本科阶段教材恐怕也是微不足道。下面这篇文章就是对书本之外相当不错的补充,当然此类书籍绝不缺少字纸版本。只不过,文章作者宋宝华老师所展现给我们的,却不比任何动辄数百页的书商们逊色。

   1.概论

  先来阐述一下DLL(Dynamic Linkable Library)的概念,你可以简单的把DLL看成一种仓库,它提供给你一些可以直接拿来用的变量、函数或类。在仓库的发展史上经历了“无库-静态链接库-动态链接库”的时代。静态链接库与动态链接库都是共享代码的方式,如果采用静态链接库,则无论你愿不愿意,lib中的指令都被直接包含在最终生成的EXE文件中了。但是若使用DLL,该DLL不必被包含在最终EXE文件中,EXE文件执行时可以“动态”地引用和卸载这个与EXE独立的DLL文件。静态链接库和动态链接库的另外一个区别在于静态链接库中不能再包含其他的动态链接库或者静态库,而在动态链接库中还可以再包含其他的动态或静态链接库。

  对动态链接库,我们还需建立如下概念:

  (1)DLL 的编制与具体的编程语言及编译器无关

  只要遵循约定的DLL接口规范和调用方式,用各种语言编写的DLL都可以相互调用。譬如Windows提供的系统DLL(其中包括了Windows的API),在任何开发环境中都能被调用,不在乎其是Visual Basic、Visual C++还是Delphi。

  (2)动态链接库随处可见

  我们在Windows目录下的system32文件夹中会看到kernel32.dll、user32.dll和gdi32.dll,windows的大多数API都包含在这些DLL中。kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。

  一般的程序员都用过类似MessageBox的函数,其实它就包含在user32.dll这个动态链接库中。由此可见DLL对我们来说其实并不陌生。

  (3)VC动态链接库的分类

  Visual C++支持三种DLL,它们分别是Non-MFC DLL(非MFC动态库)、MFC Regular DLL(MFC规则DLL)、MFC Extension DLL(MFC扩展DLL)。

  非MFC动态库不采用MFC类库结构,其导出函数为标准的C接口,能被非MFC或MFC编写的应用程序所调用;MFC规则DLL 包含一个继承自CWinApp的类,但其无消息循环;MFC扩展DLL采用MFC的动态链接版本创建,它只能被用MFC类库所编写的应用程序所调用。

  由于本文篇幅较长,内容较多,势必需要先对阅读本文的有关事项进行说明,下面以问答形式给出。

  问:本文主要讲解什么内容?

  答:本文详细介绍了DLL编程的方方面面,努力学完本文应可以对DLL有较全面的掌握,并能编写大多数DLL程序。

  问:如何看本文?

  答:本文每一个主题的讲解都附带了源代码例程,可以随文下载(每个工程都经WINRAR压缩)。所有这些例程都由笔者编写并在VC++6.0中调试通过。

  当然看懂本文不是读者的最终目的,读者应亲自动手实践才能真正掌握DLL的奥妙。

  问:学习本文需要什么样的基础知识?

  答:如果你掌握了C,并大致掌握了C++,了解一点MFC的知识,就可以轻松地看懂本文。

  2.静态链接库

  对静态链接库的讲解不是本文的重点,但是在具体讲解DLL之前,通过一个静态链接库的例子可以快速地帮助我们建立“库”的概念。

图1 建立一个静态链接库

  如图1,在VC++6.0中new一个名称为libTest的static library工程(单击此处下载本工程),并新建lib.h和lib.cpp两个文件,lib.h和lib.cpp的源代码如下:

//文件:lib.h #ifndef LIB_H #define LIB_H extern “C” int add(int x,int y);   //声明为C编译、连接方式的外部函数 #endif //文件:lib.cpp #include “lib.h” int add(int x,int y) {  return x + y; }

  编译这个工程就得到了一个.lib文件,这个文件就是一个函数库,它提供了add的功能。将头文件和.lib文件提交给用户后,用户就可以直接使用其中的add函数了。

  标准Turbo C2.0中的C库函数(我们用来的scanf、printf、memcpy、strcpy等)就来自这种静态库。

  下面来看看怎么使用这个库,在libTest工程所在的工作区内new一个libCall工程。libCall工程仅包含一个main.cpp文件,它演示了静态链接库的调用方法,其源代码如下:

#include <stdio.h> #include “..\lib.h” #pragma comment( lib, “..\debug\libTest.lib” )  //指定与静态库一起连接 int main(int argc, char* argv[]) {  printf( “2 + 3 = %d”, add( 2, 3 ) ); }

  静态链接库的调用就是这么简单,或许我们每天都在用,可是我们没有明白这个概念。代码中#pragma comment( lib , “..\debug\libTest.lib” )的意思是指本文件生成的.obj文件应与libTest.lib一起连接。如果不用#pragma comment指定,则可以直接在VC++中设置,如图2,依次选择tools、options、directories、library files菜单或选项,填入库文件路径。图2中加红圈的部分为我们添加的libTest.lib文件的路径。

图2 在VC中设置库文件路径

  这个静态链接库的例子至少让我们明白了库函数是怎么回事,它们是哪来的。我们现在有下列模糊认识了:

  (1)库不是个怪物,编写库的程序和编写一般的程序区别不大,只是库不能单独执行;

  (2)库提供一些可以给别的程序调用的东东,别的程序要调用它必须以某种方式指明它要调用之。

  以上从静态链接库分析而得到的对库的懵懂概念可以直接引申到动态链接库中,动态链接库与静态链接库在编写和调用上的不同体现在库的外部接口定义及调用方式略有差异。

3.库的调试与查看

  在具体进入各类DLL的详细阐述之前,有必要对库文件的调试与查看方法进行一下介绍,因为从下一节开始我们将面对大量的例子工程。

  由于库文件不能单独执行,因而在按下F5(开始debug模式执行)或CTRL+F5(运行)执行时,其弹出如图3所示的对话框,要求用户输入可执行文件的路径来启动库函数的执行。这个时候我们输入要调用该库的EXE文件的路径就可以对库进行调试了,其调试技巧与一般应用工程的调试一样。

图3 库的调试与“运行”

  通常有比上述做法更好的调试途径,那就是将库工程和应用工程(调用库的工程)放置在同一VC工作区,只对应用工程进行调试,在应用工程调用库中函数的语句处设置断点,执行后按下F11,这样就单步进入了库中的函数。第2节中的libTest和libCall工程就放在了同一工作区,其工程结构如图4所示。

图4 把库工程和调用库的工程放入同一工作区进行调试

  上述调试方法对静态链接库和动态链接库而言是一致的。所以本文提供下载的所有源代码中都包含了库工程和调用库的工程,这二者都被包含在一个工作区内,这是笔者提供这种打包下载的用意所在。

  动态链接库中的导出接口可以使用Visual C++的Depends工具进行查看,让我们用Depends打开系统目录中的user32.dll,看到了吧?红圈内的就是几个版本的MessageBox了!原来它真的在这里啊,原来它就在这里啊!

图5 用Depends查看DLL

  当然Depends工具也可以显示DLL的层次结构,若用它打开一个可执行文件则可以看出这个可执行文件调用了哪些DLL。

  好,让我们正式进入动态链接库的世界,先来看看最一般的DLL,即非MFC DLL。

Comments

C++学习笔记(重温计划之三)

3、数据类型(上)

 c++中的数据类型,主要分为内部数据类型和外部数据类型两大类。其中,内部数据类型主要有:

  1)整数类型int;

   计算机内部是以二进制补码的形式表示整数的。从十进制转换的具体方法为除二取余、高位补零、再取反。整形数的表示范围如下:

类型 字节数 位数 表示范围 解释
下限 上限
char 1 8 -128 128 -2^7~(2^7-1)
signed char 1 8 -128 128 -2^7~(2^7-1)
unsigned char 1 8 0 255 0~(2^8-1)
short int 2 16 -32768 32767 -2^15~(2^15-1)
signed short int 2 16 -32768 32767 -2^15~(2^15-1)
unsigned short int 2 16 0 65535 0~(2^16-1)
int 4 32 -2147483648 2147483647 -2^31~(2^31-1)
signed int 4 32 -2147483648 2147483647 -2^31~(2^31-1)
unsigned int 4 32 0 4294967295 0~(2^32-1)
long int 4 32 -2147483648 2147483647 -2^31~(2^31-1)
signed long int 4 32 -2147483648 2147483647 -2^31~(2^31-1)
unsigned long int 4 32 0 4294967295 0~(2^32-1) 

 不过,随着64位cpu的应用,上表内容将有进一步扩充。同时在进位计数制中,以0起始的数字表示八进制,以0x起始表示十六进制。在基本取余运算%中,余数的符号决定于被除数的符号。

  2)字符类型char;

   ASCII码有128个字符,字符型需加’ ‘。例如ASCII码为65的字符’A’。下面是除此之外的转义字符。

字符形式 整数值 代表符号 字符形式 整数值 代表符号
\a 0x07 响铃bell " 0x22 双引号
\b 0x08 退格backspace ' 0x27 单引号
\t 0x09 水平制表符HT \? 0x3F 问号
\n 0x0A 换行return \ 0x5C 反斜杠字符
\v 0x0B 垂直制表符VT \ddd 0ddd 1~3位八进制数
\r 0x0C 回车 \xhh 0xhh 1~2位十六进制数

  3)枚举类型enum;

   枚举型是对整数区间的自定义类型,通常用于整数常量。例如:

   enum Week{mon,tue,wed,thu.fri,sat,sun};

   默认第一个枚举符mon=0,以此类推。或者:

   enum Color{Red=5,Green,Yellow,Blue=20,Orange};

   由上可知,Green=6…Orange=21;枚举的好处是,在代替整数常量的过程中,枚举不需要初始化常量而直接使用,提高编写效率。

  4)布尔类型bool;

   布尔类型实际的存在意义为:

   enum bool{false,true};

   因此可以进行0或1的逻辑运算。例如:

   bool a = 3;  //a为true;

   在i/o流中,cout<<boolalpha<<a<<endl;//输出结果为false,如果不做boolalpha设置则输出为0;

  5)浮点型;

   浮点型包含单精度浮点float、双精度浮点double以及长双精度浮点long double。其中float、double、long double的十进制有效位数分别为7位、15位和19位。

  外部数据类型等变异类型:

  1)整形数组int[];

  2)浮点应用double&;

  3)字符指针char*;

Comments

简单的winapi实现

 第一次接触windows编程,初步领略了下api的复杂程度。修改了一个基本的窗口显示程序,并加入了md5算法模块。较费时的地方在md5和win函数本身的结合部分,包括格式转换和部分函数的参数限制。现在的想法是先了解下mfc,以便进行下一步的设计实现。

 md5.jpg

 scode download:md5

Comments

C++学习笔记(重温计划之二)

2、基本编程语句

 沿用c语言的方法,c++包含了说明语句、赋值语句、表达式语句、过程控制语句。其中,过程控制语句又分为条件语句、循环语句和转移语句。

 2.1 说明语句(略)

 2.2 条件语句

  1)if语句: if(条件) 语句 or if(条件) 语句1 else 语句2;

  2)条件表达式:(条件)?表达式1:表达式2

  3)switch语句:switch(表达式){

                                                          case 常量表达式1:语句1;break;

                                                          case 常量表达式1:语句2;break;

                                                          // …

                                                          case 常量表达式n:语句n;break;

                                                          default:                   语句n+1;break;

                                                       }

 2.3循环语句

  1)for循环结构:for(i=0;i<n;i++){ 语句};

  2)while循环结构:while(条件){语句};

  3)do-while循环结构:do{语句}while(条件);

 2.4输入输出语句

  1)标准I\O流:cout<<类型<<内容<<格式<<(条件判断);

  2)设置参数 setw(int)宽度、setfill(char)填充字符、setprecision(int)设置精度和有效位数;

  3)文件流:ifstream fin(filename,openmode=ios::in);

                  ofstream fout(filename,openmode=ios::out);

 2.5转移语句

  1)break语句:跳出本级循环;

  2)continue语句:结束本次循环,直接进入下次循环。

  3)goto语句:略。

Comments

C++学习笔记(重温计划之一)

1、C++新特点

 这里所说的新特点,是相较于C语言而言的.本笔记旨在加强记忆和理解,本身内容并未超出教材的范围.我们仍将沿用《面向对象程序设计c++版》(刘振安,机械工业出版)和《c++程序设计教程第二版》(钱能,清华大学出版社)的基本思路,开始都是最基本的东西。

 C++与C语言的关系,向来是众说纷纭的。但在我结束首轮学习之后发现,C++与C的关系,仅表明C++包含了C,C++完全可以当作一门新语言来学习,有C基础的人也未必比没有基础而开始学习C++的占多大优势。以下是C++一则例示:

/功能:将结构的两个域值相加,乘以2再减去50/

include<iostream>

//————hanyi.name 1.1—————

using namespace std;

int result(int,int);

const int k = 2;

struct Point{    int x,y;

};

int main(){

 int z(0),b(50);    Point a;    cout  <<“输入两个整数(以空格区分):”;

    cin  >>a.x>>a.y;

    z = (a.x + a.y)*k;

    z = result(z,b);

    cout  <<“计算结果如下:”<<endl;

    cout  <<“((”<<a.x<<“+”<<a.y<<“)*”<<k<<“)-”<<b<<“=”<<z<<endl;

    return 0;

}

int result(int a,int b){

 a = a - b;    return a;

}

 下面对以上内容作一说明:

 1、c++源程序以.cpp作为文件扩展名,其中main()主函数只能有一个。由于main()是过程化语言的标志,c++也被称为混合型语言。

 2、注释符号基本以/ /和左起开头//为主。

 3、引入“流”的概念,例如cin和cout分别是c++标准输入输出库iostream的一员,这类库总称为STL(standard library标准函数库)。endl类似于转义字符\n,功能均为换行。

 4、头文件中iostream.h的.h可以省略,但必须新标准类库的使用头文件载入的方法:using namespace std;

 5、对象定义和初始化,新引入了int z(0);定义的方法,此方法功能与int z = 0相同。

 6、函数必须有类型声明,例如int main(),且必须有返回值return。当然void类型也是被允许的,但不和return同时使用。

 7、const修饰符。

       在c语言中,我们使用宏定义#define定义常量,这里推荐使用const。以下文章摘自csdn: 

====================================================================================     

对于基本声明

1.       const int r=100; //标准const变量声明加初始化,因为默认内部连接所以必须被初始化,其作用域为此文件,编译器经过类型检查后直接用100在编译时替换

2.       extend const int r=100; //const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义

3.       const int r[ ]={1,2,3,4};struct S {int a,b;}; const S s[ ]={(1,2),(3.4)}; //以上两种都是常量集合,编译器会为其分配内存,所以不能在编译期间使用其中的值,例如:int temp[r[2]];这样的编译器会报告不能找到常量表达式

对于指针

1.       const int *r=&x; //声明r为一个指向常量的x的指针,r指向的对象不能被修改,但他可以指向任何地址的常量

2.       int const *r=&x; //与用法1完全等价,没有任何区别

3.       int * const r=&x; //声明r为一个常量指针,他指向xr这个指针的指向不能被修改,但他指向的地址的内容可以修改

4.       const int * const r=&x; //综合13用法,r是一个指向常量的常量型指针

对于类型检查

可以把一个非const对象赋给一个指向const的指针,因为有时候我们不想从这个指针来修改其对象的值;但是不可以把一个const对象赋值给一个非const指针,因为这样可能会通过这个指针改变指向对象的值,但也存在使这种操作通过的合法化写法,使用类型强制转换可以通过指针改变const对象: const int r=100; int * ptr = const_cast<int>(&r);  //C++标准,C语言使用:int * ptr =(int)&r;

对于字符数组

char * name = “china”; 这样的语句,在编译时是能够通过的,但是”china”是常量字符数组,任何想修改他的操作也能通过编译但会引起运行时错误,如果我们想修改字符数组的话就要使用char name[ ] = “china”; 这种形式。

对于函数

1.       void Fuction1 ( const int r ); //此处为参数传递const值,意义是变量初值不能被函数改变

2.       const int Fuction1 (int); //此处返回const值,意思指返回的原函数里的变量的初值不能被修改,但是函数按值返回的这个变量被制成副本,能不能被修改就没有了意义,它可以被赋给任何的const或非const类型变量,完全不需要加上这个const关键字。但这只对于内部类型而言(因为内部类型返回的肯定是一个值,而不会返回一个变量,不会作为左值使用),对于用户自定义类型,返回值是常量是非常重要的,见下面条款3

3.       Class CX; //内部有构造函数,声明如CX(int r =0) CX  Fuction1 () { return CX(); } const CX Fuction2 () { return CX(); } 如有上面的自定义类CX,和函数Fuction1()Fuction2(),我们进行如下操作时: Fuction1() = CX(1); //没有问题,可以作为左值调用 Fuction2() = CX(1); //编译错误,const返回值禁止作为左值调用。因为左值把返回值作为变量会修改其返回值,const声明禁止这种修改。

4.       函数中指针的const传递和返回: int F1 (const char * pstr); //作为传递的时候使用const修饰可以保证不会通过这个指针来修改传递参数的初值,这里在函数内部任何修改*pstr的企图都会引起编译错误。 const char * F2 (); //意义是函数返回的指针指向的对象是一个const对象,它必须赋给一个同样是指向const对象的指针。 const char * const F3(); //比上面多了一个const,这个const的意义只是在他被用作左值时有效,它表明了这个指针除了指向const对象外,它本身也不能被修改,所以就不能当作左值来处理。

5.       函数中引用的const传递: void F1 ( const X& px); //这样的一个const引用传递和最普通的函数按值传递的效果是一模一样的,他禁止对引用的对象的一切修改,唯一不同的是按值传递会先建立一个类对象的副本,然后传递过去,而它直接传递地址,所以这种传递比按值传递更有效。 **另外只有引用的const传递可以传递一个临时对象,因为临时对象都是const属性,且是不可见的,他短时间存在一个局部域中,所以不能使用指针,只有引用的const传递能够捕捉到这个家伙。

对于类

1.       首先,对于const的成员变量,只能在构造函数里使用初始化成员列表来初始化,试图在构造函数体内进行初始化const成员变量会引起编译错误。初始化成员列表形如: X:: X ( int ir ): r(ir) {} //假设r是类Xconst成员变量

2.       const成员函数。提到这个概念首先要谈到const对象,正象内置类型能够定义const对象一样(const int r=10;),用户自定义类型也可以定义const对象(const X px(10);),编译器要保证这个对象在其生命周期内不能够被改变。如果你定义了这样的一个const对象,那么对于这个对象的一切非const成员函数的调用,编译器为了保证对象的const特性,都会禁止并在编译期间报错。所以如果你想让你的成员函数能够在const对象上进行操作的话,就要把这个函数声明为const成员函数。假如f( )是类中的成员函数的话,它的声明形如: int f( ) const; //const放在函数的最后,编译器会对这个函数进行检查,在这个函数中的任何试图改变成员变量和调用非const成员函数的操作都被视为非法 **类的构造和析构函数都不能是const函数。

3.       建立了一个const成员函数,但仍然想用这个函数改变对象内部的数据。这样的一个要求也会经常遇到,尤其是在一个苛刻的面试考官那里。首先我们要弄清楚考官的要求,因为有两种方法可以实现,如果这位考官要求不改变原来类的任何东西,只让你从当前这个const成员函数入手,那么你只有使用前面提到的类型强制转换方法。实例如下: //假如有一个叫做X的类,它有一个int成员变量r,我们需要通过一个const成员函数f( )来对这个r进行++r操作,代码如下 void X::f( ) const {  (const_cast<X*>(this)) -> ++r;  } //通过this指针进行类型强制转换实现另外一种方法就是使用关键字:mutable如果你的成员变量在定义时是这个样子的: mutable int r ; 那么它就告诉编译器这个成员变量可以通过const成员函数改变。编译器就不会再理会对他的检查了。=========================================================================================================== 

8、变量对象命名以及初始化

 c++本身区别字母的大小写语言,以开头的标识符一般为系统使用,应当尽量避免。同时宏定义标识符需用大写字母标

识。另外,名字的第1个字母不能是数字或运算符,也不能包含运算符。为标识符命名时可遵循如下规律:

   1)使用能代表数据类型的前缀;

   2)名称与变量的作用尽量接近;

   3)如果名称由多个英文单词组成,每个英文单词首字母大写;

   4)在库函数中尽量不要使用以开头的名字,以免与系统命名冲突;

   5)局部变量名称较短,尤其是循环控制变量;

   6)外部变量应使用较长的名字,并能与其代表的变量含义接近;

 9、程序的书写格式:

  

   1)括号需紧跟在函数名的后面,但在for和while后面,应用一个空格和左括号隔开以增加可读性;

   2)数学运算符的左右两边各留一个空格,与表达式区别;

   3)在表示参数时括号后面留一个空格;

   4)在for、do…while、while中,应合理使用缩进、一对话括号和空行;

Comments

C++重温计划

Dear Mr.Bjarne Stroustrup,

 如果让您看到我至今对令嫒仍一知半解的状况,恐怕接下来的生产工作就很难继续进行下去了。因此,我也愿意拿出部分时间来弥补这一缺憾。相信这次我们能真正相互了解,提高今后衍生产品基因的质量。

 再次感谢!

                                                                                                                                          您真诚的

                                                                                                                                               mp77

Comments

远征结束

 “你的梦境会改变吗?我的梦境从来都不会改变,我只有一个梦:数量超乎想象的燃烧军团士兵像深红色的地平线包围着我,战争在远处打响。” 

                                                                                                                       ——  救赎者阿拉托尔 

 远征军最高指挥官下令进军的那一刻,西线部队已在外域奋战了七个月。又过了同样的时间,我们终于可以说:远征结束了。 然而,燃烧军团并没有因伊利丹·怒风在黑暗神庙的失败而怯步。恶魔领主—基尔加丹亲自坐镇太阳井之岛,指挥军团卷土重来。  

 通常我们尊重历史,是因为它本身的真实性和未来的不可预知。但是,当角色的扮演者们将不得不依据编剧的笔来被迫接受他自身的“命运”的时候,兴趣已经丧失了一半了。后来他们坚持,是因为对世界仍有的一丝留恋。当战友一个个远去,独自冷静想想,也只不过是一部游戏而已。  

 回首三载光阴,探险者生涯似乎该就此终结了。2005年4月26日2点40分,我们迎来了世界,但它并非起点。起点应当更早,终点也不会仅限于此。探险者应当试图去感受不同世界的风景,三年时间已有些长了,以至于彼此之间几乎留不下一点些许惦念。

1.JPG

druid 古殿小芋 雷霆之王 2005.4

1.JPG

warrior keroro 红龙军团 2006.6

1.JPG

druid 梦境之翼 古加尔-达文格尔-灰谷 2006.10

1.JPG

paladin 梦境之树 古加尔 2007.4

1.JPG

warlock 遗失的梦境 灰谷 2008.1

Comments

暂离了几天

开学事多耽搁了一阵,blog近日恢复,修正了几个网站的bug,1280*1024下美观性增强了。

Comments