基于Visual C++ 6.0的DLL通论(翟超)

VC++ MFC DLL动态链接库编写详解 虽然能用DLL实现的功能都可以用COM来替代,但DLL的优点确实不少,它更容易创建。本文将讨论如何利用VC MFC来创建不同类型的DLL,以及如何使用他们。一、DLL的不同类型使用VC++可以生成两种类型的DLL:MFC扩展DLL和常规DLL。常规DLL有可以分为动态连接和静态连接。Visual C++还可以生成WIN32 DLL,但不是这里讨论的主要对象。1、MFC扩展DLL每个DLL都有某种类 阅读详情
 
                  基于Visual C++ 6.0的DLL通论
一、前言
  在Windows操作系统中几乎所有的内容都由DLL以一种或另外一种形式代表着,(例如显示的字体和图标存储在GDI DLL中、显示Windows桌面和处理用户的输入所需要的代码被存储在一个User DLL中、Windows编程所需要的大量的API函数也被包含在Kernel DLL中)。
  在Windows操作系统中使用DLL有很多优点,最主要的一点是多个应用程序、甚至是不同语言编写的应用程序可以共享一个DLL文件,真正实现了资源"共享",大大缩小了应用程序的执行代码,更加有效的利用了内存;使用DLL的另一个优点是DLL文件作为一个单独的程序模块,封装性、独立性好,在软件需要升级的时候,开发人员只需要修改相应的DLL文件就可以了,而且,当DLL中的函数改变后,只要不是参数的改变,程序代码并不需要重新编译。这在编程时十分有用,大大提高了软件开发和维护的效率。
 
二、DLL的概念
   DLL是建立在客户/服务器通信的概念上,包含若干函数、类或资源的库文件。DLL库不同于静态库,在静态库情况下,函数和数据被编译进一个二进制文件(通常扩展名为*.LIB),Visual C++的编译器在处理程序代码时将从静态库中恢复这些函数和数据并把他们和应用程序中的其他模块组合在一起生成可执行文件。这个过程称为"静态链接",此时因为应用程序所需的全部内容都是从库中复制了出来,所以静态库本身并不需要与可执行文件一起发行。
  在动态库的情况下,有两个文件,一个是引入库(.LIB)文件,一个是DLL文件,引入库文件包含被DLL导出的函数的名称和位置,DLL包含实际的函数和数据,应用程序使用LIB文件链接到所需要使用的DLL文件,库中的函数和数据并不复制到可执行文件中,因此在应用程序的可执行文件中,存放的不是被调用的函数代码,而是DLL中所要调用的函数的内存地址,这样当一个或多个应用程序运行是再把程序代码和被调用的函数代码链接起来,从而节省了内存资源。从上面的说明可以看出,DLL和.LIB文件必须随应用程序一起发行,否则应用程序将会产生错误。
  微软的Visual C++支持三种DLL,它们分别是Non-MFC Dll(非MFC动态库)、Regular Dll(常规DLL)、Extension Dll(扩展DLL)。Non-MFC DLL指的是不用MFC的类库结构,直接用C语言写的DLL(基于Win32的dll),其导出的函数是标准的C接口,能被非MFC或MFC编写的应用程序所调用。
Regular DLL和Extension Dlls一样,是用MFC类库编写的,它的一个明显的特点是在源文件里有一个继承CWinApp的类,被导出的函数是C函数、C++类或者C++成员函数(注意不要把术语C++类与MFC的微软基础C++类相混淆),调用常规DLL的应用程序不必是MFC应用程序,只要是能调用类C函数的应用程序就可以,它们可以是在Visual C++、Dephi、Visual Basic、Borland C等编译环境下利用DLL开发应用程序,常规DLL又可细分成静态链接到MFC和动态链接到MFC上的。
常规DLL也有缺点:它不能和客户程序发送指针或MFC派生类和对象的引用,一句话就是常规DLL和客户程序的接口不能使用MFC,但在DLL和客户程序的内部还是可以使用MFC。当在常规DLL的内部使用MFC代码库的DLL时,可以是动态连接/静态连接。如果是动态连接,也就是常规DLL需要的MFC代码没有构建到DLL中,这种情况有点和扩展DLL类似,在DLL运行的计算机上必须要MFC代码库的DLL。如果是静态连接,常规DLL里面已经包含了需要的MFC代码,这样DLL的体积将比较大,但它可以在没有MFC代码库DLL的计算机上正常运行。
与常规DLL相比,使用扩展DLL用于导出增强MFC基础类的函数或子类,用这种类型的动态链接库,可以用来输出一个从MFC所继承下来的类,扩展DLL是使用MFC的动态链接版本所创建的,使用MFC扩展DLL的一个问题就是DLL仅能和MFC客户程序一起工作。例如你已经创建了一个从MFC的CtoolBar类的派生类用于创建一个新的工具栏,为了导出这个类,你必须把它放到一个MFC扩展的DLL中。扩展DLL 和常规DLL不一样,它没有一个从CWinApp继承而来的类的对象,所以,开发人员必须在DLL中的DllMain函数添加初始化代码和结束代码。
三、动态链接库的创建
  在Visual C++6.0开发环境下,打开FileNewProject选项,可以选择Win32 Dynamic-Link Library或MFC AppWizard[dll]来以不同的方式来创建Non-MFC Dll、Regular Dll、Extension Dll等不同种类的动态链接库。
   1. Win32 Dynamic-Link Library方式创建Non-MFC DLL动态链接库
  每一个DLL必须有一个入口点,这就象我们用C编写的应用程序一样,必须有一个WINMAIN函数一样。在Non-MFC DLL中DllMain是一个缺省的入口函数,你不需要编写自己的DLL入口函数,用这个缺省的入口函数就能使动态链接库被调用时得到正确的初始化。如果应用程序的DLL需要分配额外的内存或资源时,或者说需要对每个进程或线程初始化和清除操作时,需要在相应的DLL工程的.CPP文件中对DllMain()函数按照下面的格式书写。
BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
switch( ul_reason_for_call )
{
case DLL_PROCESS_ATTACH:
.......
case DLL_THREAD_ATTACH:
.......
case DLL_THREAD_DETACH:
.......
case DLL_PROCESS_DETACH:
.......
}
return TRUE;
}
 参数中,hMoudle是动态库被调用时所传递来的一个指向自己的句柄(实际上,它是指向_DGROUP段的一个选择符);ul_reason_for_call是一个说明动态库被调原因的标志,当进程或线程装入或卸载动态链接库的时候,操作系统调用入口函数,并说明动态链接库被调用的原因,它所有的可能值为:DLL_PROCESS_ATTACH: 进程被调用、DLL_THREAD_ATTACH: 线程被调用、DLL_PROCESS_DETACH: 进程被停止、DLL_THREAD_DETACH: 线程被停止;lpReserved为保留参数。到此为止,DLL的入口函数已经写了,剩下部分的实现也不难,你可以在DLL工程中加入你所想要输出的函数或变量了。
 我们已经知道DLL是包含若干个函数的库文件,应用程序使用DLL中的函数之前,应该先导出这些函数,以便供给应用程序使用。要导出这些函数有两种方法,一是在定义函数时使用导出关键字_declspec(dllexport),另外一种方法是在创建DLL文件时使用模块定义文件.Def。需要读者注意的是在使用第一种方法的时候,不能使用DEF文件。下面通过两个例子来说明如何使用这两种方法创建DLL文件。
  1)使用导出函数关键字_declspec(dllexport)创建MyDll.dll,该动态链接库中有两个函数,分别用来实现得到两个数的最大和最小数。在MyDll.h和MyDLL.cpp文件中分别输入如下原代码:
//MyDLL.h
extern "C" _declspec(dllexport) int Max(int a, int b);
extern "C" _declspec(dllexport) int Min(int a, int b);
//MyDll.cpp
#include
#include"MyDll.h"
int Max(int a, int b)
{
if(a>=b)return a;
else
return b;
}
int Min(int a, int b)
{
if(a>=b)return b;
else
return a;
}
 该动态链接库编译成功后,打开MyDll工程中的debug目录,可以看到MyDll.dll、MyDll.lib两个文件。LIB文件中包含DLL文件名和DLL文件中的函数名等,该LIB文件只是对应该DLL文件的"映像文件",与DLL文件比LIB文件的长度要小的多,在进行隐式链接DLL时要用到它。读者可能已经注意到在MyDll.h中有关键字"extern C",它可以使其他编程语言访问你编写的DLL中的函数。
  2)用.def文件创建工程MyDll
  为了用.def文件创建DLL,请先删除上个例子创建的工程中的MyDll.h文件,保留MyDll.cpp并在该文件头删除#include MyDll.h语句,同时往该工程中加入一个文本文件,命名为MyDll.def,再在该文件中加入如下代码:
LIBRARY MyDll
EXPORTS
Max
Min
 其中LIBRARY语句说明该def文件是属于相应DLL的,EXPORTS语句下列出要导出的函数名称。我们可以在.def文件中的导出函数后加@n,如Max@1,Min@2,表示要导出的函数顺序号,在进行显式连时可以用到它。该DLL编译成功后,打开工程中的Debug目录,同样也会看到MyDll.dll和MyDll.lib文件。
2
.MFC AppWizard[dll]方式生成常规/扩展DLL
  在MFC AppWizard[dll]下生成DLL文件又有三种方式,在创建DLL是,要根据实际情况选择创建DLL的方式。一种是常规DLL静态链接到MFC,另一种是常规DLL动态链接到MFC。两者的区别是:前者使用的是MFC的静态链接库,生成的DLL文件长度大,一般不使用这种方式,后者使用MFC的动态链接库,生成的DLL文件长度小;动态链接到MFC的规则DLL所有输出的函数应该以如下语句开始:
    
利用Visual C++提供的向导功能可以很容易建立一个不完成任何实质任务的DLL,这里就不多讲了,主要的任务是如何给DLL添加功能,以及在客户程序中利用这个DLL
1
)、导出类
  
用向导建立好框架后,就可以添加需要导出类的.cpp .h文件到DLL中来,或者用向导创建C++ Herder File/C++ Source File。为了能导出这个类,在类声明的时候要加“_declspec(dllexport)”,如:
class _declspec(dllexport) CMyClass
{
     ...//
声明
}
如果创建的MFC扩展DLL,可以使用宏:AFX_EXT_CLASS:
class AFX_EXT_CLASS CMyClass
{
     ...//
声明
}
这样导出类的方法是最简单的,也可以采用.def文件导出,这里暂不详谈。
2
)、导出变量、常量、对象
  
很多时候不需要导出一个类,可以让DLL导出一个变量、常量、对象,导出它们只需要进行简单的声明:                  _declspec(dllexport) int MyInt;
  _declspec(dllexport) extern const COLORREF MyColor=RGB(0,0,0);
  _declspec(dllexport) CRect rect(10,10,20,20);
要导出一个常量时必须使用关键字extern,否则会发生连接错误。
注意:如果客户程序识别这个类而且有自己的头文件,则只能导出一个类对象。如果在DLL中创建一个类,客户程序不使用头文件就无法识别这个类。
 
当导出一个对象或者变量时,载入DLL的每个客户程序都有一个自己的拷贝。也就是如果两个程序使用的是同一个DLL,一个应用程序所做的修改不会影响另一个应用程序。
 
我们在导出的时候只能导出DLL中的全局变量或对象,而不能导出局部的变量和对象,因为它们过了作用域也就不存在了,那样DLL就不能正常工作。如:
MyFunction()
{
      _declspec(dllexport) int MyInt;
      _declspec(dllexport) CMyClass object;
}
3
)、导出函数
 
导出函数和导出变量/对象类似,只要把_declspec(dllexport)加到函数原型开始的位置:
_declspec(dllexport) int MyFunction(int);
如果是常规DLL,它将和C写的程序使用,声明方式如下:
extern "c" _declspec(dllexport) int MyFunction(int);
实现:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
     ...//
操作
}
如果创建的是动态连接到MFC代码库DLL的常规DLL,则必须插入AFX_MANAGE_STATE作为导出函数的首行,因此定义如下:
extern "c" _declspec(dllexport) int MyFunction(int x)
{
     AFX_MANAGE_STATE(AfxGetStaticModuleState());
     ...//
操作
}
有时候为了安全起见,在每个常规DLL里都加上,也不会有任何问题,只是在静态连接的时候这个宏无效而已。这是导出函数的方法,记住只有MFC扩展DLL才能让参数和返回值使用MFC的数据类型(指导出函数,内部函数可以)。
4
)、导出指针
导出指针的方式如下:
_declspec(dllexport) int *pint;
_declspec(dllexport) CMyClass *pobject = new CMyClass;
如果声明的时候同时初始化了指针,就需要找到合适的地方类释放指针。在扩展DLL中有个函数DllMain(),可以在这个函数中处理指针:
# include "MyClass.h"
_declspec(dllexport) CMyClass *pobject = new CMyClass;
DllMain(HINSTANCE hInstance,DWORD dwReason,LPVOID lpReserved)
{
    if(dwReason == DLL_PROCESS_ATTACH)
    {
        .....//
    }
    else if(dwReason == DLL_PROCESS_DETACH)
    {
        delete pobject;
    }
}
常规DLL有一个从CWinApp派生的类对象处理DLL的开和关,可以使用类向导添加InitInstance/ExitInstance函数。
int CMyDllApp::ExitInstance()
{
    delete pobject;
    return CWinApp::ExitInstance();
}

