Programming Windows程式开发设计指南 第21章 动态连结程式库

21. 动态连结程式库

copyright dedecms

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

本文来自织梦

动态连结程式库的基本知识
  内容来自dedecms

正如前面所看到的,Windows应用程式是一个可执行档案,它通常建立一个或几个视窗,并使用讯息回圈接收使用者输入。通常,动态连结程式库并不能直接执行,也不接收讯息。它们是一些独立的档案,其中包含能被程式或其他DLL呼叫来完成一定作业的函式。只有在其他模组呼叫动态连结程式库中的函式时,它才发挥作用。

dedecms.com

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

织梦内容管理系统

KERNEL32.DLLUSER32.DLLGDI32.DLL、各种驱动程式档案如KEYBOARD.DRVSYSTEM.DRVMOUSE.DRV和视讯及印表机驱动程式都是动态连结程式库。这些动态连结程式库能被所有Windows应用程式使用。

织梦好,好织梦

有些动态连结程式库(如字体档案等)被称为「纯资源」。它们只包含资料(通常是资源的形式)而不包含程式码。由此可见,动态连结程式库的目的之一就是提供能被许多不同的应用程式所使用的函式和资源。在一般的作业系统中,只有作业系统本身才包含其他应用程式能够呼叫来完成某一作业的常式。在Windows中,一个模组呼叫另一个模组函式的程序被推广了。结果使得编写一个动态连结程式库,也就是在扩充Windows。当然,也可认为动态连结程式库(包括构成Windows的那些动态连结程式库常式)是对使用者程式的扩充。

织梦好,好织梦

尽管一个动态连结程式库模组可能有其他副档名(如.EXE.FON),但标准副档名是.DLL。只有带.DLL副档名的动态连结程式库才能被Windows自动载入。如果档案有其他副档名,则程式必须另外使用LoadLibrary或者LoadLibraryEx函式载入该模组。

织梦好,好织梦

您通常会发现,动态连结程式库在大型应用程式中最有意义。例如,假设要为Windows编写一个由几个不同的程式组成的大型财务套装软体,就会发现这些应用程式会使用许多共同的常式。可以把这些公共常式放入一个一般性的目的码程式库(带.LIB副档名)中,并在使用LINK静态连结时把它们加入各程式模组中。但这种方法是很浪费的,因为套装软体中的每个程式都包含与公共常式相同的程式码。而且,如果修改了程式库中的某个常式,就要重新连结使用此常式的所有程式。然而,如果把这些公共常式放到称为ACCOUNT.DLL的动态连结程式库中,就可解决这两个问题。只有动态连结程式库模组才包含所有程式都要用到的常式。这样能为储存档案节省磁碟空间,并且在同时执行多个应用程式时节省记忆体,而且,可以修改动态连结程式库模组而不用重新连结各个程式。 copyright dedecms

动态连结程式库实际上是可以独立存在的。例如,假设您编写了一系列3D绘图常式,并把它们放入名为GDI3.DLLDLL中。如果其他软体发展者对此程式库很感兴趣,您就可以授权他们将其加入他们的图形程式中。使用多个这样的图形程式的使用者只需要一个GDI3.DLL档案。

织梦内容管理系统

程式库:一词多义
 

织梦好,好织梦

动态连结程式库有著令人困惑的印象,部分原因是由於「程式库」这个词被放在几种不同的用语之後。除了动态连结程式库之外,我们也用它来称呼「目的码程式库」或「引用程式库」。

内容来自dedecms

目的码程式库是带.LIB副档名的档案。在使用连结程式进行静态连结时,它的程式码就会加到程式的.EXE档案中。例如,在Microsoft Visual C++中,连同程式连结的一般C执行目的码程式库被称为LIBC.LIB织梦好,好织梦

引用程式库是目的码程式库档案的一种特殊形式。像目的码程式库一样,引用程式库有.LIB副档名,并且被连结器用来确定程式码中的函式呼叫来源。但引用程式库不含程式码,而是为连结程式提供资讯,以便在.EXE档案中建立动态连结时要用到的重定位表。包含在Microsoft编译器中的KERNEL32.LIBUSER32.LIBGDI32.LIB档案是Windows函式的引用程式库。如果一个程式呼叫Rectangle函式,Rectangle将告诉LINK,该函式在GDI32.DLL动态连结程式库中。该资讯被记录在.EXE档案中,使得程式执行时,Windows能够和GDI32.DLL动态连结程式库进行动态连结。 织梦内容管理系统

目的码程式库和引用程式库只用在程式开发期间使用,而动态连结程式库在执行期间使用。当一个使用动态连结程式库的程式执行时,该动态连结程式库必须在磁片上。当Windows要执行一个使用了动态连结程式库的程式而需要载入该程式库时,动态连结程式库档案必须储存在含有该.EXE程式的目录下、目前的目录下、Windows系统目录下、Windows目录下,或者是在通过MS-DOS环境中的PATH可以存取到的目录下(Windows会按顺序搜索这些目录)。 织梦内容管理系统

一个简单的DLL
  织梦内容管理系统

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

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

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

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

这样就建立了一个空的工作空间。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」对话方块的Build页面标签中选择了「Export Makefile」选项」。

内容来自dedecms

现在您已经在此专案中添加了一对档案。从「File」功能表选择「New」,然後选择「Files」页面标签。选择「C/C++ Header File」,然後输入档案名EDRLIB.H。输入程式21-1所示的档案(或者从本书光碟中复制)。再次从「File」功能表中选择「New」,然後选择「Files」页面标签。这次选择「C++ Source File」,然後输入档案名EDRLIB.C。继续输入程式21-1所示的程式。 织梦好,好织梦

 程式21-1  EDRLIB动态连结程式库 

dedecms.com

EDRLIB.H 织梦好,好织梦 
/*-------------------------------------------------------------------------- 

copyright dedecms

        EDRLIB.H header file dedecms.com 
----------------------------------------------------------------------------*/ 

织梦内容管理系统

  
dedecms.com
#ifdef __cplusplus 织梦好,好织梦 
#define        EXPORT extern "C" __declspec (dllexport) 织梦好,好织梦 
#else 

dedecms.com

#define        EXPORT __declspec (dllexport) 

织梦好,好织梦

#endif 

本文来自织梦

  
本文来自织梦
EXPORT BOOL CALLBACK EdrCenterTextA (HDC, PRECT, PCSTR) ; 内容来自dedecms 
EXPORT BOOL CALLBACK EdrCenterTextW (HDC, PRECT, PCWSTR) ; 内容来自dedecms 
  
copyright dedecms
#ifdef UNICODE dedecms.com 
#define        EdrCenterText EdrCenterTextW 织梦好,好织梦 
#else 织梦内容管理系统 
#define        EdrCenterText EdrCenterTextA copyright dedecms 
#endif 
copyright dedecms
EDRLIB.C copyright dedecms 
/*--------------------------------------------------------------------------- 内容来自dedecms 
        EDRLIB.C -- Easy Drawing Routine Library module 本文来自织梦 
                                                     (c) Charles Petzold, 1998 织梦好,好织梦 
-----------------------------------------------------------------------------*/ 
dedecms.com
#include  windows.h> 
本文来自织梦
#include "edrlib.h" 织梦内容管理系统 
  
copyright dedecms
int WINAPI DllMain (   HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) 

dedecms.com

{ 
内容来自dedecms
        return TRUE ; 

织梦好,好织梦

} 
织梦好,好织梦
  
dedecms.com
EXPORT BOOL CALLBACK EdrCenterTextA (HDC hdc, PRECT prc, PCSTR pString) 内容来自dedecms 
{ dedecms.com 
        int  iLength ; 
织梦好,好织梦
        SIZE size ; 

dedecms.com

  
织梦好,好织梦
        iLength = lstrlenA (pString) ; 织梦好,好织梦 
        GetTextExtentPoint32A (hdc, pString, iLength, &size) ; 
copyright dedecms
        return TextOutA (hdc,(prc->right - prc->left - size.cx) / 2, 
织梦内容管理系统
                                (      prc->bottom - prc->top - size.cy) / 2, 本文来自织梦 
                                      pString, iLength) ; 织梦好,好织梦 
} 内容来自dedecms 
  dedecms.com 
