常见问题总结
大家好,我在http://expert.csdn.net/Expert/topic/2485/2485955.xml?temp=.4244348上贴的太长,现在这整理一下我在那贴的内容,如果各位也遇到过类似问题,请大家看看吧。
回调函数,就是由你自己写的。你需要调用另外一个函数,而这个函数的其中一个参数,就
是你的这个回调函数名。这样,系统在必要的时候,就会调用你写的回调函数,这样你就可
以在回调函数里完成你要做的事。
capVideoStreamCallback 这个回调函数,我没有做过,看了一下Help,应该是通过发送消息
WM_CAP_SET_CALLBACK_VIDEOSTREAM,来设置的,或者调用宏capSetCallbackOnVideoStream
来完成的。这样设定之后,系统在进行图像捕捉的过程中,就会自动调用你写的回调函数。
这个回调函数的函数体需要你自已来写,然后在另一函数中调用,即是说:
LRESULT CALLBACK capVideoStreamCallback(HWND hWnd,LPVIDEOHDR lpVHdr)
{
........
}
//在另一函数中调用它(即以capVideoStreamCallback的地址作为一参数)
Function(1,......,capVideoStreamCallback,.....);
这就好像我们用定时器一样,在设置定时器时需要为定时器设置一回调函数:
::SetTimer(m_hWnd,1,1000,(TIMERPROC)TMProc);这里的TMProc就是回调函数
模块A有一个函数foo,它向模块B传递foo的地址,然后在B里面发生某种事件(event)时,通过从A里面传递过来的foo的地址调用foo,通知A发生了什么事情,让A作出相应反应。
那么我们就把foo称为回调函数。
“这个回调函数不是VFW.h中声明的么,“
----那是声明了回调函数原型,是告诉你传递进来的回调函数必须和它定义的原型保持一致。
”为什么要自己写函数体呢?“
----比如在上面模块B里面,它只知道当event发生时,向模块A发出通知,具体怎么回应这个事件,不是B所关心的,也不是B所能预料到的。
你站在A的角度上思考,当然要你自己作出对event的反应,也就是你要自己写函数体。
你如果明白了C++里面的函数指针,就很容易理解回调函数了。
"不知道系统调用后有什么结果,或者我怎么利用这个结果啊"
---如果你向系统传递一个回调函数地址,那么你的程序就相当于上面我说的模块A,系统就相当于模块B,系统只是调用你的函数,它根本不可能知道会有什么结果。
你怎么利用这个结果,看你是怎么定义这个回调函数的。
回调函数和回调机制是不同的概念,。,,函数是被调用的,但是回调机制在不同的语言中不都是以函数指针来实现的。。。。比如c#...一般的在windows api 中,会调都是使用函数指针实现的。。。
函数调用方式
我们知道在进行函数调用时,有几种调用方法,主要分为C式,Pascal式.在C和C++中
C式调用是缺省的,类的成员函数缺省调用为_stdcall。二者是有区别的,下面我们用
实例说明一下:
1. __cdecl :C和C++缺省调用方式
例子:
void Input( int &m,int &n);/*相当于void __cdecl Input(int &m,int &n);*/
以下是相应的汇编代码:
00401068 lea eax,[ebp-8] ;取[ebp-8]地址(ebp-8),存到eax
0040106B push eax ;然后压栈
0040106C lea ecx,[ebp-4] ;取[ebp-4]地址(ebp-4),存到ecx
0040106F push ecx ;然后压栈
00401070 call @ILT+5(Input) (0040100a);然后调用Input函数
00401075 add esp,8 ;恢复栈
从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然
后压栈ebp-4,然后调用函数Input,最后Input函数调用结束后,利用esp+8恢复栈。由
此可见,在C语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且恢复
堆栈。
下面看一下:地址ebp-8和ebp-4是什么?
在VC的VIEW下选debug windows,然后选Registers,显示寄存器变量值,然后在
选debug windows下面的Memory,输入ebp-8的值和ebp-4的值(或直接输入ebp-8和-4),
看一下这两个地址实际存储的是什么值,实际上是变量 n 的地址(ebp-8),m的地址
(ebp-4),由此可以看出:在主调用函数中进行实参的压栈并且顺序是从右到左。另外,
由于实参是相应的变量的引用,也证明实际上引用传递的是变量的地址(类似指针)。
总结:在C或C++语言调用中默认的函数修饰_cdecl,由主调用函数进行参数压栈并且
恢复堆栈,实参的压栈顺序是从右到左,最后由主调函数进行堆栈恢复。由于主调用
函数管理堆栈,所以可以实现变参函数。另外,命名修饰方法是在函数前加一个下划
线(_).
2. WINAPI (实际上就是PASCAL,CALLBACK,_stdcall)
例子:
void WINAPI Input( int &m,int &n);
看一下相应调用的汇编代码:
00401068 lea eax,[ebp-8]
0040106B push eax
0040106C lea ecx,[ebp-4]
0040106F push ecx
00401070 call @ILT+5(Input) (0040100a)
从以上调用Input函数的过程可以看出:在调用此函数之前,首先压栈ebp-8,然
后压栈ebp-4,然后调用函数Input,在调用函数Input之后,没有相应的堆栈恢复工作
(为其它的函数调用,所以我没有列出)
下面再列出Input函数本身的汇编代码:(实际此函数不大,但做汇编例子还是大了些,
大家可以只看前和后,中间代码与此例子无关)
39: void WINAPI Input( int &m,int &n)
40: {
00401110 push ebp
00401111 mov ebp,esp
00401113 sub esp,48h
00401116 push ebx
00401117 push esi
00401118 push edi
00401119 lea edi,[ebp-48h]
0040111C mov ecx,12h
00401121 mov eax,0CCCCCCCCh
00401126 rep stos dword ptr [edi]
41: int s,i;
42:
43: while(1)
00401128 mov eax,1
0040112D test eax,eax
0040112F je Input+0C1h (004011d1)
44: {
45: printf("\nPlease input the first number m:");
00401135 push offset string "\nPlease input the first number m"... (004260b8)
0040113A call printf (00401530)
0040113F add esp,4
46: scanf("%d",&m);
00401142 mov ecx,dword ptr [ebp+8]
00401145 push ecx
00401146 push offset string "%d" (004260b4)
0040114B call scanf (004015f0)
00401150 add esp,8
47:
48: if ( m= s )
004011B3 mov eax,dword ptr [ebp+8]
004011B6 mov ecx,dword ptr [eax]
004011B8 cmp ecx,dword ptr [ebp-4]
004011BB jl Input+0AFh (004011bf)
57: break;
004011BD jmp Input+0C1h (004011d1)
58: else
59: printf(" m < n*(n+1)/2,Please input again!\n");
004011BF push offset string " m < n*(n+1)/2,Please input agai"... (00426060)
004011C4 call printf (00401530)
004011C9 add esp,4
60: }
004011CC jmp Input+18h (00401128)
61:
62: }
004011D1 pop edi
004011D2 pop esi
004011D3 pop ebx
004011D4 add esp,48h
004011D7 cmp ebp,esp
004011D9 call __chkesp (004015b0)
004011DE mov esp,ebp
004011E0 pop ebp
004011E1 ret 8
最后,我们看到在函数末尾部分,有ret 8,明显是恢复堆栈,由于在32位C++中,
变量地址为4个字节(int也为4个字节),所以弹栈两个地址即8个字节。
由此可以看出:在主调用函数中负责压栈,在被调用函数中负责恢复堆栈。因此不能
实现变参函数,因为被调函数不能事先知道弹栈数量,但在主调函数中是可以做到的,
因为参数数量由主调函数确定。
下面再看一下,ebp-8和ebp-4这两个地址实际存储的是什么值,ebp-8地址存储的是n
的值,ebp -4存储的是m的值。说明也是从右到左压栈,进行参数传递。
总结:在主调用函数中负责压栈,在被调用函数中负责弹出堆栈中的参数,并且
负责恢复堆栈。因此不能实现变参函数,参数传递是从右到左。另外,命名修饰方法
是在函数前加一个下划线(_),在函数名后有符号(@),在@后面紧跟参数列表中的参数
所占字节数(10进制),如:void Input(int &m,int &n),被修饰成:_Input@8
对于大多数api函数以及窗口消息处理函数皆用 CALLBACK ,所以调用前,主调函数会
先压栈,然后api函数自己恢复堆栈。
如:
push edx
push edi
push eax
push ebx
call getdlgitemtexta
问题点数:100、回复次数:8Top
1 楼yintongshun(左岸思雨)回复于 2003-12-02 11:50:30 得分 100
Win32程序函数调用时堆栈变化情况分析
2002-12-10 18:19:13 PCVC.NET halk 阅读次数: 4804
在经典的汇编语言教程中,函数调用时堆栈的使用都是着重讲解的问题。如今随着高级语言的越来越完善,单纯使用汇编开发的程序已经不多了。但对函数调用时堆栈动向的了解仍有助于我们明晰程序的执行流程,从而在程序编写和调试的过程中有一个清晰的思路。
一.调用约定
在Win32中,有关函数的调用主要有两种约定。
1._stdcall
以__stdcall方式调用的函数有以下特征:
• 参数由右至左压栈
• 调用返回时,堆栈由被调函数调整
2.__cdecl
__cdecl约定是C/C++函数的默认调用约定。它有以下特征:
• 参数由右至左压栈
• 调用返回时,堆栈由调用者调整
二.Win32函数调用过程
1. 压入参数
这里依据以上的调用方式将调用者给出的参数一一压入堆栈。
2. 压入断点
当程序执行到Call指令的时候,当前语句的地址作为断点地址压入堆栈。
3. 跳转
eip的值被重新设置为被调函数的起始地址。
4. mov ebp, esp
这里ebp被用来在堆栈中寻找调用者压入的参数,同时作为调用者堆栈指针的一个备份。在此前还应该执行一条:
push ebp
把ebp中原来的数值保存。
5. sub esp,N
这里N是函数内局部变量的总字节数加上一个整数,一般为40。此后esp即为被调函数的堆栈指针了。
6. 初始化esp ~ esp-N之间的N字节空间
这是对堆栈中已分配给局部变量使用的内存空间的初始化,一般全部设置为0xcc。
7. 顺序执行函数内语句。
此时函数的堆栈位于所有局部变量的内存空间之后,二者之间一般有40字节的隔离带。
8.返回
为保障调用的正常返回,函数内应当保证规范使用堆栈,使即将返回的时候esp的值恢复为执行第一条语句前的状态。说明白点,就是每一条push都要有相应的pop。
调用返回的过程如下:
mov esp, ebp
执行后,esp恢复为调用者的堆栈指针,栈顶除断点地址外,还存有原ebp的值和调用时压入的参数。
然后依次弹出ebp的值和断点地址。如果是__cdecl约定则直接返回调用者,调用者将负责调整堆栈,丢弃调先前压入的参数。如果是__stdcall则这个工作由被调函数来执行。
程序样例如下:
……
0040B8E8 push 1 ;压入参数
0040B8EA call 00401028 ;调用函数
……
00401028 jmp 0040b7c0 ;跳转到函数入口
……
0040B7C0 push ebp ;保存ebp
0040B7C1 mov ebp,esp
0040B7C3 sub esp,44h ;设置函数的堆栈指针,此函数中有4
;字节的局部变量
0040B7C6 push ebx
0040B7C7 push esi
0040B7C8 push edi
0040B7C9 lea edi,[ebp-44h]
0040B7CC mov ecx,11h
0040B7D1 mov eax,0CCCCCCCCh
0040B7D6 rep stos dword ptr [edi] ;初始化局部变量空间
0040B7D8 mov eax,dword ptr [ebp+8]
0040B7DB mov dword ptr [ebp-4],eax
……
0040B7DE pop edi ;弹出曾压栈的数据
0040B7DF pop esi
0040B7E0 pop ebx
0040B7E1 mov esp,ebp ;恢复调用者的堆栈
0040B7E3 pop ebp ;弹出原ebp值
0040B7E4 ret 4 ;返回并将堆栈向上调整4字节。
;此处为__stdcall约定,所以由函数调
;整堆栈
相应的C代码如下:
void __stdcall fun(int);
int main(void)
{
……
fun(1);
……
return 0;
}
void __stdcall fun(int para)
{
int localpara = para;
……
}
C 与C++的两种调用方法
(1)_stdcall:用于Win32 API函数和COM+接口。它从右向左将参数推入堆栈,被调函数在返回之前从堆栈中弹出自己的参数。从堆栈中弹出自己参数的函数不支持参数数目的变化。
(2)_cdecl:C和C++默认方式)。它也是从右向左传递参数。但是被调函数不负责从堆栈中弹出参数,调用函数将在函数调用返回后晴空堆栈。
CALLBACK采用方式(1)。它称为回调函数,即供系统调用的函数。
例如窗口函数、定时处理函数、线程处理函数等。
CALLBACK 是老式叫法,现在微软都改为WINAPI.
至于编译器的解释上。c与c++搞的很麻烦。
我也记不住每次碰到都去查一下.
我列个表:
语言 调用约定 输出方式
c stdcall _SomeFunctionname@Number
c cdecl _SomeFunctionname
C++的烦的要死。
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”。
编写DLL的时候输出函数老是不认就是编译器解释的不同造成的。EXTERN "C"就是为了统一。全部用c方式解释。
两种声明是不同的,vc默认的调用是__cdecl ,所以 extern "C" __declspec(dllimport) int mydllfunc(int);是extern "C" __declspec(dllimport) int __cdecl mydllfunc(int);
因此和extern "C" __declspec(dllimport) int __stdcall mydllfunc(int);是有区别的
Top
2 楼yintongshun(左岸思雨)回复于 2003-12-02 11:50:46 得分 0
调用约定
调用约定(Calling convention)决定以下内容:函数参数的压栈顺序,由调用者还是被调用者把参数弹出栈,以及产生函数修饰名的方法。MFC支持以下调用约定:
_cdecl
按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于“C”函数或者变量,修饰名是在函数名前加下划线。对于“C++”函数,有所不同。
如函数void test(void)的修饰名是_test;对于不属于一个类的“C++”全局函数,修饰名是?test@@ZAXXZ。
这是MFC缺省调用约定。由于是调用者负责把参数弹出栈,所以可以给函数定义个数不定的参数,如printf函数。
_stdcall
按从右至左的顺序压参数入栈,由被调用者把参数弹出栈。对于“C”函数或者变量,修饰名以下划线为前缀,然后是函数名,然后是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是_func@12。对于“C++”函数,则有所不同。
所有的Win32 API函数都遵循该约定。
_fastcall
头两个DWORD类型或者占更少字节的参数被放入ECX和EDX寄存器,其他剩下的参数按从右到左的顺序压入栈。由被调用者把参数弹出栈,对于“C”函数或者变量,修饰名以“@”为前缀,然后是函数名,接着是符号“@”及参数的字节数,如函数int func(int a, double b)的修饰名是@func@12。对于“C++”函数,有所不同。
未来的编译器可能使用不同的寄存器来存放参数。
thiscall
仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压栈。thiscall不是关键词,因此不能被程序员指定。
naked call
采用1-4的调用约定时,如果必要的话,进入函数时编译器会产生代码来保存ESI,EDI,EBX,EBP寄存器,退出函数时则产生代码恢复这些寄存器的内容。naked call不产生这样的代码。
naked call不是类型修饰符,故必须和_declspec共同使用,如下:
__declspec( naked ) int func( formal_parameters )
{
// Function body
}
过时的调用约定
原来的一些调用约定可以不再使用。它们被定义成调用约定_stdcall或者_cdecl。例如:
#define CALLBACK __stdcall
#define WINAPI __stdcall
#define WINAPIV __cdecl
#define APIENTRY WINAPI
#define APIPRIVATE __stdcall
#define PASCAL __stdcall
表7-1显示了一个函数在几种调用约定下的修饰名(表中的“C++”函数指的是“C++”全局函数,不是成员函数),函数原型是void CALLTYPE test(void),CALLTYPE可以是_cdecl、_fastcall、_stdcall。
表7-1 不同调用约定下的修饰名
调用约定
extern “C”或.C文件
.cpp, .cxx或/TP编译开关
_cdecl
_test
?test@@ZAXXZ
_fastcall
@test@0
?test@@YIXXZ
_stdcall
_test@0
?test@@YGXXZ
函数调用的几个概念:_stdcall,_cdecl....
(剑灵时空-www.SwordElf.com)
原作者: csdn,sinman
来自于: 不详
共有213位读者阅读过此文
内容:
左通过栈传递,被调用的函数在返回前清理传送参数的内存栈,但不同的是函数名的修饰部分(关于函数名的修饰部分在后面将详细说明)。
_stdcall是Pascal程序的缺省调用方式,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,在函数名后加上"@"和参数的字节数。
2、C调用约定(即用__cdecl关键字说明)按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的(正因为如此,实现可变参数的函数只能使用该调用约定)。另外,在函数名修饰约定方面也有所不同。
_cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小会比调用_stdcall函数的大。函数采用从右到左的压栈方式。VC将函数编译后会在函数名前面加上下划线前缀。是MFC缺省调用约定。
3、__fastcall调用约定是“人”如其名,它的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
_fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上"@"前缀,在函数名后加上"@"和参数的字节数。
4、thiscall仅仅应用于“C++”成员函数。this指针存放于CX寄存器,参数从右到左压。thiscall不是关键词,因此不能被程序员指定。
5、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。
2)名字修饰约定
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;
Top
3 楼yintongshun(左岸思雨)回复于 2003-12-02 11:51:20 得分 0
最高窗口的实现
VC++中对基于SDI、MDI的运用程序,要实现最高窗口,只要在框架窗口类CMainFrame中的PreCreateWindow()函数中加入“cs.dwExStyle =WS_EX_TOPMOST;”即可。
而对基于对话框的运用程序,如何实现最高窗口却很少论及,以下便是一种实现方法。
重载要实观最高窗口的对话框的OnInitDialog()函数,方法是进入ClassWizard,在Object ID列表框中选择该对话框的ID,在Message列表框中选择WM_INITDIALOGG,单击Add Function按钮后,即对onlnitDialog函数进行了重载。再按下Edit code按钮,加入以下语句:
const CWnd * pWndInsertAfter;
pWndInsertAfter = &wndTopMost;
SetWindowPos(pWndInsertAfter,0,0,0,0,SWP_NOSIZE | SWP_NOMOVE);
函数SetWindowPos原型为BOOL SetWindowPos(const CWnd * pWndInsertAfter,int x,int y,int cx,int cy,UINT nFlags);
pWndInsertAfter为指向标识窗口类型的CWnd对象的指针。
x,y为窗口左上角的坐标。
cx,cy为窗口的宽与高。
nFlags确定窗口的大小及位置。当为SWP_NOSIZE时,忽略cx,cy。当为SWP_NOMOVE时,忽略x,y。
改变程序的窗口设置
可以在视图类的PRECREATEWINDOW中加入语句改变程序的窗口设置。程序在生成窗口之前调用这个函数。此函数接受CREATESTRUCT结构的引用,这个结构的字段存放生成窗口时MFC指定的窗口特性。如果赋值这个结构的一个或几个字段,则MFC用所赋值而不是缺省值。CREATESTRUCT结构的IpszClass字段存放WINDOWS窗口类名,这是WINDOWS系统维护的数据结构,存放窗口生成时一组一般特性。加入的AfxRegisterWndClass生成新的WINDOWS窗口类,然后将类名赋予结构的IpszClass字段,使视图窗口用存放在这个WINDWOS窗口类中的用户化特性生成。AfxRegisterWndClass是MFC提供的全局函数。
BOOL CminiDrawView::PreCreateWindow(CREATESTRUCT &cs)
{
M_Class=AfxRegisterWndClass
(CS_HREDRAW|CS_VREDRAW,
0,
(HBRUSH)::GetStockObject(WHITE_BRUSH);
0);
cs.lpszClass=m_ClassName;
returen cview::precreatewindow(cs);
}
第一个参数指定类样式。第二个参数指定WINDOWS在窗口内自动显示的鼠标光标,赋值为0则是不准备显示光标。第三个参数提供标准的白色刷笔。最后一个参数指定窗口图标,由于视图窗口不显示图标,他赋值为0。
纯资源DLL的编写
下面的例子是一个纯资源DLL的源程序
纯资源的DLL就是只包含资源的DLL,例如:图标,位图,字符串,声音,视频,对话框等。使用纯资源DLL可以节约可执行文件的大小,可以被所有的应用程序所共享,从而提高系统性能。纯资源DLL的编写比普通的DLL要简单的多,首先创建一个WIN32
DLL工程,不是MFC的DLL,然后创建一个资源文件 *.RC,添加到资源DLL的工程中去。然后添加一个初始化DLL的原文件。
#include <windows.h>
extern "C"
BOOL WINAPI DllMain( HINSTANCE hInstance, DWORD dwReason, LPVOID )
{
return 1;
}
这是纯资源DLL所必须需的代码,保存这个文件为*.CPP。编译这个资源DLL。
在应用程序显示的调用这个DLL,使用LoadLibrary函数装入资源DLL,FindResource和LoadResource来装入各种资源,或者使用下列的特定的资源装入函数:
FormatMessage
LoadAccelerators
LoadBitmap
LoadCursor
LoadIcon
LoadMenu
LoadString
当资源使用结束,你的应用程序须调用FreeLibrary函数来释放资源。
下面就讲一下如何调用编写好的资源DLL
首先在应用程序中声明一个DLL的句柄,HINSTANCE m_hLibrary;在OnCreate( )函数中调用LoadLirbrary(
),在OnDestory( )中调用FreeLibrary()。
空心字
在开始一个路径前,我们先调用CDC类的成员函数BeginPath,然后调用一系列的输出函数,在完成绘制之后,我们可以调用CDC类的成员函数EndPath。在完成一个路径之后,我们可以调用StrokePath来绘制该路径。为了简单起见,我们仅给出应用程序的OnPaint成员函数如下:
// 应用程序主窗口的重绘函数
void CMyWnd::OnPaint()
{
// 获得窗口的客户区设备上下文句柄
CPaintDC dc(this);
// 更改当前字体
LOGFONT lf;
dc.GetCurrentFont()->GetLogFont(&lf);
CFont font;
CFont *pOldFont; // 保存设备上下文最初使用的字体对象
lf.lfCharSet=134;
lf.lfHeight=-150;
lf.lfWidth=0;
strcpy(lf.lfFaceName, "隶书");
font.CreateFontIndirect(&lf);
pOldFont=dc.SelectObject(&font);
dc.SetBkMode(TRANSPARENT);
// 更改当前画笔
CPen pen(PS_SOLID, 1, RGB(255, 0, 0));
CPen *pOldPen;
pOldPen=dc.SelectObject(&pen);
// 开始一个路径
dc.BeginPath();
dc.TextOut(10, 10, "空心字");
dc.EndPath();
// 绘制路径
dc.StrokePath();
// 恢复设备上下文的原有设置
dc.SelectObject(pOldFont);
dc.SelectObject(pOldPen);
}
函数FillPath可以使用当前刷子填充路径的内部。按下面的代码修改前面的OnPaint成员函数:
// 应用程序主窗口的重绘函数
void CMyWnd::OnPaint()
{
// 获得窗口的客户区设备上下文句柄
CPaintDC dc(this);
// 更改当前字体
LOGFONT lf;
dc.GetCurrentFont()->GetLogFont(&lf);
CFont font, *pOldFont;
lf.lfCharSet=134;
lf.lfHeight=-150;
lf.lfWidth=0;
strcpy(lf.lfFaceName, "隶书");
font.CreateFontIndirect(&lf);
pOldFont=dc.SelectObject(&font);
dc.SetBkMode(TRANSPARENT);
// 更改当前画笔
CPen pen(PS_SOLID, 1, RGB(255, 0, 0)), *pOldPen;
pOldPen=dc.SelectObject(&pen);
// 更改当前刷子
CBrush br(HS_DIAGCROSS, RGB(0, 255, 255)), *pOldBrush;
pOldBrush=dc.SelectObject(&br);
// 开始一个路径
dc.BeginPath();
dc.TextOut(10, 10, "空心字");
dc.EndPath();
// 绘制路径
dc.StrokeAndFillPath();
// 恢复设备上下文的原有设置
dc.SelectObject(pOldFont);
dc.SelectObject(pOldPen);
dc.SelectObject(pOldBrush);
}
渐变字
在完成一个路径之后,如前所述,我们可以调用CDC类的成员函数FillPath、StrokePath或StrokeAndFillPath来绘制和填充路径。这只是最初级的技巧。更进一步,我们可以使用成员函数SelectClipPath将路径选入当前剪辑区域,这样,所有的绘制操作都将只作用于这个剪辑区域。
使用此技巧可以绘制具有渐变颜色效果的字体。如下面的OnPaint成员函数所示:
// 应用程序主窗口的重绘函数
void CMyWnd::OnPaint()
{
// 获得窗口的客户区设备上下文句柄
CPaintDC dc(this);
// 更改当前字体
LOGFONT lf;
dc.GetCurrentFont()->GetLogFont(&lf);
CFont font, *pOldFont;
lf.lfCharSet=134;
lf.lfHeight=-150;
lf.lfWidth=0;
strcpy(lf.lfFaceName, "隶书");
font.CreateFontIndirect(&lf);
pOldFont=dc.SelectObject(&font);
dc.SetBkMode(TRANSPARENT);
// 更改当前画笔为空
CPen pen(PS_NULL, 1, RGB(255, 0, 0)), *pOldPen;
pOldPen=dc.SelectObject(&pen);
// 更改当前刷子
CBrush br(0), *pOldBrush;
pOldBrush=dc.SelectObject(&br);
// 开始一个路径
dc.BeginPath();
dc.TextOut(10, 10, "渐变字");
dc.EndPath();
// 绘制渐变效果
dc.SelectClipPath(RGN_COPY);
for (int i=255; i>0; i--)
{
int iRadius=(600*i)/255;
dc.SelectObject(pOldBrush);
br.DeleteObject();
br.CreateSolidBrush(RGB(255, i, 0));
dc.SelectObject(&br);
dc.Ellipse(-iRadius, -iRadius/3, iRadius, iRadius/3);
}
// 恢复设备上下文的原有设置
dc.SelectObject(pOldFont);
dc.SelectObject(pOldPen);
dc.SelectObject(pOldBrush);
}
在上面的示例中,我们以RGN_COPY方式将路径选作当前剪辑区域,然后,在该剪辑区域上进行一系列的绘制操作,这些绘制操作以不同的颜色绘制了一系列的同心椭圆,这些同心椭圆有视觉上给用户以渐变的感觉。上面的程序的运行结果如图。
测试ALT键是否按下:
GetKeyState(VK_MENU);
GetAlt();
元文件
用于记录和回放GDI函数调用。首先通过CMetaFileDC:Create来创建新的对象,如果有文件名,则保存到文件内,如果没有则建立在内存中。完毕后,用Close()关闭。
可以调用PlayMetaFile()来回放元文件。还可以用CopyMetaFile()将文件存盘。当结束元文件的时候,用DeleteMetaFile()从内存中删除。
另外有增强型元文件。
CMetaFileDC dc;
BOOL bCreate=dc.CeeateEnblanced(pDC,”metaest,wmf\0”,0,vectosd\0 this is l\0\0”);
if(!bCreated) return;
(具体的绘制内容……)
//关闭文件,并返回文件句柄
HENHMETAFILE hemf=dc.CloseEnhanced();
//播放文件
pDC->SaveDC();
pDC->SetMapMode();
pDC->PlayMetaFile(hemf,&rc);
pDC->RestoreDC(-1);
//当文件不再需要
if(hemf)
::DeleteEnhMetaFile(hemf);
如何将窗口的大小限制为特定值:
Windows发送一个WM_GETMAXMININFO消息来确定窗口的位置、大小以及跟踪尺寸,下面将窗口的追踪尺寸限制为1/4屏幕大小:
void CMainFrame::OnGetMinMaxInfo(MINMAXINFO FAR *LPmmI)
{
lpMMI->ptMaxTrackSize.x=GetSystemMetrics(SM_CXSCREEN)/2;
lpMMI->ptMaxTrackSize.Y=GetSystemMetrics(SM_CYSCREEN)/2;
CMDIFrameWnd::OnGetMinMaxInfo(lpMMI);
}
Top
4 楼yintongshun(左岸思雨)回复于 2003-12-02 11:51:44 得分 0
字符串太长的时候如果用省略号显示
调用CDC::DrawText并指定DT_END_ELLIPSIS标志,就可以用省略号代替末尾的字符来适合指定的边界矩形,如果要显示路径信息,指定DT_PATH_ELLIPSIS标志来取代中间的字符。
CClientDC dc(this);
dc.DrawText(CString("This is a long string."),
CRect(10,10,80,30),DT_LEFT|DT_END_ELLIPSIS);
dc.DrawText(AfxGetApp()->m_pszHelpFilePath,
CRect(10,40,200,60),DT_LEFT|DT_PATH_ELLIPSIS);
WINDOWS异常分支
异常分支是WINDOWS消息处理路径中的点,子例程根据这一路径被注入或附加,以在某些类型的消息到达某目的地之前将其过滤。一旦这些消息被捕获了,就可以被修改,记录或者简单的放弃。子例程本身被称为过滤器。他们按照正在过滤的事件的类型进行分类。
为了设置一个异常分支,必须调用SetWindowsHookEx()函数。
SetWindowsHookEX(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadld);
第一个参数,代表了一个常量,标识了正要设置的异常分支的类型。
第二个参数,指向一个函数类型为HOOKPROC的指针。对于键盘异常分支,必须要如下的形式:
LRESULT CALLBACK KeyboardProc(int code ,WPARAM,LPARAM);
第三个参数,是包含过滤函数的DLL的HINSTANCE。
第四个参数允许程序指定要为之设置异常分支的线程。
为了把一个异常分支从链中去掉,必须调用函数:
BOOL UnhookWindowsHookEx(HHOOK hHook);
文档模板字符串处理
文档类包含了GetDocString(),可以接受CString对象的引用以及一个索引,该函数可以用来查询已经注册的字符串资源的值。方式如下:
virtual BOOL GetDocString(CString &rString,enum DocStringIndex intex)const;
具体参数索引的有效值:
CDocTemplate::windowTitle 应用程序窗口标题栏的文本,只对SDI程序很重要
CDocTemplate::docName
CDocTemplate::fileNewName
CDocTemplate::filterName
CDocTemplate::filterExt
CDocTemplate::regFileTypeId 注册表数据库内部名称,由OLE和FILE MANAGER使用
CDocTemplate::regFileTypeName 注册表数据库文档名称,由所有OLE应用程序使用并且对用户可见
简单的打印
void Print2()
{
HDC hDC;
DOCINFO di;
hDC=CreateDC(“WINSPOOL”,”HP Laser Printer”,NULL,NULL);
memset(&di,sizeof(DOCINFO));
di.cbSize=sizeof(DOCINFO);
di.lpszDocName=”Sample Document”;
if(StartDoc(hDC,&di)!=SP_ERROR)
{
StartPage(hDC);
TextOut(hDC,10,10,”This is on Page 1.”,18);
EndPage(hDC);
StartPage(hDC);
TextOut(hDC,10,10,”This is on Page 2.”,18);
EndPage(hDC);
EndDoc(hDC);
}
DeleteDC(hDC);
}
时间控制函数
传统的方法使用WM_TIMER(),精度最低。
在要求不大于1毫秒的情况下,可以采用GetTickCount()函数,该函数的返回值是DWORD型,表示以毫秒为单位的计算机启动后经历的时间间隔。实现50毫秒精确定时方法:
DWORD dwstart,dwstop;
dwstop=GetTickCount();
while(TRUE)
{
dwstart=dwstop;
do{
dwstop=GetTickCount();
}while(dwstop-50<dwstart);
}
仅供WIN9X使用的高精度定时器:QueryPerformanceFrequency()和QueryPerformanceCounter(), 要求计算机从硬件上支持高精度定时器。函数的原形是:BOOL QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
BOOL QueryPerformanceCounter (LARGE_INTEGER *lpCount);
数据类型LARGEINTEGER既可以是一个作为8字节长的整数,也可以是作为两个4字节长的整数的联合结构,其具体用法根据编译器是否支持64位而定。该类型的定义如下:typeef union _ LARGE_INTEGER
{
struct
{
DWORD LowPart;
LONG HighPart;
};
LONGLONG QuadPart;
} LARGE_INTEGER;
在定时前应该先调用QueryPerformanceFrequency()函数获得机器内部计时器的时钟频率。接着在需要严格计时的事件发生前和发生之后分别调用QueryPerformanceCounter(), 利用两次获得的计数之差和时钟频率, 就可以计算出事件经历的精确时间。测试函数SLEEP(100)的精确持续时间方法:
LARGE_INTEGER litmp;
LONGLONG qt1,qt2;
double dft,dff,dfm;
QueryPerformanceFrequency(&litmp);//获得时钟频率
dff=(double)litmp.QuadPart;
QueryPerformanceCounter(&litmp);//获得初始值
qt1=litmp.QuadPart;Sleep(100);
QueryPerformanceCounter(&litmp);//获得终止值
qt2=litmp.QuadPart;
dfm=(double)(qt2-qt1);
dft=dfm/dff;//获得对应的时间值
需要注意的是DFT计算的结果单位是秒。
文本绘制函数
DrawText() 在文本被绘制时,提供一些文本格式编排。将一个字符串放入一个矩形区域使之格式化。
CRect rectangle;
GetClientRect(rectangle);
pDC->DrawText("Hello",-1,&rectangle,
DT_CENTER|DT_SINGLELINE|DT_VCENTER);
ExtTextOut() 使用三个新增特性绘制一行文本。首先可以指定一个剪取矩形以将文本限制在矩形内。其次可以指定一个不透明的矩形,它涉及使用背景文本颜色填充矩形。第三,可以通过提供一组字符单元宽度值,控制字符间距。
GrayString() 创建虚化的文本。类似于使用在菜单中显示被禁用的菜单项的文本。
TabbedTextOUt() 绘制一行文本,类似TextOut()。区别是支持制表符。
TextOut() 绘制单行文本。其坐标只能识别16位,在NT中才可以识别32。
文本坐标的计算
GetCharWidth(0 为当前选入DC中的字体的一系列字符获取默认字符宽度值的副本。
GetDeviceCaps() 获取特定于具体设备的数据的各种位。
GetTextExtent() 当使用当前选入DC中的字体绘图时,获得给定字符串将占据的空间宽度和高度。
GetTabbedTextExtent()类似于GetTextExtent(),当使用当前选入DC中的字体绘图时,此函数获取字符串将占据的空间宽度和高度。也考虑了制表设置。
GetTextMetrics() 为当前选入DC中的字体获取数据副本。包含基本的字体测量信息。
使用定时器
定时器的时间单位是毫秒,数值是1-32位整数,最大的数字是49.5天左右。虽然可以设定间隔,但是因为硬件定时器是54.9毫秒为基准,所以设定的定时器都是每55秒发送一个定时消息,有时定时就是近似的,并不完全精确。
鼠标操作
1.非客户鼠标事件:
非客户区域包括客户区域以外的所有窗口部分,包括标题栏、菜单栏、滚动条等。
在客户鼠标的事件前面加上NC的内容是非客户事件,行为之间的差别是根据鼠标按钮被单击时的鼠标位置决定的。例如:WM_NCLBUTTONDOWN
2.俘获和释放鼠标
调用CWND成员函数SETCAPTURE抓住鼠标,即所有后续鼠标信息都发送到视图窗口,直到放开命令::ReleaseCapture(),此函数是WIN32 API函数。
3.限制鼠标移动区域
将鼠标光标限制在视图窗口内,使用户不会到窗口外去画线,调用CWND::GETCLENTRECT()取得视图窗口的当前坐标,然后调用CWND::CLIENTTOSCREEN将坐标换算成屏幕坐标,最后调用::CLIPCURSOR将鼠标光标限定在指定的屏幕坐标内,从而保证光标在视图窗口内,使用命令::CLIPCURSOR(NULL)则可以使鼠标移动到屏幕的任何位置。
::ClipCursor是WIN32 API提供的函数,而不是MFC提供的。
4.鼠标处理函数的参数NFLAGS的位的位屏蔽:
MK_CONTROL 按下CTRL键时设置
MK_LBUTTON 按下鼠标左键时设置
MK_MBUTTON 按下鼠标中键时设置
MK_RUBUTTON 按下鼠标右键时设置
MK_SHIFT 按下SHIFT时设置
使用下面的语句检测SHIFT键是否按下:
if(nFlags&MK_SHIFT)
{
...
}
5.更改鼠标光标:
::SetCursor(::LoadCursor(AfxGetInstanceHandle(),IDC_ARROW));
指针数组:
CTypedPtrArray<CObArray,CLine*>m_linearray;
新数据成员m_LineArray是MFC类样板CTyped PrtArray的实例。CTyped PtrArray生成一族类,各由第一个样板参数指定的派生,各用于存放第二个参数指定类型的数据项。这样m_LineArray是CObArray派生的类的对象,存放CLine对象的指针。使用派生类的原因是他的编译器能进行更广泛的类型检查,帮助减少错误,并减少使用类对象时需要的校正操作。
为了使用CTypedPtrArray或者其他的任何MFC类样板,必须包含AfxTempl.h头文件,将其加入到StdAfx.h文件中。
删除数据的时候,必须先用DELETE命令删除对象,然后使用类的删除命令,删除对象的指针。
如何使程序保持极小状态?
在恢复程序窗体大小时, Windows会发送WM_QUERY-OPEN消息, 用 ClassWizard设置成员函数 OnQueryOpen() ,add following code:
Bool CMainFrame:: OnQueryOpen( )
{
Return false;
}
如何获取有关窗口正在处理的当前消息的信息
调用CWnd: : GetCurrentMessage可以获取一个MSG指针。例如,可以使用ClassWizard将几个菜单项处理程序映射到一个函数中,然后调用GetCurrentMessage来确定所选中的菜单项。
viod CMainFrame : : OnCommmonMenuHandler ( )
{
//Display selected menu item in debug window .
TRACE ("Menu item %u was selected . \n" ,
GetCruuentMessage ( ) -> wParam );
}
检查是否按下鼠标左键
if((nFlags&MK_LBUTTON)==MK_LBUTTON)
检查键盘输入
1.在OnKeyDown中的参数nChar是一个数值,当显示的时候,需要转换成字符,使用如下的命令:
char lsChar;
lsChar=char(nChar);
if(lsChar=='A');
{
.......
}
2.调用另一个函数::GetKeyState(),用一个特定的键代码来确定法键是否被按下。如果::GetKeyState函数的返回值是负的,表示该键被按下。如果返回值是非负的,表示该留未被按下。例如:如果要确定shift键是否被按下,可以使用下面的代码:
if(::GetKeyState(VK_SHIFT)<O)
{
AfxMessageBox("shift is pressed");
}
Top
5 楼yintongshun(左岸思雨)回复于 2003-12-02 11:51:58 得分 0
WMI使用技巧集
很多的朋友对WMI可能见过但理解不深,我也是十分想了解关于WMI的知识,可一直找不对太合适的资料,在网上的一些资料不是有很多错误,就是讲解不清,我有空的时候将关于WMI的知识集中一下,放在这里便于大家学习。本贴会不断增加。
1、 什么是WMI
WMI是英文Windows Management Instrumentation的简写,它的功能主要是:访问本地主机的一些信息和服务,可以管理远程计算机(当然你必须要拥有足够的权限),比如:重启,关机,关闭进程,创建进程等。
2、 如何用WMI获得本地磁盘的信息?
首先要在VS.NET中创建一个项目,然后在添加引用中引用一个.net的装配件:System.Management.dll,这样你的项目才能使用WMI。代码如下:
using System;
using System.Management;
class Sample_ManagementObject
{
public static int Main(string[] args)
{
SelectQuery query=new SelectQuery("Select * From Win32_LogicalDisk");
ManagementObjectSearcher searcher=new ManagementObjectSearcher(query);
foreach(ManagementBaseObject disk in searcher.Get())
{
Console.WriteLine("\r\n"+disk["Name"] +" "+disk["DriveType"] + " " + disk["VolumeName"]);
}
Console.ReadLine();
return 0;
}
}
disk["DriveType"] 的返回值意义如下:
1 No type
2 Floppy disk
3 Hard disk
4 Removable drive or network drive
5 CD-ROM
6 RAM disk
3、如何用WMI获得指定磁盘的容量?
using System;
using System.Management;
// This example demonstrates reading a property of a ManagementObject.
class Sample_ManagementObject
{
public static int Main(string[] args)
{
ManagementObject disk = new ManagementObject(
"win32_logicaldisk.deviceid=\"c:\"");
disk.Get();
Console.WriteLine("Logical Disk Size = " + disk["Size"] + " bytes");
Console.ReadLine();
return 0;
}
}
4、 如何列出机器中所有的共享资源?
using System;
using System.Management;
class TestApp {
[STAThread]
static void Main()
{
ManagementObjectSearcher searcher = new ManagementObjectSearcher(
"SELECT * FROM Win32_share");
foreach (ManagementObject share in searcher.Get())
{
Console.WriteLine(share.GetText(TextFormat.Mof));
}
}
}
别忘记在引用中把System.Management添加进来。
5、 怎样写程控制让系统中的某个文件夹共享或取消共享.?
首先,这需要以有相应权限的用户登录系统才行。然后,试试下面的代码:
using System;
using System.Management;
class CreateShare
{
public static void Main(string[] args)
{
ManagementClass _class = new ManagementClass(new ManagementPath("Win32_Share"));
object[] obj = {"C:\\Temp","我的共享",0,10,"Dot Net 实现的共享",""};
_class.InvokeMethod("create",obj);
}
}
将
object[] obj = {"C:\\Temp","我的共享",0,10,"Dot Net 实现的共享",""};
改为
object[] obj = {"C:\\Temp","我的共享",0,null,"Dot Net 实现的共享",""};
就可以实现授权给最多用户了。
6、 如何获得系统服务的运行状态?
private void getServices()
{
ManagementObjectCollection queryCollection;
string[] lvData = new string[4];
try
{
queryCollection = getServiceCollection("SELECT * FROM Win32_Service");
foreach ( ManagementObject mo in queryCollection)
{
//create child node for operating system
lvData[0] = mo["Name"].ToString();
lvData[1] = mo["StartMode"].ToString();
if (mo["Started"].Equals(true))
lvData[2] = "Started";
else
lvData[2] = "Stop";
lvData[3] = mo["StartName"].ToString();
//create list item
ListViewItem lvItem = new ListViewItem(lvData,0);
listViewServices.Items.Add(lvItem);
}
}
catch (Exception e)
{
MessageBox.Show("Error: " + e);
}
}
7、 通过WMI修改IP,而实现不用重新启动?
using System;
using System.Management;
using System.Threading;
namespace WmiIpChanger
{
class IpChanger
{
[MTAThread]
static void Main(string[] args)
{
ReportIP();
// SwitchToDHCP();
SwitchToStatic();
Thread.Sleep( 5000 );
ReportIP();
Console.WriteLine( "end." );
}
static void SwitchToDHCP()
{
ManagementBaseObject inPar = null;
ManagementBaseObject outPar = null;
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
foreach( ManagementObject mo in moc )
{
if( ! (bool) mo["IPEnabled"] )
continue;
inPar = mo.GetMethodParameters("EnableDHCP");
outPar = mo.InvokeMethod( "EnableDHCP", inPar, null );
break;
}
}
static void SwitchToStatic()
{
ManagementBaseObject inPar = null;
ManagementBaseObject outPar = null;
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
foreach( ManagementObject mo in moc )
{
if( ! (bool) mo[ "IPEnabled" ] )
continue;
inPar = mo.GetMethodParameters( "EnableStatic" );
inPar["IPAddress"] = new string[] { "192.168.1.1" };
inPar["SubnetMask"] = new string[] { "255.255.255.0" };
outPar = mo.InvokeMethod( "EnableStatic", inPar, null );
break;
}
}
static void ReportIP()
{
Console.WriteLine( "****** Current IP addresses:" );
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
foreach( ManagementObject mo in moc )
{
if( ! (bool) mo[ "IPEnabled" ] )
continue;
Console.WriteLine( "{0}\n SVC: '{1}' MAC: [{2}]", (string) mo["Caption"],
(string) mo["ServiceName"], (string) mo["MACAddress"] );
string[] addresses = (string[]) mo[ "IPAddress" ];
string[] subnets = (string[]) mo[ "IPSubnet" ];
Console.WriteLine( " Addresses :" );
foreach(string sad in addresses)
Console.WriteLine( "\t'{0}'", sad );
Console.WriteLine( " Subnets :" );
foreach(string sub in subnets )
Console.WriteLine( "\t'{0}'", sub );
}
}
}
}
Top
6 楼yintongshun(左岸思雨)回复于 2003-12-02 11:52:13 得分 0
8、 如何利用WMI远程重启远程计算机?
using System;
using System.Management;
namespace WMI3
{
/// <summary>
/// Summary description for Class1.
/// </summary>
class Class1
{
static void Main(string[] args)
{
Console.WriteLine("Computer details retrieved using Windows Management Instrumentation (WMI)");
Console.WriteLine("mailto:Written%2002/01/02%20By%20John%20O'Donnell%20-%20csharpconsulting@hotmail.com");
Console.WriteLine("========================================
=================================");
//连接远程计算机
ConnectionOptions co = new ConnectionOptions();
co.Username = "john";
co.Password = "john";
System.Management.ManagementScope ms = new System.Management.ManagementScope("\\\\192.168.1.2\\root\\cimv2", co);
//查询远程计算机
System.Management.ObjectQuery oq = new System.Management.ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher query1 = new ManagementObjectSearcher(ms,oq);
ManagementObjectCollection queryCollection1 = query1.Get();
foreach( ManagementObject mo in queryCollection1 )
{
string[] ss={""};
mo.InvokeMethod("Reboot",ss);
Console.WriteLine(mo.ToString());
}
}
}
}
9、 利用WMI创建一个新的进程?
using System;
using System.Management;
// This sample demonstrates invoking a WMI method using parameter objects
public class InvokeMethod
{
public static void Main()
{
//Get the object on which the method will be invoked
ManagementClass processClass = new ManagementClass("Win32_Process");
//Get an input parameters object for this method
ManagementBaseObject inParams = processClass.GetMethodParameters("Create");
//Fill in input parameter values
inParams["CommandLine"] = "calc.exe";
//Execute the method
ManagementBaseObject outParams = processClass.InvokeMethod ("Create", inParams, null);
//Display results
//Note: The return code of the method is provided in the "returnvalue" property of the outParams object
Console.WriteLine("Creation of calculator process returned: " + outParams["returnvalue"]);
Console.WriteLine("Process ID: " + outParams["processId"]);
}
}
10、 如何通过WMI终止一个进程?
using System;
using System.Management;
// This example demonstrates how to stop a system service.
class Sample_InvokeMethodOptions
{
public static int Main(string[] args) {
ManagementObject service =
new ManagementObject("win32_service=\"winmgmt\"");
InvokeMethodOptions options = new InvokeMethodOptions();
options.Timeout = new TimeSpan(0,0,0,5);
ManagementBaseObject outParams = service.InvokeMethod("StopService", null, options);
Console.WriteLine("Return Status = " + outParams["Returnvalue"]);
return 0;
}
}
11、 如何用WMI 来获取远程机器的目录以及文件.比如如何列出一个目录下的所有文件,或者所有子目录;如何删除,舔加,更改文件?
using System;
using System.Management;
// This example demonstrates reading a property of a ManagementObject.
class Sample_ManagementObject
{
public static int Main(string[] args) {
ManagementObject disk = new ManagementObject(
"win32_logicaldisk.deviceid=\"c:\"");
disk.Get();
Console.WriteLine("Logical Disk Size = " + disk["Size"] + " bytes");
return 0;
}
}
12、 参考资料
可以参考下列内容:
msdn WMI SDK:
http://msdn.microsoft.com/library/default.asp?url=/library/en- us/wmisdk/wmi/wmi_start_page.asp
WMI使用说明(csdn):
http://www.csdn.net/develop/article/15/15346.shtm
WMI技术的应用 for .net(csdn):
http://www.csdn.net/develop/article/16/16419.shtm
在.NET中轻松获取系统信息(1) -WMI篇(csdn):
http://www.csdn.net/develop/article/15/15744.shtm
:)
13、 一些技巧
我使用WMI可以取出网卡的MAC地址,CPU的系列号,主板的系列号,其中主板的系列号已经核对过没有错的,其余的有待于验证,因为我使用的是笔记本,笔记本背面有一个主板的系列号,所以可以肯定主板系列号没有问题
网卡的MAC地址
SELECT MACAddress FROM Win32_NetworkAdapter WHERE ((MACAddress Is Not NULL) AND (Manufacturer <> 'Microsoft'))
结果:08:00:46:63:FF:8C
CPU的系列号
Select ProcessorId From Win32_Processor
结果:3FEBF9FF00000F24
主板的系列号
Select SerialNumber From Win32_BIOS
结果:28362630-3700521
获取硬盘ID
String HDid;
ManagementClass cimobject = new ManagementClass("Win32_DiskDrive");
ManagementObjectCollection moc = cimobject.GetInstances();
foreach(ManagementObject mo in moc)
{
HDid = (string)mo.Properties["Model"].value;
MessageBox.Show(HDid );
}
14、 一个使用WMI后的异常处理的问题
下面是我整理的一段代码.
ManagementObjectCollection queryCollection1;
ConnectionOptions co = new ConnectionOptions();
co.Username = "administrator";
co.Password = "111";
try
{
System.Management.ManagementScope ms = new System.Management.ManagementScope(@"\\csnt3\root\cimv2", co);
System.Management.ObjectQuery oq = new System.Management.ObjectQuery("SELECT * FROM Win32_OperatingSystem");
ManagementObjectSearcher query1 = new ManagementObjectSearcher(ms,oq);
queryCollection1 = query1.Get();
foreach( ManagementObject mo in queryCollection1 )
{
string[] ss={""};
mo.InvokeMethod("Reboot",ss);
Console.WriteLine(mo.ToString());
}
}
catch(Exception ee)
{
Console.WriteLine("error");
}
Top
7 楼yintongshun(左岸思雨)回复于 2003-12-02 11:52:36 得分 0
15、Windows 管理规范 (WMI) 是可伸缩的系统管理结构,它采用一个统一的、基于标准的、可扩展的面向对象接口。WMI 为您提供与系统管理信息和基础 WMI API 交互的标准方法。WMI 主要由系统管理应用程序开发人员和管理员用来访问和操作系统管理信息。
WMI 可用于生成组织和管理系统信息的工具,使管理员或系统管理人员能够更密切地监视系统活动。例如,可以使用 WMI 开发一个应用程序,用于在 Web 服务器崩溃时呼叫管理员。
将 WMI 与 .NET 框架一起使用
WMI 提供了大量的规范以便为许多高端应用程序(例如,Microsoft Exchange、Microsoft SQL Server 和 Microsoft Internet 信息服务 (IIS))实现几乎任何管理任务。管理员可以执行下列任务:
• 监视应用程序的运行状况。
• 检测瓶颈或故障。
• 管理和配置应用程序。
• 查询应用程序数据(使用对象关系的遍历和查询)。
• 执行无缝的本地或远程管理操作。
WMI 结构由以下三层组成:
• 客户端
使用 WMI 执行操作(例如,读取管理详细信息、配置系统和预订事件)的软件组件。
• 对象管理器
提供程序与客户端之间的中间装置,它提供一些关键服务,如标准事件发布和预订、事件筛选、查询引擎等。
• 提供程序
软件组件,它们捕获实时数据并将其返回到客户端应用程序,处理来自客户端的方法调用并将客户端链接到所管理的基础结构。
通过定义完善的架构向客户端和应用程序无缝地提供了数据和事件以及配置系统的能力。在 .NET 框架中,System.Management 命名空间提供了用于遍历 WMI 架构的公共类。
除了 .NET 框架,还需要在计算机上安装 WMI 才能使用该命名空间中的管理功能。如果使用的是 Windows Millennium Edition、Windows 2000 或 Windows XP,那么已经安装了 WMI。否则,将需要从 MSDN 下载 WMI。
用 System.Management 访问管理信息
System.Management 命名空间是 .NET 框架中的 WMI 命名空间。此命名空间包括下列支持 WMI 操作的第一级类对象:
• ManagementObject 或 ManagementClass:分别为单个管理对象或类。
• ManagementObjectSearcher:用于根据指定的查询或枚举检索 ManagementObject 或 ManagementClass 对象的集合。
• ManagementEventWatcher:用于预订来自 WMI 的事件通知。
• ManagementQuery:用作所有查询类的基础。
System.Management 类的使用编码范例对 .NET 框架环境很适合,并且 WMI 在任何适当的时候均使用标准基框架。例如,WMI 广泛利用 .NET 集合类并使用推荐的编码模式,如 .NET 异步操作的“委托”模式。因此,使用 .NET 框架的开发人员可以使用他们的当前技能访问有关计算机或应用程序的管理信息。
请参见
使用 WMI 管理应用程序 | 检索管理对象的集合 | 查询管理信息 | 预订和使用管理事件 | 执行管理对象的方法 | 远程处理和连接选项 | 使用强类型对象
获取CPU序列号代码
string cpuInfo = "";//cpu序列号
ManagementClass cimobject = new ManagementClass("Win32_Processor");
ManagementObjectCollection moc = cimobject.GetInstances();
foreach(ManagementObject mo in moc)
{
cpuInfo = mo.Properties["ProcessorId"].value.ToString();
Console.WriteLine(cpuInfo);
Console.ReadLine();
}
获取网卡硬件地址
using System.Management;
...
ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration");
ManagementObjectCollection moc = mc.GetInstances();
foreach(ManagementObject mo in moc)
{
if((bool)mo["IPEnabled"] == true)
Console.WriteLine("MAC address\t{0}", mo["MacAddress"].ToString());
mo.Dispose();
}
}
获取硬盘ID
String HDid;
ManagementClass cimobject = new ManagementClass("Win32_DiskDrive");
ManagementObjectCollection moc = cimobject.GetInstances();
foreach(ManagementObject mo in moc)
{
HDid = (string)mo.Properties["Model"].value;
MessageBox.Show(HDid );
}
16、在.NET中轻松获取系统信息(1) -WMI篇
Montaque
申明:
1、个人的一点心得,仅供参考
2、转载时候,请保留原本。
概述:
不知道大家有没有这种体会?有时候为了获取系统一点点信息,比如考虑一下操作系统的版本号,或者当前屏幕的分辨率。其实说到底就是读操作系统某个方面的一个属性值而已,然后就看到我们的程序中密密麻麻的Win32 API申明,调用,代码的可读性和维护性不言而喻。到了.NET,微软提供了更为丰富的类,有很多以前要调用API的方法可以在.NET中轻而易举的调用实现。今天简单介绍一个在.NET中如何通过与WMI(Windows 管理规范)的通讯,从而得到获取信息的目的。
主要思路:
举一个获取操作系统共享目录和获取主板号的例子,介绍如何利用System.Managment下面的类获取系统相关的信息:
正文:
WMI(Windows管理规范:Windows Management Instrumentation)是Microsoft基于Web的企业管理(WBEM)的实现,同时也是一种基于标准的系统管理接口。WMI最早出现在Microsoft Windows 2000系统上,但它同样可以安装在Windows NT 4和Windows 9x计算机上。WMI是一种轻松获取系统信息的强大工具。
在.NET中,有一个System.Management名空间(系统默认没有引用,我们可以手动添加引用),通过下面的Class的操作,可以查询系统软硬件的信息,先看一个简单的例子:
Imports System.Management
Dim searcher As New ManagementObjectSearcher("SELECT * FROM Win32_share")
Dim share As ManagementObject
For Each share In searcher.Get()
MessageBox.Show(share.GetText(TextFormat.Mof))
Next share
运行的结果是列出了所有系统当前共享的目录、以及描述等等。
分析一下上面的代码,可以看到一下几点:
1、似乎是在进行数据库操作,有点像SQL语句。其实就是SQL操作,这种语句被成WQL(WMI Query Language),实际上是标准SQL的一个子集加上了WMI的扩展.
2、WQL是个只读的查询语言,我们只能查询响应的数据,不能用UPDATE,INSERT等更新操作
3、代码很简单、通俗易懂
4、我们采用了一种MOF(托管对象格式)的显示。
例子二:获取当前主板的信息
上面的例子是一个软件方面的信息,下面看一个获取硬件信息的例子,获取主板的序列号以及制造商:
Dim searcher As New ManagementObjectSearcher("SELECT * FROM Win32_BaseBoard")
Dim share As ManagementObject
For Each share In searcher.Get()
Debug.WriteLine("主板制造商:" & share("Manufacturer"))
Debug.WriteLine("型号:" & share("Product"))
Debug.WriteLine("序列号:" & share("SerialNumber"))
Next share
总结以及补充:
WMI类也是分层次的,具体可以参考msdn中的WMI;转向.NET平台开发的时候,最好能多看一些关于.NET新特性的介绍,这样可以大幅度的提升代码的开发效率以及运行效率。
Top
8 楼bluebohe(薄荷)回复于 2003-12-02 12:30:10 得分 0
谢谢!
这个贴子一会我结掉吧
Top

