stdcall调用约定

stdcall调用约定
stdcall调用约定

__stdcall,__cdecl,_cdecl,_stdcall,__fastcall,_fastcall 区别简介

2008-03-16 13:05:51| 分类:C++/VC/C#|字号订阅

1.

今天写线程函数时,发现msdn中对ThreadProc的定义有要求:DWORD WINAPI ThreadProc(LPVOID lpParameter);

不解为什么要用WINAPI宏定义,查了后发现下面的定义。于是乎需要区别__stdcall和

__cdecl两者的区别;#define CALLBACK __stdcall

#define WINAPI __stdcall

#define WINAPIV __cdecl

#define APIENTRY WINAPI

#define APIPRIVATE __stdcall

#define PASCAL __stdcall

#define cdecl _cdecl

#ifndef CDECL

#define CDECL _cdecl

#endif

几乎我们写的每一个WINDOWS API函数都是__stdcall类型的,首先,需要了解两者之间的区别:WINDOWS的函数调用时需要用到栈(STACK,一种先入后出的存储结构)。当函数调用完成后,栈需要清楚,这里就是问题的关键,如何清除??如果我们的函数使用了_cdecl,那么栈的清除工作是由调用者,用COM的术语来讲就是客户来完成的。这样带来了一个棘手的问题,不同的编译器产生栈的方式不尽相同,那么调用者能否正常的完成清除工作呢?答案是不能。如果使用__stdcall,上面的问题就解决了,函数自己解决清除工作。所以,在跨(开发)平台的调用中,我们都使用__stdcall(虽然有时是以WINAPI 的样子出现)。那么为什么还需要_cdecl呢?当我们遇到这样的函数如fprintf()它的参数是可变的,不定长的,被调用者事先无法知道参数的长度,事后的清除工作也无法正常的进行,因此,这种情况我们只能使用_cdecl。到这里我们有一个结论,如果你的程序中没有涉及可变参数,最好使用__stdcall关键字。

2.

__cdecl,__stdcall是声明的函数调用协议.主要是传参和弹栈方面的不同.一般c++用的是__cdecl,windows里大都用的是__stdcall(API)

__cdecl是C/C++和MFC程序默认使用的调用约定,也可以在函数声明时加上__cdecl关

键字来手工指定。采用__cdecl约定时,函数参数按照从右到左的顺序入栈,并且由调用函数者把参数弹出栈以清理堆栈。因此,实现可变参数的函数只能使用该调用约定。由于每一个使用__cdecl约定的函数都要包含清理堆栈的代码,所以产生的可执行文件大小会比较大。__cdecl可以写成_cdecl。

__stdcall调用约定用于调用Win32 API函数。采用__stdcall约定时,函数参数按照从右到左的顺序入栈,被调用的函数在返回前清理传送参数的栈,函数参数个数固定。由于函数体本身知道传进来的参数个数,因此被调用的函数可以在返回前用一条ret n指令直接清理传递参数的堆栈。__stdcall可以写成_stdcall。

__fastcall约定用于对性能要求非常高的场合。__fastcall约定将函数的从左边开始的两个大小不大于4个字节(DWORD)的参数分别放在ECX和EDX寄存器,其余的参数仍旧自

右向左压栈传送,被调用的函数在返回前清理传送参数的堆栈。__fastcall可以写成_fastcall

3.

__stdcall:

_stdcall 调用约定相当于16位动态库中经常使用的PASCAL调用约定。

在32位的VC++5.0中PASCAL调用约定不再被支持(实际上

它已被定义为__stdcall。除了__pascal外,__fortran和__syscall也不被支持),取而代之的是__stdcall调用约定。两者实质上是一致的,即函数的参数自右向左通过栈传递,被调

用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。

_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。

_cdecl:

_cdecl c调用约定, 按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。

_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。

__fastcall:

__fastcall调用约定是"人"如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参

数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。

_fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。

thiscall:

thiscall仅仅应用于"C++"成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。

naked call:

采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。

naked call不产生这样的代码。naked call不是类型修饰符,故必须和_declspec共同使用。

另附:

关键字__stdcall、__cdecl和__fastcall可以直接加在要输出的函数前,也可以在编译环境的Setting...\C/C++ \Code Generation项选择。当加在输出函数前的关键字与编译环境中的选择不同时,直接加在输出函数前的关键字有效。它们对应的命令行参数分别为/Gz、/Gd和/Gr。缺省状态为/Gd,即__cdecl。

要完全模仿PASCAL调用约定首先必须使用__stdcall调用约定,至于函数名修饰约定,可以通过其它方法模仿。还有一个值得一提的是WINAPI宏,Windows.h支持该宏,它可

以将出函数翻译成适当的调用约定,在WIN32中,它被定义为__stdcall。使用WINAPI宏可以创建自己的APIs。

名字修饰约定

1、修饰名(Decoration name)

“C”或者“C++”函数在内部(编译和链接)通过修饰名识别。修饰名是编译器在编译函数定义或者原型时生成的字符串。有些情况下使用函数的修饰名是必要的,如在模块定义文件里头指定输出“C++”重载函数、构造函数、析构函数,又如在汇编代码里调用“C””或“C++”函数等。

修饰名由函数名、类名、调用约定、返回类型、参数等共同决定。

2、名字修饰约定随调用约定和编译种类(C或C++)的不同而变化。函数名修饰约定随编译

种类和调用约定的不同而不同,下面分别说明。

a、C编译时函数名修饰约定规则:

__stdcall调用约定在输出函数名前加上一个下划线前缀,后面加上一个“@”符号和其参数的字节数,格式为_functionname@number。

__cdecl调用约定仅在输出函数名前加上一个下划线前缀,格式为_functionname。

__fastcall调用约定在输出函数名前加上一个“@”符号,后面也是一个“@”符号和其参数的字节数,格式为@functionname@number。

它们均不改变输出函数名中的字符大小写,这和PASCAL调用约定不同,PASCAL约定输出的函数名无任何修饰且全部大写。

b、C++编译时函数名修饰约定规则:

__stdcall调用约定:

1、以“?”标识函数名的开始,后跟函数名;

2、函数名后面以“@@YG”标识参数表的开始,后跟参数表;