EXPORT BOOL CALLBACK EdrCenterTextW (HDC hdc, PRECT prc, PCWSTR pString) 
织梦内容管理系统
{ dedecms.com 
        int  iLength ; 本文来自织梦 
        SIZE size ; 织梦好,好织梦 
  copyright dedecms 
        iLength = lstrlenW (pString) ; 

dedecms.com

        GetTextExtentPoint32W (hdc, pString, iLength, &size) ; 
织梦好,好织梦
        return TextOutW (hdc, (       prc->right - prc->left - size.cx) / 2, 本文来自织梦 
                                      (       prc->bottom - prc->top - size.cy) / 2, 
本文来自织梦
                                              pString, iLength) ; 

织梦好,好织梦

} 
织梦好,好织梦

这里您可以按Release设定,或者也可以按Debug设定来建立EDRLIB.DLL。之後,RELEASEDEBUG目录将包含EDRLIB.LIB(即动态连结程式库的引用程式库)和EDRLIB.DLL(动态连结程式库本身)。 织梦内容管理系统

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

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

dedecms.com

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

程式库入口/出口点
  内容来自dedecms

当动态连结程式库首次启动和结束时,我们呼叫了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不执行该程式。 内容来自dedecms

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

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

dedecms.com

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

copyright dedecms

测试程式
 

织梦好,好织梦

现在让我们在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」。 copyright dedecms

从「File」功能表再次选择「New」。选择「Files」页面标签然後选择「C++ Source File」。确保「Add To Project」清单方块显示「EDRTEST」而不是「EDRLIB」。输入档案名称EDRTEST.C,然後输入程式21-2所示的程式。此程式用EdrCenterText函式将显示区域中的字串居中对齐。

织梦内容管理系统

 程式21-2  EDRTEST copyright dedecms 
EDRTEST.C 织梦好,好织梦 
/*--------------------------------------------------------------------------- 

dedecms.com

        EDRTEST.C -- Program using EDRLIB dynamic-link library copyright dedecms 
                                                             (c) Charles Petzold, 1998 
内容来自dedecms
----------------------------------------------------------------------------*/ 本文来自织梦 
  copyright dedecms 
#include <windows.h> 织梦好,好织梦 
#include "edrlib.h" copyright dedecms 
  copyright dedecms 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; dedecms.com 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, copyright dedecms 
                                                                     PSTR szCmdLine, int iCmdShow) 
本文来自织梦
{ dedecms.com 
        static TCHAR   szAppName[] = TEXT ("StrProg") ; 

内容来自dedecms

        HWND                                  hwnd ; 
内容来自dedecms
        MSG                                   msg ; 织梦好,好织梦 
        WNDCLASS                       wndclass ; 本文来自织梦 
  内容来自dedecms 
        wndclass.style                        = CS_HREDRAW | CS_VREDRAW ; 
本文来自织梦
        wndclass.lpfnWndProc                  = WndProc ; 

本文来自织梦

        wndclass.cbClsExtra                   = 0 ; 织梦内容管理系统 
        wndclass.cbWndExtra                   = 0 ; 
dedecms.com
        wndclass.hInstance                    = hInstance ; 

本文来自织梦

        wndclass.hIcon                        = LoadIcon (NULL, IDI_APPLICATION) ; 
本文来自织梦
        wndclass.hCursor                      = LoadCursor (NULL, IDC_ARROW) ; 

copyright dedecms

        wndclass.hbrBackground         = (HBRUSH) GetStockObject (WHITE_BRUSH) ; dedecms.com 
        wndclass.lpszMenuName          = NULL ; dedecms.com 
        wndclass.lpszClassName         = szAppName ; 
本文来自织梦
      织梦好,好织梦 
        if (!RegisterClass (&wndclass)) 内容来自dedecms 
        { 
织梦好,好织梦
                       MessageBox (   NULL, TEXT ("This program requires Windows NT!"), 

copyright dedecms

                                                                     szAppName, MB_ICONERROR) ; 
dedecms.com
                       return 0 ; dedecms.com 
     } 

织梦好,好织梦

      织梦好,好织梦 
        hwnd = CreateWindow (szAppName, TEXT ("DLL Demonstration Program"), 
copyright dedecms
                               WS_OVERLAPPEDWINDOW, 
dedecms.com
                               CW_USEDEFAULT, CW_USEDEFAULT, 

本文来自织梦

                               CW_USEDEFAULT, CW_USEDEFAULT, 织梦内容管理系统 
                               NULL, NULL, hInstance, NULL) ; 
dedecms.com
  织梦好,好织梦 
        ShowWindow (hwnd, iCmdShow) ; 
织梦内容管理系统
        UpdateWindow (hwnd) ; dedecms.com 
      
内容来自dedecms
        while (GetMessage (&msg, NULL, 0, 0)) dedecms.com 
        { 

织梦内容管理系统

                       TranslateMessage (&msg) ; 
织梦内容管理系统
                       DispatchMessage (&msg) ; 
dedecms.com
        } 织梦好,好织梦 
        return msg.wParam ; 

织梦内容管理系统

}        织梦好,好织梦 
  

织梦内容管理系统

