-
- 菜单
1.1 概念
-
分类
1、静态菜单:在菜单资源编辑器预先编辑好
2、动态菜单:在程序运行的过程中通过代码生成
3、快捷菜单:是前两种菜单的组合,在菜单资源编辑器中预先编辑好,然后在程序运行的过程中动态显
示
-
菜单项
菜单里面的每一个元素都是一个菜单项
菜单项包含两个最基本的要素:1、菜单项名字,2、该菜单项唯一的标识ID
每一个菜单项还可以是嵌套的子菜单数组
1.2 操作
![c3926945b802ff2f82ea7070f9ae77d0.png](https://img-blog.csdnimg.cn/img_convert/c3926945b802ff2f82ea7070f9ae77d0.png)
@1 点击资源文件中的”菜单和库.rc*"
![c7914766a96791b623b8dd5edf31d004.png](https://img-blog.csdnimg.cn/img_convert/c7914766a96791b623b8dd5edf31d004.png)
@2 鼠标右击Menu->选择添加资源或者直接插入
![92e95b78f580f3987d456b59be9d9120.png](https://img-blog.csdnimg.cn/img_convert/92e95b78f580f3987d456b59be9d9120.png)
然后就可以直接编辑了
![01fcf349422dc0ddad462589735e40ed.png](https://img-blog.csdnimg.cn/img_convert/01fcf349422dc0ddad462589735e40ed.png)
这是vs的菜单栏 ,我们发现在菜单名后面有字母 起始这是快捷键
如果我们也要添加菜单快捷键 只要在编辑的时候在菜单名之后加上(&按键)即可 -----注意:括号为英文格式的括号,若要加载进窗口只需要在系统命令下 加入对应的条件就行了 先找到对应菜单项的ID
![a22564fc95080a0c0a974614104ba311.png](https://img-blog.csdnimg.cn/img_convert/a22564fc95080a0c0a974614104ba311.png)
![2a109e25716dea3f29ab598dbf051b8a.png](https://img-blog.csdnimg.cn/img_convert/2a109e25716dea3f29ab598dbf051b8a.png)
![bab4e86acf34879922eb881862fa4c90.png](https://img-blog.csdnimg.cn/img_convert/bab4e86acf34879922eb881862fa4c90.png)
在一个菜单项之后还可以编辑它的子菜单(但是实际开发中一般不超过三级菜单)
![40453e461c68c3b3edb83d53a929e509.png](https://img-blog.csdnimg.cn/img_convert/40453e461c68c3b3edb83d53a929e509.png)
![0e9cc6d3205c7dfd80facb64166187a2.png](https://img-blog.csdnimg.cn/img_convert/0e9cc6d3205c7dfd80facb64166187a2.png)
我们发现在不同的菜单项中有分割线
一般是为了区分不同的功能类
我们想要实现只要在添加菜单项的时候输入‘-’(减号)即可
![cf63911f3bc558970973817f35f86db8.png](https://img-blog.csdnimg.cn/img_convert/cf63911f3bc558970973817f35f86db8.png)
在资源文件中编辑好了之后,我们还要把它加载到窗口中
有两种方式:
1、窗口类中加载菜单
![8517ee53619ecccd13fa9ebec0a47ff6.png](https://img-blog.csdnimg.cn/img_convert/8517ee53619ecccd13fa9ebec0a47ff6.png)
在上图位置将默认的菜单名改为自己的菜单名
2、创建窗口时加载
在创建主窗口的函数中我们先获得一个菜单的句柄,然后再在CreateWindow函数的倒数第三的参数传入菜单句柄就行了
![0c703eb7048e008e932cf2077bd0444a.png](https://img-blog.csdnimg.cn/img_convert/0c703eb7048e008e932cf2077bd0444a.png)
不能把这条注释掉,为了验证第二种方法,把值改为nullptr就行了
![a36e0101547d42bad68ed6600040ddc6.png](https://img-blog.csdnimg.cn/img_convert/a36e0101547d42bad68ed6600040ddc6.png)
动态加载
学习怎么实现动态加载之前,我先给大家介绍几个函数
-
CreateMenu()
参数
此函数无参数。
返回值
如果函数成功,则返回值是新建菜单的句柄。
如果函数失败,返回值为NULL。
-
AppendMenu()
将一个新项目附加到指定的菜单栏,下拉菜单,子菜单或快捷菜单的末尾。您可以使用此函数指定菜单项的内容,外观和行为。
InsertMenuItem功能已被InsertMenuItem功能取代。但是,如果您不需要InsertMenuItem的任何扩展功能,您仍然可以使用AppendMenu.
BOOL AppendMenu(
HMENU 【HMENU】, | //处理要更改的菜单 |
UINT 【uFlags】, | //菜单项标志 |
UINT 【uIDNewItem】, | //菜单项标识符或下拉菜单或子菜单的句柄 |
LPCTSTR 【lpNewItem】 | //菜单项内容即菜单项得名字 |
); |
|
-
InsertMenu()
将一个新的菜单项插入菜单,将其他项目向下移动到菜单中。
InsertMenuItem功能已被InsertMenuItem功能取代。但是,如果您不需要InsertMenuItem的任何扩展功能,您仍然可以使用InsertMenu.
BOOL InsertMenu(
HMENU 【HMENU】, | //菜单的句柄 |
UINT 【uPositionbs】, | //新菜单项前面的菜单项 |
UINT 【uFlags】, | //菜单项标志 |
UINT 【uIDNewItem】, | //菜单项标识符或下拉菜单或子菜单的句柄 |
LPCTSTR 【lpNewItem】 | //菜单项内容 |
); |
|
-
EnableMenuItem
禁用或灰化指定的菜单项。
BOOL EnableMenuItem(
HMENU 【HMENU】, | //处理菜单的句柄 |
UINT 【uIDEnableItem】, | //菜单项启用,禁用或灰色 |
UINT 【uEnable】 | //菜单项标志 |
); |
|
//动态菜单:
HMENU hMenu1 = nullptr;
hMenu1 = CreateMenu();//创建了一个新菜单(空菜单)
HMENU h_1 = CreateMenu();
AppendMenu(h_1, //菜单句柄,指明在哪一个菜单的尾部进行追加
0, //菜单风格
10087, //菜单项id
_T("新建(&N)\t Ctrl+N"));//菜单项名
AppendMenu(h_1, MF_SEPARATOR, 0, _T("-"));
AppendMenu(h_1, 0, 10088, _T("打开(&O)\t Ctrl+O"));
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_1, _T("更多(&F)"));
HMENU h_2 = CreateMenu();
//第2个参数为坐标,该参数会受到第3个参数(MF_BYCOMMAND or MF_BYPOSITION)的影响
InsertMenu(h_2, 0, MF_BYPOSITION, 10089, _T("a"));
InsertMenu(h_2, 0, MF_BYPOSITION, 10090, _T("b"));
InsertMenu(h_2, 10089, MF_BYCOMMAND, 10091,_T("c"));
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_2, _T("abc"));
//使菜单项是否变灰或是否激活
EnableMenuItem(h_1, 10087, MF_GRAYED);
EnableMenuItem(hMenu, 10087, MF_ENABLED);
EnableMenuItem(h_1, 0, MF_DISABLED | MF_BYPOSITION);
SetMenu(hWnd, hMenu1);//更新菜单,在窗口显示更新后修改要加
![a6c19c72ccbd14857353f1ffbd7aee28.png](https://img-blog.csdnimg.cn/img_convert/a6c19c72ccbd14857353f1ffbd7aee28.png)
当我把上面这段代码加入之前的代码中之后效果如上图
注意到:多了两个菜单项 即h_1 h_2, abc菜单项项有三个子项 顺序为bca a是最先插入的但是在最下面 所以说明了使用InsertMenu()时上面的后插入,下面的先插入(当然这是默认的插入,没有指定下面的菜单项是谁,比如上面的c就指定了下面是a)
但是使用AppendMenu()时默认是先插入的在上面
运行效果如下
![d90558db8ae5342f07344bdc83fbe355.png](https://img-blog.csdnimg.cn/img_convert/d90558db8ae5342f07344bdc83fbe355.png)
右键菜单
1、在资源编辑器中编辑好新的右键菜单
2、初始化加载,可以在WM_CREATE内或者在主消息循环之前加载。(下面的演示代码是在窗口创建的时候加载的)
-
在WM_RBUTTONDOWN内可以响应
case WM_CREATE:
g_hRMemu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU2));
break;
case WM_RBUTTONUP://鼠标右键抬起
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
//把客户区坐标转为屏幕坐标
ClientToScreen(hWnd, &pt);
//从菜单中获取要弹出的分级菜单
HMENU tempMenu = GetSubMenu(g_hRMemu, 0);//把g_hRMenu里面的0列表示的菜单给到tempMenu
//弹出右键菜单
TrackPopupMenu(tempMenu,//哪一个菜单
TPM_LEFTALIGN | TPM_TOPALIGN,//菜单风格,对齐方式
pt.x, pt.y,//鼠标坐标,菜单在哪个坐标开始显示
0,
hWnd,
nullptr);
}
break;
客户区坐标:就是相对于我们打开的窗口的左上角的坐标
屏幕坐标:相对于电脑屏幕左上角的坐标
我们直接用lParam获取的是客户区坐标 但是TrackPopupMenu这个弹出菜单的函数里的参数是屏幕坐标所以我们要用 ClientToScreen转换一下
![a967e0b33e1163c309e45f944cdd34fd.png](https://img-blog.csdnimg.cn/img_convert/a967e0b33e1163c309e45f944cdd34fd.png)
以上源文件的所有代码如下:
// 菜单和库.cpp : 定义应用程序的入口点。
//
#include "stdafx.h"
#include "菜单和库.h"
#define MAX_LOADSTRING 100
// 全局变量:
HINSTANCE hInst; // 当前实例
TCHAR szTitle[MAX_LOADSTRING]; // 标题栏文本
TCHAR szWindowClass[MAX_LOADSTRING]; // 主窗口类名
HMENU g_hRMemu = nullptr; //右键菜单
// 此代码模块中包含的函数的前向声明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY _tWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此放置代码。
MSG msg;
HACCEL hAccelTable;
// 初始化全局字符串
LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadString(hInstance, IDC_MY, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 执行应用程序初始化:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));
// 主消息循环:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// 函数: MyRegisterClass()
//
// 目的: 注册窗口类。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = nullptr;
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassEx(&wcex);
}
//
// 函数: InitInstance(HINSTANCE, int)
//
// 目的: 保存实例句柄并创建主窗口
//
// 注释:
//
// 在此函数中,我们在全局变量中保存实例句柄并
// 创建和显示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd;
hInst = hInstance; // 将实例句柄存储在全局变量中
HMENU hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MENU1));
hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, hMenu, hInstance, NULL);
//动态菜单:
HMENU hMenu1 = nullptr;
hMenu1 = CreateMenu();//创建了一个新菜单(空菜单)
HMENU h_1 = CreateMenu();
AppendMenu(h_1, //菜单句柄,指明在哪一个菜单的尾部进行追加
0, //菜单风格
10087, //菜单项id
_T("新建(&N)\t Ctrl+N"));//菜单项名
AppendMenu(h_1, MF_SEPARATOR, 0, _T("-"));
AppendMenu(h_1, 0, 10088, _T("打开(&O)\t Ctrl+O"));
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_1, _T("更多(&F)"));
HMENU h_2 = CreateMenu();
//第2个参数为坐标,该参数会受到第3个参数(MF_BYCOMMAND or MF_BYPOSITION)的影响
InsertMenu(h_2, 0, MF_BYPOSITION, 10089, _T("a"));
InsertMenu(h_2, 0, MF_BYPOSITION, 10090, _T("b"));
InsertMenu(h_2, 10089, MF_BYCOMMAND, 10091, _T("c"));
AppendMenu(hMenu, MF_POPUP, (UINT_PTR)h_2, _T("abc"));
HMENU h_3 = CreateMenu();
HMENU h_4 = CreateMenu();
AppendMenu(h_3, MF_POPUP, (UINT)h_4, _T("菜单选项一"));
AppendMenu(h_3, 0, 1001, _T("菜单选项二"));
AppendMenu(h_3, 0, 1002, _T("菜单选项三"));
AppendMenu(h_3, 0, 1003, _T("菜单选项四"));
AppendMenu(hMenu, MF_POPUP, (UINT)h_3, _T("选项"));// MF_POPUP指定菜单项打开下拉菜单或子菜单 第三个参数就是子菜单项的菜单句柄
AppendMenu(h_4, 0, 2000, _T("小黑子"));
AppendMenu(h_4, 0, 2001, _T("IKUN"));
//使菜单项是否变灰或是否激活
EnableMenuItem(h_1, 10087, MF_GRAYED);
// EnableMenuItem(hMenu, 10087, MF_ENABLED);
// EnableMenuItem(h_1, 0, MF_DISABLED | MF_BYPOSITION);
// SetMenu(hWnd, hMenu1);//更新菜单,在窗口显示更新后修改要加
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函数: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目的: 处理主窗口的消息。
//
// WM_COMMAND - 处理应用程序菜单
// WM_PAINT - 绘制主窗口
// WM_DESTROY - 发送退出消息并返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
int wmId, wmEvent;
PAINTSTRUCT ps;
HDC hdc;
switch (message)
{
case WM_CREATE:
g_hRMemu = LoadMenu(hInst, MAKEINTRESOURCE(IDR_MENU2));
break;
case WM_RBUTTONUP:
{
POINT pt;
pt.x = LOWORD(lParam);
pt.y = HIWORD(lParam);
//把客户区坐标转为屏幕坐标
ClientToScreen(hWnd, &pt);
//从菜单中获取要弹出的分级菜单
HMENU tempMenu = GetSubMenu(g_hRMemu, 0);//把g_hRMenu里面的0列表示的菜单给到tempMenu
//弹出右键菜单
TrackPopupMenu(tempMenu,//哪一个菜单
TPM_LEFTALIGN | TPM_TOPALIGN,//菜单风格,对齐方式
pt.x, pt.y,//鼠标坐标,菜单在哪个坐标开始显示
0,
hWnd,
nullptr);
}
break;
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// 分析菜单选择:
switch (wmId)
{
case 1234:
MessageBox(hWnd,_T("你干嘛~"), _T("帮助"), MB_OK);
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
break;
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: 在此添加任意绘图代码...
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// “关于”框的消息处理程序。
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
2.库
静态库
静态库是指我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”,理解为“仓库”,
在“仓库”中存储了很多东西,每次再要使用时只需要去“仓库”拿就可以了。
程序有“预处理”->“编译”->“链接”这几个步骤。静态库就不需要再次编译,只需要在“链接”步骤中,链接器
将从“仓库”的文件中取得所需的代码,复制到生成的可执行文件中即可。
动态库
动态库又称动态链接库。英文“dll”,是“Dynamic Link Library”的缩写形式。
DLL是一个包含可由多个程序同时使用的代码和数据的库。
DLL不是可执行文件。
动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。
函数的可执行代码位于一个DLL中,该DLL包含一个或多个已被编译、链接并与使用它们的进程分开存储
的函数。
DLL还有助于共享数据和资源。多个应用程序可同时访问内存中单 个DLL副本的内容。
windows下动态库为.dll后缀。
linux下为.so后缀。
静态库和动态库的区别
1、静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系。
2、动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
优点
使用静态库的优点:
1、代码装载速度快,执行速度略比动态链接库快
2、发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题
使用动态库的优点:
1、更加节省内存并减少页面交换
2、dll文件和exe文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换
DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性
3、不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数
4、适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和
测试。不足之处
1、使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费
2、使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,
程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终
止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果
新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。这在早期Windows中很常
见
注意:不管是静态库还是动态库,都能实现共享代码。只不过是有没有被包含到exe文件中而已。
静态库、动态库文件关系
静态库和动态库中会需要 一些文件。主要用到以下3类中的文件。
1、.h文件。编译时必须的,声明函数接口用
2、.lib文件。链接时需要的,当我们在程序中引用了一个.h文件里面的函数,链接器怎么知道要调用哪
个dll文件呢,这就是lib文件的作用。它告诉链接器调用的函数在哪个dll中,函数执行代码在dll中的什么
位置。
注意:如果是生成静态库文件,没有dll文件,这个时候函数的可执行代码部分也在lib文件中。所以在链
接时就能直接链接进exe文件。
3、.dll文件。运行时需要的,函数的可执行代码
静态库只用到前两种,动态库用到了所有的三种。
注意:附加依赖项添加的是.lib而不是.dll,若生成了dll,则肯定也生成了lib文件 。
静态库示例
1、创建一个”WIN32“项目,”win32控制台“或”win32项目“。
2、选择”静态库“。
3、新建一个.h文件。
然后在这个.h文件中进行如下代码编写:
4、添加cpp文件。
#ifndef __MYTOOLS_H__ //如果没有定义宏名
#define __MYTOOLS_H__ //定义宏名
extern "C" int MyAdd(int num1, int num2);//实现两个数相加
extern "C" int MySub(int num1, int num2);//实现两个数相减
#endif //结束预处理5、编译生成解决方案。
导入lib文件
动态库示例
1、创建”WIN32“项目,”win32控制台“或”win32项目“。
2、选择”DLL“。
3、添加头文件”MyDll.h“:
4、添加源文件”MyDll.cpp“:
-
编译生成解决方案。
#include "stdafx.h"
#include "MyTools.h"
int MyAdd(int num1, int num2)
{
return num1 + num2;
}
int MySub(int num1, int num2)
{
return num1 - num2;
}
#pragma comment(lib,"某某.lib")//第二个参数为拷贝到这个新工程中的.lib文件的全称
#ifndef __MYDLL_H__
#define __MYDLL_H__
extern "C" _declspec(dllexport) void MyFun(int op1,int op2);
#endif
#include "stdafx.h"
#include <stdio.h>
#include "MyDll.h"
void MyFun(int op1, int op2)
{
for (int i = 1; i <= op1; ++i)
{
for (int j = i; j <= op2; ++j)
{
printf("%d * %d = %d\t",j,i,j * i);
}
printf("\n");
}
}
6、在”dll项目“中有它的入口函数的。有一个dllmain.cpp。这里面有一个动态库的入口函数。DllMain函
数,一般这个函数不用。它只是一个可选的入口函数。里面第二个参数的switch分支用来表示”系统何时
调用了DllMain
静态库
静态库是指我们的应用中,有一些公共代码是需要反复使用,就把这些代码编译为“库”,理解为“仓库”,
在“仓库”中存储了很多东西,每次再要使用时只需要去“仓库”拿就可以了。
程序有“预处理”->“编译”->“链接”这几个步骤。静态库就不需要再次编译,只需要在“链接”步骤中,链接器
将从“仓库”的文件中取得所需的代码,复制到生成的可执行文件中即可。
动态库
动态库又称动态链接库。英文“dll”,是“Dynamic Link Library”的缩写形式。
DLL是一个包含可由多个程序同时使用的代码和数据的库。
DLL不是可执行文件。
动态链接提供了一种方法,使进程可以调用不属于其可执行代码的函数。
函数的可执行代码位于一个DLL中,该DLL包含一个或多个已被编译、链接并与使用它们的进程分开存储
的函数。
DLL还有助于共享数据和资源。多个应用程序可同时访问内存中单 个DLL副本的内容。
windows下动态库为.dll后缀。
linux下为.so后缀。
静态库和动态库的区别
1、静态库在程序的链接阶段被复制到了程序中,和程序运行的时候没有关系。
2、动态库在链接阶段没有被复制到程序中,而是程序在运行时由系统动态加载到内存中供程序调用。
优点
使用静态库的优点:
1、代码装载速度快,执行速度略比动态链接库快
2、发布程序时不需考虑在用户的计算机上.LIB文件是否存在及版本问题
使用动态库的优点:
1、更加节省内存并减少页面交换
2、dll文件和exe文件独立,只要输出接口不变(即名称、参数、返回值类型和调用约定不变),更换
DLL文件不会对EXE文件造成任何影响,因而极大地提高了可维护性和可扩展性
3、不同编程语言编写的程序只要按照函数调用约定就可以调用同一个DLL函数
4、适用于大规模的软件开发,使开发过程独立、耦合度小,便于不同开发者和开发组织之间进行开发和
测试。不足之处
1、使用静态链接生成的可执行文件体积较大,包含相同的公共代码,造成浪费
2、使用动态链接库的应用程序不是自完备的,它依赖的DLL模块也要存在,如果使用载入时动态链接,
程序启动时发现DLL不存在,系统将终止程序并给出错误信息。而使用运行时动态链接,系统不会终
止,但由于DLL中的导出函数不可用,程序会加载失败;速度比静态链接慢。当某个模块更新后,如果
新模块与旧的模块不兼容,那么那些需要该模块才能运行的软件,统统撕掉。这在早期Windows中很常
见
注意:不管是静态库还是动态库,都能实现共享代码。只不过是有没有被包含到exe文件中而已。
静态库、动态库文件关系
静态库和动态库中会需要 一些文件。主要用到以下3类中的文件。
1、.h文件。编译时必须的,声明函数接口用
2、.lib文件。链接时需要的,当我们在程序中引用了一个.h文件里面的函数,链接器怎么知道要调用哪
个dll文件呢,这就是lib文件的作用。它告诉链接器调用的函数在哪个dll中,函数执行代码在dll中的什么
位置。
注意:如果是生成静态库文件,没有dll文件,这个时候函数的可执行代码部分也在lib文件中。所以在链
接时就能直接链接进exe文件。
3、.dll文件。运行时需要的,函数的可执行代码
静态库只用到前两种,动态库用到了所有的三种。
注意:附加依赖项添加的是.lib而不是.dll,若生成了dll,则肯定也生成了lib文件 。
静态库示例
1、创建一个”WIN32“项目,”win32控制台“或”win32项目“。
2、选择”静态库“。
3、新建一个.h文件。
然后在这个.h文件中进行如下代码编写:
-
添加cpp文件。
#ifndef __MYTOOLS_H__ //如果没有定义宏名
#define __MYTOOLS_H__ //定义宏名
extern "C" int MyAdd(int num1, int num2);//实现两个数相加
extern "C" int MySub(int num1, int num2);//实现两个数相减
#endif //结束预处理5、编译生成解决方案。
导入lib文件
动态库示例
1、创建”WIN32“项目,”win32控制台“或”win32项目“。
2、选择”DLL“。
3、添加头文件”MyDll.h“:
4、添加源文件”MyDll.cpp“:
-
编译生成解决方案。
#include "stdafx.h"
#include "MyTools.h"
int MyAdd(int num1, int num2)
{
return num1 + num2;
}
int MySub(int num1, int num2)
{
return num1 - num2;
}
#pragma comment(lib,"某某.lib")//第二个参数为拷贝到这个新工程中的.lib文件的全称
#ifndef __MYDLL_H__
#define __MYDLL_H__
extern "C" _declspec(dllexport) void MyFun(int op1,int op2);
#endif
#include "stdafx.h"
#include <stdio.h>
#include "MyDll.h"
void MyFun(int op1, int op2)
{
for (int i = 1; i <= op1; ++i)
{
for (int j = i; j <= op2; ++j)
{
printf("%d * %d = %d\t",j,i,j * i);
}
printf("\n");
}
}
6、在”dll项目“中有它的入口函数的。有一个dllmain.cpp。这里面有一个动态库的入口函数。DllMain函
数,一般这个函数不用。它只是一个可选的入口函数。里面第二个参数的switch分支用来表示”系统何时
调用了DllMain