3、参数表以代号表示:

X--void ,

D--char,

E--unsigned char,

F--short,

H--int,

I--unsigned int,

J--long,

K--unsigned long,

M--float,

N--double,

_N--bool,

....

PA--表示指针,后面的代号表明指针类型,如果相同类型的指针连续出现,以“0”代替,一个“0”代表一次重复;

4、参数表的第一项为该函数的返回值类型,其后依次为参数的数据类型,指针标识在其所指数据类型前;

5、参数表后以“@Z”标识整个名字的结束,如果该函数无参数,则以“Z”标识结束。

其格式为“?functionname@@YG*****@Z”或“?functionname@@YG*XZ”,例如

int Test1(char *var1,unsigned long)-----“?Test1@@YGHPADK@Z”

void Test2()-----“?Test2@@YGXXZ”

__cdecl调用约定:

规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YA”。

__fastcall调用约定:

规则同上面的_stdcall调用约定,只是参数表的开始标识由上面的“@@YG”变为“@@YI”。VC++对函数的省缺声明是“__cedcl“,将只能被C/C++调用.

CB在输出函数声明时使用4种修饰符号

//__cdecl

cb的默认值,它会在输出函数名前加_,并保留此函数名不变,参数按照从右到左的顺序依次传递给栈,也可以写成_cdecl和cdecl形式。

//__fastcall

她修饰的函数的参数将尽肯呢感地使用寄存器来处理,其函数名前加@,参数按照从左到右的顺序压栈;

//__pascal

它说明的函数名使用Pascal格式的命名约定。这时函数名全部大写。参数按照从左到右的顺序压栈;

//__stdcall

使用标准约定的函数名。函数名不会改变。使用__stdcall修饰时。参数按照由右到左的顺序压栈,也可以是_stdcall;

VC++对函数的省缺声明是"__cedcl",将只能被C/C++调用.

注意:

1、_beginthread需要__cdecl的线程函数地址,_beginthreadex和CreateThread需要__stdcall的线程函数地址。

2、一般WIN32的函数都是__stdcall。而且在Windef.h中有如下的定义:

#define CALLBACK __stdcall

#define WINAPI__stdcall

3、extern "C" _declspec(dllexport) int __cdecl Add(int a, int b);

typedef int (__cdecl*FunPointer)(int a, int b);

修饰符的书写顺序如上。

4、extern "C"的作用:如果Add(int a, int b)是在c语言编译器编译,而在c++文件使用,则需要在c++文件中声明:extern "C" Add(int a, int b),因为c编译器和c++编译器对函数名的解释不一样(c++编译器解释函数名的时候要考虑函数参数,这样是了方便函数重载,而在c语言中不存在函数重载的问题),使用extern "C",实质就是告诉c++编译器,该函数是c库里面的函数。如果不使用extern "C"则会出现链接错误。

一般象如下使用:

#ifdef _cplusplus

#define EXTERN_C extern "C"

#else

#define EXTERN_C extern

#endif

#ifdef _cplusplus

extern "C"{

#endif

EXTERN_C int func(int a, int b);

#ifdef _cplusplus

}

#endif

5、MFC提供了一些宏,可以使用AFX_EXT_CLASS来代替__declspec(DLLexport),并修饰类名,从而导出类,AFX_API_EXPORT来修饰函数,AFX_DATA_EXPORT来修饰变量

AFX_CLASS_IMPORT:__declspec(DLLexport)

AFX_API_IMPORT:__declspec(DLLexport)

AFX_DATA_IMPORT:__declspec(DLLexport)

AFX_CLASS_EXPORT:__declspec(DLLexport)

AFX_API_EXPORT:__declspec(DLLexport)

AFX_DATA_EXPORT:__declspec(DLLexport)

AFX_EXT_CLASS:#ifdef _AFXEXT

AFX_CLASS_EXPORT

#else

AFX_CLASS_IMPORT

6、DLLMain负责初始化(Initialization)和结束(Termination)工作,每当一个新的进程或者该进程的新的线程访问DLL时,或者访问DLL的每一个进程或者线程不再使用DLL或者结束时,都会调用DLLMain。但是,使用TerminateProcess或TerminateThread结束进程或者线程,不会调用DLLMain。

7、一个DLL在内存中只有一个实例

DLL程序和调用其输出函数的程序的关系:

1)、DLL与进程、线程之间的关系

DLL模块被映射到调用它的进程的虚拟地址空间。

DLL使用的内存从调用进程的虚拟地址空间分配,只能被该进程的线程所访问。

DLL的句柄可以被调用进程使用;调用进程的句柄可以被DLL使用。

DLLDLL可以有自己的数据段,但没有自己的堆栈,使用调用进程的栈,与调用它的应用程序相同的堆栈模式。

2)、关于共享数据段

DLL定义的全局变量可以被调用进程访问;DLL可以访问调用进程的全局数据。使用同一DLL的每一个进程都有自己的DLL全局变量实例。如果多个线程并发访问同一变量,则需要使用同步机制;对一个DLL的变量,如果希望每个使用DLL的线程都有自己的值,则应该使用线程局部存储(TLS,Thread Local Strorage)。

_stdcall介绍

stdcall调用约定: stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall。在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK。 stdcall调用约定声明的语法为(以前文的那个函数为例): int __stdcall function(int a,int b) stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈 3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸。 以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处 翻译成汇编语言将变成: push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数,注意此时自动把cs:eip入栈 而对于函数自身,则可以翻译为: push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复mov ebp,esp 保存堆栈指针mov eax,[ebp + 8H] 堆栈中ebp指向位置之前依次保存有ebp,cs:eip,a,b,ebp +8指向a add eax,[ebp + 0CH] 堆栈中ebp + 12处保存了b mov esp,ebp 恢复esp pop ebp ret 8 而在编译时,这个函数的名字被翻译成_function@8 注意不同编译器会插入自己的汇编代码以提供编译的通用性,但是大体代码如此。其中在函数开始处保留esp到ebp中,在函数结束恢复是编译器常用的方法。 从函数调用看,2和1依次被push进堆栈,而在函数中又通过相对于ebp(即刚进函数时的堆栈指针)的偏移量存取参数。函数结束后,ret 8表示清理8个字节的堆栈,函数自己恢复了堆栈。 cdecl调用约定:

回调函数与回调机制

回调函数与回调机制 1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: ?属于工作流的一个部分; ?必须按照工作流指定的调用约定来申明(定义); ?他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 如上图所示,工作流提供了两个对外接口(获取参数、显示结果),以回调函数的形式实现。 ?“获取参数”回调函数,需要工作流使用者设定工作流计算需要的参数。 ?“显示结果”回调函数,提供计算结果给工作流使用者。

再以Windows的枚举顶级窗体为例。函数EnumWindows用于枚举当前系统中的所有顶级窗口,其函数原型为: BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ); 其中lpEnumFunc是一个回调函数,他用于返回枚举过程中的获得的窗口的句柄。其定义约定为: BOOL CALLBACK EnumWindowsProc( HWND hwnd, // handle to parent window LPARAM lParam // application-defined value ); 在这个例子中,EnumWindows 是一个工作流,这个工作流用于遍历windows的所有窗口并获得其句柄。用户使用EnumWindows工作流的目的是想通过工作流来来获取窗口的句柄以便针对特定的一个或多个窗口进行相关处理。于是EnumWindows就扩展出接口lpEnumFunc,用于返回遍历的窗口句柄。 EnumWindows工作流的结束有两个方式:1,用户在回调函数中返回FALSE;2,再也找不到顶级窗口。我们可以推测EnumWindows的实现机制如下: 注:下列代码中的FindFirstTopWindows(), FindNextTopWindow()为假设的,Windows API 没有此函数,只是为了表明Enumwindows的内部流程。 BOOL EnumWindows( WNDENUMPROC lpEnumFunc, // callback function LPARAM lParam // application-defined value ) { BOOL bRet = TRUE; HWND hWnd = ::FindFirstTopWindows(); // 此函数是假设的,查找第一个顶级窗口 // 当hWnd为0时表示再也找不到顶级窗口 while( hWnd ) { bRet = (*lpEnumFunc)( hWnd, value ); if( !bRet) break; // 终止EnumWindows工作流; hWnd = ::FindNextWindow(); // 此函数是假设的,查找下一个顶级窗口 } } 在EnumWindows(...)函数中,实现了窗口枚举的工作流,他通过回调机制把用户关心(顶级窗口句柄)的和枚举工作流分开,用户不需要知道EnumWindows的具体实现,用户只要知道,设定了lpEnumFunc函数,然后把函数指针传给EnumWindwos就可以获得想要的窗口句柄。

第三章 使用MASM

标题:【原创】windows下32位汇编语言学习笔记第三章使用MASM 作者:jasonnbfan 时间: 2009-05-03,02:48:43 链接: https://www.360docs.net/doc/9a5046766.html,/showthread.php?t=87752 windows下32位汇编语言学习笔记第三章使用MASM 本章讲述的是masm 汇编的程序结构,基本语法,定义等,本章这些内容只是汇编指令里比较常用的,在下面的章节将要用到的指令。实际上汇编指令远不止这些。感兴趣可以参照其他的汇编书籍了解一下。不过对于本书下面的章节来说,这些指令基本上够用了。 Win32汇编程序的基本结构 从例子可以看出来,Win32汇编的结构很简单,下面简单分析下。 模式定义 .386 .model falt,stdcall option casemap:none 这个地方书上已经将的很清楚了。关于.386 .486 .586 .686 之类的指令集,我没找到资料,试验了一下写成.686也没什么问题。 include includelib语句 include windows.inc includelib kernel32.lib 这里的include 和C语言里的include 头文件一个道理,都是导入预先声明好的函数,包括定义好的各种结构。 includelib 就是指定连接的时候告诉连接器从那个lib里找你通过include引入并使用的函数,win32API都是以动态链接库的形式提供的,所以这里就需要对你使用的winAPI包含在那个dll里做到心中有数,不知道的就查msdn,每个API说明后面都有这个API包含在那个头文件中,比如: Header: Declared in Winuser.h; include Windows.h. winAPI是C语言写的,所以头文件都是.h的,汇编的头文件声明是.inc的,打开kernel32.inc 找找Exitprocess 的申明 ExitProcess PROTO :DWORD 你也可以不用预定义的.inc头文件,自己定义。 如果你使用了函数确没有包含对应的.lib,比如使用了ExitProcess函数,没有includelib kernel32.lib,连接时就会报错: error LNK2001: 无法解析的外部符号 __imp__ExitProcess@4 这个外部符号名就是你要调用的函数,名字很诡异吧,这里先有个了解,讲到调用约定的时候再详细说明。

C#调用API函数详细说明

C#:[DllImport("kernel32.dll")]是什么意思?? 这叫引入kernel32.dll这个动态连接库。 这个动态连接库里面包含了很多WindowsAPI函数,如果你想使用这面的函数,就需要这么引入。举个例子: [DllImport("kernel32.dll")] private static extern void 函数名(参数,[参数]); 函数名就是一个属于kernel32.dll里的一个函数。完了你就可以用那个函数了。 kernel32.dll调用kernel32.dll这个DLL里面的API接口! 系统API 例如 [DllImport("user32.dll")]//--引入API public static extern ReturnT ype FunctionName(type arg1,type arg2,...);//--声明方法 调用该方法是和调用普通方法没区别 DLL Import 属性 现在是更深入地进行探讨的时候了。在对托管代码进行P/Invoke 调用时,DllImportAttribute 类型扮演着重要的角色。DllImportAttribute 的主要作用是给CLR 指示哪个DLL 导出您想要调用的函数。相关DLL 的名称被作为一个构造函数参数传递给DllImportAttribute。 如果您无法肯定哪个DLL 定义了您要使用的Windows API 函数,Platform SDK 文档将为您提供最好的帮助资源。在Windows API 函数主题文字临近结尾的位置,SDK 文档指定了 C 应用程序要使用该函数必须链接的.lib 文件。在几乎所有的情况下,该.lib 文件具有与定义该函数的系统DLL 文件相同的名称。例如,如果该函数需要 C 应用程序链接到Kernel32.lib,则该函数就定义在Kernel32.dll 中。您可以在MessageBeep 中找到有关MessageBeep 的Platform SDK 文档主题。在该主题结尾处,您会注意到它指出库文件是User32.lib;这表明MessageBeep 是从User32.dll 中导出的。 可选的DllImportAttribute 属性 除了指出宿主DLL 外,DllImportAttribute 还包含了一些可选属性,其中四个特别有趣:EntryPoint、CharSet、SetLastError 和CallingConvention。 EntryPoint 在不希望外部托管方法具有与DLL 导出相同的名称的情况下,可以设置该属性来指示导出的DLL 函数的入口点名称。当您定义两个调用相同非托管函数的外部方法时,这特别有用。另外,在Windows 中还可以通过它们的序号值绑定到导出的DLL 函数。如果您需要这样做,则诸如“#1”或“#129”的EntryPoint 值指示DLL 中非托管函数的序号值而不是函数名。 CharSet 对于字符集,并非所有版本的Windows 都是同样创建的。Windows 9x 系列产品缺少重要的Unicode 支持,而Windows NT 和Windows CE 系列则一开始就使用Unicode。

调用类的方法

语法如下: 语法 [访问修饰符] 返回值的类型方法名([参数列表]){ //方法体 }

(1)访问修饰符 已经讲述过类的访问修饰符,其实同理,这里的方法的访问修饰符功能也是一样,public 表示公共的,private 表示私有的。 在程序中,如果将变量或者方法声明为public,就表示其他类可以访问,如果声明为private,

(2)方法的返回类型。 方法是供别人调用的,调用后可以返回一个值,这个返回值的数据类型就是方法的返回类型,可以是int、float、double、bool、string 等。如果方法不返回任何值,就使用void。

语法 return 表达式; 如果方法没有返回值,则返回类型应该使用void(空虚;空的),用于说明无返回值。 如:public void Singing() //无返回值 { Console.Write(“在唱歌。。。”); } return 语句做两件事情:表示已经完成,现在要离开这个方法;如果方法产生一个值,这个值放置在return 后面,即<表达式>部分。意思就是“离开该方法,并且将<表达式>的值返回给调用其的程序”。

注意:在编写程序的时候,一定要注意方法声明中返回值的类型和方法体中真正的返 回的值的类型是否匹配,如果不匹配,后果很严重。比如在下面这个ToString()方法中,返 回类型是String 类型,因此在方法体中必须用return 返回一个字符串,否则编译器将报错。

(3)方法名 定义一个方法都要有一个名称 注意:方法名主要用于调用这个方法时用,命名方法就像命名变量、类一样,要遵守一定的规则,如必须以字母、下划线“_”或“$”开头,绝对不能以数字开头。

C++中函数调用时的三种参数传递方式

在C++中,参数传递的方式是“实虚结合”。 ?按值传递(pass by value) ?地址传递(pass by pointer) ?引用传递(pass by reference) 按值传递的过程为:首先计算出实参表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的,然后把以求出的实参表达式的值一一存入到形参变量分配的存储空间中,成为形参变量的初值,供被调用函数执行时使用。这种传递是把实参表达式的值传送给对应的形参变量,故称这种传递方式为“按值传递”。 使用这种方式,调用函数本省不对实参进行操作,也就是说,即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。 [cpp]view plaincopy 1./* 2. pass By value 3.*/ 4.#include https://www.360docs.net/doc/9a5046766.html,ing namespace std; 6.void swap(int,int); 7.int main() 8.{ 9.int a = 3, b = 4; 10. cout << "a = " << a << ", b = " 11. << b << endl; 12. swap(a,b); 13. cout << "a = " << a << ", b = " 14. << b << endl; 15.return 0; 16.} 17.void swap(int x, int y) 18.{ 19.int t = x; 20. x = y; 21. y = t; 22.}

如果在函数定义时将形参说明成指针,对这样的函数进行调用时就需要指定地址值形式的实参。这时的参数传递方式就是地址传递方式。 地址传递与按值传递的不同在于,它把实参的存储地址传送给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。 [cpp]view plaincopy 1.#include https://www.360docs.net/doc/9a5046766.html,ing namespace std; 3.void swap(int*,int*); 4.int main() 5.{ 6.int a = 3, b = 4; 7. cout << "a = " << a << ", b = " 8. << b << endl; 9. swap(&a,&b); 10. cout << "a = " << a << ", b = " 11. << b << endl; 12. system("pause"); 13.return 0; 14.} 15.void swap(int *x,int *y) 16.{ 17.int t = *x; 18. *x = *y; 19. *y = t; 20.} 按值传递方式容易理解,但形参值的改变不能对实参产生影响。 地址传递方式虽然可以使得形参的改变对相应的实参有效,但如果在函数中反复利用指针进行间接访问,会使程序容易产生错误且难以阅读。

C语言函数调用规定

在C语言中,假设我们有这样的一个函数: int function(int a,int b) 调用时只要用result = function(1,2)这样的方式就可以使用这个函数。但是,当高级语言被编译成计算机可以识别的机器码时,有一个问题就凸现出来:在CPU中,计算机没有办法知道一个函数调用需要多少个、什么样的参数,也没有硬件可以保存这些参数。也就是说,计算机不知道怎么给这个函数传递参数,传递参数的工作必须由函数调用者和函数本身来协调。为此,计算机提供了一种被称为栈的数据结构来支持参数传递。 栈是一种先进后出的数据结构,栈有一个存储区、一个栈顶指针。栈顶指针指向堆栈中第一个可用的数据项(被称为栈顶)。用户可以在栈顶上方向栈中加入数据,这个操作被称为压栈(Push),压栈以后,栈顶自动变成新加入数据项的位置,栈顶指针也随之修改。用户也可以从堆栈中取走栈顶,称为弹出栈(pop),弹出栈后,栈顶下的一个元素变成栈顶,栈顶指针随之修改。 函数调用时,调用者依次把参数压栈,然后调用函数,函数被调用以后,在堆栈中取得数据,并进行计算。函数计算结束以后,或者调用者、或者函数本身修改堆栈,使堆栈恢复原装。 在参数传递中,有两个很重要的问题必须得到明确说明: 当参数个数多于一个时,按照什么顺序把参数压入堆栈 函数调用后,由谁来把堆栈恢复原装 在高级语言中,通过函数调用约定来说明这两个问题。常见的调用约定有:stdcall cdecl fastcall thiscall naked call stdcall调用约定 stdcall很多时候被称为pascal调用约定,因为pascal是早期很常见的一种教学用计算机程序设计语言,其语法严谨,使用的函数调用约定就是stdcall.在Microsoft C++系列的C/C++编译器中,常常用PASCAL宏来声明这个调用约定,类似的宏还有WINAPI和CALLBACK. stdcall调用约定声明的语法为(以前文的那个函数为例): int __stdcall function(int a,int b) stdcall的调用约定意味着:1)参数从右向左压入堆栈,2)函数自身修改堆栈3)函数名自动加前导的下划线,后面紧跟一个@符号,其后紧跟着参数的尺寸 以上述这个函数为例,参数b首先被压栈,然后是参数a,函数调用function(1,2)调用处翻译成汇编语言将变成: push 2 第二个参数入栈 push 1 第一个参数入栈 call function 调用参数,注意此时自动把cs:eip入栈 而对于函数自身,则可以翻译为: push ebp 保存ebp寄存器,该寄存器将用来保存堆栈的栈顶指针,可以在函数退出时恢复 mov ebp,esp 保存堆栈指针