LRESULT CALLBACK WndProc (     HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 

织梦内容管理系统

{ 

织梦好,好织梦

        HDC                    hdc ; 

织梦内容管理系统

        PAINTSTRUCT            ps ; 

内容来自dedecms

        RECT                   rect ; dedecms.com 
      

dedecms.com

        switch (message) 
织梦好,好织梦
        { 

织梦好,好织梦

        case    WM_PAINT: 本文来自织梦 
                               hdc = BeginPaint (hwnd, &ps) ; 
copyright dedecms
           本文来自织梦 
                               GetClientRect (hwnd, &rect) ; 
copyright dedecms
           copyright dedecms 
                               EdrCenterText (hdc, &rect,  dedecms.com 
                    TEXT ("This string was displayed by a DLL")) ; 织梦内容管理系统 
           dedecms.com 
                               EndPaint (hwnd, &ps) ; 
本文来自织梦
                               return 0 ; copyright dedecms 
           

织梦内容管理系统

        case    WM_DESTROY: 

织梦内容管理系统

                               PostQuitMessage (0) ; 

本文来自织梦

                               return 0 ; 
本文来自织梦
        } 
本文来自织梦
     return DefWindowProc (hwnd, message, wParam, lParam) ; 
织梦好,好织梦
} 织梦好,好织梦 

注意,为了定义EdrCenterText函式,EDRTEST.C包括EDRLIB.H表头档案,此函式将在WM_PAINT讯息处理期间呼叫。 copyright dedecms

在编译此程式之前,您可能希望做以下几件事。首先,在「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之前重新重新编译EDRLIBcopyright dedecms

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

织梦好,好织梦

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

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

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

织梦内容管理系统

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

dedecms.com

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

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

内容来自dedecms

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

多个程序能够共用一个动态连结程式库中相同的程式码。但是,DLL为每个程序所储存的资料都不同。每个程序都为DLL所使用的全部资料配置了自己的位址空间。我们将在下以节看到,共用记忆体需要额外的工作。 dedecms.com

DLL中共用记忆体
 

copyright dedecms

令人兴奋的是,Windows能够将同时使用同一个动态连结程式库的应用程式分开。不过,有时却不太令人满意。您可能希望写一个DLL,其中包含能够被不同应用程式或者同一个程式的不同常式所共用的记忆体。这包括使用共用记忆体。共用记忆体实际上是一种记忆体映射档案。

本文来自织梦

让我们测试一下,这项工作是如何在程式STRPROG(「字串程式(string program)」)和动态连结程式库STRLIB(「字串程式库(string library)」)中完成的。STRLIB有三个输出函式被STRPROG呼叫,我们只对此感兴趣,STRLIB中的一个函式使用了在STRPROG定义的callback函式。 dedecms.com

STRLIB是一个动态连结程式库模组,它储存并排序了最多256个字串。在STRLIB中,这些字串均为大写,并由共用记忆体维护。利用STRLIB的三个函式,STRPROG能够添加字串、删除字串以及从STRLIB获得目前的所有字串。STRPROG测试程式有两个功能表项(「Enter」和「Delete」),这两个功能表项将启动不同的对话方块来添加或删除字串。STRPROG在其显示区域列出目前储存在STRLIB中的所有字串。

本文来自织梦

下面这个函式在STRLIB定义,它将一个字串添加到STRLIB的共用记忆体。

织梦内容管理系统

EXPORT BOOL CALLBACK AddString (pStringIn) 织梦内容管理系统 

参数pStringIn是字串的指标。字串在AddString函式中变成大写。如果在STRLIB的列表中有一个相同的字串,那么此函式将添加一个字串的复本。如果成功,AddString传回「TRUE」(非0),否则传回「FALSE」(0)。如果字串的长度为0,或者不能配置储存字串的记忆体,或者已经储存了256个字串,则传回值将都是FALSE

织梦内容管理系统

STRLIB函式从STRLIB的共用记忆体中删除一个字串。 织梦好,好织梦

EXPORT BOOL CALLBACK DeleteString (pStringIn) 

dedecms.com

另外,参数pStringIn是一个字串指标。如果有多个相同内容字串,则删除第一个。如果成功,那么DeleteString传回「TRUE」(非0),否则传回「FALSE」(0)。传回「FALSE」表示字串长度为0,或者找不到相同内容的字串。 内容来自dedecms

STRLIB函式使用了呼叫程式中的一个callback函式,以便列出目前储存在STRLIB共用记忆体中的字串: 内容来自dedecms

EXPORT int CALLBACK GetStrings (pfnGetStrCallBack, pParam) 织梦内容管理系统 

在呼叫程式中,callback函式必须像下面这样定义: copyright dedecms

EXPORT BOOL CALLBACK GetStrCallBack (PSTR pString, PVOID pParam) copyright dedecms 

GetStrings的参数pfnGetStrCallBack指向callback函式。直到callback函式传回「FALSE」(0),GetStrings将为每个字串都呼叫一次GetStrCallBackGetStrings传回传递给callback函式的字串数。pParam参数是一个远程指标,指向程式写作者定义的资料。

织梦好,好织梦

当然,此程式可以编译成Unicode程式,或者在STRLIB的支援下,编译成Unicode和非Unicode应用程式。与EDRLIB一样,所有的函式都有「A」和「W」两种版本。在内部,STRLIBUnicode储存所有的字串。如果非Unicode程式使用了STRLIB(也就是说,程式将呼叫AddStringADeleteStringAGetStringsA),字串将在Unicode和非Unicode之间转换。

织梦好,好织梦

STRPROGSTRLIB专案相关的工作空间名为STRPROG。此档案按EDRTEST工作空间的方式组合。程式21-3显示了建立STRLIB.DLL动态连结程式库所必须的两个档案。 本文来自织梦

 程式21-3  STRLI 
内容来自dedecms
STRLIB.H 

copyright dedecms

/*---------------------------------------------------------------------------- 

dedecms.com

        STRLIB.H header file 
内容来自dedecms
-----------------------------------------------------------------------------*/ 织梦内容管理系统 
  本文来自织梦 
#ifdef __cplusplus 织梦好,好织梦 
#define EXPORT extern "C" __declspec (dllexport) 内容来自dedecms 
#else 
内容来自dedecms
#define EXPORT __declspec (dllexport) 
织梦好,好织梦
#endif 
本文来自织梦
  织梦好,好织梦 
        // The maximum number of strings STRLIB will store and their lengths 

dedecms.com

#define        MAX_STRINGS 256 
织梦内容管理系统
#define        MAX_LENGTH  63 织梦内容管理系统 
  dedecms.com 
        // The callback function type definition uses generic strings 
织梦内容管理系统
  
dedecms.com
typedef BOOL (CALLBACK * GETSTRCB) (PCTSTR, PVOID) ; 织梦好,好织梦 
  

copyright dedecms

        // Each function has ANSI and Unicode versions 内容来自dedecms 
  
织梦好,好织梦
EXPORT BOOL CALLBACK AddStringA (PCSTR) ; 
本文来自织梦
EXPORT BOOL CALLBACK AddStringW (PCWSTR) ; 

本文来自织梦

  

织梦好,好织梦

EXPORT BOOL CALLBACK DeleteStringA (PCSTR) ; dedecms.com 
EXPORT BOOL CALLBACK DeleteStringW (PCWSTR) ; 织梦内容管理系统 
  

copyright dedecms

EXPORT int CALLBACK GetStringsA (GETSTRCB, PVOID) ; 内容来自dedecms 
EXPORT int CALLBACK GetStringsW (GETSTRCB, PVOID) ; 本文来自织梦 
 织梦好,好织梦 
        // Use the correct version depending on the UNICODE identifier 
copyright dedecms
  织梦内容管理系统 
#ifdef UNICODE 

本文来自织梦

#define        AddString      AddStringW 
织梦好,好织梦
#define        DeleteString           DeleteStringW 本文来自织梦 
#define        GetStrings             GetStringsW dedecms.com 
#else 内容来自dedecms 
#define        AddString      AddStringA dedecms.com 
#define        DeleteString           DeleteStringA 
dedecms.com
#define        GetStrings             GetStringsA 
内容来自dedecms
#endif 内容来自dedecms 
STRLIB.C 
内容来自dedecms
/*--------------------------------------------------------------------------- 

织梦内容管理系统

        STRLIB.C -     Library module for STRPROG program 

copyright dedecms

                                              (c) Charles Petzold, 1998 

copyright dedecms

----------------------------------------------------------------------------*/ dedecms.com 
  
本文来自织梦
#include <windows.h> 

内容来自dedecms

#include <wchar.h>                                   // for wide-character string functions 

本文来自织梦

#include "strlib.h" copyright dedecms 
  内容来自dedecms 
        // shared memory section (requires /SECTION:shared,RWS in link options) 
织梦好,好织梦
#pragma                data_seg ("shared") 

copyright dedecms

int                    iTotal = 0 ; 织梦好,好织梦 
WCHAR                  szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { '\0' } ; dedecms.com 
#pragma                data_seg () 

内容来自dedecms

  
内容来自dedecms
#pragma                comment(linker,"/SECTION:shared,RWS") 织梦好,好织梦 
  copyright dedecms 
int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) 内容来自dedecms 
{ 
织梦内容管理系统
        return TRUE ; 

dedecms.com

} copyright dedecms 
  

内容来自dedecms

EXPORT BOOL CALLBACK AddStringA (PCSTR pStringIn) 织梦内容管理系统 
{ dedecms.com 
        BOOL           bReturn ; 
本文来自织梦
        int            iLength ; 
本文来自织梦
        PWSTR          pWideStr ; 

dedecms.com

  dedecms.com 
        // Convert string to Unicode and call AddStringW 

dedecms.com

        iLength = MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, NULL, 0) ; 本文来自织梦 
        pWideStr = malloc (iLength) ; dedecms.com 
        MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ; 
dedecms.com
        bReturn = AddStringW (pWideStr) ; 内容来自dedecms 
        free (pWideStr) ; 内容来自dedecms 
  dedecms.com 
        return bReturn ; 

内容来自dedecms

} 

dedecms.com

  
本文来自织梦
EXPORT BOOL CALLBACK AddStringW (PCWSTR pStringIn) 

织梦内容管理系统