四、在客户程序中使用DLL
编译一个DLL时将创建两个文件.dll文件和.lib文件。首先将这两个文件复制到客户程序项目的文件夹里,这里需要注意DLL和客户程序的版本问题,尽量使用相同的版本,都使用RELEASE或者都是DEBUG版本
接着就需要在客户程序中设置LIB文件,打开Project Settings--->Link--->Object/library Modules中输入LIB的文件名和路径。如:Debug/SampleDll.lib。除了DLL和LIB文件外,客户程序需要针对导出类、函数、对象和变量的头文件,现在进行导入添加的关键字就是:_declspec(dllimport),如:
_declspec(dllimport) int MyFunction(int);
_declspec(dllimport) int MyInt;
_declspec(dllimport) CMyClass object;
extern "C" _declspec(dllimport) int MyFunction(int);
在有的时候为了导入类,要把相应类的头文件添加到客户程序中,不同的是要修改类声明的标志:
class _declspec(dllimport) CMyClass
,如果创建的是扩展DLL,两个位置都是:
class AFX_EXT_CLASS CMyClass
。
  
1.隐式链接
 隐式链接就是在程序开始执行时就将DLL文件加载到应用程序当中。实现隐式链接很容易,只要将导入函数关键字_declspec(dllimport)函数名等写到应用程序相应的头文件中就可以了。下面的例子通过隐式链接调用MyDll.dll库中的Min函数。首先生成一个项目为TestDll,在DllTest.h、DllTest.cpp文件中分别输入如下代码:
//Dlltest.h
#pragma comment(lib
,"MyDll.lib")
extern "C"_declspec(dllimport) int Max(int a,int b);
extern "C"_declspec(dllimport) int Min(int a,int b);
//TestDll.cpp
#include
#include"Dlltest.h"
void main()
{int a;
a=min(8,10)
printf("
比较的结果为%dn",a);
}
 在创建DllTest.exe文件之前,要先将MyDll.dll和MyDll.lib拷贝到当前工程所在的目录下面,也可以拷贝到windows的System目录下。如果DLL使用的是def文件,要删除TestDll.h文件中关键字extern "C"。TestDll.h文件中的关键字Progam commit是要Visual C+的编译器在link时,链接到MyDll.lib文件,当然,开发人员也可以不使用#pragma comment(lib,"MyDll.lib")语句,而直接在工程的Setting->Link页的Object/Moduls栏填入MyDll.lib既可。
2
.显式链接
 显式链接是应用程序在执行过程中随时可以加载DLL文件,也可以随时卸载DLL文件,这是隐式链接所无法作到的,所以显式链接具有更好的灵活性,对于解释性语言更为合适。不过实现显式链接要麻烦一些。在应用程序中用LoadLibrary或MFC提供的AfxLoadLibrary显式的将自己所做的动态链接库调进来,动态链接库的文件名即是上述两个函数的参数,此后再用GetProcAddress()获取想要引入的函数。自此,你就可以象使用如同在应用程序自定义的函数一样来调用此引入函数了。在应用程序退出之前,应该用FreeLibrary或MFC提供的AfxFreeLibrary释放动态链接库。下面是通过显式链接调用DLL中的Max函数的例子。
#include
#include
void main(void)
{
typedef int(*pMax)(int a,int b);
typedef int(*pMin)(int a,int b);
HINSTANCE hDLL;
PMax Max
HDLL=LoadLibrary("MyDll.dll");//
加载动态链接库MyDll.dll文件;
Max=(pMax)GetProcAddress(HDLL,"Max");
A=Max(5,8);
Printf("
比较的结果为%dn",a);
FreeLibrary(hDLL);//
卸载MyDll.dll文件;
}
 在上例中使用类型定义关键字typedef,定义指向和DLL中相同的函数原型指针,然后通过LoadLibray()将DLL加载到当前的应用程序中并返回当前DLL文件的句柄,然后通过GetProcAddress()函数获取导入到应用程序中的函数指针,函数调用完毕后,使用FreeLibrary()卸载DLL文件。在编译程序之前,首先要将DLL文件拷贝到工程所在的目录或Windows系统目录下。
    
使用显式链接应用程序编译时不需要使用相应的Lib文件。另外,使用GetProcAddress()函数时,可以利用MAKEINTRESOURCE()函数直接使用DLL中函数出现的顺序号,如将GetProcAddress(hDLL,"Min")改为GetProcAddress(hDLL, MAKEINTRESOURCE(2))(函数Min()在DLL中的顺序号是2),这样调用DLL中的函数速度很快,但是要记住函数的使用序号,否则会发生错误。
五、在dll中输出对话框
现在最常看见的关于DLL的问题就是如何在DLL中使用对话框,这是一个很普遍的关于如何在DLL中使用资源的问题。这里我们从Win32 DLL和MFC DLL两个方面来分析并解决这个问题。
1
.Win32 DLL
   
在Win32 DLL中使用对话框很简单,你只需要在你的DLL中添加对话框资源,而且可以在对话框上面设置你所需要的控件。然后使用DialogBox或者CreateDialog这两个函数(或相同作用的其它函数)来创建对话框,并定义你自己的对话框回调函数处理对话框收到的消息。下面通过一个具体实例来学习如何在Win32 DLL中使用对话框,可以按照以下步骤来完成这个例子:
1
)在VC菜单中File->New新建一个命名为UseDlg的Win32 Dynamic-Link Library工程,下一步选择A simple DLL project。
2
)在VC菜单中Insert->Resource添加一个ID为IDD_DLG_SHOW的Dialog资源,将此Dialog上的Cancel按钮去掉,仅保留OK按钮。再添加一个ID为IDD_ABOUTBOX的对话框,其Caption为About。保存此资源,将资源文件命名为UseDlg.rc。并将resource.h和UseDlg.rc加入到工程里面。
3
)在UseDlg.app中包含resource.h,并添加如下代码:
HINSTANCE hinst = NULL;
HWND hwndDLG = NULL;
BOOL CALLBACK DlgProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam);
extern "C" __declspec(dllexport) void ShowDlg();
BOOL APIENTRY DllMain( HANDLE hModule,
            DWORD ul_reason_for_call,
            LPVOID lpReserved
                  )
{
    switch(ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
       hinst = (HINSTANCE)hModule;
    case DLL_PROCESS_DETACH:
       break;
    }
  return TRUE;
}
extern "C" __declspec(dllexport) void ShowDlg()
{
    hwndDLG = CreateDialog(hinst,MAKEINTRESOURCE(IDD_DLG_SHOW),
NULL,(DLGPROC)DlgProc);
    ShowWindow(hwndDLG, SW_SHOW);
}
BOOL CALLBACK DlgProc(HWND hDlg, UINT message,
WPARAM wParam, LPARAM lParam)
{
  switch(message)
  {
    case WM_INITDIALOG:
       return TRUE;
     case WM_COMMAND:
       if(LOWORD(wParam)==IDOK)
DialogBox(hinst,MAKEINTRESOURCE(IDD_ABOUTBOX),
hDlg,(DLGPROC)AboutProc);
       return TRUE;    
   case WM_CLOSE:
       DestroyWindow(hDlg);
       hwndDLG = NULL;
       return TRUE;
  }
  return FALSE;
}
BOOL CALLBACK AboutProc(HWND hDlg, UINT message,
                            WPARAM wParam, LPARAM lParam)
{
    switch(message)
  {
       case WM_CLOSE:
           EndDialog(hDlg,NULL);
           hwndDLG = NULL;
           return TRUE;
  }
  return FALSE;
}
4
)编译生成UseDlg.dll和UseDlg.lib。
接下来我们建立调用此DLL的应用程序,其步骤如下:
1
)在VC菜单中File->New新建一个命名为Use的MFC AppWizard(exe)工程,下一步选择Dialog Based之后点击Finish按钮。
2)在主对话框上面添加一个按钮,之后双击此按钮,会弹出Add Member Function的对话框,直接点击OK进入void CUseDlg::OnButton1()函数。并在此函数内添加一个函数调用:ShowDlg();。
3
)紧跟在#include语句后面加上如下代码:
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/UseDlg")
4
)将上面UseDlg工程中生成的UseDlg.dll和UseDlg.lib两个文件复制到Use工程的Debug目录内。
5
)编译生成Use.exe。
   
