MFC核心技术探索

news2024/12/26 0:02:00

原文地址:李浩的博客 lihaohello.top


本文采用逐步调试的方法,带你一起探索MFC程序的执行流程、窗体创建、消息映射、运行时类型识别、对象动态创建这些核心机制。

相信读者在深入理解这些核心机制后,会由衷感叹于MFC框架实现的精妙,在使用MFC框架进行开发时也能“胸中自由丘壑”!

MFC程序的执行流程

以下是最小的MFC程序,“麻雀虽小,五脏俱全”,借此程序研究MFC程序的基本执行流程。

#include <afxwin.h>
// 创建自定义框架类
class CMyFrameWnd :public CFrameWnd {};
// 创建自定义程序类
class CMyWinApp :public CWinApp {
public:
    virtual BOOL InitInstance() {
        CMyFrameWnd* pFrame = new CMyFrameWnd();
        pFrame->Create(NULL, "MFCBase");
        m_pMainWnd = pFrame;
        pFrame->ShowWindow(SW_SHOW);
        pFrame->UpdateWindow();
        return TRUE;
    }
};
// 程序爆破点
CMyWinApp theApp;

从本质上讲,MFC框架就是采用面向对象的方式对Win32库进行封装。进一步的抽象,使开发者不需要关注过多的技术细节,可以极大提升软件开发效率;但要想熟练掌握MFC并达到游刃有余的境界,必须向底层求索,将Win32和MFC联合对照打通理解脉络。

以上程序的主要执行流程如下:

// 先进行全局变量初始化
CMyWinApp{
    CWinApp(){
        AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
        AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
        // 初始化完成之后,就可以在进程的任何地方使用AfxGetThread()、AfxGetApp()两个全局函数获取theApp的地址
        pThreadState->m_pCurrentWinThread = this;
        ASSERT(AfxGetThread() == this);
        pModuleState->m_pCurrentWinApp = this;
        ASSERT(AfxGetApp() == this);
    }
}
// 主函数
WinMain(){
    AfxWinMain(){
    	// 先获取theApp指针
        CWinThread* pThread = AfxGetThread();
        CWinApp* pApp = AfxGetApp();
        // 为theApp的一些变量进行赋值
        AfxWinInit();
        // 初始化程序
        pApp->InitApplication();
        // 初始化实例,在这里创建窗体
        pThread->InitInstance(){
            pFrame->Create();
            pFrame->ShowWindow();
            pFrame->UpdateWindow();
        }    
        // 消息循环
        pThread->Run(){
        	CWinThread::Run(){
                for(;;){
                    while(没有消息)
                    	OnIdle();
                    do{
                    	if(GetMessage()抓到WM_QUIT消息)
                    		return ExitInstance();
                    }while(抓消息)
                }
        	}   
        }
    }
}

其中几个关键函数都可以由开发者重写:InitApplication()、InitInstance()、OnIdle()、Run()。

MFC程序的窗体创建

以上最小MFC程序创建窗体的代码是:

CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase");

其具体流程如下:

CMyFrameWnd* pFrame = new CMyFrameWnd();
pFrame->Create(NULL, "MFCBase"){
	// 加载菜单
	LoadMenu(hInst, lpszMenuName);
	CreateEx(...){
		CreateEx(...){
			// 构造窗体类实例
			CREATESTRUCT cs;
			cs.lpszClass=NULL; // 下面会赋值
			...
			PreCreateWindow(cs){
				AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG){
					WNDCLASS wndcls;
					wndcls.lpfnWndProc = DefWindowProc;	// 后面修改!!!
					...
					_AfxRegisterWithIcon(&wndcls,_afxWndFrameOrView...){
						pWndCls->lpszClassName = lpszClassName;
						LoadIconW(...);
						RegisterClass(&wndCls);						
					}					
				}
				cs.lpszClass = _afxWndFrameOrView;  //“AfxFrameOrView140sd”				
			}
			AfxHookWindowCreate(pFrame){
				// 当前程序线程信息
				_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
				// 埋下一个类型位WH_CRT钩子
				::SetWindowsHookEx(WH_CBT,_AfxCbtFilterHook, NULL, ::GetCurrentThreadId());
				// 将pFrame保存到全局变量中
				pThreadState->m_pWndInit = pFrame;
			}
			// 调用Windows API函数创建窗体
			CreateWindowEx(...); // 此函数一产生就会到对应的钩子处理函数中			
		}
	}
}
// WH_CRT钩子的处理函数
_AfxCbtFilterHook(){
	// 获取第三个全局变量
	_AFX_THREAD_STATE* pThreadState = _afxThreadState.GetData();
	// 获取pFrame
	CWnd* pWndInit = pThreadState->m_pWndInit;
	// 拿到窗口句柄
	HWND hWnd = (HWND)wParam;
	// 将pFrame和窗口句柄进行绑定
	pWndInit->Attach(hWnd){
		CHandleMap* pMap = afxMapHWND(TRUE){
			AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
			pState->m_pmapHWND = new CHandleMap();			
		}
		// 建立从窗口句柄到pFrame的映射
		pMap->SetPermanent(m_hWnd = hWndNew/*将句柄保存到pFrame的成员变量中*/,pFrame)
			m_permanentMap[(LPVOID)h] = pFrame;
		}
	}
	// 更改窗口函数为afxWndProc
	(WNDPROC)SetWindowLongPtr(hWnd, GWLP_WNDPROC,(DWORD_PTR)afxWndProc);
}