{ 织梦好,好织梦 
        PWSTR          pString ; 内容来自dedecms 
        int            i, iLength ; 内容来自dedecms 
      dedecms.com 
        if (iTotal == MAX_STRINGS - 1) 
内容来自dedecms
                               return FALSE ; copyright dedecms 
        if ((iLength = wcslen (pStringIn)) == 0) 

织梦好,好织梦

                               return FALSE ; 织梦好,好织梦 
                       // Allocate memory for storing string, copy it, convert to uppercase 

本文来自织梦

        pString = malloc (sizeof (WCHAR) * (1 + iLength)) ; 

内容来自dedecms

        wcscpy (pString, pStringIn) ; 

内容来自dedecms

        _wcsupr (pString) ; 
织梦好,好织梦
  
dedecms.com
                       //  Alphabetize the strings 
内容来自dedecms
        for (i = iTotal ; i > 0 ; i-) 
copyright dedecms
        { dedecms.com 
                       if (wcscmp (pString, szStrings[i - 1]) >= 0) 
本文来自织梦
                                      break ; dedecms.com 
                       wcscpy (szStrings[i], szStrings[i - 1]) ; copyright dedecms 
        } 

copyright dedecms

        wcscpy (szStrings[i], pString) ; 

dedecms.com

        iTotal++ ; 织梦内容管理系统 
  copyright dedecms 
        free (pString) ; 
copyright dedecms
        return TRUE ; 织梦好,好织梦 
} 织梦内容管理系统 
  

dedecms.com

EXPORT BOOL CALLBACK DeleteStringA (PCSTR pStringIn) 内容来自dedecms 
{ 

内容来自dedecms

        BOOL           bReturn ; 

dedecms.com

        int            iLength ; 
本文来自织梦
        PWSTR          pWideStr ; 内容来自dedecms 
  织梦内容管理系统 
                               // Convert string to Unicode and call DeleteStringW copyright dedecms 
  dedecms.com 
        iLength = MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, NULL, 0) ; copyright dedecms 
        pWideStr = malloc (iLength) ; 织梦好,好织梦 
        MultiByteToWideChar (CP_ACP, 0, pStringIn, -1, pWideStr, iLength) ; copyright dedecms 
        bReturn = DeleteStringW (pWideStr) ; 

内容来自dedecms

        free (pWideStr) ; 织梦内容管理系统 
  dedecms.com 
        return bReturn ; 本文来自织梦 
} 内容来自dedecms 
  copyright dedecms 
EXPORT BOOL CALLBACK DeleteStringW (PCWSTR pStringIn) 

织梦好,好织梦

{ 

织梦内容管理系统

        int i, j ; 内容来自dedecms 
        if (0 == wcslen (pStringIn)) 织梦内容管理系统 
                               return FALSE ; 
dedecms.com
        for (i = 0 ; i < iTotal ; i++) 

copyright dedecms

        { 内容来自dedecms 
                       if (_wcsicmp (szStrings[i], pStringIn) == 0) dedecms.com 
                                      break ; 
内容来自dedecms
        } 本文来自织梦 
                       // If given string not in list, return without taking action 
内容来自dedecms
        if (i == iTotal) 

本文来自织梦

                               return FALSE ; 

copyright dedecms

                       // Else adjust list downward 

织梦好,好织梦

        for (j = i ; j < iTotal ; j++) copyright dedecms 
                               wcscpy (szStrings[j], szStrings[j + 1]) ; dedecms.com 
        szStrings[iTotal-][0] = '\0' ; dedecms.com 
        return TRUE ; 
dedecms.com
} 内容来自dedecms 
  

本文来自织梦

EXPORT int CALLBACK GetStringsA (GETSTRCB pfnGetStrCallBack, PVOID pParam) dedecms.com 
{ 内容来自dedecms 
        BOOL           bReturn ; dedecms.com 
        int            i, iLength ; copyright dedecms 
        PSTR           pAnsiStr ; 
织梦好,好织梦
  
织梦好,好织梦
        for (i = 0 ; i < iTotal ; i++) 
内容来自dedecms
        { 织梦好,好织梦 
                               // Convert string from Unicode 织梦内容管理系统 
iLength = WideCharToMultiByte (       CP_ACP, 0, szStrings[i], -1, NULL, 0, NULL, NULL) ; 织梦内容管理系统 
                       pAnsiStr = malloc (iLength) ; 

内容来自dedecms

          WideCharToMultiByte (       CP_ACP, 0, szStrings[i], -1, pAnsiStr, iLength, NULL, NULL) ; 
本文来自织梦
  
dedecms.com
                                              // Call callback function 本文来自织梦 
  

织梦好,好织梦

                       bReturn = pfnGetStrCallBack (pAnsiStr, pParam) ; 
内容来自dedecms
           织梦好,好织梦 
                       if (bReturn == FALSE) 

copyright dedecms

                                              return i + 1 ; 内容来自dedecms 
  copyright dedecms 
                       free (pAnsiStr) ; 
织梦内容管理系统
        } 织梦内容管理系统 
        return iTotal ; 织梦内容管理系统 
} 
织梦好,好织梦
  本文来自织梦 
EXPORT int CALLBACK GetStringsW (GETSTRCB pfnGetStrCallBack, PVOID pParam) 织梦好,好织梦 
{ 
本文来自织梦
        BOOL           bReturn ; 
织梦内容管理系统
        int            i ; 织梦内容管理系统 
      
内容来自dedecms
        for (i = 0 ; i < iTotal ; i++) 
copyright dedecms
        { copyright dedecms 
                       bReturn = pfnGetStrCallBack (szStrings[i], pParam) ; 
织梦内容管理系统
                       if (bReturn == FALSE) dedecms.com 
                                      return i + 1 ; 本文来自织梦 
        } 内容来自dedecms 
        return iTotal ; 
本文来自织梦
} 织梦内容管理系统 

除了DllMain函式以外,STRLIB中只有六个函式供其他函式输出用。所有这些函式都按EXPORT定义。这会使LINKSTRLIB.LIB引用程式库中列出它们。 dedecms.com

STRPROG程式
  dedecms.com

STRPROG程式如程式21-4所示,其内容相当浅显易懂。两个功能表选项(EnterDelete)启动一个对话方块,让您输入一个字串,然後STRPROG呼叫AddString或者DeleteString。当程式需要更新它的显示区域时,呼叫GetStrings并使用函式GetStrCallBack来列出所列举的字串。

dedecms.com

 程式21-4  STRPROG 内容来自dedecms 
STRPROG.C 织梦好,好织梦 
/*---------------------------------------------------------------------------- copyright dedecms 
        STRPROG.C -    Program using STRLIB dynamic-link library 

织梦内容管理系统

                                                             (c) Charles Petzold, 1998 
织梦内容管理系统
-----------------------------------------------------------------------------*/ 

copyright dedecms

  

本文来自织梦

#include <windows.h> 
内容来自dedecms
#include "strlib.h" 

本文来自织梦

#include "resource.h" 织梦好,好织梦 
  copyright dedecms 
typedef struct 
dedecms.com
{ 织梦好,好织梦 
        HDC     hdc ; 织梦好,好织梦 
        int     xText ; 

内容来自dedecms

        int     yText ; 本文来自织梦 
        int     xStart ; 
内容来自dedecms
        int     yStart ; 
织梦内容管理系统
        int     xIncr ; 织梦好,好织梦 
        int     yIncr ; 

内容来自dedecms

        int     xMax ; 

织梦好,好织梦

        int     yMax ; 
本文来自织梦
} 

织梦好,好织梦