运行Use.exe,点击Button1按钮,可以看到一个名称为Dialog的非模态对话框弹出。点击上面的按钮,可以弹出模态对话框About。运行成功。
   
让我们来回顾一下在Win32 DLL中使用对话框的过程。
在DLL中,我们定义了两个对话框资源:IDD_DLG_SHOW和IDD_ABOUTBOX,并且导出了函数ShowDlg。在函数ShowDlg之中使用CreateDialog函数创建了非模态对话框IDD_DLG_SHOW,并指定了该对话框的回调函数DlgProc。在DlgProc之中处理了WM_INITDIALOG、WM_COMMAND和WM_CLOSE消息,以响应用户对对话框所做的动作。在处理按钮动作的时候,使用DialogBox函数创建IDD_ABOUTBOX这个模态对话框,指定其回调函数为AboutProc,并且在AboutProc中处理其相应消息。
在EXE中,我们使用隐式链接的方法来调用DLL,并使用DLL中导出的ShowDlg函数来调用DLL中的对话框。
   
在Win32 DLL中使用对话框就是这么简单,下面让我们来看一下在MFC DLL中如何使用对话框。
2
.MFC DLL
   
在MFC DLL中使用对话框不像Win32 DLL中那么简单,主要是因为MFC程序中存在一个模块状态(Module State)的问题,也就是资源重复的问题。(此处的术语模块是指一个可执行程序,或指其操作不依赖于应用程序的其余部分但使用MFC运行库的共享副本的一个DLL(或一组DLL)。我们所创建的MFC DLL就是这种模块的一个典型实例。)
   
在每个模块(EXE或DLL)中,都存在一种全局的状态数据,MFC依靠这种全局的状态数据来区分不同的模块,以执行正确的操作。这种数据包括:Windows实例句柄(用于加载资源),指向应用程序当前的CWinApp和CWinThread对象的指针,OLE模块引用计数,以及维护Windows对象句柄与相应的MFC对象实例之间连接的各种映射等。但当应用程序使用多个模块时,每个模块的状态数据不是应用程序范围的。相反,每个模块具有自已的MFC状态数据的私有副本。这种全局的状态数据就叫做MFC模块状态。
   
模块的状态数据包含在结构中,并且总是可以通过指向该结构的指针使用。当代码在执行时进入了某一个模块时,只有此模块的状态为“当前”或“有效”状态时,MFC才能正确的区分此模块并执行正确的操作。
例如,MFC应用程序可以使用下面代码从资源文件中加载字符串:
CString str;
str.LoadString(IDS_MYSTRING);
使用这种代码非常方便,但它掩盖了这样一个事实:即此程序中IDS_MYSTRING可能不是唯一的标识符。一个程序可以加载多个DLL,某些DLL可能也用IDS_MYSTRING标识符定义了一个资源。MFC怎样知道应该加载哪个资源呢?MFC使用当前模块状态查找资源句柄。如果当前模块不是我们要使用的正确模块,那么就会产生不正确的调用或者错误。
   
按照MFC库的链接方法,一个MFC DLL有两种使用MFC库的方法:静态链接到MFC的DLL和动态链接到MFC的DLL。下面我们就按照这两种类型的MFC DLL来介绍如何切换当前模块状态以正确的在MFC DLL中使用资源。
1
、静态链接到MFC的DLL
   
