完成一个MFC程序并进行分析;用VS命令行工具编译链接

一、基于对话框的MFC程序

1.创建工程

这里我用的是VS2015专业版,首先应该保证你的IDE安装了MFC相关组件。在这里插入图片描述
接着创建一个MFC工程在这里插入图片描述
vs会在你完成创建之前先提供大量的选项,包括创建的文件,窗口样式等等,应用程序类型这里我们选择“基于对话框”。

在这里插入图片描述

可以看到生成的类。

在这里插入图片描述
其他步骤都不做更改直接使用默认即可。在这里插入图片描述

创建成功。

2.根据生成的代码进行分析

vs已经给我们生成了不少的代码,这些代码很详尽,但是也有一些冗余,好在它们都有详细的注释,让我可以看懂它们各个部分是起什么作用的。
打开readme.txt文件,可以得知各个文件的作用。
在这里插入图片描述
在这里插入图片描述
打开stdafx.h,查看包含的头文件,可以得知一个MFC程序用到了哪些库文件。

#include <afxwin.h>         // MFC 核心组件和标准组件
#include <afxext.h>         // MFC 扩展


#include <afxdisp.h>        // MFC 自动化类



#ifndef _AFX_NO_OLE_SUPPORT
#include <afxdtctl.h>           // MFC 对 Internet Explorer 4 公共控件的支持
#endif
#ifndef _AFX_NO_AFXCMN_SUPPORT
#include <afxcmn.h>             // MFC 对 Windows 公共控件的支持
#endif // _AFX_NO_AFXCMN_SUPPORT

#include <afxcontrolbars.h>     // 功能区和控件条的 MFC 支持

这里我们可以看到#include <afxwin.h> // MFC 核心组件和标准组件,想要实现一个最基本的MFC,仅需要包含这一个头文件。

参考其他博文,我总结了MFC执行流程:

全局对象的定义

CMFCApplication1App theApp;

构造

CMFCApplication1App::CMFCApplication1App()
{
	// 支持重新启动管理器
	m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;

	// TODO: 在此处添加构造代码,
	// 将所有重要的初始化放置在 InitInstance 中
}

进入winmain

在VS中看到这个MFC程序连一个main函数都没有,这很反常识,其实它并非没有,只是隐藏在目录里,不用我们自己定义,但是为了搞清楚流程,先要把程序所依赖的源文件找到。程序继续执行进入main函数;MFC的main函数在appmodul.cpp文件当中,而这个文件与winmain.cpp都在

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\src\mfc

这个目录下在这里插入图片描述
找到main函数如下

_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	_In_ LPTSTR lpCmdLine, int nCmdShow)
#pragma warning(suppress: 4985)
{
	// call shared/exported WinMain
	return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}

程序会进入tWinMain,转而执行AfxWinMain。AfxWinMain函数在文件winmain.cpp当中。往下拉找到winmain.cpp在这里插入图片描述
找到这个函数如下所示

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	_In_ LPTSTR lpCmdLine, int nCmdShow)
{
	ASSERT(hPrevInstance == NULL);

	int nReturnCode = -1;
	CWinThread* pThread = AfxGetThread();
	CWinApp* pApp = AfxGetApp();

	// AFX internal initialization
	if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))
		goto InitFailure;

	// App global initializations (rare)
	if (pApp != NULL && !pApp->InitApplication())
		goto InitFailure;

	// Perform specific initializations
	if (!pThread->InitInstance())
	{
		if (pThread->m_pMainWnd != NULL)
		{
			TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
			pThread->m_pMainWnd->DestroyWindow();
		}
		nReturnCode = pThread->ExitInstance();
		goto InitFailure;
	}
	nReturnCode = pThread->Run();

InitFailure:
#ifdef _DEBUG
	// Check for missing AfxLockTempMap calls
	if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
	{
		TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
			AfxGetModuleThreadState()->m_nTempMapLock);
	}
	AfxLockTempMaps();
	AfxUnlockTempMaps(-1);
#endif

	AfxWinTerm();
	return nReturnCode;
}

初始化:注册窗口,产生窗口,更新窗口,消息循环

pThread->InitInstance() (由于InitInstance是虚函数,所以这次调用的是派生类的InitInstance()函数)

BOOL CMFCApplication1App::InitInstance()

对于MFC程序,MainFrame,View,ToolBar,Controlbar等都是窗口,所以下面的窗口注册与创建、显示等要反复调用多次,一次对应一个窗口

(1) 注册窗口类
AfxEndDeferRegisterClass()(相当于SDK里面的RegisterClass()函数)
(2)创建窗口
CMainFrame::PreCreateWindow()
CFrameWnd::Create()
(3)显示和更新窗口
m_pMainWnd->ShowWindow(SW_SHOW);//显示窗口,m_pMainWnd指向框架窗口
m_pMainWnd->UpdateWindow();//更新窗口
(4)消息循环
PumpMessage()
至此整个MFC程序运行流程结束在这里插入图片描述
运行成功

二、win32工程写一个最简单的MFC程序

既然很多文件我们用不上,但是MFC工程都帮我们包含了,那么我们也可以建一个win32工程,调用MFC,实现一个简单的MFC程序。

1、工程创建

在这里插入图片描述
这里并没有什么好说的,工程名为win32project2

2、头文件

首先要把stdafx.h中的#include<windows.h>删除,因为我们需要#include<afxwin.h>,而后者已包含前者,保留前者后面会报错。
然后在win32project2.h头文件中写入以下内容

#define _AFXDLL
#include <afxwin.h>

class MyApp :public CWinApp 
{
public:
	//程序入口
	virtual BOOL InitInstance();
};