CBPARAM ; 织梦内容管理系统 
LRESULT        CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; copyright dedecms 
TCHAR          szAppName [] = TEXT ("StrProg") ; 
内容来自dedecms
TCHAR szString [MAX_LENGTH + 1] ; 织梦好,好织梦 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 内容来自dedecms 
                                                                     PSTR szCmdLine, int iCmdShow) 织梦内容管理系统 
{ 本文来自织梦 
        HWND                           hwnd ; 

copyright dedecms

        MSG                    msg ; 织梦内容管理系统 
        WNDCLASS               wndclass ; 

内容来自dedecms

      内容来自dedecms 
        wndclass.style                                = CS_HREDRAW | CS_VREDRAW ; 
dedecms.com
        wndclass.lpfnWndProc                          = WndProc ; 

copyright dedecms

        wndclass.cbClsExtra                           = 0 ; 
dedecms.com
        wndclass.cbWndExtra                           = 0 ; 

copyright dedecms

        wndclass.hInstance                            = hInstance ; 
织梦好,好织梦
        wndclass.hIcon                                = LoadIcon (NULL, IDI_APPLICATION) ; 织梦内容管理系统 
        wndclass.hCursor                     = LoadCursor (NULL, IDC_ARROW) ; copyright dedecms 
        wndclass.hbrBackground                = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 织梦内容管理系统 
        wndclass.lpszMenuName                 = szAppName ; dedecms.com 
        wndclass.lpszClassName                = szAppName ; 
内容来自dedecms
      

copyright dedecms

        if (!RegisterClass (&wndclass)) 
dedecms.com
        { 内容来自dedecms 
                       MessageBox (   NULL, TEXT ("This program requires Windows NT!"), 
dedecms.com
                                                                     szAppName, MB_ICONERROR) ; 织梦内容管理系统 
                       return 0 ; 
织梦好,好织梦
     } 本文来自织梦 
  
本文来自织梦
       hwnd = CreateWindow (  szAppName, TEXT ("DLL Demonstration Program"), 

本文来自织梦

                                      WS_OVERLAPPEDWINDOW, 织梦内容管理系统 
                                      CW_USEDEFAULT, CW_USEDEFAULT, 织梦内容管理系统 
                                      CW_USEDEFAULT, CW_USEDEFAULT, 内容来自dedecms 
                                      NULL, NULL, hInstance, NULL) ; 

织梦内容管理系统

      

内容来自dedecms

        ShowWindow (hwnd, iCmdShow) ; 
织梦内容管理系统
        UpdateWindow (hwnd) ; 内容来自dedecms 
      织梦好,好织梦 
        while (GetMessage (&msg, NULL, 0, 0)) 织梦内容管理系统 
        { 织梦内容管理系统 
                       TranslateMessage (&msg) ; 织梦内容管理系统 
                       DispatchMessage (&msg) ; 内容来自dedecms 
        } 
织梦好,好织梦
        return msg.wParam ; 
织梦内容管理系统
} 
dedecms.com
  内容来自dedecms 
BOOL CALLBACK DlgProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) 内容来自dedecms 
{ 

dedecms.com

        switch (message) 织梦好,好织梦 
        { 织梦内容管理系统 
        case    WM_INITDIALOG: 

织梦内容管理系统

                               SendDlgItemMessage (hDlg, IDC_STRING, EM_LIMITTEXT, MAX_LENGTH, 0) ; 

内容来自dedecms

                               return TRUE ; 
内容来自dedecms
           织梦内容管理系统 
        case    WM_COMMAND: 织梦内容管理系统 
                               switch (wParam) copyright dedecms 
                               { 
copyright dedecms
                               case    IDOK: 
织梦好,好织梦
                                              GetDlgItemText (hDlg, IDC_STRING, szString, MAX_LENGTH) ; 织梦好,好织梦 
                                              EndDialog (hDlg, TRUE) ; dedecms.com 
                                              return TRUE ; 
copyright dedecms
                

织梦好,好织梦

                               case    IDCANCEL: 
织梦好,好织梦
                                              EndDialog (hDlg, FALSE) ; 

内容来自dedecms

                                              return TRUE ; 
copyright dedecms
                              } 

copyright dedecms

        } dedecms.com 
        return FALSE ; 本文来自织梦 
} dedecms.com 
  
copyright dedecms
BOOL CALLBACK GetStrCallBack (PTSTR pString, CBPARAM * pcbp) dedecms.com 
{ dedecms.com 
        TextOut (      pcbp->hdc, pcbp->xText, pcbp->yText, 内容来自dedecms 
                                              pString, lstrlen (pString)) ; 织梦好,好织梦 
      
dedecms.com
        if ((pcbp->yText += pcbp->yIncr) > pcbp->yMax) 本文来自织梦 
        { 织梦好,好织梦 
                               pcbp->yText = pcbp->yStart ; copyright dedecms 
                               if ((pcbp->xText += pcbp->xIncr) > pcbp->xMax) 
dedecms.com
                                              return FALSE ; 
织梦好,好织梦
        } 
dedecms.com
        return TRUE ; 
织梦内容管理系统
} 织梦内容管理系统 
  织梦好,好织梦 