MFC窗体创建异常迂回,但是包含了注册窗体类、创建窗体、设置窗体处理函数等核心步骤,与Win32桌面程序一致。

为了能让开发者自定义各类消息处理函数,在消息处理函数部分会有大文章,下一章将重点跟踪该步骤。

MFC程序的消息映射

消息处理的流程

在不使用MFC消息映射机制的前提下,要自定义消息处理函数,可以采取以下方式:

#include <afxwin.h>
// 创建自定义框架类
class CMyFrameWnd :public CFrameWnd {
public:
    // 重写CMyFrameWnd类的WindowProc()虚函数
    virtual LRESULT WindowProc(UINT msgID, WPARAM wParam, LPARAM lParam) {
        switch (msgID) {
        case WM_CREATE:
            AfxMessageBox("WM_CREATE消息被处理!");
            break;
        }
        return CFrameWnd::WindowProc(msgID, wParam, lParam);
    }
};
// 创建自定义程序类
class CMyWinApp :public CWinApp {
public:
    virtual BOOL InitInstance() {
        CMyFrameWnd* pFrame = new CMyFrameWnd();
        pFrame->Create(NULL, "MFCBase");
        m_pMainWnd = pFrame;
        pFrame->ShowWindow(SW_SHOW);
        pFrame->UpdateWindow();
        return TRUE;
    }
};
// 程序爆破点
CMyWinApp theApp;

上节中,窗体处理函数最终被设置为afxWndProc,可以推断:afxWndProc窗口处理函数肯定会调用WindowProc函数。

那么MFC框架是怎么从最初的消息处理函数一步步进入到我们自定义的消息处理函数里面的,下面一探究竟:

AfxWndProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam){
	// 从句柄得到pFrame
	CWnd* pWnd = CWnd::FromHandlePermanent(hWnd);
	AfxCallWndProc(pWnd, hWnd, nMsg, wParam, lParam){
		// 进到虚函数中
		pWnd->WindowProc(nMsg, wParam, lParam)
	}
}

消息映射机制

在不重写WinfowProc虚函数的前提下处理消息,就需要用到消息映射机制,基本用法如下:

类内声明宏:DECLARE_MESSAGE_MAP()
类外实现宏:
BEGIN__MESSAGE_MAP(theClass,baseClass)
	ON_MESSAGE(WM_CREATE,OnCreate)
END_MESSAGE_MAP()

基本程序如下:

#include <afxwin.h>
// 创建自定义框架类
class CMyFrameWnd :public CFrameWnd {
	DECLARE_MESSAGE_MAP()
public:
    LRESULT OnCreate(WPARAM wParam,LPARAM lParam) {
        AfxMessageBox("WM_CREATE消息被处理!");
        return 0;
    }
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
    ON_MESSAGE(WM_CREATE,OnCreate)
END_MESSAGE_MAP()

// 创建自定义程序类
class CMyWinApp :public CWinApp {
public:
    virtual BOOL InitInstance() {
        CMyFrameWnd* pFrame = new CMyFrameWnd();
        pFrame->Create(NULL, "MFCBase");
        m_pMainWnd = pFrame;
        pFrame->ShowWindow(SW_SHOW);
        pFrame->UpdateWindow();
        return TRUE;
    }
};
// 程序爆破点
CMyWinApp theApp;

下面展开宏,还原真相:

class CMyFrameWnd :public CFrameWnd {
//DECLARE_MESSAGE_MAP()
protected:
    static const AFX_MSGMAP* PASCAL GetThisMessageMap();
    virtual const AFX_MSGMAP* GetMessageMap() const;

public:
    LRESULT OnCreate(WPARAM wParam, LPARAM lParam) {
        AfxMessageBox("WM_CREATE消息被处理!");
        return 0;
    }
};

//BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
// ON_MESSAGE(WM_CREATE, OnCreate)
//END_MESSAGE_MAP()
const AFX_MSGMAP* CMyFrameWnd::GetMessageMap() const{
    return GetThisMessageMap();
}
const AFX_MSGMAP* PASCAL CMyFrameWnd::GetThisMessageMap(){
    static const AFX_MSGMAP_ENTRY _messageEntries[] = {
        { WM_CREATE, ..., &OnCreate },
        {0, ..., 0 }
    };
    static const AFX_MSGMAP messageMap = { &CFrameWnd::GetThisMessageMap, &_messageEntries[0] };
    return &messageMap;
}

_messageEntries和messageMap在父类和子类之间形成了一个链表,如下图所示:

继续上一节消息处理流程的分析:

// 进到虚函数中,以下为WM_CREATE消息,WM_COMMAND、WM_NOTIFY等路径不一样
pWnd->WindowProc(nMsg, wParam, lParam){
    OnWndMsg(message, wParam, lParam, &lResult){
    	// 获取本类的链表节点
        pMessageMap = GetMessageMap();
        const AFX_MSGMAP_ENTRY* lpEntry;
        // 遍历链表,从子类向父类遍历
        for (; pMessageMap->pfnGetBaseMap != NULL; pMessageMap = (*pMessageMap->pfnGetBaseMap)()){
        	// 找到了就返回数组元素的地址
        	lpEntry = AfxFindMessageEntry(pMessageMap->lpEntries, message, 0, 0);
        	if(lpEntry != NULL){
        		goto LDispatch;
        	}
	 	}  
        LDispatch:
        // 调用lpEntry->pfn,处理消息
    }
}

个性化消息映射

MFC中的消息分为三类:

  • 标准Windows消息:可使用ON_WM_XXX宏处理
  • 自定义消息:可使用ON_MESSAGE宏处理
  • 命令消息:可使用ON_COMMAND宏处理,可以在任何类中处理,但是有优先顺序

例子如下:

#include <afxwin.h>
class CMyFrameWnd :public CFrameWnd {
    DECLARE_MESSAGE_MAP()
public:
    afx_msg int OnCreate(LPCREATESTRUCT pcs) {
        AfxMessageBox("WM_CREATE消息被处理!");
        return CFrameWnd::OnCreate(pcs);
    }
};
BEGIN_MESSAGE_MAP(CMyFrameWnd, CFrameWnd)
    // ON_WM_CREATE()
    { WM_CREATE, 0, 0, 0, AfxSig_is, 
		(AFX_PMSG)(AFX_PMSGW)(static_cast< int (AFX_MSG_CALL CWnd::*)(LPCREATESTRUCT) > ( &ThisClass :: OnCreate)) },
END_MESSAGE_MAP()

class CMyWinApp :public CWinApp {
public:
    virtual BOOL InitInstance() {
        CMyFrameWnd* pFrame = new CMyFrameWnd();
        pFrame->Create(NULL, "MFCBase");
        m_pMainWnd = pFrame;
        pFrame->ShowWindow(SW_SHOW);
        pFrame->UpdateWindow();
        return TRUE;
    }
};
CMyWinApp theApp;

ON_COMMAND消息来源于菜单或工具栏按钮的点击事件,下面简要介绍菜单(主菜单、上下文菜单)和工具栏的加载。

菜单加载:

  • 方式1:pFrame->Create(NULL, “MFCBase”)中可以传递菜单资源ID
  • 方式2:在WM_CREATE消息处理函数中加载菜单
  • 设置菜单勾选状态:在WM_INITMENUPOPUP消息处理函数中调用::CheckMenuItem()函数

上下文菜单:

  • 实现WM_CONTEXTMENU消息处理函数,调用::TrackPopupMenu()函数

工具栏加载:CToolBarCtrl、CToolBar

