本节将介绍如何编写动态链接库,并更全面地探讨动态链接库的使用方法,包括以不同的方法装入动态链接库和以不同的方法调用其中的函数等。
本节必须掌握的知识点:
动态链接库的概念
第157练:编写动态链接库DLL
第158练:共享链接库共享内存
第159练:资源库
20.1.1动态链接库的概念
动态链接库(Dynamic Link Library,DLL)是一种在 Windows 操作系统中常见的可执行文件格式。与静态链接库(Static Link Library)不同,DLL 是在运行时加载和链接的,而不是在编译时。
DLL 文件包含可重用的代码和数据,可以被多个应用程序同时使用。它们提供了一种在应用程序之间共享代码和资源的方式,以减少重复开发和减小可执行文件的大小。
■以下是一些关于动态链接库的重要特点:
●动态加载:DLL 是在应用程序运行时动态加载的,而不是编译时静态链接的。这意味着应用程序可以根据需要在运行时加载 DLL,而无需将其包含在应用程序的可执行文件中。
●共享代码和资源:多个应用程序可以共享同一个 DLL 文件。这样可以减少重复的代码和数据,提高代码的复用性和维护性。
●动态更新:由于 DLL 是在运行时加载的,因此可以通过更新 DLL 文件来修复和升级应用程序的功能,而无需重新编译和发布整个应用程序。
●动态链接:应用程序通过动态链接来使用 DLL 中的函数和资源。这意味着应用程序只在需要时才加载和链接 DLL 中的代码,减小了可执行文件的大小。
●导出函数:DLL 中的函数需要通过导出表(Export Table)来公开给应用程序使用。导出表中列出了 DLL 中的可供外部调用的函数和数据。
●运行时依赖:应用程序在运行时需要正确的 DLL 文件才能正常工作。如果应用程序需要的 DLL 文件不存在或版本不匹配,可能会导致运行错误。
在 Windows 平台上,可以使用编程语言(如C/C++)和开发工具(如Visual Studio)来创建和使用动态链接库。在编写 DLL 时,开发人员需要定义导出函数,并在编译时生成 DLL 文件。在应用程序中,可以使用动态链接库的函数和资源,通过加载 DLL 文件并调用其中的导出函数来实现。
■动态链接库与静态链接库的区别
动态链接库(Dynamic Link Library,DLL)和静态链接库(Static Link Library)是两种可执行文件格式和链接方式的不同概念。它们在链接时和运行时的行为和特点有所区别。
以下是动态链接库(DLL)和静态链接库(静态库)之间的主要区别:
●链接时刻:
静态链接库:在编译时将静态库的代码和数据与应用程序的代码一起链接到一个可执行文件中。链接是在编译时完成的。
动态链接库:在运行时将 DLL 的代码和数据动态加载到内存中,然后与应用程序进行链接。链接是在运行时完成的。
●内存占用:
静态链接库:静态库的代码和数据被完全复制到应用程序的可执行文件中,因此每个应用程序都包含一份静态库的拷贝。这意味着每个应用程序独立占用内存,可能会导致可执行文件较大。
动态链接库:DLL 的代码和数据被共享,多个应用程序可以共用同一个 DLL 文件。这样可以减少内存占用,多个应用程序共享同一个 DLL 文件的一份拷贝。
●更新和维护:
静态链接库:每当静态库的代码或数据发生变化时,需要重新编译和重新分发整个应用程序。这可能会导致更复杂的更新和维护过程。
动态链接库:通过更新 DLL 文件,可以独立地修复和升级应用程序的功能,而无需重新编译和重新分发整个应用程序。这样可以简化更新和维护过程。
●运行时依赖:
静态链接库:应用程序在编译时静态链接到静态库,因此不需要额外的运行时依赖项。可执行文件包含了静态库的完整实现。
动态链接库:应用程序在运行时需要正确的 DLL 文件才能正常工作。如果所需的 DLL 文件不存在或版本不匹配,可能会导致运行错误。
●文件大小:
静态链接库:静态库的代码和数据被完全复制到应用程序的可执行文件中,这可能会增加可执行文件的大小。
动态链接库:DLL 文件只包含代码和数据的一份拷贝,并在运行时被多个应用程序共享。这可以减小每个应用程序的体积。
选择动态链接库还是静态链接库取决于具体的需求和应用场景。静态库适用于希望将所有代码和数据包含在单个可执行文件中的情况,而动态库适用于需要代码共享和运行时更新的情况。
■静态链接库和动态链接库的特点
名称 | 特点 | |
静态链接库 | 缺点 | ①多个程序使用相同库函数时,要存多份相同的代码到各个exe中,显然浪费空间。 ②如果某个函数有错或更新算法,则所有用到此函数的exe要重新编译一遍,升级麻烦。 ③多个exe运行时,要载入相同的代码,浪费内存。 |
优点 | ①仅在链接时使用,链接完后,可执行文件可脱离库文件单独存在 ②代码的访问速度快 | |
动态链接库 | 特点 | ①程序运行时被载入,且内存中只保留一份代码,这份代码是通过分页机制被映射到不同进程的地址空间。但数据段仍是多份的,会被映射到不同的物理内存中,有多少个程序,就会产生多少份的数据段 ②动态链接库与可执行文件的不同,仅在文件头的属性位不同而己,exe文件的一些特征,动态链接库中也有。如动态链接库也可以使用各种资源。 ③动态链接库是被映射到其他应用程序的地址空间的,与应用程序是“一体”的。它所拥有的资源也可被应用程序使用。它的任何操作都代表应用程序进行,当在库中打开文件、分配内存和创建窗口,这些都为应用程序所拥的。 ④不能独立于应用程序而单独运行。 |
■动态链接库的入口点和退出点
int WINAPI DllMain(HINSTANCE hInstance, DWORD fdwReason, PVOID pvReserved)
fdwReason | 含义 |
DLL_PROCESS_ATTACH | ①当动态链接链被映射到程序进程的地址空间时,相当于初始化信号 ②在进程的整个生命周期内,只会用这个参数调用一次。如果以后的线程通过调用LoadLibraryEx函数,只会递增DLL的使用计数,而不再用这个参数去调用DllMain。 ③返回值TRUE,表明初始化成功。返回FALSE,系统会终止整个进程的运行。 ④hInstance为动态链接库的模块实例句柄(注意不是“宿主”的实例句柄),获得该句柄的唯一途径是在入口函数被调用时,保存这个参数,否则运行时没有其他方法可获取了。 |
DLL_PROCESS_DETACH | ①表示动态链接库将被卸载,给库提供一个自清理的机会 ②同样,进程的整个生命期内,只会用这个参数调用函数一次。 ③注意,DLL能够阻止进程的终止。例如,当收到DLL_PROCESS_DETACH时,让程序进入一个无限循环。只有当每个DLL都处理完该通知后,操作系统才会中止进程。 |
DLL_THREAD_ATTACH | ①表示应用程序创建了一个新的线程,系统会向当前进程中的所有DLL发送该通知。只有系统处理后这个通知,系统才允许新线程开始执行它的线程函数。 ②如果程序频繁的创建创建和结束线程,会以该参数和DLL_THREAD_DETACH频繁地调用该函数。 ③系统只会新的线程调用该函数,如果系统己经存的线程则不会再次收到该通知。 ④主线程比较特殊,他不会调用以该参数去调用DllMain。 |
DLL_THREAD_DETACH | ①线程将要结束时,会以该参数调用DllMain。注意,此时的线程还没结束,甚到还可以发送线程消息。但不应该再使用PostMessage,因为该线程可能在消息被收取取之前就消失了。 ②如果调用TerminateThread终止线程时,那么不会收到该通知。 ③如果DLL被撤消时,仍有线程在运行,那么就不为任何线程调用该函数。 ④该通知能阻止线程的中止 |
■动态链接库导出函数名称问题
图20-1 动态链接库导出函数名
动态链接库中的导出函数采用C语言编译后的函数名格式,而不是繁复的C++函数名,目的是为了方便调用函数时在动态链接库中准确快速的找到导出函数。
.def(Module Definition)文件是一种文本文件,用于在Windows平台上定义动态链接库(Dynamic Link Library,DLL)的导出函数和符号。
.def文件可以包含以下内容:
LIBRARY:指定DLL的名称。
DESCRIPTION:提供有关DLL的描述信息。
EXPORTS:列出要导出的函数和符号。
下面是一个示例.def文件的结构:
LIBRARY "MyDll"
DESCRIPTION "My DLL"
EXPORTS
Add @1
Subtract @2
Multiply @3
在上述示例中,我们定义了一个名为"MyDll"的DLL,并给它添加了一个描述信息"My DLL"。然后,通过EXPORTS部分列出了要导出的函数和符号,每个函数或符号占据一行。导出函数的格式为"函数名 @ 序号",其中序号是可选的,用于指定导出函数的序号。
.def文件的作用是告诉链接器(如Microsoft Visual Studio中的链接器)要导出哪些函数和符号。在构建DLL时,可以将.def文件与DLL的源代码一起提供给链接器。链接器将根据.def文件中的导出列表来生成DLL,并确保这些函数和符号在DLL中是可见和可调用的。
需要注意的是,.def文件是可选的。如果未提供.def文件,链接器仍然可以根据函数的声明和修饰符来自动导出函数。但使用.def文件可以提供更精确的控制,例如指定导出函数的名称和序号(C\C++引用动态链接库时,将.def文件包含在了头文件中)。
20.1.2第157练:编写动态链接库DLL
在VS中,按以下步骤编写动态链接库:
第一步:生成项目解决方案EDRTEST。
第二步:在项目解决方案中添加-新建项目-Windows桌面向导。
第三步:选择创建动态链接库
第4步:添加源文件
第5步:添加头文件
第6步:先编译生成EDRLIB.DLL和EDRLIB.LIB文件,如果没有生成,重新生成解决方案就可以了,然后在添加新建项目EDRTEST.C编译生成exe文件。
可能出现的错误提示
错误原因:查看解决方案-属性-启动项设置,修改启动项为exe程序即可。
项目依赖项:
解决方案的配置:
或者将编译好的dll文件和头文件直接添加到EDRTEST项目文件夹下,重新生成项目解决方案也可以。
一个简单的动态链接库实例
edrlib.h
#pragma once
//EXPORT标识符会包含存储类关键字_declspec(dllexport)和extern “C”。
//EXPORT是用以确保函数名称被添加到EDRLIB.LIB中的关键字而已(使得链接器在链接使用这些函数的应用程序时可以正确解析该函数名),
//并且用于确保这些函数在EDRLIB.DLL中可见。
//extren "C":防止编译器对C++函数进行“名称重整”,从而使C和C++程序都能使用该DLL
//_declspec:用于指定存储类信息的扩展属性语法使用__declspec关键字
#ifdef _cplusplus //如果是C++
#define EXPORT extern "C" _declspec(dllexport)
#else //C
#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:动态链接库DLL
/*------------------------------------------------------------------------
157 WIN32 API 每日一练
第157个例子EDRLIB.C:动态链接库DLL
DllMain函数
EXPORT标识符
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
#include "edrlib.h"
//如果用这个就不需要_declspec(dllexport)语句
//#pragma comment(linker,"/EXPORT:EdrCenterText=_EdrCenterTextA@12,PRIVATE")
//#pragma comment(linker,"/EXPORT:EdrCenterText=_EdrCenterTextW@12,PRIVATE")
//入口和退出点
//当系统启动或终止进程或线程时,将使用进程的第一个线程为每个加载的DLL调用入口点函数
//当使用LoadLibrary和FreeLibrary函数加载或卸载DLL时,系统还会调用DLL的入口点函数
int WINAPI DllMain(HINSTANCE hInstance,DWORD fdwReason,LPVOID lpvReserved )
{
return TRUE;
}
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);
}
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);
}
/******************************************************************************
DllMain入口点函数:动态链接库(DLL)的可选入口点。当系统启动或终止进程或线程时,它将使用进程的第一个线程为每个加载的DLL调用入口点函数。
当使用LoadLibrary和FreeLibrary函数加载或卸载DLL时,系统还会调用DLL的入口点函数。
BOOL WINAPI DllMain(
_In_ HINSTANCE hinstDLL,//DLL模块的句柄。该值是DLL的基地址。DLL 的HINSTANCE与DLL的HMODULE相同,因此hinstDLL可用于对需要模块句柄的函数的调用。
_In_ DWORD fdwReason,//指示为什么调用DLL入口点函数的原因码。
_In_ LPVOID lpvReserved//系统保留参数
);
返回值
当系统使用DLL_PROCESS_ATTACH值调用DllMain函数时,如果成功,则该函数返回TRUE,如果初始化失败,则返回FALSE。
如果由于进程使用LoadLibrary函数动态加载DLL时,而在调用DllMain时返回值为FALSE,则LoadLibrary返回NULL。
*******************************************************************************
EXPORT标识符:会包含存储类关键字_declspec(dllexport)和extern “C”。防止编译器对C++函数进行“名称重整”,从而使C和C++程序都能使用该DLL。
EXPORT是用以确保函数名称被添加到EDRLIB.LIB中的关键字而已(使得链接器在链接使用这些函数的应用程序时可以正确解析该函数名),并且用于确保这些函数在EDRLIB.DLL中可见。
*/
EDRTEST.C:动态链接库DLL测试程序
/*------------------------------------------------------------------------
157 WIN32 API 每日一练
第157个例子EDRTEST.C:动态链接库DLL
DLL静态调用
DLL动态调用
(c) www.bcdaren.com 编程达人
-----------------------------------------------------------------------*/
#include <windows.h>
//#include "D:\code\windows5.0\A daily practice\149_EDRTEST\EDRLIB\edrlib.h"
//静态调用加载导入库
//#pragma comment(lib,"D:\\code\\windows5.0\\A daily practice\\149_EDRTEST\\Debug\\EDRLIB.LIB")
/*-----------------------动态调用(显式链接)--------------------*/
#ifdef UNICODE
typedef BOOL(CALLBACK *PEDRCENTERTEXT)(HDC, PRECT, PCWSTR);//CALLBACK == __stdcall
#define EdrCenterText "EdrCenterTextW"
#else
typedef BOOL(CALLBACK *PEDRCENTERTEXT)(HDC, PRECT, PCSTR);
#define EdrCenterText "EdrCenterTextA"
#endif
/*---------------------------------------------------------*/
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("StrProg");
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("DLL Demonstration Program"),
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;
/*动态调用DLL定义一个函数类型和一个句柄变量和一个函数指针*/
static HANDLE hDll;
static PEDRCENTERTEXT pEdrCenterText;
switch (message)
{
//DLL动态调用
/**/case WM_CREATE:
hDll = LoadLibrary(TEXT("EDRLIB.DLL"));//加载DLL
if(hDll)
pEdrCenterText = (PEDRCENTERTEXT)GetProcAddress(hDll,"_EdrCenterTextW@12");//获取DLL函数地址
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rect);
//DLL静态调用--VS属性中设置附加依赖项或者使用#pragma comment(lib,".lib")
//EdrCenterText(hdc, &rect,
// TEXT("This string was display by a DLL"));
/*DLL动态调用*/
pEdrCenterText(hdc, &rect,
TEXT("This string was display by a DLL"));
EndPaint(hwnd, &ps);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam);
}
运行结果:
图20-2 一个简单的动态链接库DLL
总结
实例创建两个模块,一个是测试主程序EDRTEST.C,另一个是动态链接库EDRLIB.C。
■动态链接库
动态链接库的源文件EDRLIB.C包含一个动态链接库入口函数DllMain,并定义了两个导出函数。
●DllMain:在Windows操作系统中,DLL文件可以定义一个入口函数,该函数在DLL加载时被自动调用。这个入口函数被称为DLL入口点(DLL entry point),它是DLL文件的起始执行点。
在C/C++语言中,可以使用DllMain函数作为DLL的入口函数。DllMain函数的原型如下:
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved);
参数说明:
hinstDLL:DLL模块的句柄,表示正在加载的DLL文件的实例句柄(HINSTANCE)。DLL模块句柄只能在入口函数DllMain中获取。
fdwReason:表示DLL入口点被调用的原因,可以是以下几种情况之一:
1.DLL_PROCESS_ATTACH:DLL被加载到进程的地址空间中。
2.DLL_PROCESS_DETACH:DLL从进程的地址空间中卸载。
3.DLL_THREAD_ATTACH:进程创建了一个新线程,并且DLL被加载到这个新线程的地址空间中。
4.DLL_THREAD_DETACH:线程退出,并且DLL已加载到该线程的地址空间中。
lpvReserved:保留参数,通常为NULL,不使用。
DllMain函数返回一个BOOL类型的值,表示函数执行的结果。如果返回TRUE,表示DLL入口点执行成功;如果返回FALSE,表示DLL入口点执行失败,导致DLL加载失败。
●导出函数:
EdrCenterTextA:ANSI字符集函数,调用TextOutA函数输出ANSI字符集字符串。
EdrCenterTextW:ANSI字符集函数,调用TextOutW函数输出UNICODE字符集字符串。
在编写DLL(Dynamic Link Library)时,可以通过导出函数(Exported Function)的方式,将某些函数或符号暴露给其他程序或模块使用。导出函数可以在DLL内部定义,并在DLL加载后供其他程序动态链接调用。
要使用这个DLL导出函数的程序,需要进行以下步骤:
引入DLL的头文件(edrlib.h):
#include "edrlib.h"
链接DLL的导入库(.lib文件)。
在Visual Studio中,可以在项目属性的链接器设置中指定导入库的名称。或者,可以使用#pragma comment(lib, "MyDll.lib")指令在代码中引入导入库。
调用DLL导出函数:
EdrCenterText(hdc, &rect,TEXT("This string was display by a DLL"));
在调用DLL导出函数时,需要确保正确连接DLL的导入库,并将DLL文件放置在可执行文件所能访问到的路径中。
■动态链接库头文件
动态链接库必须要有一个包含导出函数声明的头文件。本例动态链接库头文件为edrlib.h。
动态链接库(Dynamic Link Library,DLL)的头文件通常包含了用于访问和调用DLL中导出函数的声明。通过包含DLL的头文件,程序可以了解到DLL中导出函数的名称、参数和返回类型等信息,以便正确地调用这些函数。
通常情况下,DLL的头文件可以按照以下方式进行编写:
首先,包含必要的标准库头文件或其他依赖的头文件。这些头文件可能包括windows.h(在Windows平台上使用DLL时需要包含)或其他相关的库头文件。
使用extern "C"来指定C语言的链接规范,以确保函数名不会被C++的名称修饰机制修改。
声明DLL中需要导出的函数。对于每个导出函数,需要包含函数的返回类型、函数名和参数列表。
要使用这个DLL头文件的程序,需要按照以下步骤进行:
在程序的源文件中包含DLL的头文件:
#include "edrlib.h"
确保程序链接了DLL的导入库(.lib文件)。在Visual Studio中,可以在项目属性的链接器设置中指定导入库的名称。
使用DLL导出函数:
EdrCenterText(hdc, &rect,TEXT("This string was display by a DLL"));
需要注意的是,具体的DLL头文件的内容和结构会根据DLL中导出函数的数量和类型而有所不同。在实际使用中,应根据DLL的具体情况编写相应的头文件。
■测试主程序EDRTEST.C
使用动态链接库有两种方法:静态调用和动态调用。
●以下是使用DLL的静态调用方法:
创建一个新的C/C++项目,并将DLL的源代码或对象文件添加到项目中。
在程序中包含DLL的头文件edrlib.h,并使用DLL函数的声明。
#include " edrlib.h "
//静态调用加载导入库
//#pragma comment(lib," EDRLIB.LIB")
或者直接在VS项目属性->链接器->输入->附加依赖项中添加导入库EDRLIB.LIB。
在程序中直接调用DLL的函数,就像调用普通的静态库函数一样。
EdrCenterText(hdc, &rect,TEXT("This string was display by a DLL"));
确保在编译时将DLL的对象文件链接到程序中。这可以通过在编译命令中指定DLL的对象文件或将DLL的源代码直接添加到项目中来完成。
需要注意的是,使用DLL的静态调用方法需要确保DLL的源代码或对象文件可用,并与程序的源代码一起进行编译。这样,DLL的函数将被静态地链接到程序中,而不是在运行时动态加载。
静态调用DLL的优点是可以消除对DLL的依赖,使程序更加独立。然而,需要注意的是,静态调用会使程序的体积增大,并且不支持动态加载和替换DLL的功能。
●以下是使用DLL的动态调用方法:
1.加载DLL:
在Windows平台上,可以使用LoadLibrary函数加载DLL。该函数接受DLL文件的路径作为参数,并返回一个句柄(HMODULE)。
hDll = LoadLibrary(TEXT("EDRLIB.DLL"));//加载DLL
2.获取函数地址:
在Windows平台上,可以使用GetProcAddress函数获取DLL中导出函数的地址。该函数接受DLL句柄和函数名作为参数,并返回函数的地址。
pEdrCenterText = (PEDRCENTERTEXT)GetProcAddress(hDll,"_EdrCenterTextW@12");
3.调用DLL函数:
将获取到的函数地址转换为函数指针,并调用该函数指针即可。
/*-----------------------动态调用(显式链接)--------------------*/
#ifdef UNICODE
typedef BOOL(CALLBACK *PEDRCENTERTEXT)(HDC, PRECT, PCWSTR);//CALLBACK == __stdcall
#define EdrCenterText "EdrCenterTextW"
#else
typedef BOOL(CALLBACK *PEDRCENTERTEXT)(HDC, PRECT, PCSTR);
#define EdrCenterText "EdrCenterTextA"
#endif
/*---------------------------------------------------------*/
/*DLL动态调用*/
pEdrCenterText(hdc, &rect,TEXT("This string was display by a DLL"));