静态链接到MFC的规则DLL与MFC库静态链接,则此时MFC库不能共享,所以MFC总是使用它所链接的DLL的模块状态。这样也就不存在管理模块状态的问题。但使用这种方法的缺点是DLL程序将会变大,而且会在程序中留下重复代码。下面给出的例子验证了这一点。本例可以按照以下步骤来完成:
1
)在VC菜单中File->New新建一个命名为DLLStatic的MFC AppWizard的工程,下一步选择Regular DLL with MFC statically linked。
2
)在工程中添加一个对话框资源,其ID为:IDD_ABOUTBOX。并在resource.h之中将IDD_ABOUTBOX 的数值改为100。
3
)在DLLStatic.cpp中定义如下函数:
void ShowDlg()
{
    CDialog dlg(IDD_ABOUTBOX);
    dlg.DoModal();
}
4
)在DLLStatic.def文件中的EXPORTS语句中添加一行:ShowDlg,以导出ShowDlg函数。



5
)编译生成DLLStatic.dll和DLLStatic.lib。
继续使用上一节中的Use工程,将前面生成的DLLStatic.dll和DLLStatic.lib两个文件复制到工程的Debug目录内,并将
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/UseDlg")
这两行改为:
void ShowDlg();
#pragma comment(lib,"debug/DLLStatic")
编译并运行Use.exe。点击按钮,可以看到DLLStatic中的模态对话框弹出。
  
本例中,可以注意到DLL中所定义的About对话框资源与EXE中所定义的About对话框资源ID完全相同,但是当我们点击Use.exe上面的按钮时,弹出的是DLL中的模态对话框。说明,当使用静态链接到MFC的规则DLL时,不存在管理模块状态的问题
2、动态链接到MFC的DLL
   
在讨论关于动态链接到MFC的DLL的模块状态问题之前,先来看一个例子。本例可以通过如下步骤来完成:
1
)在VC菜单中File->New新建一个命名为DLLShared的MFC AppWizard的工程,下一步选择Regular DLL using shared MFC DLL。
2
)在工程中添加一个对话框资源,其ID为:IDD_ABOUTBOX。并在resource.h之中将IDD_ABOUTBOX 的数值改为100。
3
)在DLLShared.cpp中定义如下函数:
void ShowDlg()
{
    CDialog dlg(IDD_ABOUTBOX);
    dlg.DoModal();
}
4
)在DLLShared.def文件中的EXPORTS语句中添加一行:ShowDlg,以导出ShowDlg函数。
5
)编译生成DLLShared.dll和DLLShared.lib。
继续使用上面的Use工程,将前面生成的DLLShared.dll和DLLShared.lib两个文件复制到工程的Debug目录内,并将
extern "C" __declspec(dllexport) void ShowDlg();
#pragma comment(lib,"debug/DLLStatic")
这两行改为:
void ShowDlg();
#pragma comment(lib,"debug/DLLShared")
编译并运行Use.exe。点击按钮,这次你看到了什么?对,没错,这次弹出的是Use.exe的关于对话框。将上述例子的DLL类型换成MFC Extension DLL(using shared MFC DLL)也会出现相同的问题。
   
为什么会出现上面的问题?这是因为在使用了MFC共享库的时候,默认情况下,MFC使用主应用程序的资源句柄来加载资源模板。虽然我们调用的是DLL中的函数来显示DLL中的对话框,并且对应的对话框模板是存储在DLL中的,但MFC仍旧在主应用程序也就是Use.exe中寻找相应的对话框模板。由于在DLL中所定义的对话框资源ID与主应用程序中所定义的关于对话框的资源ID相同,所以MFC就把主应用程序中的关于对话框显示了出来。如果二者不同,则MFC就认为DLL中所定义的对话框资源不存在,dlg.DoModal会返回0,也就是什么都不会显示。

  
那么如何解决上述问题呢?解决办法就是在适当的时候进行模块状态切换,以保证具有当前状态的模块是我们所需要的模块从而使用正确的资源。MFC提供了下列函数和宏来完成这些工作:
AfxGetStaticModuleState
:这是一个函数,其函数原型为:
AFX_MODULE_STATE* AFXAPI AfxGetStaticModuleState( );
   
此函数在堆栈上构造AFX_MODULE_STATE类的实例pModuleState并对其赋值后将其返回。在AFX_MODULE_STATE类的构造函数中,该类获取指向当前模块状态的指针并将其存储在成员变量中,然后将pModuleState设置为新的有效模块状态。在它的析构函数中,该类将存储在其成员变量中的指针还原为存贮的前一个模块状态。
AFX_MANAGE_STATE
:这是一个宏,其原型为:
AFX_MANAGE_STATE( AFX_MODULE_STATE* pModuleState )
  
该宏用于将pModuleState(指向包含模块全局数据也就是模块状态的AFX_MODULE_STATE结构的指针)设置为当前的即时作用空间中(the remainder of the immediate containing scope)的有效模块状态。在离开包含该宏的作用空间时,前一个有效的模块状态自动还原。
AfxGetResourceHandle
:这个函数的原型为:
   HINSTANCE AfxGetResourceHandle( );
   
该函数返回了一个保存了HINSTANCE类型的、应用程序默认所加载资源的模块的句柄。
AfxSetResourceHandle
:这个函数的原型为:
void AfxSetResourceHandle( HINSTANCE hInstResource );
该函数将hInstResource所代表的模块设置为具有当前状态的模块。
   
通过使用上述四个函数或宏就可以正确的在动态链接到MFC的DLL中切换模块状态。接下来我们将通过修改上面出现问题的那个例子来介绍如何使用上述四个函数或宏。先来看看Regular DLL using shared MFC DLL类型:
在上述例子的第三步的ShowDlg函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
AFX_MANAGE_STATE(AfxGetStaticModuleState());
之后重新编译生成DLLShared.dll和DLLShared.lib,并将这两个文件重新拷贝到Use工程的Debug目录内。这次编译生成Use.exe并运行,点击按钮,可以看到弹出的时我们在DLL中所加入的那个对话框,而不再是Use.exe的关于对话框了。
  
通过上面的讲解,相信你已经知道该语句的作用了。在函数ShowDlg的第一行加上这么一句后,每次调用DLL的应用程序使用该函数的时候,MFC库都会自动切换当前模块状态,这样就保证了资源读取的正确性。
AFX_MANAGE_STATE(AfxGetStaticModuleState());
是自动切换当前模块状态,也可以通过使用AfxGetResourceHandle和AfxSetResourceHandle来手动切换当前模块状态。具体使用方法如下:
在上述例子的第三步的ShowDlg函数的第一条语句前加上如下语句(要确保该语句在函数实现的第一行):
HINSTANCE save_hInstance = AfxGetResourceHandle();
    AfxSetResourceHandle(theApp.m_hInstance);
在调用对话框成功之后,在析构函数中添加:
AfxSetResourceHandle(save_hInstance);
   