  • 步骤1:添加工具栏资源:可视化制作
  • 步骤2:创建工具栏(CToolBar::CreateEx)
  • 步骤3:加载工具栏(CToolBar::LoadToolBar)
  • 步骤4:设置工具栏的停靠(CToolBar::EnableDocking、CFrameWnd::EnableDocking、CFrameWnd::DockControlBar)

MFC程序的运行时类型识别

在运行时能获得对象是否属于某个类的信息就是“运行时类信息机制”,基本用法如下:

类必须派生自CObject
类内声明宏:DECLARE_DYNAMIC(theClass)
类外实现宏:IMPLEMENT_DYNAMIC(theClass,baseClass)
使用CObject::IsKindOf()就可以判断对象是否属于某个类

测试程序:

#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
    DECLARE_DYNAMIC(CAnimal)
};
IMPLEMENT_DYNAMIC(CAnimal, CObject)

class CDog :public CAnimal {
    DECLARE_DYNAMIC(CDog)
};
IMPLEMENT_DYNAMIC(CDog, CAnimal)

int main() {
    CAnimal a;
    CDog d;

    if (d.IsKindOf(RUNTIME_CLASS(CDog)))
        cout << "YES" << endl;
    else
        cout << "NO" << endl;

    system("pause");
    return 0;
}

下面展开宏,还原真相:

#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
    //DECLARE_DYNAMIC(CAnimal)
public:
    static const CRuntimeClass classCAnimal;
    virtual CRuntimeClass* GetRuntimeClass() const;
};

//IMPLEMENT_DYNAMIC(CAnimal, CObject)
AFX_COMDAT const CRuntimeClass CAnimal::classCAnimal = { 
    "CAnimal", 
    sizeof(class CAnimal), 
    0xFFFF, 
    NULL, 
    RUNTIME_CLASS(CObject), 
    NULL, 
    NULL 
};

CRuntimeClass* CAnimal::GetRuntimeClass() const{
    //return RUNTIME_CLASS(CAnimal);
    return (CRuntimeClass*)(&CDog::classCDog);
}


class CDog :public CAnimal {
    DECLARE_DYNAMIC(CDog)
};
IMPLEMENT_DYNAMIC(CDog, CAnimal)

int main() {
    CAnimal a;
    CDog d;

    // (CRuntimeClass*)(&CDog::classCDog)
    if (d.IsKindOf(RUNTIME_CLASS(CDog)))
        cout << "YES" << endl;
    else
        cout << "NO" << endl;

    system("pause");
    return 0;
}

每个类中都包含一个CRuntimeClass类变量,其中包含父类信息,构成了一个链表,如下图所示:

IsKindOf(CRuntimeClass*)的执行流程如下:

IsKindOf(const CRuntimeClass* pClass){
	// 获取当前对象的运行是类信息
	CRuntimeClass* pClassThis = GetRuntimeClass();
	// 判断该对象是不是参数类的派生类
	pClassThis->IsDerivedFrom(pClass){
		// 从派生类向父类方向遍历链表
    	const CRuntimeClass* pClassThis = this;
    	while (pClassThis != NULL){
    		if (pClassThis == pClass)
				return TRUE;
			pClassThis = pClassThis->m_pBaseClass;
    	}
    	return FALSE;
	}
}

MFC程序的对象动态创建

在不知道类名的情况下创建类对象的机制就是“动态创建机制”,基本用法如下:

类必须派生自CObject
类内声明宏:DECLARE_DYNCREATE(theClass)
类外实现宏:IMPLEMENT_DYNCREATE(theClass,baseClass)
使用CRuntimeClass::CreateObject()就可以创建类对象

测试程序:

#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
    DECLARE_DYNAMIC(CAnimal)
};
IMPLEMENT_DYNAMIC(CAnimal, CObject)

// 动态创建CDog
class CDog :public CAnimal {
	DECLARE_DYNCREATE(CDog)
};
IMPLEMENT_DYNCREATE(CDog, CAnimal)

int main() {
	
    system("pause");
    return 0;
}

下面展开宏,还原真相:

#include <afxwin.h>
#include <iostream>
using namespace std;

class CAnimal :public CObject {
    DECLARE_DYNAMIC(CAnimal)
};
IMPLEMENT_DYNAMIC(CAnimal, CObject)