函数的定义和调用

函数的定义和调用 7.2函数定义 函数定义的一般形式: 类型标识符函数名(形式参数表列) 函数定义函数首部不要以分号结尾 { 说明部分 执行部分 } 例: int max(int a,int b)/*函数首部*/ ○1类型标识符○2函数名○3形式参数表列 { /*函数体开始*/○4 int z;/*说明部分*/ if(a>b)z=a; /*执行部分*/ else z=b; return(z); } 说明:函数定义包括函数首部和函数体两部分。 ○1类型标识将是指函数返回值的类型,简称函数值类型。函数的返回值由函数中的return 语句获得,即return后的表达式的值,可以是简单类型、void类型或构造类型等,注意一般函数返回什么类型的数据,函数的类型就定义成相应的类型。void类型为空类型,表示函数没有返回值。如希望不返回值,可以定义函数类型为void类型,当函数值类型为int时,可省略函数类型的说明。关于return:函数的值只能通过return语句返回主调函数,返回函数值的类型和函数定义中函数的类型应保持一致,如果函数值为int型可以省略函数类型说明,不返回函数值的函数,明确定义成空类型。 ○2函数名是函数的标识符。函数名取名遵循c语言标识符的命名规则,区分大小写。函数名后的形式参数表列给出函数的形式参数及其类型说明。 ○3形式参数简称形参,形式参数及其类型说明放在函数名后的一对圆括号中.无论函数是否有形式参数,函数名后的圆括号不可省;圆括号内没有形式参数的函数我们称之为无参函数,有形式参数的函数我们称为有参函数。强调:没有形式参数圆括号也不能省。形式参数可以是各种类型的变量,形式为:形参1类型形参1,形参2类型形参2 各参数之间用逗号间隔。在进行函数调用时,主调函数将赋予这些形式参数实际的值。 ○4函数体:函数说明之后的花括号“{}”括起来的部分,包括声明部分和执行部分: 1)声明部分:用来对函数中使用的变量和函数作说明。 2)执行部分由基本语句组成.函数的功能由函数体内的各个语句的执行来实现。 解释函数 函数的调用 一个函数被定义后,程序中的其他函数就可以使用这个函数,这个过程称为函数调用。 1。函数调用的一般形式 函数名(实参表列);实际参数表中的参数可以是常数、变量或构造类型数据,各实参之间也是用逗号分隔。对无参函数调用时无实际参数表。 函数有以下三种调用方式: (1) 函数表达式:函数调用出现在一个表达式中、这种表达式称为函数表达式。例如w =max(x,y);此时要求函数返回一个确定的值.参加表达式的计算。这里把max的返回值

