飘云阁

 找回密码
 加入我们

QQ登录

只需一步,快速开始

查看: 4122|回复: 1

WINDOWS程序设计学习体会

[复制链接]
  • TA的每日心情
    开心
    2016-6-16 14:07
  • 签到天数: 10 天

    [LV.3]偶尔看看II

    发表于 2004-12-14 11:03:38 | 显示全部楼层 |阅读模式
    动态连接库的学习
    动态连接 库DLL编程.

    开发环境VC6.0
    动态连结程式库(也称为DLL)是Microsoft Windows最重要的组成要素之一。大多数与Windows相关的磁碟档案如果不是程式模组,就是动态连结程式。迄今为止,我们都是在开发Windows应用程式;现在是尝试编写动态连结程式库的时候了。许多您已经学会的编写应用程式的规则同样适用於编写这些动态连结程式库模组,但也有一些重要的不同。

    所谓「动态连结」,是指Windows把一个模组中的函式呼叫连结到动态连结程式库模组中的实际函式上的程序。在程式开发中,您将各种目的模组(.OBJ)、执行时期程式库(.LIB)档案,以及经常是已编译的资源(.RES)档案连结在一起,以便建立Windows的.EXE档案,这时的连结是「静态连结」。动态连结与此不同,它发生在执行时期。

    KERNEL32.DLL、USER32.DLL和GDI32.DLL、各种驱动程式档案如KEYBOARD.DRV、SYSTEM.DRV和MOUSE.DRV和视讯及印表机驱动程式都是动态连结程式库。这些动态连结程式库能被所有Windows应用程式使用。
    一个简单的DLL
     

    虽然动态连结程式库的整体概念是它们可以被多个应用程式所使用,但您通常最初设计的动态连结程式库只与一个应用程式相联系,可能是一个「测试」程式在使用DLL。

    下面就是我们要做的。我们建立一个名为EDRLIB.DLL的DLL。档案名中的「EDR」代表「简便的绘图常式(easy drawing routines)」。这里的EDRLIB只含有一个函式(名称为EdrCenterText),但是您还可以将应用程式中其他简单的绘图函式添加进去。应用程式EDRTEST.EXE将通过呼叫EDRLIB.DLL中的函式来利用它。

    要做到这一点,需要与我们以前所做的略有不同的方法,也包括Visual C++ 中我们没有看过的特性。在Visual C++ 中「工作空间(workspaces)」和「专案(projects)」不同。专案通常与建立的应用程式(.EXE)或者动态连结程式库(.DLL)相联系。一个工作空间可以包含一个或多个专案。迄今为止,我们所有的工作空间都只包含一个专案。我们现在就建立一个包含两个专案的工作空间EDRTEST-一个用於建立EDRTEST.EXE,而另一个用於建立EDRLIB.DLL,即EDRTEST使用的动态连结程式库。

    现在就开始。在Visual C++中,从「File」功能表选择「New」,然後选择「Workspaces」页面标签。(我们以前从来没有选择过。)在「Location」栏选择工作空间要储存的目录,然後在「Workspace Name」栏输入「EDRTEST」,按Enter键。

    这样就建立了一个空的工作空间。Developer Studio还建立了一个名为EDRTEST的子目录,以及工作空间档案EDRTEST.DSW(就像两个其他档案)。

    现在让我们在此工作空间里建立一个专案。从「File」功能表选择「New」,然後选择「Projects」页面标签。尽管过去您选择「Win32 Application」,但现在「Win32 Dynamic-Link Library」。另外,单击单选按钮「Add To Current Workspace」,这使得此专案是「EDRTEST」 工作空间的一部分。在「Project Name栏输入EDRLIB,但先不要按「OK」按钮。当您在Project Name栏输入EDRLIB时,Visual C++将改变「Location」栏,以显示EDRLIB作为EDRTEST的一个子目录。这不是我们要的,所以接著在「Location」栏删除EDRLIB子目录以便专案建立在EDRTEST目录。现在按「OK」。萤幕将显示一个对话方块,询问您建立什么型态的DLL。选择「An Empty DLL Project」,然後按「Finish」。Visual C++将建立一个专案档案EDRLIB.DSP和一个构造档案EDRLIB.MAK(如果「Tools Options」对话方块的B「uild页面标签中选择了「Export Makefile」选项」。

    现在您已经在此专案中添加了一对档案。从「File」功能表选择「New」,然後选择「Files」页面标签。选择「C/C++ Header File」,然後输入档案名EDRLIB.H。输入程式21-1所示的档案(或者从本书光碟中复制)。再次从「File」功能表中选择「New」,然後选择「Files」页面标签。这次选择「C++ Source File」,然後输入档案名EDRLIB.C。继续输入程式21-1所示的程式。
    这里您可以按Release设定,或者也可以按Debug设定来建立EDRLIB.DLL。之後,RELEASE和DEBUG目录将包含EDRLIB.LIB(即动态连结程式库的引用程式库)和EDRLIB.DLL(动态连结程式库本身)。

    纵观全书,我们建立的所有程式都可以根据UNICODE识别字来编译成使用Unicode或非Unicode字串的程式码。当您建立一个DLL时,它应该包括处理字元和字串的Unicode和非Unicode版的所有函式。因此,EDRLIB.C就包含函式EdrCenterTextA(ANSI版)和EdrCenterTextW(宽字元版)。EdrCenterTextA定义为带有参数PCSTR(指向const字串的指标),而EdrCenterTextW则定义为带有参数PCWSTR(指向const宽字串的指标)。EdrCenterTextA函式将呼叫lstrlenA、GetTextExtentPoint32A和TextOutA。EdrCenterTextW将呼叫lstrlenW、GetTextExtentPoint32W和TextOutW。如果定义了UNICODE识别字,则EDRLIB.H将EdrCenterText定义为EdrCenterTextW,否则定义为EdrCenterTextA。这样的做法很像Windows表头档案。

    EDRLIB.H也包含函式DllMain,取代了DLL中的WinMain。此函式用於执行初始化和未初始化(deinitialization),我将在下一节讨论。我们现在所需要的就是从DllMain传回TRUE。

    在这两个档案中,最後一点神秘之处就是定义了EXPORT识别字。DLL中应用程式使用的函式必须是「输出(exported)」的。这跟税务或者商业制度无关,只是确保函式名添加到EDRLIB.LIB的一个关键字(以便连结程式在连结使用此函式的应用程式时,能够解析出函式名称),而且该函式在EDRLIB.DLL中也是看得到的。EXPORT识别字包括储存方式限定词__declspec(dllexport)以及在表头档案按C++模式编译时附加的「C」。这将防止编译器使用C++的名称轧压规则(name mangling)来处理函式名称,使C和C++程式都能使用这个DLL。
    程式库入口/出口点
     

    当动态连结程式库首次启动和结束时,我们呼叫了DllMain函式。DllMain的第一个参数是程式库的执行实体代号。如果您的程式库使用需要执行实体代号(诸如DialogBox)的资源,那么您应该将hInstance储存为一个整体变数。DllMain的最後一个参数由系统保留。

    fdwReason参数可以是四个值之一,说明为什么Windows要呼叫DllMain函式。在下面的讨论中,请记住一个程式可以被载入多次,并在Windows下一起执行。每当一个程式载入时,它都被认为是一个独立的程序(process)。

    fdwReason的一个值DLL_PROCESS_ATTACH表示动态连结程式库被映射到一个程序的位址空间。程式库可以根据这个线索进行初始化,为以後来自该程序的请求提供服务。例如,这类初始化可能包括记忆体配置。在一个程序的生命周期内,只有一次对DllMain的呼叫以DLL_PROCESS_ATTACH为参数。使用同一DLL的其他任何程序都将导致另一个使用DLL_PROCESS_ATTACH参数的DllMain呼叫,但这是对新程序的呼叫。

    如果初始化成功,DllMain应该传回一个非0值。传回0将导致Windows不执行该程式。

    当fdwReason的值为DLL_PROCESS_DETACH时,意味著程序不再需要DLL了,从而提供给程式库自己清除自己的机会。在32位元的Windows下,这种处理并不是严格必须的,但这是一种良好的程式写作习惯。

    类似地,当以DLL_THREAD_ATTACH为fdwReason参数呼叫DllMain时,意味著某个程序建立了一个新的执行绪。当执行绪中止时,Windows以DLL_THREAD_DETACH为fdwReason参数呼叫DllMain。请注意,如果动态连结程式库是在执行绪被建立之後和一个程序连结的,那么可能会得到一个没有事先对应一个DLL_THREAD_ATTACH呼叫的DLL_THREAD_DETACH呼叫。

    当使用一个DLL_THREAD_DETACH参数呼叫DllMain时,执行绪仍然存在。动态连结程式库甚至可以在这个程序期间发送执行绪讯息。但是它不应该使用PostMessage,因为执行绪可能在此讯息被处理到之前就已经退出执行了。

    测试程式
     

    现在让我们在EDRTEST工作空间里建立第二个专案,程式名称为EDRTEST,而且使用EDRLIB.DLL。在Visual C++中载入EDRTEST工作空间时,请从「File」功能表选择「New」,然後在「New」对话方块中选择「Projects」页面标签。这次选择「Win32 Application」,并确保选中了「Add To Current Workspace」按钮。输入专案名称EDRTEST。再在「Locations」栏删除第二个EDRTEST子目录。按下「OK」,然後在下一个对话方块选择「An Empty Project」,按「Finish」。

    从「File」功能表再次选择「New」。选择「Files」页面标签然後选择「C++ Source File」。确保「Add To Project」清单方块显示「EDRTEST」而不是「EDRLIB」。输入档案名称EDRTEST.C,然後输入程式21-2所示的程式。此程式用EdrCenterText函式将显示区域中的字串居中对齐。
    注意,为了定义EdrCenterText函式,EDRTEST.C包括EDRLIB.H表头档案,此函式将在WM_PAINT讯息处理期间呼叫。

    在编译此程式之前,您可能希望做以下几件事。首先,在「Project」功能表选择「Select Active Project」。这时您将看到「EDRLIB」和「EDRTEST」,选择「EDRTEST」。在重新编译此工作空间时,您真正要重新编译的是程式。另外,在「Project」功能表中,选择「Dependencies」,在「Select Project To Modify」清单方块中选择「EDRTEST」。在「Dependent On The Following Project(s)」列表选中「EDRLIB」。此操作的意思是:EDRTEST需要EDRLIB动态连结程式库。以後每次重新编译EDRTEST时,如果必要的话,都将在编译和连结EDRTEST之前重新重新编译EDRLIB。

    从「Project」功能表选择「Settings」,单击「General」标签。当您在左边的窗格中选择「EDRLIB」或者「EDRTEST」专案时,如果设定为「Win32 Release」,则显示在右边窗格中的「Intermediate Files」和「Output Files」将位於RELEASE目录;如果设定为「Win32 Debug」,则位於DEBUG目录。如果不是,请按此修改。这样可确保EDRLIB.DLL与EDRTEST.EXE在同一个目录中,而且程式在使用DLL时也不会产生问题。

    在「Project Setting」对话方块中依然选中「EDRTEST」,单击「C/C++」页面标签。按本书的惯例,在「Preprocessor Definitions」中,将「UNICODE」添加到Debug设定。

    现在您就可以在「Debug」或「Release」设定中重新编译EDRTEST.EXE了。必要时,Visual C++将首先编译和连结EDRLIB。RELEASE和DEBUG目录都包含EDRLIB.LIB(引用程式库)和EDRLIB.DLL。当Developer Studio连结EDRTEST时,将自动包含引用程式库。

    了解EDRTEST.EXE档案中不包含EdrCenterText程式码很重要。事实上,要证明执行了EDRLIB.DLL档案和EdrCenterText函式很简单:执行EDRTEST.EXE需要EDRLIB.DLL。

    执行EDRTEST.EXE时,Windows按外部程式库模组执行固定的函式。其中许多函式都在一般Windows动态连结程式库中。但Windows也看到程式从EDRLIB呼叫了函式,因此Windows将EDRLIB.DLL档案载入到记忆体,然後呼叫EDRLIB的初始化常式。EDRTEST呼叫EdrCenterText函式是动态连结到EDRLIB中函式的。

    在EDRTEST.C原始码档案中包含EDRLIB.H与包含WINDOWS.H类似。连结EDRLIB.LIB与连结Windows引用程式库(例如USER32.LIB)类似。当您的程式执行时,它连结EDLIB.DLL的方式与连结USER32.DLL的方式相同。恭喜您!您已经扩展了Windows的功能!

    在继续之前,我还要对动态连结程式库多说明一些:

    首先,虽然我们将DLL作为Windows的延伸,但它也是您的应用程式的延伸。DLL所完成的每件工作对於应用程式来说都是应用程式所交代要完成的。例如,应用程式拥有DLL配置的全部记忆体、DLL建立的全部视窗以及DLL打开的所有档案。多个应用程式可以同时使用同一个DLL,但在Windows下,这些应用程式不会相互影响。

    多个程序能够共用一个动态连结程式库中相同的程式码。但是,DLL为每个程序所储存的资料都不同。每个程序都为DLL所使用的全部资料配置了自己的位址空间。我们将在下以节看到,共用记忆体需要额外的工作。
    PYG19周年生日快乐!
  • TA的每日心情
    开心
    2016-6-16 14:07
  • 签到天数: 10 天

    [LV.3]偶尔看看II

     楼主| 发表于 2004-12-14 11:03:54 | 显示全部楼层
    /*----------------------
       EDRLIB.H header file
      ----------------------*/

    #ifdef __cplusplus
    #define EXPORT extern "C" __declspec (dllexport)
    #else
    #define EXPORT __declspec (dllexport)
    #endif

    EXPORT BOOL CALLBACK EdrCenterTextA (HDC, PRECT, PCSTR) ;
    EXPORT BOOL CALLBACK EdrCenterTextW (HDC, PRECT, PCWSTR) ;

    #ifdef UNICODE
    #define EdrCenterText EdrCenterTextW
    #else
    #define EdrCenterText EdrCenterTextA
    #endif

    /*-------------------------------------------------
       EDRLIB.C -- Easy Drawing Routine Library module
                   (c) Charles Petzold, 1998
      -------------------------------------------------*/

    #include <windows.h>
    #include "edrlib.h"

    int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
    {
         return TRUE ;
    }

    EXPORT BOOL CALLBACK EdrCenterTextA (HDC hdc, PRECT prc, PCSTR pString)
    {
         int  iLength ;
         SIZE size ;

         iLength = lstrlenA (pString) ;


         GetTextExtentPoint32A (hdc, pString, iLength, &size) ;
          
         return TextOutA (hdc, (prc->right - prc->left - size.cx) / 2,
                               (prc->bottom - prc->top - size.cy) / 2,
                          pString, iLength) ;
    }

    EXPORT BOOL CALLBACK EdrCenterTextW (HDC hdc, PRECT prc, PCWSTR pString)
    {
         int  iLength ;
         SIZE size ;

         iLength = lstrlenW (pString) ;

         GetTextExtentPoint32W (hdc, pString, iLength, &size) ;

         return TextOutW (hdc, (prc->right - prc->left - size.cx) / 2,
                               (prc->bottom - prc->top - size.cy) / 2,
                          pString, iLength) ;
    }
    /*--------------------------------------------------------
       EDRTEST.C -- Program using EDRLIB dynamic-link library
                    (c) Charles Petzold, 1998
      --------------------------------------------------------*/

    #include <windows.h>
    #include "edrlib.h"

    LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

    int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                        PSTR szCmdLine, int iCmdShow)
    {
         static TCHAR szAppName[] = TEXT ("http://www.5icrack.com/ ") ;
         HWND         hwnd ;
         MSG          msg ;
         WNDCLASS     wndclass ;

         wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
         wndclass.lpfnWndProc   = WndProc ;
         wndclass.cbClsExtra    = 0 ;
         wndclass.cbWndExtra    = 0 ;
         wndclass.hInstance     = hInstance ;
         wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
         wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
         wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
         wndclass.lpszMenuName  = NULL ;
         wndclass.lpszClassName = szAppName ;
         
         if (!RegisterClass (&wndclass))
         {
              MessageBox (NULL, TEXT ("This program requires Windows NT!"),
                          szAppName, MB_ICONERROR) ;
              return 0 ;
         }
         
         hwnd = CreateWindow (szAppName, TEXT ("http://www.5icrack.com/ "),
                              WS_OVERLAPPEDWINDOW,
                              CW_USEDEFAULT, CW_USEDEFAULT,
                              CW_USEDEFAULT, CW_USEDEFAULT,
                              NULL, NULL, hInstance, NULL) ;
         
         ShowWindow (hwnd, iCmdShow) ;
         UpdateWindow (hwnd) ;
         
         while (GetMessage (&msg, NULL, 0, 0))
         {
              TranslateMessage (&msg) ;
              DispatchMessage (&msg) ;
         }
         return msg.wParam ;
    }

    LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
    {
         HDC         hdc ;
         PAINTSTRUCT ps ;
         RECT        rect ;
         
         switch (message)
         {
         case WM_PAINT:
              hdc = BeginPaint (hwnd, &ps) ;
             
              GetClientRect (hwnd, &rect) ;
              SetTextColor( hdc, RGB(255,0,2));
              SetBkColor( hdc, RGB(25,230,26));
              EdrCenterText (hdc, &rect,
                             TEXT ("http://www.5icrack.com/ ")) ;
             
              EndPaint (hwnd, &ps) ;
              return 0 ;
         case WM_CLOSE:
                     MessageBox(hwnd, "http://www.5icrack.com/ ","http://www.5icrack.com/ ",MB_YESNO );

              PostQuitMessage (0) ;
              return 0 ;   
         case WM_DESTROY:
              PostQuitMessage (0) ;
              return 0 ;
         }
         return DefWindowProc (hwnd, message, wParam, lParam) ;
    }
    PYG19周年生日快乐!
    您需要登录后才可以回帖 登录 | 加入我们

    本版积分规则

    快速回复 返回顶部 返回列表