class CDog :public CAnimal {
// DECLARE_DYNCREATE(CDog)
public:
    static const CRuntimeClass classCDog;
    virtual CRuntimeClass* GetRuntimeClass() const;
    static CObject* PASCAL CreateObject();
};

//IMPLEMENT_DYNCREATE(CDog, CAnimal)
CObject* PASCAL CDog::CreateObject() {
    return new CDog;
}
AFX_COMDAT const CRuntimeClass CDog::classCDog = {
    "CDog",
    sizeof(class CDog),
    0xFFFF,
    CDog::CreateObject,
    RUNTIME_CLASS(CAnimal),
    NULL,
    NULL
};
CRuntimeClass* CDog::GetRuntimeClass() const{
    return (CRuntimeClass*)(&CDog::classCDog);
}

int main() {
    CDog* pDog = (CDog*)RUNTIME_CLASS(CDog)->CreateObject();
    return 0;
}

RUNTIME_CLASS(CDog)->CreateObject()的执行流程如下:

RUNTIME_CLASS(CDog)->CreateObject(){
	// m_pfnCreateObject = CDog::CreateObject
	pObject = (*m_pfnCreateObject)();
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2036359.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Outh2四种授权模式详解

1.oauth 2.0 简介 2.各个角色介绍 3.四种模式 4.授权码模式 ①&#xff1a;获取授权码 ②&#xff1a;申请授权接口 ③&#xff1a;申请token ④&#xff1a;申请token接口 5.简单模式 6.密码模式 7.客户端模式

StarRocks 存算分离 Data Cache 二三事

前言 StarRocks 存算分离模式架构中&#xff0c;数据导入后&#xff0c;会被写入远端对象存储。而对象存储由于其访问延迟较高特性&#xff0c;如果没有任何优化&#xff0c;每次查询直接访问后端对象存储&#xff0c;那么性能就会变得非常差&#xff0c;也就失去了 StarRocks…

【C#】一个项目移动了位置,或者换到其他电脑上,编译报错 Files 的值“IGEF,解决方法

文章目录 1 问题分析2 本文解决方法 一个项目可以正常运行编译的项目&#xff0c;所有路径均为相对路径。 移动了位置&#xff0c;或者换到其他电脑上&#xff0c;编译报错 Files 的值“IGEF&#xff0c; 1 问题分析 这个错误信息表明在处理文件时&#xff0c;Files 的值出…

tiktok 搜索接口请求与翻页

这几天有小伙伴问tk的搜索接口的问题, 一个是搜索热门接口请求返回 {“status_code”: 0},这个使用curl_cffi的requests库改一下指纹请求就行了。 再一个就是翻页问题 细心一些比对一下翻页参数都能做到的(小伙伴以为只改个offset就完事了) 要不然你只能得到这样的结果:…

JavaWeb—XML_Tomcat10_HTTP

一、XML XML是EXtensible MarkupLanguage的缩写&#xff0c;翻译过来就是可扩展标记语言。所以很明显&#xff0c;XML和HTML一样都是标记语言&#xff0c;也就是说它们的基本语法都是标签。 可扩展:三个字表面上的意思是XML允许自定义格式。但这不代表你可以随便写; 在XML基…

极狐GitLab 如何设置访问令牌前缀?

极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门面向中国程序员和企业提供企业级一体化 DevOps 平台&#xff0c;用来帮助用户实现需求管理、源代码托管、CI/CD、安全合规&#xff0c;而且所有的操作都是在一个平台上进行&#xff0c;省事省心省钱。可以一键安装极狐GitL…

GSON转换将Long类型转换Double导致精度丢失的问题排查

问题描述 项目中同步订单时发现一个问题&#xff0c;同一条的数据&#xff0c;order_id和item_id为Long类型&#xff0c;在同步时&#xff0c;数值变了。比如原本是6930414387088791188变成了69304143870884512001。 问题排查 经过排查发现http请求的返回参数是正常的&#…

树形结构数据数据查询优化过程

树形结构数据统计查询优化过程 初始方案&#xff1a; 组织树数据结构如下&#xff1a; 数据请求参数&#xff1a; 原技术方案&#xff1a; public List<Map<String, List<Long>>> getSelectParam(List<DepartmentQueryDTO> departmentList, Stri…

图论(二):图的度分析——度数bar图度数等级图度数直方图根据度数渲染节点颜色

本期所用数据集&#xff0c;空手道俱乐部关系数据集&#xff1a; 数据集中共有34个节点&#xff0c;每个节点代表俱乐部中的一名成员数据集中共有78条边&#xff0c;每条边表示两名成员之间的友谊关系或社交联系常见数据集格式为GML和TXT格式&#xff0c;还可能包含其他格式的…

检索增强生成算法

检索增强生成算法&#xff08;Retrieval-Augmented Generation&#xff0c;RAG)是由Patrick Lewis等人于2020年提出的&#xff08;https://arxiv.org/pdf/2005.11401&#xff09;&#xff0c;主要用于辅助大规模语言模型&#xff08;Large Language Models, LLMs&#xff09;在…

【网络】代理服务器

目录 正向代理 反向代理 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 正向代理 正向代理&#xff08;Forward Proxy&#xff09;是一种常见的网络代理方式&#xff0c;它位于客户端和目标 服务器之间&#xff0c;代表客户端向目标服务器发送请求。正向代理服务器接收客户…

晓北斗 - 北斗七星、北斗导航系统

北斗导航 北斗卫星导航 芯片、模块、天线、板卡等基础产品,是北斗系统应用的基础。通过卫星导航专项的集智攻关,我国实现了卫星导航基础产品的自主可控,形成了完整的产业链。 北斗卫星导航系统&#xff08;Beidou Navigation Satellite System&#xff0c;简称&#xff1a;BD…

Effective-Java-Chapter5-泛型

https://github.com/clxering/Effective-Java-3rd-edition-Chinese-English-bilingual/blob/dev/Chapter-5/Chapter-5-Introduction.md 准则一 不要使用原始类型 首先来看一下什么是原始类型呢&#xff1f; List 对应的原始类型是 List&#xff0c;那其实就是说不带参数化类…

Selenium + Python 自动化测试13(HTML报告)

我们的目标是&#xff1a;按照这一套资料学习下来&#xff0c;大家可以独立完成自动化测试的任务。 上一篇我们讨论了unittest中discover 的构建&#xff0c;可以组织测试更多测试用例。 本篇文章我们接着讲。如何生成HTML报告&#xff0c;提高我们测试报告的可读性。 1、引入…

IOS 01 CocoaPods 安装与使用

什么是CocoaPods CocoaPods是一个依赖管理工具&#xff0c;类似Java语言的Maven&#xff0c;Gradle这样的工具&#xff1b;只是他是管理iOS&#xff0c;macOS等平台项目的依赖而已。 同类软件 Carthage&#xff0c;Swift官方提供的Swift Package Manager。 流行程度 Cocoa…

若依框架将Mybatis改成MybatisPlus

1.引入MybatisPlus的maven依赖 <mybatis-plus.version>3.2.0</mybatis-plus.version> <dependencies><!-- mybatis-plus --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifac…

国联证券:蛇吞象

券商蛇吞象&#xff0c;难解业绩荒 今天我们来聊——国联证券 重大资产重组预案发布两个半月后&#xff0c;国联证券最新重组方案出炉&#xff0c;作价294.92亿收购民生证券99.26%的股份。 炒了这么久的券商重组&#xff0c;终于迎来实质性落地。 也因为这两家的营收差异&…

PHP多商家营销活动平台系统小程序源码

解锁营销新境界&#xff01;「多商家营销活动平台」让你的品牌火出圈✨ &#x1f680;【聚合力量&#xff0c;共创辉煌】&#x1f680; 在这个竞争激烈的市场中&#xff0c;单打独斗早已不是最佳选择&#xff01;「多商家营销活动平台」横空出世&#xff0c;它像一座桥梁&…

Redis操作--RedisTemplate(一)介绍

一、介绍 1、简介 RedisTemplate 是 Spring Data Redis 提供的一个高级抽象&#xff0c;由 Spring 官方提供的方便操作 Redis 数据库的一个工具类&#xff0c;支持模板设计模式&#xff0c;使得操作 Redis 更加符合 Spring 的编程模型。还支持序列化机制&#xff0c;可以处理…

基于SpringBoot的航空公司管理系统设计与实现,源码、部署+讲解

摘要 随着互联网时代的日益发展&#xff0c;互联网技术得到了广泛的应用。互联网正在经历一场由传统线下管理模式转变为以互联网主导的线上管理模式的深刻变革。在线管理技术犹如雨后春笋一般冒芽而出&#xff0c;这为我们的生活带来了许多变动。 在近些年国内航空业的蓬勃发…