一 可变参数
(1). 实现可变参数
C++ 用 <...> 表示这里可变(任意多个)的参数,我们又知道 参数是又右向左一次入栈的,有了这两个条件只要知道 可变参数的类型,我们就可以实现可变参数
int
Add(
int
size, ... )
{
int
sum = 0;
int
*p = &
size
+ 1;
while
(
size
)
{
sum += (*(p++) );
--
size
;
}
return
sum;
}
其中 size 表示可变参数的个数,
(2). 运行库实现的可变参数
其实在运行库(
stdarg.h)
里已经提供了类似的宏实现的可变参数
- va_start(可变参数前的首个参数, 可变参数个数) -- 设置起始位置
- va_arg(可变参数前的首个参数,可变参数类型) -- 返回当前可变参数,并指向下一个
- va_end(可变参数前的首个参数) -- 结束可变参数访问
int
Add(
int
size, ... )
{
int
sum = 0;
va_list
ap;
va_start(ap,
size
);
while
(
size
)
{
sum +=
va_arg
(ap,
int
);
--
size
;
}
va_end(ap);
return
sum;
}
二 链接库
(1). 什么是库
链接库可分为 库 和 链接(方式)两部分,当开发一个较大的
项目
或该
项目
有多人参与时,将该项目按照某种条件分为多个部分(库),并且每个
部分(库)
可以单独编译测试,在使用时多个部分(库)之间只需进行简单调用(链接)即可, 这样开发
效率
会大幅提升. 而且也可简化以后程序升级和维护工作。
(2). 链接方式
库按链接方式可分为
静态链接库 和 动态连接库
三 静态链接库
(1). 什么是静态链接库
静态链接库本身可单独编写,编译和测试,但当整个项目需要发布时,静态链接库会作为目标文件的一部分编译在一起。通常后缀为 ".lib" .
(2). 示例
// a.cpp -> a.lib
// $ cl /c a.cpp
// $ lib a.obj /out:a.lib
#pragma
once
#define
WIN32_LEAN_AND_MEAN
#include
<windows.h>
BOOL
APIENTRY DllMain(
HMODULE
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:
break ;
}
return
TRUE ;
}
__declspec (
dllexport )
double Add (
double
a ,
double
b )
{
return
a +
b ;
}
__declspec (
dllexport )
double Sub (
double
a ,
double
b )
{
return
a -
b ;
}
__declspec (
dllexport )
double Mul (
double
a ,
double
b )
{
return
a *
b ;
}
...........................................
// b.cpp -> b.exe
// $ cl /c b.cpp
// $ link b.obj a.lib
#include
<iostream>
#include
<windows.h>
using
namespace std;
__declspec (
dllexport )
double Sub (
double a,
double b);
int
_tmain (
int
argc,
_TCHAR*
argv [])
{
cout << Sub(5.0,5.0);
return 0;
};
四 动态链接库
(1). 什么是动态链接库
动态
链接库本身可单独编写,编译和测试,当整个项目需要发布时,动态链接库会作为单独文件独立存储,当虚调用动态链接库中的代码时,需要使用特定方法先将其加载进内存,一般后缀为“.dll”.
(2). 示例
// a.cpp -> a.lib
// $ cl /LDd a.cpp
#pragma
once
#define
WIN32_LEAN_AND_MEAN
#include
<windows.h>
BOOL
APIENTRY DllMain(
HMODULE
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:
break ;
}
return
TRUE ;
}
extern
"C"
__declspec (
dllexport )
double Add (
double
a ,
double
b )
{
return
a +
b ;
}
extern "C"
__declspec (
dllexport )
double Sub (
double
a ,
double
b )
{
return
a -
b ;
}
extern "C"
__declspec (
dllexport )
double Mul (
double
a ,
double
b )
{
return
a *
b ;
}
............
// b.cpp -> b.exe
// $ cl b.cpp
#include
<iostream>
#include
<windows.h>
using
namespace std;
typedef double (*func) ( double, double );
int
_tmain (
int
argc,
_TCHAR*
argv[])
{
func pFunc;
HINSTANCE hinstLib =
LoadLibraryA(
"a.dll");
if (hinstLib ==
NULL)
{
cout <<
"动态库加载失败" << endl;
return 1;
}
pFunc = (
func)
GetProcAddress(hinstLib,
"Add" );
if (pFunc ==
NULL)
{
cout <<
"函数加载失败" << endl;
FreeLibrary(hinstLib);
return 1;
}
cout << pFunc(5.0,5.0);
FreeLibrary(hinstLib);
return 0;
};
说明:
由于 C++的默认函数调用方式为 __stdcall ,而这种调用方式在编译时函数会被改名,
所以要用 < extern "C" > __cdecl 而 C 默认方式
(3). 通过植入桩代码实现动态链接
上面的代码在调用动态链接库前,调用方对库是“一无所知”的,必须在调用时先手动加载动态库(dll)。而另一种编译方式可以预先在 调用方链接时插入桩代码,从而可以直接调用 dll 中的导出函数, 这里我们要用到动态库的 lib 文件。编译命令如下
$ cl /LD a.cpp
$ cl /c b.cpp
$ link b.obj a.lib