VC++深入详解 - 窗口的创建

1.4.2 窗口的创建 创建一个完整的窗口,需要经过下面几个操作步骤: *设计一个窗口类; *注册窗口类; *创建窗口; *显示及更新窗口。 下面的四个小分节将分别介绍创建窗口的过程。完整的例程请参见光盘中的例子代码Chapter1目录 下WinMain。 1.设计一个窗口类 一个完整的窗口具有许多特征,包括光标(鼠标进入该窗口时的形状)、图标、背景色等。窗口的创建过程类似于汽车的制造过程。我们在生产一个型号的汽车之前,首先要对该型号的汽车进行设计,在图纸上画出汽车的结构图,设计各个零部件,同时还要给该型号的汽车取一个响亮的名字,例如“奥 迪A6”。在完成设计后,就可以按照“奥迪A6”这个型号生产汽车了。 类似地,在创建一个窗口前,也必须对该类型的窗口进行设计,指定窗口的特征。当然,在我们设计一个窗口时,不像汽车的设计这么复杂,因为Windows已经为我们定义好了一个窗口所应具有的基本属性,我们只需要像考试时做填空题一样,将需要我们填充的部分填写完整,一种窗口就设计好了。 在Windows中,要达到作填空题的效果,只能通过结构体来完成,窗口的特征就是由WNDCLASS结构体来定义的。WNDCLASS结构体的定义如下(请读者自行参看MSDN): typedef struct _WNDCLASS { UINT style; WNDPROC lpfnWndProc; int cbClsExtra; int cbWndExtra; HANDLE hInstance; HICON hIcon; HCURSOR hCursor; HBRUSH hbrBackground;

LPCTSTR lpszMenuName; LPCTSTR lpszClassName; } WNDCLASS; 下面对该结构体的成员变量做一个说明。 第一个成员变量style指定这一类型窗口的样式,常用的样式如下: n CS_HREDRAW 当窗口水平方向上的宽度发生变化时,将重新绘制整个窗口。当窗口发生重绘时,窗口中的文字和图形将被擦除。如果没有指定这一样式,那么在水平方向上调整窗口宽度时,将不会重绘窗口。 n CS_VREDRAW 当窗口垂直方向上的高度发生变化时,将重新绘制整个窗口。如果没有指定这一样式,那么在垂直方向上调整窗口高度时,将不会重绘窗口。 n CS_NOCLOSE 禁用系统菜单的Close命令,这将导致窗口没有关闭按钮。 n CS_DBLCLKS 当用户在窗口中双击鼠标时,向窗口过程发送鼠标双击消息。 style成员的其他取值请参阅MSDN。 知识点在Windows.h中,以CS_开头的类样式(Class Style)标识符被定义为16位的常量,这些常量都只有某1位为1。在VC++开发环境中,利用goto definition功能,可以看 到CS_VREDRAW=0x0001,CS_HREDRAW=0x0002,CS_DBLCLKS =0x0008,CS_NOCLOSE=0x0200,读者可以将这些16进制数转换为2进制数,就可以发现它们都只有1位为1,并且为1的位各不相同。用这种方式定义的标识符称为“位标志”,我们可以使用位运算操作符来组合使用这些样式。例如,要让窗口在水平和垂直尺寸发生变化时发生重绘,我们可以使用位或(|)操作符将CS_HREDRAW和CS_VREDRAW组合起来,如style=CS_HREDRAW | CS_VREDRAW。假如有一个变量具有多个样式,而我们并不清楚该变量都有哪些样式,现在我们想要去掉该变量具有的某个样式,那么可以先对该样式标识符进行取反(~)操作,然后再和这个变量进行与(&)操作即可实现。例如,要去掉先前的style变量所具有的CS_VREDRAW样式,可以编写代 码:style=style & ~ CS_VREDRAW。 在Windows程序中,经常会用到这种位标志标识符,后面我们在创建窗口时用到的窗口样式,也是属于位标志标识符。

