本文学习博文:?http://www.sychzs.cn/luoweifu/article/details/49847749
VS构建工具介绍
我们都知道C/C++源代码要生成可执行的.exe程序,需要经过编译、链接的过程。你在VS工具中只需要选择菜单Build或按一下F5可以编译、链接、运行了,其实IDE帮我隐藏了好多的具体细节。
首先找到VS2010的安装位置:
方法之一:右击快捷键->属性->起始位置。
打开安装目录下的VSDIR\VC\bin可以看到一系列的可执行程序.exe和批处理文件,这些就是VS2010构建、编译、链接时要用到的工具。看一下几个主要的工具:? cl.exe:编译程序? link.exe:链接程序? lib.exe:加载lib库的程序?
nmake.exe:用makefile进行构建、编译的工具
命令行编译程序
配置环境变量
VS2010_DIR:?
C:\Program Files (x86)\VS2010 ?( 软件安装的绝对地址 )
WIN_SDK:? C:\Program Files (x86)\VS2010\SDK ?(SDK的绝对地址) path:? C:\Users\Administrator.dnx\bin;%VS2010_DIR%\VC\bin;%VS2010_DIR%\Common7\IDE? include:? %VS2010_DIR%\VC\include;%WIN_SDK%Windows\v7.0A\Include;? lib:? %VS2010_DIR%\VC\lib;%WIN_SDK%\Windows\v7.0A\Lib;
测试
D:\CppWorkspace\CommandTest\HelloWorld.cpp:
#include
#include
int main()
{
std::cout << "This is a native C++ program." << std::endl;
printf("printf: Hello World");
return 0;
}
123456789
123456789
编译结果:?
?
命令行中编译C/C++程序
HelloWorld.obj就是编译出的二进制文件,HelloWorld.exe就是链接成的可执行文件。
说明
在以上的编译过程中我们只用了cl的编译命令就帮我们最终的可执行文件HelloWorld.exe,这是因为cl.exe程序在编译时自己会去调用link.exe、lib.exe等程序。
可通过”cl -help “查看常用的编译选项
选项 作用 /O1 创建小代码 /O2 创建快速代码 /Oa 假设没有别名 /Ob 控制内联展开 /Od 禁用优化 /Og 使用全局优化 /Oi 生成内部函数
更详细的中文介绍也可参考这篇博文:? http://www.sychzs.cn/%E5%BE%AE%E8%BD%AF-cl-exe-%E7%BC%96%E8%AF%91%E5%99%A8.html
本文遇到的问题及其解决方案:
1、丢失mspdb100.dll
解决:在cmd中键入cl执行编译(或lib)时会出现mspdb100.dll无法找到的情况,是因为VC\Bin\下没有mspdb100.dll“这个文件,直接从Common7\IDE\下复制这个文件到VC\Bin\下即可解决。
2、无法打开kernel32.lib
找到VS安装中的v7.0A的文件夹,我的在C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A;
点击项目里的属性,找到vc++库路径浏览选择添加C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\lib 再添加可执行文件目录添加: ?C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\include和C:\Program Files (x86)\Microsoft SDKs\Windows\v7.0A\bin
什么是DLL(动态链接库)?
? ?DLL是一个包含可由多个程序同时使用的代码和数据的库。例如:在Windows操作系统中,Comdlg32 DLL执行与对话框有关的常见函数。因此,每个程序都可以使用该DLL中包含的功能来实现“打开”对话框。这有助于促进代码重用和内存的有效使用。这篇文章的目的就是让你一次性就能了解和掌握DLL。
为什么要使用DLL(动态链接库)?
? ?代码复用是提高软件开发效率的重要途径。一般而言,只要某部分代码具有通用性,就可以将它构造成相对独立的功能模块并在之后的项目中重复使用。比较常见的例子是各种应用程序框架,它们都以源代码的形式发布。由于这种复用是源代码级别的,源代码完全暴露给了程序员,因而称之为“白盒复用”。白盒复用有以下三个缺点:
暴露源代码,多份拷贝,造成存储浪费;容易与程序员的本地代码发生命名冲突;更新模块功能比较困难,不利于问题的模块化实现;
? ? 为了弥补这些不足,就提出了“二进制级别”的代码复用了。使用二进制级别的代码复用一定程度上隐藏了源代码,对于“黑盒复用”的途径不只DLL一种,静态链接库,甚至更高级的COM组件都是。
使用DLL主要有以下优点:
使用较少的资源;当多个程序使用同一函数库时,DLL可以减少在磁盘和物理内存中加载的代码的重复量。这不仅可以大大影响在前台运行的程序,而且可以大大影响其它在Windows操作系统上运行的程序;推广模块式体系结构;简化部署与安装。
创建DLL? ?
? ? 打开Visual Studio 2012,创建如下图的工程:
输入工程名字,单击[OK];
单击[Finish],工程创建完毕了。现在,我们就可以在工程中加入我们的代码了。加入MyCode.h和MyCode.cpp两个文件;在MyCode.h中输入以下代码:
#ifndef _MYCODE_H_
#define _MYCODE_H_
#ifdef DLLDEMO1_EXPORTS
#define EXPORTS_DEMO _declspec( dllexport )
#else
#define EXPORTS_DEMO _declspec(dllimport)
#endif
extern "C" EXPORTS_DEMO int Add (int a , int b);
#endif
在MyCode.cpp中输入以下代码:
#include "stdafx.h"
#include "MyCode.h"
int Add ( int a , int b )
{
return ( a + b );
}
编译工程,就会生成DLLDemo1.dll文件。在代码中,很多细节的地方,我稍后进行详细的讲解。
使用DLL
? ?当我们的程序需要使用DLL时,就需要去加载DLL,在程序中加载DLL有两种方法,分别为加载时动态链接和运行时动态链接。
在加载时动态链接中,应用程序像调用本地函数一样对导出的DLL函数进行显示调用。要使用加载时动态链接,需要在编译和链接应用程序时提供头文件和导入库文件(.lib)。当这样做的时候,链接器将向系统提供加载DLL所需的信息,并在加载时解析导出的DLL函数的位置;在运行时动态链接中,应用程序调用LoadLibrary函数或LoadLibraryEx函数以在运行时加载DLL。成功加载DLL后,可以使用GetProcAddress函数获得要调用的导出的DLL函数的地址。在使用运行时动态链接时,不需要使用导入库文件。
在实际编程时有两种使用DLL的方法,那么到底应该使用那一种呢?在实际开发时,是基于以下几点进行考虑的:
启动性能如果应用程序的初始启动性能很重要,则应使用运行时动态链接;易用性在加载时动态链接中,导出的DLL函数类似于本地函数,我们可以方便地进行这些函数的调用;应用程序逻辑在运行时动态链接中,应用程序可以分支,以便按照需要加载不同的模块。
下面,我将分别使用两种方法调用DLL动态链接库。
加载时动态链接:
#include
#include
//#include "..\\DLLDemo1\\MyCode.h"
using namespace std;
#pragma comment(lib, "..\\debug\\DLLDemo1.lib")
extern "C" _declspec(dllimport) int Add(int a, int b);
int main(int argc, char *argv[])
{
cout< return 0; } 运行时动态链接: #include #include using namespace std; typedef int (*AddFunc)(int a, int b); int main(int argc, char *argv[]) { HMODULE hDll = LoadLibrary("DLLDemo1.dll"); if (hDll != NULL) { AddFunc add = (AddFunc)GetProcAddress(hDll, "Add"); if (add != NULL) { cout< } FreeLibrary(hDll); } } 上述代码都在DLLDemo1工程中。(工程下载)。 DllMain函数 ? ?Windows在加载DLL时,需要一个入口函数,就像控制台程序需要main函数一样。有的时候,DLL并没有提供DllMain函数,应用程序也能成功引用DLL,这是因为Windows在找不到DllMain的时候,系统会从其它运行库中引入一个不做任何操作的默认DllMain函数版本,并不意味着DLL可以抛弃DllMain函数。 ? ?根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数,这就说明不能在客户端直接调用DllMain函数,DllMain函数是自动被调用的。 ? ?DllMain函数在DLL被加载和卸载时被调用,在单个线程启动和终止时,DllMain函数也被调用。参数ul_reason_for_call指明了调用DllMain的原因,有以下四种情况: ??DLL_PROCESS_ATTACH:当一个DLL被首次载入进程地址空间时,系统会调用该DLL的DllMain函数,传递的ul_reason_for_call参数值为DLL_PROCESS_ATTACH。这种情况只有首次映射DLL时才发生; ??DLL_THREAD_ATTACH:该通知告诉所有的DLL执行线程的初始化。当进程创建一个新的线程时,系统会查看进程地址空间中所有的DLL文件映射,之后用DLL_THREAD_ATTACH来调用DLL中的DllMain函数。要注意的是,系统不会为进程的主线程使用值DLL_THREAD_ATTACH来调用DLL中的DllMain函数; ? ?DLL_PROCESS_DETACH:当DLL从进程的地址空间解除映射时,参数ul_reason_for_call参数值为DLL_PROCESS_DETACH。当DLL处理DLL_PROCESS_DETACH时,DLL应该处理与进程相关的清理操作。如果进程的终结是因为系统中有某个线程调用了TerminateProcess来终结的,那么系统就不会用DLL_PROCESS_DETACH来调用DLL中的DllMain函数来执行进程的清理工作。这样就会造成数据丢失; ? ?DLL_THREAD_DETACH:该通知告诉所有的DLL执行线程的清理工作。注意的是如果线程的终结是使用TerminateThread来完成的,那么系统将不会使用值DLL_THREAD_DETACH来执行线程的清理工作,这也就是说可能会造成数据丢失,所以不要使用TerminateThread来终结线程。以上所有讲解在工程DLLMainDemo(工程下载)都有体现。 函数导出方式 ??在DLL的创建过程中,我使用的是_declspec( dllexport )方式导出函数的,其实还有另一种导出函数的方式,那就是使用导出文件(.def)。你可以在DLL工程中,添加一个Module-Definition File(.def)文件。.def文件为链接器提供了有关被链接器程序的导出、属性及其它方面的信息。 对于上面的例子,.def可以是这样的: LIBRARY "DLLDemo2" EXPORTS Add @ 1 ;Export the Add function Module-Definition File(.def)文件的格式如下: LIBRARY语句说明.def文件对应的DLL;EXPORTS语句后列出要导出函数的名称。可以在.def文件中的导出函数名后加@n,表示要导出函数的序号为n(在进行函数调用时,这个序号有一定的作用)。 使用def文件,生成了DLL,客户端调用代码如下: #include #include using namespace std; typedef int (*AddFunc)(int a, int b); int main(int argc, char *argv[]) { HMODULE hDll = LoadLibrary("DLLDemo2.dll"); if (hDll != NULL) { AddFunc add = (AddFunc)GetProcAddress(hDll, MAKEINTRESOURCE(1)); if (add != NULL) { cout< } FreeLibrary(hDll); } } 可以看到,在调用GetProcAddress函数时,传入的第二个参数是MAKEINTRESOURCE(1),这里面的1就是def文件中对应函数的序号。(工程下载) extern “C” 为什么要使用extern “C”呢?C++之父在设计C++时,考虑到当时已经存在了大量的C代码,为了支持原来的C代码和已经写好的C库,需要在C++中尽可能的支持C,而extern “C”就是其中的一个策略。在声明函数时,注意到我也使用了extern “C”,这里要详细的说说extern “C”。 extern “C”包含两层含义,首先是它修饰的目标是”extern”的;其次,被它修饰的目标才是”C”的。先来说说extern;在C/C++中,extern用来表明函数和变量作用范围(可见性)的关键字,这个关键字告诉编译器,它申明的函数和变量可以在本模块或其它模块中使用。extern的作用总结起来就是以下几点: 在一个文件内,如果外部变量不在文件的开头定义,其有效范围只限定在从定义开始到文件的结束处。如果在定义前需要引用该变量,则要在引用之前用关键字”extern”对该变量做”外部变量声明”,表示该变量是一个已经定义的外部变量。有了这个声明,就可以从声明处起合理地使用该变量了,例如: /* ** FileName : Extern Demo ** Author : Jelly Young ** Date : 2013/11/18 ** Description : More information, please go to http://www.sychzs.cn */ #include using namespace std; int main(int argc, char *argv[]) { extern int a;
0条大神的评论