这种方法在进入ShowDlg函数之后,通过AfxGetResourceHandle来获得并保存当前状态模块的句柄。然后获得DLL模块的句柄theApp.m_hInstance(当然,也可以使用GetModuleHandle函数来获得DLL模块的句柄),并使用AfxSetResourceHandle函数来将其设置为当前状态状态。最后在调用对话框成功之后再用恢复AfxSetResourceHandle资源句柄,将当前模块状态恢复。
   
这样做有些麻烦,但是有一点好处是可以在完成使用资源的任务之后就可以立即恢复资源句柄。而AFX_MANAGE_STATE(AfxGetStaticModuleState());的方法只能等函数的作用空间结束之后才恢复资源句柄。由于可执行文件必须重画工具条等原因,因此建议只要有可能就必须恢复资源句柄,否则可能会遇到许多问题。比如说,如果用户移动DLL的对话框,而此时资源句柄仍然为DLL的资源,那么程序就会崩溃。最好的恢复句柄的时机在对话框响应WM_INITDIALOG消息的时候,因为这时对话框的模板等已经读出了。
对于MFC Extension DLL(using shared MFC DLL)类型的MFC DLL,切换当前模块状态的方法与Regular DLL using shared MFC DLL类型的MFC DLL所使用的方法很相似,这里不再举例实现。二者不同的地方如下:
   
在MFC扩展DLL中使用AFX_MANAGE_STATE(AfxGetStaticModuleState());时,会产生如下错误:
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in dllextend.obj
mfcs42d.lib(dllmodul.obj) : error LNK2005: _DllMain@12 already defined in dllextend.obj
mfcs42d.lib(dllmodul.obj) : error LNK2005: __pRawDllMain already defined in dllextend.obj
因此在MFC扩展DLL中需要将AFX_MANAGE_STATE(AfxGetStaticModuleState());换成AFX_MANAGE_STATE(AfxGetAppModuleState());才能正确切换当前模块状态。
   
在MFC扩展DLL中使用AfxGetResourceHandle和AfxSetResourceHandle的方法与在Regular DLL using shared MFC DLL类型的MFC DLL中所使用的方法相同。并且,DLL模块的句柄可以通过MFC提供的DlgextentDLL这个结构的hModule成员来获得。即使用AfxSetResourceHandle(DlgextentDLL.hModule);语句。
  
当然,对于动态链接到MFC的DLL,也可以在调用该DLL的MFC应用程序中使用AfxGetResourceHandle和AfxSetResourceHandle两个函数来切换当前状态模块。该DLL模块的句柄可以用GetModuleHandle函数来获得。
六、重新编译问题DLL后的问题及解决方案
1、重新编译问题
我们先来看一个在实际中可能遇到的问题:
   
比如现在建立好了一个DLL导出了CMyClass类,客户也能正常使用这个DLL,假设CMyClass对象的大小为30字节。如果我们需要修改DLL中的CMyClass类,让它有相同的函数和成员变量,但是给增加了一个私有的成员变量int类型,现在CMyClass对象的大小就是34字节了。当直接把这个新的DLL给客户使用替换掉原来30字节大小的DLL,客户应用程序期望的是30字节大小的对象,而现在却变成了一个34字节大小的对象,糟糕,客户程序出错了。
   
类似的问题,如果不是导出CMyClass类,而在导出的函数中使用了CMyClass,改变对象的大小仍然会有问题的。这个时候修改这个问题的唯一办法就是替换客户程序中的CMyClass的头文件,全部重新编译整个应用程序,让客户程序使用大小为34字节的对象。
   
这就是一个严重的问题,有的时候如果没有客户程序的源代码,那么我们就不能使用这个新的DLL了。
2
、解决方法  
 
为了能避免重新编译客户程序,这里介绍两个方法:(1)使用接口类。(2)使用创建和销毁类的静态函数。
1
)、使用接口类
  
接口类的也就是创建第二个类,它作为要导出类的接口,所以在导出类改变时,也不需要重新编译客户程序,因为接口类没有发生变化。
  
假设导出的CMyClass类有两个函数FunctionA FunctionB。现在创建一个接口类CMyInterface,下面就是在DLL中的CMyInterface类的头文件的代码:
# include "MyClass.h"
class _declspec(dllexport) CMyInterface
{
     CMyClass *pmyclass;
     CMyInterface();
     ~CMyInterface();
  public:
     int FunctionA(int);
     int FunctionB(int);
};
而在客户程序中的头文件稍不同,不需要INCLUDE语句,因为客户程序没有它的拷贝。相反,使用一个CMyClass的向前声明,即使没有头文件也能编译:
class _declspec(dllimport) CMyInterface
{
     class CMyClass;//
向前声明
     CMyClass *pmyclass;
     CMyInterface();
     ~CMyInterface();
  public:
     int FunctionA(int);
     int FunctionB(int);
};
在DLL中的CMyInterface的实现如下:
CMyInterface::CMyInterface()
{
      pmyclass = new CMyClass();
}
CMyInterface::~CMyInterface()
{
     delete pmyclass;
}
int CMyInterface::FunctionA()
{
     return pmyclass->FunctionA();
}
int CMyInterface::FunctionB()
{
     return pmyclass->FunctionB();   
}
.....
对导出类CMyClass的每个成员函数,CMyInterface类都提供自己的对应的函数。客户程序与CMyClass没有联系,这样任意改CMyClass也不会有问题,因为CMyInterface类的大小没有发生变化。即使为了能访问CMyClass中的新增变量而给CMyInterface类加了函数也不会有问题的。
  
但是这种方法也存在明显的问题,对导出类的每个函数和成员变量都要对应实现,有的时候这个接口类会很庞大。同时增加了客户程序调用所需要的时间。增加了程序的开销。
2)、使用静态函数
  
还可以使用静态函数来创建和销毁类对象。创建一个导出类的时候,增加两个静态的公有函数CreateMe()/DestroyMe(),头文件如下:
class _declspec(dllexport) CMyClass
{
     CMyClass();
     ~CMyClass();
  public:
     static CMyClass *CreateMe();
     static void DestroyMe(CMyClass *ptr);
};
实现函数就是:
CMyClass * CMyClass:: CreateMe ()
{
      return new CMyClass;
}
void CMyClass::DestroyMe(CMyClass *ptr)
{
      delete ptr;
}
然后象其他类一样导出CMyClass类,这个时候在客户程序中使用这个类的方法稍有不同了。如若想创建一个CMyClass对象,就应该是:
CMyClass x;
CMyClass *ptr = CMyClass::CreateMe();
在使用完后删除:
CMyClass::DestroyMe(ptr);
 本文通过通俗易懂的方式,全面介绍了动态链接库的概念、动态链接库的创建和动态链接库的链接以及一些扩展应用,并给出简单明了的例子,相信读者看了本文后,能够创建自己的动态链接库并应用到后续的软件开发当中去了,当然,读者要熟练操作DLL,还需要在大量的实践中不断摸索,希望本文能起到抛砖引玉的作用。
 