直调、回调、异调

1. 什么是回调函数 回调函数(callback Function),顾名思义,用于回调的函数。回调函数只是一个功能片段,由用户按照回调函数调用约定来实现的一个函数。回调函数是一个工作流的一部分,由工作流来决定函数的调用(回调)时机。回调函数包含下面几个特性: 1、属于工作流的一个部分; 2、必须按照工作流指定的调用约定来申明(定义); 3、他的调用时机由工作流决定,回调函数的实现者不能直接调用回调函数来实现工作流的功能; 2. 回调机制 回调机制是一种常见的设计模型,他把工作流内的某个功能,按照约定的接口暴露给外部使用者,为外部使用者提供数据,或要求外部使用者提供数据。 ======================================================= java回调机制: 软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。 同步调用:一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用; 回调:一种双向调用模式,也就是说,被调用方在接口被调用时也会调用对方的接口; 异步调用:一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。 回调和异步调用的关系非常紧密:使用回调来实现异步消息的注册,通过异步调用来实现消息的通知。 ======================================================== 用Java里的例子: package callbackexample; public interface ICallBack { //需要回调的方法public void postExec(); } 另外的一个类: package callbackexample; public class FooBar { //组合聚合原则 private ICallBack callBack; public void setCallBack(ICallBack callBack) { this.callBack = callBack; doSth(); } public void doSth() { callBack.postExec(); } } 第二个类在测试类里面,是一个匿名类: package callbackexample; public class Test { public static void main(String[] args) { FooBar foo = new FooBar(); foo.setCallBack(new

MATLAB函数的调用形式

MATLAB中函数的调用形式MATLAB软件是一种可用于科技开发的高效率工具软件,它将科学计算、函数绘图与快速编程集于一体,不仅功能强大,而且易学易用,深受广大科技工作者和理工科大学生的喜爱。正在逐渐成为理工科大学生必须掌握的基本工具。 1.求函数导数的命令,调用格式是: (1)y=diff(‘f(x)’) (2)diff(‘f(x)’) (3)y=’ f(x)’ ;diff(y,’x’) (4)syms 各种变量; y=f(x);diff(y,x) 一般调用格式是: diff(y,x,n) 2.定义符号变量,一般形式: syms x y a b t 注解: syms是定义符号变量的命令, 被定义的多个变量之间用空格隔开。 3.转变一个符号表达式S的显示形式: pretty(S) 注解:pretty(S)的作用是将符号表达式S显示成更符合数学习惯的形式。 4.输入格式: fplot (‘f(x)’,[X的左界,X的右界,Y的左界,Y 的右界] 注意:●在书写运算语句时,屏幕的同一行可以同时有多个语句, 但语句之间必须用逗号或分号隔开; ●命令语句以分号结尾时,屏幕不显示运行结果; ●命令语句以逗号或不用标点结尾时,屏幕将显示运行结果。

a=100/12 %显示格式为默认的短型实数格式 format rat %显示格式转换为有理格式a format long %显示格式转换为长型实数格式 a format %还原为默认的短型实数格

5.使用clear命令可以删除所有定义过的变量, 如果只是要删除其中的某几个变量,则应在clear后面指明要删除的变量名称。 6.使用clc 命令可以清除屏幕上所有显示的内容, 但不会删除内存中的变量 7.MATLAB提供了大量的函数,可以满足各种运算需要。(1)使用命令help elfun 可列出所有的初等数学函数名。(2)使用命令help elmat可列出大量的矩阵函数名。

关于DLL动态库调用

关于DLL动态库调用小结 引言 比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE 文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。 Windows系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL(Dynamic Linkable Library)文件,并可对它们单独编译和测试。在运行时,只有当EXE程序确实要调用这些DLL模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE文件的大小和对内存空间的需求,而且使这些DLL模块可以同时被多个应用程序使用。Windows自己就将一些主要的系统功能以DLL 模块的形式实现。 一般来说,DLL是一种磁盘文件,以.dll、.DRV、.FON、.SYS和许多以.EXE为扩展名的系统文件都可以是DLL。它由全局数据、服务函数和资源组成,在运行时被系统加载到进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL模块中包含各种导出函数,用于向外界提供服务。DLL可以有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL在内存中只有一个实例;DLL实现了代码封装性;DLL的编制与具体的编程语言及编译器无关。 在Win32环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。Windows在加载DLL模块时将进程函数调用与DLL文件的导出函数相匹配。Windows操作系统对DLL的操作仅仅是把DLL映射到需要它的进程的虚拟地址空间里去。DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有. 调用方式

Linux 复习题

《Linux System Programming》复习题 1.简述API及ABI。 答:API(Application Programming Interface)应用程序接口,API规定了软件模块之间在源代码层交互的接口。它通过提供一组标准接口(通常以函数的方式)进行抽象:一个程序片段(一般是较高层次的代码,但不见得一定是)可以调用另一个程序片段(通常位于较低层次)。 ABI(Application Binary Interface)应用程序二进制接口,ABI定义的是在特定的架构上两个或者多个软件模块之间的二进制接口。定义了一个应用如何和自己交互,如何和内核以及库进行交互。ABI主要关注的问题有调用约定、字节序、寄存器使用、系统调用、链接、库行为和二进制格式。在Linux系统中,每一个机器架构都有自己的ABI集合。 2.简述使用基于系统调用的文件处理函数读写文件的过程。 答:首先使用open函数打开需要调用的文件,open函数有两个参数,第一个是打开的文件路径,第二个是打开的模式,该模式可以为只读、只写等。open函数会返回打开文件的标记符fd。 利用fd可以对文件进行读写操作: 若进行读,则使用read函数,read函数有三个参数,分别是要读的文件的标记符fd,将内容读到何处对应的区域指针buf和读取的长度len。系统会直接将需要读取的内容存入到指定的缓冲区中。 若进行写,则使用write函数,write函数有三个参数,分别是要写的文件的标记符fd,将何处的内容写进文件对应的区域指针buf和写入的长度len。系统会直接将需要写入的内

容存入到指定的文件中。 最后需要利用close函数来关闭打开的文件。 3.简述常用基于系统调用的文件处理函数的使用方法,包括函数原型及参数意义。 答: (1)打开文件:通过open()函数调用来打开一个文件并获得一个文件描述符。 函数原型:#include #include #include int open (const char *name, int flags); int open (const char *name, int flags, mode_t, mode); 参数:name:文件路径名。 flags:O_RDONL Y只读、O_WRONL Y 只写、O_RDWR读写 mode:除非创建了新文件,否则mode参数会被省略;而当O_CREA T给出时则需要,mode参数用于指定新文件的权限。 返回值:调用成功时返回一个文件描述符,错误时返回-1。 (2)创建文件:通过creat()函数调用来创建一个文件并获得一个文件描述符。 函数原型:#include #include #include int creat (const char *name, mode_t mode); 参数:name:文件路径名。

五种排序的算法(包括主函数调用)

#include #define MAX 100 void Quicksort(int d[],int min,int max); void Shellsort(int r[],int n); void Bubblesort(int r[],int n); void StraInsSort(int R[],int n); void Selectsort(int r[],int n); //*************************主函数********************** void main() { int s,ch,n,x,i; int a[MAX]; int p; printf("请输入待排序列中数据的个数:"); scanf("%d",&n); printf("请输入排序前序列:"); for(s=1;s<=n;s++) scanf("%d",&a[s]); { printf("0 is exit,other is continue:"); scanf("%d",&x); while(x) { for(p=1;p<=5;p++) { for(i=1;i<20;i++) printf("%c ",p);printf("\n"); printf("please input your choice(1-5):"); printf("\n1.直接插入排序\t2.希尔排序\t3.冒泡排序\n4.快速排序\t5.直接选择排序\t6.堆排序\n"); for(i=1;i<20;i++) printf("%c ",p); printf("\n"); printf("请选择:"); scanf("%d",&ch); switch(ch) { case 1: printf("\n直接插入排序\n"); StraInsSort(a,n); break;

dll调用

目录 ?引言 ?调用方式 ?MFC中的DLL ?DLL入口函数 ?关于调用约定 ?关于DLL的函数 ?模块定义文件(.DEF) ?DLL程序和调用其输出函数的程序的关系 引言 比较大的应用程序都由很多模块组成,这些模块分别完成相对独立的功能,它们彼此协作来完成整个软件系统的工作。可能存在一些模块的功能较为通用,在构造其它软件系统时仍会被使用。在构造软件系统时,如果将所有模块的源代码都静态编译到整个应用程序EXE 文件中,会产生一些问题:一个缺点是增加了应用程序的大小,它会占用更多的磁盘空间,程序运行时也会消耗较大的内存空间,造成系统资源的浪费;另一个缺点是,在编写大的EXE 程序时,在每次修改重建时都必须调整编译所有源代码,增加了编译过程的复杂性,也不利于阶段性的单元测试。 W indows 系统平台上提供了一种完全不同的较有效的编程和运行环境,你可以将独立的程序模块创建为较小的DLL (Dy namic Linkab le L ibrary) 文件,并可对它们单独编译和测试。在运行时,只有当EXE 程序确实要调用这些DLL 模块的情况下,系统才会将它们装载到内存空间中。这种方式不仅减少了EXE 文件的大小和对内存空间的需求,而且使这些DLL 模块可以同时被多个应用程序使用。W indows 自己就将一些主要的系统功能以DLL 模块的形式实现。 一般来说,DLL 是一种磁盘文件,以.dll、.DRV、.FO N、.SYS 和许多以.EXE 为扩展名的系统文件都可以是DLL。它由全局数据、服务函数和资源组成,在运行时被系统加载到调用进程的虚拟空间中,成为调用进程的一部分。如果与其它DLL 之间没有冲突,该文件通常映射到进程虚拟空间的同一地址上。DLL 模块中包含各种导出函数,用于向外界提供服务。DLL 可以有自己的数据段,但没有自己的堆栈,使用与调用它的应用程序相同的堆栈模式;一个DLL 在内存中只有一个实例;DLL 实现了代码封装性;DLL 的编制与具体的编程语言及编译器无关。 在W in32 环境中,每个进程都复制了自己的读/写全局变量。如果想要与其它进程共享内存,必须使用内存映射文件或者声明一个共享数据段。DLL 模块需要的堆栈内存都是从运行进程的堆栈中分配出来的。W indows 在加载DLL

向其他进程注入代码的三种方法

向其他进程注入代码的三种方法 向其他进程注入代码的三种方法 本文章翻译自Robet Kuster的Three Ways to Inject Your Code into Another Process 一文,原版地址见下面。本文章版权归原作者所有。 原版地址:https://www.360docs.net/doc/9a5046766.html,/threa ... 152&msg=1025152 下载整个压缩包 下载WinSpy 作者:Robert Kuster 翻译:袁晓辉(https://www.360docs.net/doc/9a5046766.html, hyzs@https://www.360docs.net/doc/9a5046766.html,) 摘要:如何向其他线程的地址空间中注入代码并在这个线程的上下文中执行之。 目录: ●导言 ●Windows 钩子(Hooks) ●CreateRemoteThread 和LoadLibrary 技术 ○进程间通讯 ●CreateRemoteThread 和WriteProcessmemory 技术 ○如何使用该技术子类(SubClass)其他进程中的控件 ○什么情况下适合使用该技术 ●写在最后的话 ●附录 ●参考

●文章历史 导言: 我们在Code project(https://www.360docs.net/doc/9a5046766.html,)上可以找到许多密码间谍程序(译者注:那些可以看到别的程序中密码框内容的软件),他们都依赖于Windows钩子技术。要实现这个还有其他的方法吗?有!但是,首先,让我们简单回顾一下我们要实现的目标,以便你能弄清楚我在说什么。 要读取一个控件的内容,不管它是否属于你自己的程序,一般来说需要发送 WM_GETTEXT 消息到那个控件。这对edit控件也有效,但是有一种情况例外。如果这个edit控件属于其他进程并且具有ES_PASSWORD 风格的话,这种方法就不会成功。只有“拥有(OWNS)”这个密码控件的进程才可以用WM_GETTEXT 取得它的内容。所以,我们的问题就是:如何让下面这句代码在其他进程的地址空间中运行起来:::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer ); 一般来说,这个问题有三种可能的解决方案: 1. 把你的代码放到一个DLL中;然后用windows 钩子把它映射到远程进程。 2. 把你的代码放到一个DLL中;然后用CreateRemoteThread 和LoadLibrary 把它映射到远程进程。 3. 不用DLL,直接复制你的代码到远程进程(使用WriteProcessMemory)并且用CreateRemoteThread执行之。在这里有详细的说明: Ⅰ. Windows 钩子 示例程序:HookSpy 和HookInjEx Windows钩子的主要作用就是监视某个线程的消息流动。一般可分为: 1.局部钩子,只监视你自己进程中某个线程的消息流动。 2.远程钩子,又可以分为:

(全)windows+sdk编程系列文章

windows sdk编程系列文章---- 消息框 在本课中,我们将用C语言写一个 Windows 程序,程序运行时将弹出一个消息框并显示"hello world"。 理论: Windows 为编写应用程序提供了大量的资源。其中最重要的是Windows API (Application Programming Interface)。 Windows API是一大组功能强大的函数,它们本身驻扎在 Windows 中供人们随时调用。这些函数的大部分被包含在几个动态链接库(DLL)中,譬如:kernel32.dll、 user32.dll 和 gdi32.dll。 Kernel32.dll中的函数主要处理内存管理和进程调度;user32.dll 中的函数主要控制用户界面;gdi32.dll中的函数则负责图形方面的操作。除了上面主要的三个动态链接库,您还可以调用包含在其他动态链接库中的函数,当然您必须要有关于这些函数的足够的资料。 动态链接库,顾名思义,这些 API 的代码本身并不包含在 Windows 可执行文件中,而是当要使用时才被加载。为了让应用程序在运行时能找到这些函数,就必须事先把有关的重定位信息嵌入到应用程序的可执行文件中。这些信息存在于引入库中,由链接器把相关信息从引入库中找出插入到可执行文件中。您必须指定正确的引入库,因为只有正确的引入库才会有正确的重定位信息。 当应用程序被加载时 Windows 会检查这些信息,这些信息包括动态链接库的名字和其中被调用的函数的名字。若检查到这样的信息,Windows 就会加载相应的动态链接库,并且重定位调用的函数语句的入口地址,以便在调用函数时控制权能转移到函数内部。 如果从和字符集的相关性来分,API 共有两类:一类是处理 ANSI 字符集的,另一类是处理 UNICODE 字符集的。前一类函数名字的尾部带一个"A"字符,处理UNICODE的则带一个"W"字符(我想"W"也许是代表宽字符的意思吧)。我们比较熟悉的ANSI字符串是以 NULL 结尾的一串字符数组,每一个ANSI字符是一个 BYTE 宽。对于欧洲语言体系,ANSI 字符集已足够了,但对于有成千上万个唯一字符的几种东方语言体系来说就只有用 UNICODE 字符集了。每一个 UNICODE 字符占有两个 BYTE 宽,这样一来就可以在一个字符串中使用 65336 个不同字符了。 几乎每一个API都有处理这两种字符集的形式,例如:MessageBoxA和MessageBoxW, 其中MessageBoxA是适用于处理ANSI字符集的API。MessageBoxW是适用于UNICODE字符集的API。在c中为了调用方便,对这两个API进行了宏定义。统一使用MessageBox 。编译的时候会根据编译设置决定是使用MessageBoxA还是使用MessageBoxW。 下面摘自WINUSER.H中关于MessageBox 的定义。 WINUSERAPI int WINAPI MessageBoxA( HWND hWnd , LPCSTR lpText, LPCSTR lpCaption, UINT uType); WINUSERAPI int WINAPI MessageBoxW( HWND hWnd , LPCWSTR lpText, LPCWSTR lpCaption, UINT uType); #ifdef UNICODE #define MessageBox MessageBoxW #else #define MessageBox MessageBoxA #endif 其中WINUSERAPI 是一个宏,该宏定义了其修饰的函数是从其他库中导入的,还是自身定义的。 #if !defined(_USER32_) #define WINUSERAPI DECLSPEC_IMPORT #else #define WINUSERAPI #endif 为了两种字符集都可以支持,在处理字符时,我们使用_T("")宏。该宏会根据你的设置,将你的字符串转变为相应的字符集。 例子:(见光盘Helloword)

相关文档
最新文档