LRESULT CALLBACK WndProc (     HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 

内容来自dedecms

{ 织梦内容管理系统 
        static HINSTANCE               hInst ; 

copyright dedecms

        static int                                    cxChar, cyChar, cxClient, cyClient ; 
织梦内容管理系统
        static UINT                           iDataChangeMsg ; 
本文来自织梦
        CBPARAM                                               cbparam ; 
内容来自dedecms
        HDC                                                          hdc ; 内容来自dedecms 
        PAINTSTRUCT                                   ps ; 
织梦内容管理系统
        TEXTMETRIC                                    tm ; 本文来自织梦 
      内容来自dedecms 
        switch (message) dedecms.com 
        { 
内容来自dedecms
        case    WM_CREATE: 
内容来自dedecms
                               hInst          = ((LPCREATESTRUCT) lParam)->hInstance ; 

内容来自dedecms

                               hdc   = GetDC (hwnd) ; 内容来自dedecms 
                               GetTextMetrics (hdc, &tm) ; 
copyright dedecms
                               cxChar = (int) tm.tmAveCharWidth ; 织梦内容管理系统 
                               cyChar = (int) (tm.tmHeight + tm.tmExternalLeading) ; copyright dedecms 
                               ReleaseDC (hwnd, hdc) ; copyright dedecms 
  

copyright dedecms

                                      // Register message for notifying instances of data changes 
dedecms.com
  本文来自织梦 
        iDataChangeMsg = RegisterWindowMessage (TEXT ("StrProgDataChange")) ; 

织梦内容管理系统

                       return 0 ; 织梦好,好织梦 
        case    WM_COMMAND: 本文来自织梦 
                               switch (wParam) 
织梦内容管理系统
                               { dedecms.com 
                               case    IDM_ENTER: 
内容来自dedecms
                                              if (DialogBox (hInst, TEXT ("EnterDlg"), hwnd, &DlgProc)) 织梦内容管理系统 
                                              { 
织梦好,好织梦
                   if (AddString (szString)) copyright dedecms 
                    PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ; 内容来自dedecms 
                   else 
dedecms.com
                        MessageBeep (0) ; 本文来自织梦 
                       } copyright dedecms 
               break ; 

copyright dedecms

                
织梦好,好织梦
                               case    IDM_DELETE: 
内容来自dedecms
                                      if (DialogBox (hInst, TEXT ("DeleteDlg"), hwnd, &DlgProc)) 

copyright dedecms

                                              { copyright dedecms 
                    if (DeleteString (szString)) 织梦好,好织梦 
                    PostMessage (HWND_BROADCAST, iDataChangeMsg, 0, 0) ; 
copyright dedecms
                   else 
dedecms.com
                   MessageBeep (0) ; 本文来自织梦 
                                              } 
copyright dedecms
                                              break ; 
copyright dedecms
                               } copyright dedecms 
                               return 0 ; 

dedecms.com

           织梦好,好织梦 
        case    WM_SIZE: 

织梦内容管理系统

                               cxClient = (int) LOWORD (lParam) ; dedecms.com 
                               cyClient = (int) HIWORD (lParam) ; 织梦内容管理系统 
                               return 0 ; 本文来自织梦 
  
织梦好,好织梦
        case    WM_PAINT: 

织梦内容管理系统

                               hdc = BeginPaint (hwnd, &ps) ; 
本文来自织梦
                dedecms.com 
                               cbparam.hdc            = hdc ; 

本文来自织梦

                               cbparam.xText  = cbparam.xStart = cxChar ; 
dedecms.com
                               cbparam.yText  = cbparam.yStart = cyChar ; copyright dedecms 
                               cbparam.xIncr  = cxChar * MAX_LENGTH ; dedecms.com 
                               cbparam.yIncr  = cyChar ; 

织梦好,好织梦

                               cbparam.xMax   = cbparam.xIncr * (1 + cxClient / cbparam.xIncr) ; 

本文来自织梦

                               cbparam.yMax   = cyChar * (cyClient / cyChar - 1) ; dedecms.com 
                
copyright dedecms
                               GetStrings ((GETSTRCB) GetStrCallBack, (PVOID) &cbparam) ; 内容来自dedecms 
               dedecms.com 
                               EndPaint (hwnd, &ps) ; 织梦好,好织梦 
                               return 0 ; 
dedecms.com
                copyright dedecms 
        case    WM_DESTROY: 
织梦内容管理系统
                               PostQuitMessage (0) ; 

织梦内容管理系统

                               return 0 ; 
织梦好,好织梦
  织梦内容管理系统 
        default: 织梦好,好织梦 
                       if (message == iDataChangeMsg) 
dedecms.com
                                      InvalidateRect (hwnd, NULL, TRUE) ; 

织梦内容管理系统

                       break ; 织梦内容管理系统 
        } 

本文来自织梦

        return DefWindowProc (hwnd, message, wParam, lParam) ; 

织梦内容管理系统

} copyright dedecms 
 STRPROG.RC (摘录) 

dedecms.com

//Microsoft Developer Studio generated resource script. 
内容来自dedecms
#include "resource.h" 

织梦好,好织梦

#include "afxres.h" 

dedecms.com

  本文来自织梦 
///////////////////////////////////////////////////////////////////////////// dedecms.com 
// Dialog 
本文来自织梦
ENTERDLG DIALOG DISCARDABLE  20, 20, 186, 47 本文来自织梦 
STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU 
织梦好,好织梦
CAPTION "Enter" 

织梦内容管理系统

FONT 8, "MS Sans Serif" 内容来自dedecms 
BEGIN 织梦内容管理系统 
        LTEXT                                                 "&Enter:",IDC_STATIC,7,7,26,9 本文来自织梦 
        EDITTEXT                                      IDC_STRING,31,7,148,12,ES_AUTOHSCROLL dedecms.com 
        DEFPUSHBUTTON                  "OK",IDOK,32,26,50,14 
内容来自dedecms
        PUSHBUTTON                            "Cancel",IDCANCEL,104,26,50,14 

内容来自dedecms

END dedecms.com 
DELETEDLG DIALOG DISCARDABLE  20, 20, 186, 47 

织梦好,好织梦

STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU copyright dedecms 
CAPTION "Delete" 织梦内容管理系统 
FONT 8, "MS Sans Serif" 织梦好,好织梦 
BEGIN copyright dedecms 
        LTEXT                                                 "&Delete:",IDC_STATIC,7,7,26,9 织梦好,好织梦 
        EDITTEXT                                      IDC_STRING,31,7,148,12,ES_AUTOHSCROLL 本文来自织梦 
        DEFPUSHBUTTON                  "OK",IDOK,32,26,50,14 
织梦好,好织梦
        PUSHBUTTON                            "Cancel",IDCANCEL,104,26,50,14 dedecms.com 
END 织梦好,好织梦 
  内容来自dedecms 
///////////////////////////////////////////////////////////////////////////// 
织梦内容管理系统
// Menu 织梦内容管理系统 
STRPROG MENU DISCARDABLE  
copyright dedecms
BEGIN 
织梦内容管理系统
        MENUITEM "&Enter!",                                                  IDM_ENTER 

dedecms.com

        MENUITEM "&Delete!",                                                         IDM_DELETE 

本文来自织梦

END dedecms.com 
 RESOURCE.H (摘录) 本文来自织梦 
// Microsoft Developer Studio generated       include file. 
织梦内容管理系统
// Used by StrProg.rc 织梦内容管理系统 
  

copyright dedecms

#define                IDC_STRING                                                           1000 本文来自织梦 
#define                IDM_ENTER                                                            40001 
内容来自dedecms
#define                IDM_DELETE                                                           40002 织梦好,好织梦 
#define                IDC_STATIC                                                           -1 
本文来自织梦

STRPROG.C包含STRLIB.H表头档案,其中定义了STRPROG将使用的STRLIB中的三个函式。 本文来自织梦

当您执行STRPROG的多个执行实体的时候,本程式的奥妙之处就会显露出来。STRLIB将在共用记忆体中储存字串及其指标,并允许STRPROG中的所有执行实体共用此资料。让我们看一下它是如何执行的吧。 本文来自织梦

STRPROG执行实体之间共用资料
 

dedecms.com

Windows在一个Win32程序的位址空间周围筑了一道墙。通常,一个程序的位址空间中的资料是私有的,对别的程序而言是不可见的。但是执行STRPROG的多个执行实体表示了STRLIB在程式的所有执行实体之间共用资料是毫无问题的。当您在一个STRPROG视窗中增加或者删除一个字串时,这种改变将立即反映在其他的视窗中。 copyright dedecms

在全部常式之间,STRLIB共用两个变数:一个字元阵列和一个整数(记录已储存的有效字串的个数)。STRLIB将这两个变数储存在共用的一个特殊记忆体区段中:

织梦好,好织梦

#pragma        data_seg ("shared") 织梦好,好织梦 
int            iTotal = 0 ; 本文来自织梦 
WCHAR          szStrings [MAX_STRINGS][MAX_LENGTH + 1] = { '\0' } ; 
dedecms.com
#pragma        data_seg () dedecms.com 

第一个#pragma叙述建立资料段,这里命名为shared。您可以将这段命名为任何一个您喜欢的名字。在这里的#pragma叙述之後的所有初始化了的变数都放在shared资料段中。第二个#pragma叙述标示段的结束。对变数进行专门的初始化是很重要的,否则编译器将把它们放在普通的未初始化资料段中而不是放在shared中。

本文来自织梦

连结器必须知道有一个「shared」共享资料段。在「Project Settings」对话方块选择「Link」页面标签。选中「STRLIB」时在「Project Options」栏位(在ReleaseDebug设定中均可),包含下面的连结叙述:

copyright dedecms

/SECTION:shared,RWS 织梦内容管理系统 

字母RWS表示段具有读、写和共用属性。或者,您也可以直接用DLL原始码指定连结选项,就像我们在STRLIB.C那样:

本文来自织梦

#pragma comment(linker,"/SECTION:shared,RWS") 
本文来自织梦

共用的记忆体段允许iTotal变数和szStrings字串阵列在STRLIB的所有常式之间共用。因为MAX_STRINGS等於256,而MAX_LENGTH等於63,所以,共用记忆体段的长度为32,772位元组-iTotal变数需要4位元组,256个指标中的每一个都需要128位元组。

织梦内容管理系统

使用共用记忆体段可能是在多个应用程式间共用资料的最简单的方法。如果需要动态配置共用记忆体空间,您应该查看记忆体映射档案物件的用法,文件在/Platform SDK/Windows Base Services/Interprocess Communication/File Mapping

内容来自dedecms

各式各样的DLL讨论
 

织梦内容管理系统

如前所述,动态连结程式库模组不接收讯息,但是,动态连结程式库模组可呼叫GetMessagePeekMessage。实际上,从讯息伫列中得到的讯息是发给呼叫程式库函式的程式的。一般来说,程式库是替呼叫它的程式工作的,这是一项对程式库所呼叫的大多数Windows函式都适用的规则。 本文来自织梦

动态连结程式库可以从程式库档案或者从呼叫程式库的程式档案中载入资源(如图示、字串和点阵图)。载入资源的函式需要执行实体代号。如果程式库使用它自己的执行实体代号(初始化期间传给程式库的),则程式库能从它自己的档案中获得资源。为了从呼叫程式的.EXE档案中得到资源,程式程式库函式需要呼叫该函式的程式的执行实体代号。

织梦内容管理系统

在程式库中登录视窗类别和建立视窗需要一点技巧。视窗类别结构和CreateWindow呼叫都需要执行实体代号。尽管在建立视窗类别和视窗时可使用动态连结程式库模组的执行实体代号,但在程式库建立视窗时,视窗讯息仍会发送到呼叫程式库中程式的讯息伫列。如果使用者必须在程式库中建立视窗类别和视窗,最好的方法可能是使用呼叫程式的执行实体代号。 织梦内容管理系统

因为模态对话方块的讯息是在程式的讯息回圈之外接收到的,因此使用者可以在程式库中呼叫DialogBox来建立模态对话方块。执行实体代号可以是程式库代号,并且DialogBoxhwndParent参数可以为NULL

内容来自dedecms

不用输入引用资讯的动态连结
 

copyright dedecms

除了在第一次把使用者程式载入记忆体时,由Windows执行动态连结外,程式执行时也可以把程式同动态连结程式库模组连结到一起。例如,您通常会这样呼叫Rectangle函式:

本文来自织梦

Rectangle (hdc, xLeft, yTop, xRight, yBottom) ; 

copyright dedecms

因为程式和GDI32.LIB引用程式库连结,该程式库提供了Rectangle的位址,因此这种方法有效。

dedecms.com

您也可以用更迂回的方法呼叫Rectangle。首先用typedefRectangle定义一个函式型态: 本文来自织梦

typedef BOOL (WINAPI * PFNRECT) (HDC, int, int, int, int) ; dedecms.com 

然後定义两个变数: 内容来自dedecms

HANDLE  hLibrary ; copyright dedecms 
PFNRECTpfnRectangle ; 
本文来自织梦

现在将hLibrary设定为程式库代号,将lpfnRectangle设定为Rectangle函式的位址:

dedecms.com

hLibrary = LoadLibrary (TEXT ("GDI32.DLL")) 
织梦好,好织梦
pfnRectangle = (PFNPRECT) GetProcAddress (hLibrary, TEXT ("Rectangle")) 
织梦内容管理系统

如果找不到程式库档案或者发生其他一些错误,LoadLibrary函式传回NULL。现在您可以呼叫函式然後释放程式库:

织梦内容管理系统

pfnRectangle (hdc, xLeft, yTop, xRight, yBottom) ; 本文来自织梦 
FreeLibrary (hLibrary) ; 
织梦内容管理系统

尽管这项执行时期动态连结的技术并没有为Rectangle函式增加多大好处,但它肯定是有用的,如果直到执行时还不知道程式动态连结程式库模组的名称,这时就需要使用它。 dedecms.com

上面的程式码使用了LoadLibraryFreeLibrary函式。Windows为所有的动态连结程式库模组提供「引用计数」,LoadLibrary使引用计数递增。当Windows载入任何使用了程式库的程式时,引用计数也会递增。FreeLibrary使引用计数递减,在使用了程式库的程式执行实体结束时也是如此。当引用计数为零时,Windows将从记忆体中把程式库删除掉,因为不再需要它了。 织梦好,好织梦

纯资源程式库
 

织梦内容管理系统

可由Windows程式或其他程式库使用的动态连结程式库中的任何函式都必须被输出。然而,DLL也可以不包含任何输出函式。那么,DLL到底包含什么呢?答案是资源。 本文来自织梦

假设使用者正在使用需要几幅点阵图的Windows应用程式进行工作。通常要在程式的资源描述档中列出资源,并用LoadBitmap函式把它们载入记忆体。但使用者可能希望建立若干套点阵图,每一套均适用於Windows所使用的不同显示卡。将不同套的点阵图存放到不同档案中可能是明智的,因为只需要在硬碟上保留一套点阵图。这些档案就是纯资源档案。 dedecms.com

程式21-5说明如何建立包含9幅点阵图的名为BITLIB.DLL的纯资源程式库档案。BITLIB.RC档案列出了所有独立的点阵图档案并为每个档案赋予一个序号。为了建立BITLIB.DLL,需要9幅名为BITMAP1.BMPBITMAP2.BMP等等的点阵图。您可以使用附带的光碟上提供的点阵图或者在Visual C++中建立这些点阵图。它们与ID19相对应。 dedecms.com

 程式21-5  BITLIB 

织梦好,好织梦

BITLIB.C 
内容来自dedecms
/*-------------------------------------------------------------- 

copyright dedecms

        BITLIB.C --    Code entry point for BITLIB dynamic-link library 

织梦内容管理系统

                                                     (c) Charles Petzold,  1998 

内容来自dedecms

------------------------------------------------------------------*/ 

织梦好,好织梦

  织梦好,好织梦 
#include <windows.h> 
copyright dedecms
int WINAPI DllMain (HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved) 织梦好,好织梦 
{ 
dedecms.com
        return TRUE ; 织梦内容管理系统 
} 
copyright dedecms
 BITLIB.RC (摘录) 织梦内容管理系统 
//Microsoft Developer Studio generated resource script. 织梦好,好织梦 
#include "resource.h" 织梦好,好织梦 
#include "afxres.h" 织梦内容管理系统 
///////////////////////////////////////////////////////////////////////////// 

内容来自dedecms

// Bitmap 内容来自dedecms 
1                 BITMAP  DISCARDABLE     "bitmap1.bmp" copyright dedecms 
2                 BITMAP  DISCARDABLE     "bitmap2.bmp" dedecms.com 
3                 BITMAP  DISCARDABLE     "bitmap3.bmp" 
copyright dedecms
4                 BITMAP  DISCARDABLE     "bitmap4.bmp" 内容来自dedecms 
5                 BITMAP  DISCARDABLE     "bitmap5.bmp" copyright dedecms 
6                 BITMAP  DISCARDABLE     "bitmap6.bmp" 

内容来自dedecms

7                 BITMAP  DISCARDABLE     "bitmap7.bmp" 本文来自织梦 
8                 BITMAP  DISCARDABLE     "bitmap8.bmp" dedecms.com 
9                 BITMAP  DISCARDABLE     "bitmap9.bmp" 内容来自dedecms 

在名为SHOWBIT的工作空间中建立BITLIB专案。在名为SHOWBIT的另一个专案中,建立程式21-6所示的SHOWBIT程式,这与前面的一样。不过,不要使BITLIB依赖於SHOWBIT;否则,连结程序中将需要BITLIB.LIB档案,并且因为BITLIB没有任何输出函式,它也不会建立BITLIB.LIB。事实上,要分别重新编译BITLIBSHOWBIT,可以交替设定其中一个为「Active Project」然後再重新编译。

内容来自dedecms

SHOWBIT.CBITLIB读取点阵图资源,然後在其显示区域显示。按键盘上的任意键可以循环显示。

织梦好,好织梦

 程式21-6  SHOWBIT 

本文来自织梦

SHOWBIT.C 
内容来自dedecms
/*-------------------------------------------------------------------------- 
织梦好,好织梦
        SHOWBIT.C -- Shows bitmaps in BITLIB dynamic-link library 本文来自织梦 
                                                             (c) Charles Petzold, 1998 织梦好,好织梦 
---------------------------------------------------------------------------*/ 
织梦好,好织梦
  

copyright dedecms

#include <windows.h> 织梦内容管理系统 
  本文来自织梦 
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; 

copyright dedecms

  
织梦内容管理系统
TCHAR szAppName [] = TEXT ("ShowBit") ; 本文来自织梦 
  dedecms.com 
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, 
织梦好,好织梦
                                                                     PSTR szCmdLine, int iCmdShow) 织梦内容管理系统 
{ 

织梦内容管理系统

        HWND                           hwnd ; 

织梦内容管理系统

        MSG                            msg ; 
织梦好,好织梦
        WNDCLASS               wndclass ; 

dedecms.com

  

copyright dedecms

        wndclass.style                                = CS_HREDRAW | CS_VREDRAW ; 内容来自dedecms 
        wndclass.lpfnWndProc                          = WndProc ; 
织梦内容管理系统
        wndclass.cbClsExtra                           = 0 ; 
内容来自dedecms
        wndclass.cbWndExtra                           = 0 ; 织梦好,好织梦 
        wndclass.hInstance                        = hInstance ; 

dedecms.com

        wndclass.hIcon                                = LoadIcon (NULL, IDI_APPLICATION) ; 

copyright dedecms

        wndclass.hCursor                              = LoadCursor (NULL, IDC_ARROW) ; 织梦好,好织梦 
        wndclass.hbrBackground                = (HBRUSH) GetStockObject (WHITE_BRUSH) ; 织梦好,好织梦 
        wndclass.lpszMenuName  = NULL ; dedecms.com 
        wndclass.lpszClassName                = szAppName ; copyright dedecms 
      织梦好,好织梦 
        if (!RegisterClass (&wndclass)) 织梦内容管理系统 
     { 织梦内容管理系统 
               MessageBox (   NULL, TEXT ("This program requires Windows NT!"), 本文来自织梦 
                                                                     szAppName, MB_ICONERROR) ; 
织梦内容管理系统
                       return 0 ; 
织梦好,好织梦
     } 
织梦好,好织梦
  
织梦内容管理系统
     hwnd = CreateWindow (szAppName,  
内容来自dedecms
            TEXT ("Show Bitmaps from BITLIB (Press Key)"), 

dedecms.com

                 WS_OVERLAPPEDWINDOW, 本文来自织梦 
                 CW_USEDEFAULT, CW_USEDEFAULT, 
本文来自织梦
                 CW_USEDEFAULT, CW_USEDEFAULT, 

织梦好,好织梦

                 NULL, NULL, hInstance, NULL) ; 本文来自织梦 
  

本文来自织梦

        if (!hwnd) 内容来自dedecms 
                               return 0 ; 
织梦好,好织梦
        ShowWindow (hwnd, iCmdShow) ; 

织梦内容管理系统

        UpdateWindow (hwnd) ; 
dedecms.com
      

dedecms.com

        while (GetMessage (&msg, NULL, 0, 0)) 

织梦好,好织梦

        { 

dedecms.com

          TranslateMessage (&msg) ; 
copyright dedecms
          DispatchMessage (&msg) ; 

织梦好,好织梦

        }  copyright dedecms 
        return msg.wParam ; 织梦好,好织梦 
} 