class MyFrame :public CFrameWnd /
{
public:
	MyFrame();
}; 

3、源文件

将win32project2.cpp中自动生成的代码除了include全部删除,然后写入以下内容

#include "stdafx.h"
#include "Win32Project2.h"



MyApp app; //全局应用程序对象,有且仅有一个

BOOL MyApp::InitInstance()
{
	
	MyFrame *frame = new MyFrame;

	//显示和更新
	frame->ShowWindow(SW_SHOWDEFAULT);
	frame->UpdateWindow();

	m_pMainWnd = frame; 
	return TRUE;
}

MyFrame::MyFrame()
{
	Create(NULL, TEXT("哈哈哈"));
}

4、在共享DLL中使用MFC

右键项目名->属性->配置属性->常规->项目默认值->MFC的使用->更改为“在共享DLL中使用MFC”
在这里插入图片描述

5、故障排除

在一切准备就绪后,报如下错误

#error: Building MFC application with /MD[d] (CRT dll version)
requires MFC…

经过多方查找和尝试,发现
项目->属性->C/C+±>代码生成->运行库,将“多线程调试DLL(/MDd)”改成“多线程(/MT)”或“多线程调试(/MTd)”,可以解决。
在这里插入图片描述
运行成功。

6、后记

在不选择MFC工程的情况下,按照运行流程,定义了全局变量,定义了theapp类和窗口框架类,然后在主文件写了窗口的创建、显示和更新流程,就完成了一个MFC程序,没有冗余的包含文件,是一次不错的尝试。

三、搭建可供命令行工具编译链接的环境

可能遇到的ERROR

用命令行工具进行编译即使用vs提供的cl.exe和Link.exe这两个工具进行编译和链接。在经过很多次试错和查找资料后发现,当报错为没有cl.exe这个命令,就要在环境变量里加上vs中cl.exe所在的那个目录。我这里是

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\bin;

记得在末尾加上英文分号。

当报错为

fatal error cxxx: xxxxxx.h:不包括路径

就要去找vs目录下包含这个程序要用到的头文件,然后将其所在目录包含到系统变量中。
当报错为

fatal error LINKxxxx: 无法打开文件“xxxx.lib”

就要去找vs目录下包含这个程序要用到的库文件,然后将其所在目录包含到系统变量中。

INCLUDE

如果你是win10,直接搜索“高级系统设置”,就可以打开如下界面
在这里插入图片描述
选择环境变量在系统变量中新建INCLUDE(注意大写)
因为我们这里着手编译一个win32程序和一个MFC程序,所以要把这两个程序所需头文件路径都包含进去。

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include;
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\shared;
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\ucrt;
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\um;
C:\Program Files (x86)\Windows Kits\10\Include\10.0.18362.0\winrt;
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\include;

LIB

同样的新建LIB,包含以下路径

C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\lib;
C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\ucrt\x86;
C:\Program Files (x86)\Windows Kits\10\Lib\10.0.18362.0\um\x86;
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\lib;
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\lib\amd64;
C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\atlmfc\lib\arm;

至此环境算是搭建好了

四、命令行工具编译

1、windows API程序编译

程序源码见我之前发布的博客
win+R键->输入cmd,打开终端
在这里插入图片描述

cd命令,打开要编译的程序源文件所在的工作目录
在这里插入图片描述

cl.exe /c 文件名 ,只编译不连接
在这里插入图片描述
编译成功就可以在工作目录下找到main.obj文件
在这里插入图片描述
链接命令Link.exe main.obj 这里报了一堆错误
在这里插入图片描述
当时很疑惑,猜想还是库的问题,遂查找资料,发现

1、kernel32.dll
提供了线程、进程、内存管理等核心的API
2、user32.dll
提供了窗口、消息等API
3、gdi32.dll
提供了绘图的API

这三个vc常用的库,然后在链接命令后面加上user32.lib gdi32.lib。
在这里插入图片描述

没有问题
可想而知工作目录下生成了main.exe文件,我们直接运行
在这里插入图片描述
点击鼠标的效果在这里插入图片描述
在这里插入图片描述
运行成功。

2、MFC程序的编译

这里编译的MFC程序就是本篇博客记录的
同样的步骤,同样的命令先编译不连接。
在这里插入图片描述

虽然有警告,但是编译成功了
在这里插入图片描述
同样生成了.obj文件
再进行链接
在这里插入图片描述
出错了,这个错误我一开始觉得匪夷所思,因为它并不是说打不开某某,无法定义某某,说明不是库文件的问题。没有定义入口点,就是说没有main函数,但是上文我探究过了,MFCC程序就是没有main函数啊,但是它的main在vs目录下,在IDE里,能够自动调用,所以之前在IDE里运行这个程序并没有出错。既然这样,将隐藏在目录中自动调用的函数写进MFCApplication1.cpp里。

即是我在上文“基于对话框的MFC程序”中摘录的appmodul.cpp和winmain.cpp中的函数入口重新定义在主文件的后面。
问题解决,在工作目录下输入MFCApplication1.exe,运行。
在这里插入图片描述
运行成功。

五、总结

这次工作完全是摸着石头过河,甚至一开始我不知道我的vs IDE上没有安装MFC模块,导致我因此摸索了好久。其中碰到不少问题,但是我也因此搞懂了MFC的运行流程,了解了VS目录下的那些头文件,动态库。通过用命令行工具cl.exe、Link.exe的使用,了解了VS帮我们做了哪些工作,搞懂了很多以前没有关注的问题。踩了很多坑,多亏了网上的教程还有一些博主的分享,文末会附上链接。

六、参考资料和链接

windows程序分类以及库和头文件目录的详解
cl.exe和Link.exe编译和链接
MFC执行流程

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值