3dMax脚本类插件:不写 C++也能造“原生”工具 从plugin关键字到场景持久化,拆解 MAXScript 插件开发全流程! 阅读详情

相关推荐

西电2023秋季机器学习复习提纲

西电2023秋季机器学习复习提纲,希望对学弟学妹有帮助

weixin_62083045的博客 5633

收集的关于DLL较全的介绍

天极网 深入浅出Visual C++动态链接库(Dll)编程 VC++动态链接库编程之读者反馈与答复 本文出自:http://blog.sina.com.cn/u/56cacf83010005an 比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,...

weixin_34319111的博客 522

docker拉取镜像报错:Error response from daemon: Get “https://registry-1.docker.io/v2/“: dial tcp

docker拉取镜像报错:Error response from daemon: Get “https://registry-1.docker.io/v2/“: dial tcp

m0_55333789的博客 1556

C++运行时内存的分配(堆、栈等)、DLL有关的CRT堆

程序运行时分配的内存位置 栈区(stack):由编译器自动分配释放,存放函数的参数值,局部变量值等,其操作方法类似数据结构中的栈。存放的是局部变量(在方法和语句块内部定义的变量),当方法和语句块一结束,空间马上释放。 堆区(heap):一般由程序员分配释放,与数据结构中的堆毫无关系,分配方式类似于链表。存放的是成员变量(也叫属性,在方法外,在类里,定义的变量),随着对象而产生,随对象销毁而销毁...

chen_i 1252

iTunes登录协议逆向与自动化实现

本文详细解析了iTunes登录协议的逆向工程与自动化实现方法,涵盖请求体构造、签名生成和会话管理等关键技术。通过XML结构解析、X-Apple-ActionSignature生成原理及完整登录流程实现,帮助开发者高效完成iTunes自动化登录,提升工作效率。

weixin_26938645的博客 938

零基础使用Manim画傅里叶变换

0x01 准备工作 确保你的电脑中安装的有:Python(3.6以上版本,推荐3.8)、FFmpeg(在PATH环境变量中)、LaTex发行版,如果这些都有了,就可以跳过这步。 下面是安装指南: Python:Win10用户直接在微软商店搜索Python3.8安装,也可以自行百度安装方法,在开始菜单右键打开PowerShell,或者Win+R输入PowerShell打开,输入Pyhton --version,显示3.8以上。 FFmpeg:可以直接百度下载,或者用我打包好的(下载链接)。下载好后,将ff

TagBug的博客 3191

MFC DLL向导

虽然能用DLL实现的东西都可以用COM来实现,但DLL的优点确实不少,它更容易创建。本文将讨论如何利用MFC来创建不同类型的DLL,以及如何使用他们。一、DLL的不同类型    使用MFC可以生成两种类型的DLL:MFC扩展DLL和常规DLL。常规DLL有可以分为动态连接和静态连接。Visual C++还可以生成WIN32 DLL,但不是这里讨论的主要对象。1、MFC扩展DLL   每个DLL都有

867

48、住宅抵押贷款支持证券的定价、风险评估与结构化设计

本文深入探讨了住宅抵押贷款支持证券(RMBS)的定价、风险评估与结构化设计。内容涵盖提高定价准确性的方差缩减方法,如准蒙特卡罗序列的应用;分析RMBS面临的利率风险、信用风险和模型风险,并提出相应的度量与应对策略;介绍利用动态规划优化CMO分层结构以最大化发行利润的方法。同时总结了RMBS的基本类型、定价流程及市场发展趋势,为投资者和发行人提供全面的决策支持。

quiet的专栏 154

利用C++制作dll并调用dll

Abstract:本文讲解如何利用c++制作dll并调用dll,用一个简单的加法函数来作为演示,并给出图形界面。1. 打开vs(我用的是vs2010),然后点击文件—>文件—>新建—>项目,选择Win32控制台应用程序。并输入你想建立的vs工程名字和路径(就命名为addfun)。然后点击下一步,应用程序类型选择DLL(D)这一项。然后点击完成。就会出现下图。 2.里面有一个源文件addfun.

小明知道的专栏 3万+

学Simulink——基于故障诊断与容错控制的场景实例:并网逆变器在短路故障下的动态响应及恢复策略

本文详细介绍了使用Simulink进行并网逆变器短路故障模拟与保护策略设计的方法。通过构建高精度逆变器模型,模拟IGBT击穿和桥臂直通故障,分析动态响应特性。重点设计了基于Stateflow的微秒级快速保护逻辑,包括过流检测、PWM闭锁和故障报警。同时提出完整的恢复策略流程,涵盖放电、去磁、自检和软启动等关键步骤。仿真结果表明,该方法能在200μs内有效限制短路电流,保护器件安全,并实现系统可靠恢复。文章还探讨了硬件保护协同、多级保护和预测保护等高级策略,为新能源系统的安全运行提供了重要参考。

amy_mhd的博客 143

Linux内存管理(17):percpu 分配器——基本原理

随着 SMP(对称多处理器架构)的发展,在享受处理器更快的执行效率的同时,也为数据同步带来了更大的挑战,因为在 SMP 架构中,程序确实是在并发执行,从单 CPU 只需要考虑调度风险,到SMP 中还需要考虑多 CPU 中同时执行的风险,而且在单 CPU 下乱序执行也会在 SMP 架构中带来,从而也衍生了一些新的同步机制,例如:内存屏障、percpu 机制等等。

私房菜 1249

深挖C++:类和对象(中)

(先进后出)

xys_678的博客 201

【推荐100个unity插件】Animator 的替代品?—— Animancer Pro插件的使用介绍