织梦内容管理系统

  copyright dedecms 
void DrawBitmap (HDC hdc, int xStart, int yStart, HBITMAP hBitmap) 

copyright dedecms

{        

织梦好,好织梦

        BITMAP         bm ; 织梦好,好织梦 
        HDC                    hMemDC ; 

织梦好,好织梦

        POINT                  pt ; 织梦内容管理系统 
  
织梦内容管理系统
        hMemDC = CreateCompatibleDC (hdc) ; copyright dedecms 
        SelectObject (hMemDC, hBitmap) ; 
dedecms.com
        GetObject (hBitmap, sizeof (BITMAP), &bm) ; 内容来自dedecms 
        pt.x = bm.bmWidth ; 
内容来自dedecms
        pt.y = bm.bmHeight ; 

内容来自dedecms

      
织梦内容管理系统
        BitBlt (hdc, xStart, yStart, pt.x, pt.y, hMemDC, 0, 0, SRCCOPY) ; 织梦内容管理系统 
        DeleteDC (hMemDC) ; 
织梦好,好织梦
} 
织梦内容管理系统
  织梦好,好织梦 
LRESULT CALLBACK WndProc (     HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
内容来自dedecms
{ 
copyright dedecms
        static HINSTANCE               hLibrary ; 
织梦好,好织梦
        static int                                    iCurrent = 1 ; dedecms.com 
        HBITMAP                                               hBitmap ; 内容来自dedecms 
        HDC                                                          hdc ; 

织梦内容管理系统

        PAINTSTRUCT                                   ps ; 织梦好,好织梦 
  

织梦好,好织梦

        switch (message) 

copyright dedecms

        {        copyright dedecms 
        case    WM_CREATE: 
copyright dedecms
                               if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL) 

内容来自dedecms

                               { 内容来自dedecms 
           MessageBox (        hwnd, TEXT ("Can't load BITLIB.DLL."), 织梦好,好织梦 
                        szAppName, 0) ; 
织梦内容管理系统
           return -1 ; copyright dedecms 
                       } 织梦内容管理系统 
                       return 0 ; copyright dedecms 
           

织梦好,好织梦

        case    WM_CHAR: 

内容来自dedecms

                               if (hLibrary) 
织梦好,好织梦
                               { copyright dedecms 
                                      iCurrent ++ ; 

dedecms.com

                                      InvalidateRect (hwnd, NULL, TRUE) ; 本文来自织梦 
                               } 

织梦好,好织梦

                               return 0 ; 
dedecms.com
        case    WM_PAINT: 织梦好,好织梦 
                               hdc = BeginPaint (hwnd, &ps) ; 织梦好,好织梦 
           
织梦内容管理系统
                               if (hLibrary) 本文来自织梦 
                               { 织梦内容管理系统 
                       hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ; 内容来自dedecms 
  

copyright dedecms

                                      if (!hBitmap)  内容来自dedecms 
                                      { 
内容来自dedecms
                                                     iCurrent = 1 ; dedecms.com 
                                                     hBitmap = LoadBitmap (hLibrary,  内容来自dedecms 
                                        MAKEINTRESOURCE (iCurrent)) ; 

织梦内容管理系统

                                      } 
织梦内容管理系统
                                      if (hBitmap) 
织梦内容管理系统
                                              { 
织梦内容管理系统
                                                             DrawBitmap (hdc, 0, 0, hBitmap) ; 织梦内容管理系统 
                                                     DeleteObject (hBitmap) ; 

织梦内容管理系统

               } 
织梦好,好织梦
                               } 内容来自dedecms 
                               EndPaint (hwnd, &ps) ; 

本文来自织梦

                               return 0 ; copyright dedecms 
           本文来自织梦 
        case    WM_DESTROY: copyright dedecms 
                               if (hLibrary) 
织梦内容管理系统
                                              FreeLibrary (hLibrary) ; 
本文来自织梦
  织梦好,好织梦 
                               PostQuitMessage (0) ; 

内容来自dedecms

                               return 0 ; dedecms.com 
        } 本文来自织梦 
        return DefWindowProc (hwnd, message, wParam, lParam) ; 本文来自织梦 
} copyright dedecms 

在处理WM_CREATE讯息处理期间,SHOWBIT获得了BITLIB.DLL的代号: 本文来自织梦

if ((hLibrary = LoadLibrary (TEXT ("BITLIB.DLL"))) == NULL) 本文来自织梦 

如果BITLIB.DLLSHOWBIT.EXE不在同一个目录,Windows将按本章前面讨论的方法搜索。如果LoadLibrary传回NULLSHOWBIT显示一个讯息方块来报告错误,并从WM_CREATE讯息传回-1。这将导致WinMain中的CreateWindow呼叫传回NULL,而且程式终止程式。 织梦好,好织梦

SHOWBIT透过程式库代号和点阵图号码来呼叫LoadBitmap,从而得到一个点阵图代号:

织梦内容管理系统

hBitmap = LoadBitmap (hLibrary, MAKEINTRESOURCE (iCurrent)) ; 

dedecms.com

如果号码iCurrent对应的点阵图无效或者没有足够的记忆体载入点阵图,则传回一个错误。

织梦内容管理系统

在处理WM_DESTROY讯息时,SHOWBIT释放程式库: 织梦内容管理系统

FreeLibrary (hLibrary) ; 

copyright dedecms

SHOWBIT的最後一个执行实体终止时,BITLIB.DLL的引用计数变为0,并且释放所占用的记忆体。这就是实作「图片剪辑」程式的一种简单方法,所谓的「图片剪辑」程式就是能够将预先建立的点阵图(或者metafile、增强型metafile)载入到剪贴簿,以供其他程式使用的程式。

织梦内容管理系统