Animancer与Unity Animator对比及使用指南 摘要: 本文对比了Animancer与Unity官方Animator的核心差异,分析了Animancer的优势特点。Animancer采用代码驱动模式,相比Animator的状态机系统更灵活高效,支持实时调试、动态混合和深度逻辑集成。文章详细介绍了Animancer的快速使用方法,包括动画播放、切换和触发机制,并区分了免费版与专业版的功能差异。适合需要高效动画开发、复杂交互逻辑的中小项目,为开发者提供了一种现代化的动画解决方案。

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 2434

选型笔记:Type‑C 扩展坞方案评估|DP1.4 转 HDMI2.0 桥接芯片 GSV2201S@ACP

本文记录 GSV2201S 这款 DP1.4a 转 HDMI2.0 桥接芯片评估,梳理视频、HDCP、PD 集成能力,总结 Type‑C 转接 Dongle、扩展坞方案的适配边界,以及差分走线、PD 时序、HDCP 握手等高频调试问题,为 C 口视频转接方案选型提供参考。

ACP广源盛的博客 218

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

NTscan扫描工具

ntscan是一个扫描器,可以扫描没有密码和弱密码的共享,先到配置文件中填写猜测的用户名和密码,扫描过程中右侧出现的是扫描日志。

ThunderKittens 深度解析:一个不基于 MLIR 的 C++ 模板 GPU DSL

Triton 是"让更多人能写 GPU kernel",ThunderKittens 是"让会写的人写得更爽"。前者用 MLIR 换易用性,后者用 C++ 模板换可控性。它们不是替代关系,而是覆盖了 GPU 编程光谱的两端。ThunderKittens 的出现,本质上是对"GPU DSL 必须基于 MLIR"这一默认假设的挑战。编译期类型系统本身就可以是一个强大的 DSL 基础设施,不需要中间 IR,不需要黑盒 pass,也能实现 tile 级抽象和零运行时开销。

Maxwell_Newton的博客 332

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

C++移动构造函数详解

移动构造函数是C++11引入的一种特殊构造函数,用于从临时对象(右值)"窃取"资源来初始化新对象,而不是进行昂贵的深拷贝。char* data;public:// 移动构造函数:参数是右值引用 &&// 关键:把源对象的资源置空。

拒绝AI文,努力在AI时代写出感动人心的好文章 388

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

蓝桥杯·全国青少年信息素养大赛·电子学会Python/C++考级:倍增法求最近公共祖先LCA解析

蓝桥杯报名正在火热进行(软件赛报名 2026-10-08—12-18,省赛 2027-04-10),电子学会 12 月软件编程(C/C++ 一级至十级、Python 一级至六级)考级报名也已开启(9/23—12/2),全国青少年信息素养大赛算法应用主题赛同步推进——把 LCA 这个模板练熟,正是冲刺这些赛事树型考点的关键一步。所谓「最近公共祖先」:在一棵以某个节点为根的树里,两个节点 u、v 向上追溯,最早交汇的那个祖先就叫它们的 LCA。比如家谱里,你和你表哥最近的共同长辈就是你们的祖父。

IT_Scratch的博客 533

【C语言】字符与字符串函数

C语言中字符串为以\0结尾的字符数组,标准库函数基于此约定。本文解析字符串内存布局,详解ctype.h与string.h中字符分类、长度计算、拷贝、比较等函数的使用要点,强调长度判断与越界风险,最后通过命令行待办事项示例整合fgets、sscanf、strncpy、strcmp,展示实际应用中的关键细节。

记录在学习过程中学到的知识 342

C++ 多态详解:作用、原理与实战用法

多态是C++面向对象的核心特性,通过虚函数实现运行期动态绑定,使同一接口在不同对象上表现出不同行为。其核心作用包括统一接口、提升可扩展性、降低耦合、支持运行时分派与批量处理。实现依赖虚函数表(vtable)和虚指针(vptr),需注意虚析构函数、对象切片等陷阱。相比编译期多态(模板),运行期多态灵活性高但有调用开销。合理结合两者,可兼顾代码的灵活性与性能,是构建可扩展、低耦合系统的关键。

2402_85743155的博客 376

C++ 函数模版

模板参数:● template ,T 就是类型占位符,名字可以自定义,习惯用T● 占位符可以在函数返回值、形参、函数内部当作类型使用使用要点模板代码一般写在头文件:编译器需要看到完整模板源码,才能在每个编译单元完成实例化全局函数、类内成员函数都可以写成模板实例化发生在编译阶段,不是运行时隐式实例化:调用时编译器自动推导实参类型,生成对应版本函数。

2501_94041724的博客 301

C++ 运行时多态与虚函数机制全面剖析【面试重点】

构成C++ 运行时多态必须是基类的指针或者引用调用虚函数。被调用的函数必须是虚函数,并且派生类必须对该虚函数完成重写/覆盖(Override)。只有基类的指针或引用才能既指向基类对象,又指向派生类对象(切片机制);而只有完成了虚函数重写,派生类与基类才具备不同的函数行为,达到“指向谁就调用谁”的效果。在类成员函数的前面加上virtual关键字修饰,该成员函数就成为了虚函数。public:// 成员函数前加 virtual 修饰,声明为虚函数。

Fuyo_0209的博客 435

C++ 求抛物线的顶点、焦点和准线(Finding the vertex, focus and directrix of a parabola)

已知抛物线方程 $ y = ax^2 + bx + c $,可利用公式求其顶点、焦点和准线。顶点为 $ \left( -\frac{b}{2a}, \frac{4ac - b^2}{4a} \right) $,焦点为 $ \left( -\frac{b}{2a}, \frac{4ac - b^2 + 1}{4a} \right) $,准线方程为 $ y = c - \frac{b^2 + 1}{4a} $。以 $ a=5, b=3, c=2 $ 为例,计算得顶点 $(-0.3, 1.55)$,焦点 $(-0

csdn_aspnet的专栏,请点击博客主页右上角三个点中的私信联系 163

c++ 超级大坑未定义行为如何避免(下)

尤其是通过。

拒绝AI文,努力在AI时代写出感动人心的好文章 141

C 语言指针与输入输出

指针是 C 语言的魂,输入输出是和人交互的入口。这篇文章把"变量在内存里到底长什么样"讲透,接着用代码验证原码/反码/补码为什么非用补码不可,再拿进制转换、数组、printf/scanf 的格式符收尾。所有输出都是本机 gcc 真实跑出来的。

2401_89953644的博客 159
上一篇: VC常用开发技巧总结
下一篇: Target Team工作任务及签到表(翟超)
TargetStudio
博客等级 码龄20年 3粉丝 20原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值