C
和
C++
编程和学习文档
1 :
指针变量名称以
p
为首字符,这是程序员通常在定义指针时的一个习惯
2 :har * p; (int *)p
把
p
强制转换为
int
型
3.1 :
指针的问题:指针应用时最好给予定义
(
初始化
)
这样可以保证指针的指向是程序员自己能够把握的。
3.2 :
指针的越界,这恐怕是最难查出的吧!
3.3 :
指针的局部变量问题。局部的指针变量会被程序自动释放,若程序员引用此类指针就会出错。
2007-9-1
4
.二维指针的应用实例:
#include <stdio.h>
#include <string.h>
void sort(char (*client)[10]);
void main()
{
int temp;
char client[3][10];
char (*pClient)[10] = NULL;
for( temp = 0; temp < 3; temp++ )
{
gets(client[temp]);
}
pClient = client;
sort(pClient);
for( temp = 0; temp < 3; temp++ )
{
puts(*(pClient + temp));
}
}
void sort(char (*client)[10])
{
//
冒泡算法的明了写法
int temp1, temp2;
char temp[10];
for( temp1 = 2; temp1 > 0; temp1-- )//
控制每一步的比较次数
{
for( temp2 = 0; temp2 < temp1; temp2++ )//
比较指针
{
if( strcmp (*(client + temp2), *(client + temp2 + 1)) )//
比较过程
{
strcpy(temp, *(client + temp2));
strcpy(*(client + temp2), *(client + temp2 + 1));
strcpy(*(client + temp2 + 1), temp);
}
}
}
}
5
.类型转换中的结构体:如
p = (struct student * )stu[0].name
将
p
指向结构体的内部元素的地址,要进行类型转换,先转换为
p
的类型。
6.
定义在函数内部的指针结构体是不会被系统释放的。例程:猫和老鼠。
void Insert(struct student *pHeadGet, int numInsert)
{
struct student *pOne, *pTwo, *pInsert;
int temp;
pTwo = pOne = pHeadGet;
for( temp = 0; temp < numInsert - 1; temp++ )
{
pTwo = pTwo->pNext;
}
pOne = pTwo->pNext;
printf("Please into the number and the score:\n");
pInsert = (struct student *) malloc(LEN);//
这个在函数内部开辟的空间在函数调用后还是保留在内存
scanf("%d%d", &pInsert->number, &pInsert->score);
pTwo->pNext = pInsert;
pInsert->pNext = pOne;
}
7:
连接字符的错误写法:
#include <stdio.h>
main()
{
char a[]="wo shi libingbing", b[] = "li jiang ye shi wo";
char *p1, *p2;
p1 = a;
p2 = b;
//*(p1 + 17) = *(p2 +0);
//(p1 +17) = p2;
此处在尝试改变数组的值,这是不允许的
printf("%s", p1);
}
8
.指针中赋值和修改的一些容易错误和注意的地方
#include <stdio.h>
void main()
{
char amessage[] = "now is the time";
char *pmessage = "now is the time";
int *pInt = {2, 3, 6, 23};//
这样的定义是不允许的
*(amessage + 3) = 'H';
// *(pmessage +3) = 'H';
不能尝试修改指针定义的字符数组
printf("%s\n%s\n", amessage, pmessage);
pmessage = amessage;
*(pmessage +3) = 'h';
printf("%s\n%s\n", amessage, pmessage);//
从执行结果可以知道指针只是一种指向,不能达到引用并修改字符串的能力
// *(pInt + 3) = 8;
// printf("%d", pInt);
}
9
.字符串的操作:
stringDepart()
//
讲字符串进行分解成单个单词的形式
//
返回打印结果
while (tempChar = GETCHAR != EOF)
{
if (tempChar == (‘ ’ || ‘\n’ || ‘\t’))
{
if (STATE == IN)
{
//
得到一个单词
STATE == OUT;
}
else if (STATE == OUT)
{
STATE == IN;
//
操作字符
}
else
{
//
操作字符
}/
}//end if (tempChar == (‘ ’ || ‘\n’ || ‘\t’))
}///stringDepart
10
.
Stract
函数:
/*strcat function*/
//
函数将字符串
t
接到
s
中
void strcat(char *s, char *t)
{
while (*s)
s++;
while(*s++ = *t++)
;
}
11
.标准的冒泡排序:
for( times = length - 1; times > 0; times--) //
排序的次数
for( client = 0; client < times; client++) //
控制比较
if (item[client] > item[client + 1]) //
比较交换
{
//
交换
client
和
client + 1
的值
}
12
.用户控制的程序结束:
while(1)
{
//
操作代码
if(checkChar = ‘q’ || ‘Q’) return;
//
操作代码
}
13
.用堆栈实现的表达式的计算:
函数算法:
float getnumber(void)
//
提取表达式中的一个几位数字,也要判断其格式的正确与否
//
输入为
void
,返回是一个
float
型数
//
用于学习的目的编写的
{
if (point > strLen -1) //
假如没有任何字符
return NULL;
if (*(string + point) == ‘
-
’)
return NULL;
while (string[point] == '(')
{
if (string[point+1] == '-')
{ //
负号
point++;
return 0;
}
else if (*(string + point+1) == '(')
push(0); //
压入栈中,
else
break;
}
}
14
.
Main
函数的规范写法:
int
main(int argc, char **argv)
15
.
C
学习的难易:
学习
c
有一个学期了,对于
c
语言这样的一个词汇,有人会发帖问这语言如何?好学?不好学?对于语言本身没有好学与不好学的,而真正主导编程的不是语言而是语言和数据的搭配——数据结构;最这看来我们可能可以得到这样的结论:语言和数据结构没有必要有先后,可以同时学啊,是的这样就使语言有难易之分。
16
.
C++
是
c
的优化:
1.
在
c++
中我们提倡使用
const char * const authorName = "Scott Meyers";
这样的方式,对于指针我们要使指针本身常量化,也要使指针所指的变量常量化。这就是上面这样做的原因。
2.
对于
define
所造成的混乱:
#define max(a,b) ((a) > (b) ? (a) : (b))
int a = 5, b = 0;
max(++a, b);// a
的值增加了
2
次
max(++a, b+10); // a
的值只增加了
1
次
内联函数解决了这个问题:
template<class T>
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; }
3. string *stringarray1 =static_cast<string*>(malloc(10 * sizeof(string)));
string *stringarray2 = new string[10];
其结果是,
stringarray1
确实指向的是可以容纳
10
个
string
对象的足够空间,但内存里并没有创建这些对象。而且,如果你不从这种晦涩的语法怪圈
(
详见条款
m4
和
m8
的描述
)
里跳出来的话,你没有办法来初始化数组里的对象。换句话说,
stringarray1
其实一点用也没有。相反,
stringarray2
指向的是一个包含
10
个完全构造好的
string
对象的数组
,
每个对象可以在任何读取
string
的操作里安全使用。
把
new
和
delete
与
malloc
和
free
混在一起用也是个坏想法。对一个用
new
获取来的指针调用
free
,或者对一个用
malloc
获取来的指针调用
delete
,其后果是不可预测的。大家都知道
“
不可预测
”
的意思:它可能在开发阶段工作良好,在测试阶段工作良好,但也可能会最后在你最重要的客户的脸上爆炸。
如果你调用
new
时用了
[]
,调用
delete
时也要用
[]
。如果调用
new
时没有用
[]
,那调用
delete
时也不要用
[]
。
17
.
Malloc
的一个问题
对于
malloc
在
ANSI C
之前的
C
版本中,还需要用类型转换运算符。
18
.
预处理问题
C++
语法接受
#define int INT, integer
19
.“,”运算符问题
在
c
的编译器中
”
,
”
运算符是不能做为左值的,但是在
c++
中解决了这个问题,她可以做左值。
20
.关于运算符的一个例子
在不同的编译器下,有些表达式得到的结果是不尽相同的,比如下面的表达式:
a = 1 , b = 1;(a * b + ++b);
在
vc
中得到是
3,
而在
bc
中得到的却是
1
×
2
+
2
=
4
。从这个例子可以看出来,运算符之间的优先级是没有特别必要去区分的,像这样的题目要得到不同的运算方式可以采用讲语句分成两个语句,实现起来没什么不爽的。
就如在这样的一个表达式中,
++a*(a + b).
一般我们会认为括号的运算级别最高,但是错了,先是
++a.
然后再进入括号运算。呜呼!累,没必要啊,可是有些题目就是这样,悲哉!
21
.把二进制转换为十进制的算法表示(用位运算实现)
//
以字符型来表示二进制数字,对其进行检测,得到
unsign
型的
int
型数据输出。
unsigned long BtoD(char *str)
{
int i;
unsigned long m;
unsigned long n = 0;
int len = strlen(str);
if (len > 32)
len = 32;
m = 0x80000000 >> (32 - len);
for(i = 0; i < len; ++i)
{
if (str[i] == '1') //
非法字符认作
0
n |= m;
m >>= 1;
}
return n;
}
22
.一道经典题目:关键是速度。
有五位小姐排成一列,所有的小姐姓不同、穿的衣服颜色不同、喝不同的饮料、养不同的宠物、吃不同的水果。
钱小姐穿红色衣服;翁小姐养了一只狗;陈小姐喝茶;穿绿衣服的站在穿
白衣服的左边;穿绿衣服的小姐喝咖啡;吃西瓜的小姐养鸟;穿黄衣服的小姐吃
梨;站在中间的小姐喝牛奶;赵小姐站在最左边;吃橘子的小姐站在养猫的旁边;
养鱼的小姐旁边的那位吃梨;吃苹果的小姐喝香槟;江小姐吃香蕉;赵小姐站在
穿蓝衣服的小姐旁边;喝开水的小姐站在吃橘子的小姐旁边;请问哪位小姐养蛇?
答案和解析:
本题是柏林大学的一次考试题,要求在
30
分钟内做出,不过只有少于
10%
的人完成了要求。计分是这样的,共
150
分,从
1
到
30
分钟,每加
1
分钟减
2
分
,
那么
30
分钟答出就是
90
分,是及格分;从
30
分钟以后每加
1
分钟减
1
分。我当时用了
22
分钟,大家也试试,看自己能得多少分。
赵小姐穿黄色衣服,喝白开水,吃梨,奍猫
陈小姐穿蓝色衣服,喝茶,吃橘子,奍鱼
钱小姐穿红色衣服,喝牛奶,吃西瓜,奍鸟
江小姐穿绿色衣服,喝咖啡,吃香蕉,奍蛇
翁小姐穿白色衣服,喝香槟,吃苹果,奍狗
22
.
C++
中的重载函数的实现:
使用名字粉碎的方式,对不同的函数加以区分。如:
int f(char a, int b, float c);
编译后就是
f_cif()
;
23
.关于临时变量中使用
i
的看法:
对于代码规范来说使用
i
作为临时变量也是可取的,用此作为循环变量是可以的。
24
.一个字符串常量定义的应用错误:
#include <stdio.h>
#include <string.h>
void main()
{
//
这样定义就是指
getSource
,
source
指向的是一组字符串常量
char *source = "China";//
改为这样的定义方式就对了:
source[] = “China”;
char *getSource = "Republic of";
printf("%s %s ",getSource, source); //
可以引用常量,但是不能改变常量
printf("%s", strcpy(getSource, source)); //
执行错误
printf("%s", strncpy(getSource, source, sizeof(source))); //
执行错误
}
25
.一个不明白的执行错误:
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __endthreadex
nafxcwd.lib(thrdcore.obj) : error LNK2001: unresolved external symbol __beginthreadex
26
.匈牙利命名法:
windows API
的命名法则是匈牙利籍微软专家:查尔斯西蒙尼在博士论文里提出的。
<!--[if !supportLists]-->1. <!--[endif]-->标示符的名字以一个或者多个小写字母开头,用这些字母来指定数据类型:
前缀
|
数据类型
|
c
|
字符
|
s
|
字符串
|
cb
|
用于定义对象
(
一般为一个结构
)
尺寸的整数
|
n
|
整数
|
by
|
字节
|
i
|
Int
(整数)
|
x
|
短整数
(
坐标
x)
|
y
|
短整数
(
坐标
Y)
|
b
|
Boolean
(布尔值)
|
w
|
字(
Word
,
焐符号短整数)
|
l
|
长整数
(long)
|
g
|
HANDLE(
无符号
int)
|
m
|
类成员变量
|
fn
|
函数
(function)
|
dw
|
双字
(double word
,无符号长整形
)
|
2
.其他则从第二个字符起采用“驼峰”命名规则。
27
.
Cstring
转换
LPBYTE
格式
:
//////////////////////////////////////////////////////////////////////////
//
将
CString
转换为
LPBYTE
//
LPBYTE lpb = new BYTE[name.GetLength()+1];
for(int i = 0; i< name.GetLength();i++)
lpb[i] = name[i];
lpb[name.GetLength()] = 0;
//////////////////////////////////////////////////////////////////////////
28
.网络编程用的基本函数:
本文所谈到的
Socket
函数如果没有特别说明,都是指的
Windows Socket API
。
一、
WSAStartup
函数
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
使用
Socket
的程序在使用
Socket
之前必须调用
WSAStartup
函数。该函数的第一个参数指明程序请求使用的
Socket
版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的
Socket
的版本信息。当一个应用程序调用
WSAStartup
函数时,操作系统根据请求的
Socket
版本来搜索相应的
Socket
库,然后绑定找到的
Socket
库到该应用程序中。以后应用程序就可以调用所请求的
Socket
库中的其它
Socket
函数了。该函数执行成功后返回
0
。
例:假如一个程序要使用
2.1
版本的
Socket,
那么程序代码如下
wVersionRequested = MAKEWORD( 2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
二、
WSACleanup
函数
int WSACleanup (void);
应用程序在完成对请求的
Socket
库的使用后,要调用
WSACleanup
函数来解除与
Socket
库的绑定并且释放
Socket
库所占用的系统资源。
三、
socket
函数
SOCKET socket(
int af,
int type,
int protocol
);
应用程序调用
socket
函数来创建一个能够进行网络通信的套接字。第一个参数指定应用程序使用的通信协议的协议族,对于
TCP/IP
协议族,该参数置
PF_INET;
第二个参数指定要创建的套接字类型,流套接字类型为
SOCK_STREAM
、数据报套接字类型为
SOCK_DGRAM
;第三个参数指定应用程序所使用的通信协议。
该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回
INVALID_SOCKET
。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。下面是一个创建流套接字的例子:
struct protoent *ppe;
ppe=getprotobyname("tcp");
SOCKET ListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);
四、
closesocket
函数
int closesocket(
SOCKET s
);
closesocket
函数用来关闭一个描述符为
s
套接字。由于每个进程中都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构的被引用次数,即有多少个套接字描述符指向该结构。当调用
closesocket
函数时,操作系统先检查套接字数据结构中的该字段的值,如果为
1
,就表明只有一个套接字描述符指向它,因此操作系统就先把
s
在套接字描述符表中对应的那条表项清除,并且释放
s
对应的套接字数据结构;如果该字段大于
1
,那么操作系统仅仅清除
s
在套接字描述符表中的对应表项,并且把
s
对应的套接字数据结构的引用次数减
1
。
closesocket
函数如果执行成功就返回
0
,否则返回
SOCKET_ERROR
。
五、
send
函数
int send(
SOCKET s,
const char FAR *buf,
int len,
int flags
);
不论是客户还是服务器应用程序都用
send
函数来向
TCP
连接的另一端发送数据。客户程序一般用
send
函数向服务器发送请求,而服务器则通常用
send
函数来向客户程序发送应答。该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置
0
。这里只描述同步
Socket
的
send
函数的执行流程。当调用该函数时,
send
先比较待发送数据的长度
len
和套接字
s
的发送缓冲区的长度,如果
len
大于
s
的发送缓冲区的长度,该函数返回
SOCKET_ERROR
;如果
len
小于或者等于
s
的发送缓冲区的长度,那么
send
先检查协议是否正在发送
s
的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送
s
的发送缓冲中的数据或者
s
的发送缓冲中没有数据,那么
send
就比较
s
的发送缓冲区的剩余空间和
len
,如果
len
大于剩余空间大小
send
就一直等待协议把
s
的发送缓冲中的数据发送完,如果
len
小于剩余空间大小
send
就仅仅把
buf
中的数据
copy
到剩余空间里(注意并不是
send
把
s
的发送缓冲中的数据传到连接的另一端的,而是协议传的,
send
仅仅是把
buf
中的数据
copy
到
s
的发送缓冲区的剩余空间里)。如果
send
函数
copy
数据成功,就返回实际
copy
的字节数,如果
send
在
copy
数据时出现错误,那么
send
就返回
SOCKET_ERROR
;如果
send
在等待协议传送数据时网络断开的话,那么
send
函数也返回
SOCKET_ERROR
。要注意
send
函数把
buf
中的数据成功
copy
到
s
的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个
Socket
函数就会返回
SOCKET_ERROR
。(每一个除
send
外的
Socket
函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该
Socket
函数就返回
SOCKET_ERROR
)
注意:在
Unix
系统下,如果
send
在等待协议传送数据时网络断开的话,调用
send
的进程会接收到一个
SIGPIPE
信号,进程对该信号的默认处理是进程终止。
六、
recv
函数
int recv(
SOCKET s,
char FAR *buf,
int len,
int flags
);
不论是客户还是服务器应用程序都用
recv
函数从
TCP
连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放
recv
函数接收到的数据;第三个参数指明
buf
的长度;第四个参数一般置
0
。这里只描述同步
Socket
的
recv
函数的执行流程。当应用程序调用
recv
函数时,
recv
先等待
s
的发送缓冲中的数据被协议传送完毕,如果协议在传送
s
的发送缓冲中的数据时出现网络错误,那么
recv
函数返回
SOCKET_ERROR
,如果
s
的发送缓冲中没有数据或者数据被协议成功发送完毕后,
recv
先检查套接字
s
的接收缓冲区,如果
s
接收缓冲区中没有数据或者协议正在接收数据,那么
recv
就一直等待,只到协议把数据接收完毕。当协议把数据接收完毕,
recv
函数就把
s
的接收缓冲中的数据
copy
到
buf
中(注意协议接收到的数据可能大于
buf
的长度,所以在这种情况下要调用几次
recv
函数才能把
s
的接收缓冲中的数据
copy
完。
recv
函数仅仅是
copy
数据,真正的接收数据是协议来完成的),
recv
函数返回其实际
copy
的字节数。如果
recv
在
copy
时出错,那么它返回
SOCKET_ERROR
;如果
recv
函数在等待协议接收数据时网络中断了,那么它返回
0
。
注意:在
Unix
系统下,如果
recv
函数在等待协议接收数据时网络断开了,那么调用
recv
的进程会接收到一个
SIGPIPE
信号,进程对该信号的默认处理是进程终止。
七、
bind
函数
int bind(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
当创建了一个
Socket
以后,套接字数据结构中有一个默认的
IP
地址和默认的端口号。一个服务程序必须调用
bind
函数来给其绑定一个
IP
地址和一个特定的端口号。客户程序一般不必调用
bind
函数来为其
Socket
绑定
IP
地址和断口号。该函数的第一个参数指定待绑定的
Socket
描述符;第二个参数指定一个
sockaddr
结构,该结构是这样定义的:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
sa_family
指定地址族,对于
TCP/IP
协议族的套接字,给其置
AF_INET
。当对
TCP/IP
协议族的套接字进行绑定时,我们通常使用另一个地址结构:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
其中
sin_family
置
AF_INET
;
sin_port
指明端口号;
sin_addr
结构体中只有一个唯一的字段
s_addr
,表示
IP
地址,该字段是一个整数,一般用函数
inet_addr
()把字符串形式的
IP
地址转换成
unsigned long
型的整数值后再置给
s_addr
。有的服务器是多宿主机,至少有两个网卡,那么运行在这样的服务器上的服务程序在为其
Socket
绑定
IP
地址时可以把
htonl(INADDR_ANY)
置给
s_addr
,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;如果只给运行在多宿主机上的服务程序的
Socket
绑定一个固定的
IP
地址,那么就只有与该
IP
地址处于同一个网段上的客户程序才能与该服务程序通信。我们用
0
来填充
sin_zero
数组,目的是让
sockaddr_in
结构的大小与
sockaddr
结构的大小一致。下面是一个
bind
函数调用的例子:
struct sockaddr_in saddr
;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(ListenSocket,(struct sockaddr *)&saddr,sizeof(saddr))
;
八、
listen
函数
int listen( SOCKET s, int backlog );
服务程序可以调用
listen
函数使其流套接字
s
处于监听状态。处于监听状态的流套接字
s
将维护一个客户连接请求队列,该队列最多容纳
backlog
个客户连接请求。假如该函数执行成功,则返回
0
;如果执行失败,则返回
SOCKET_ERROR
。
九、
accept
函数
SOCKET accept(
SOCKET s,
struct sockaddr FAR *addr,
int FAR *addrlen
);
服务程序调用
accept
函数从处于监听状态的流套接字
s
的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回
INVALID_SOCKET
。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。下面是一个调用
accept
的例子:
struct sockaddr_in ServerSocketAddr;
int addrlen;
addrlen=sizeof(ServerSocketAddr);
ServerSocket=accept(ListenSocket,(struct sockaddr *)&ServerSocketAddr,&addrlen);
十、
connect
函数
int connect(
SOCKET s,
const struct sockaddr FAR *name,
int namelen
);
客户程序调用
connect
函数来使客户
Socket s
与监听于
name
所指定的计算机的特定端口上的服务
Socket
进行连接。如果连接成功,
connect
返回
0
;如果失败则返回
SOCKET_ERROR
。下面是一个例子:
struct sockaddr_in daddr;
memset((void *)&daddr,0,sizeof(daddr));
daddr.sin_family=AF_INET;
daddr.sin_port=htons(8888);
daddr.sin_addr.s_addr=inet_addr("133.197.22.4");
connect(ClientSocket,(struct sockaddr *)&daddr,sizeof(daddr))
;
29
.线程和进程:
[前言:]当前流行的
Windows
操作系统,它能同时运行几个程序
(
独立运行的程序又称之为进程
)
,对于同一个程序,它又可以分成若干个独立的执行流,我们称之为线程,线程提供了多任务处理的能力。用进程和线程的观点来研究软件是当今普遍采用的方法,进程和线程的概念的出现,对提高软件的并行性有着重要的意义。现在的应用软件无一不是多线程多任务处理,单线城的软件是不可想象的。因此掌握多线程多任务设计方法对每个程序员都是必需要掌握的。本文针对多线程技术在应用中经常遇到的问题,如线程间的通信、同步等,对它们分别进行探讨。
一、
理解线程
要讲解线程,不得不说一下进程,进程是应用程序的执行实例,每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成。进程在运行时创建的资源随着进程的终止而死亡。线程的基本思想很简单,它是一个独立的执行流,是进程内部的一个独立的执行单元,相当于一个子程序,它对应
Visual C++
中的
CwinThread
类的对象。单独一个执行程序运行时,缺省的运行包含的一个主线程,主线程以函数地址的形式,如
main
或
WinMain
函数,提供程序的启动点,当主线程终止时,进程也随之终止,但根据需要,应用程序又可以分解成许多独立执行的线程,每个线程并行的运行在同一进程中。
一个进程中的所有线程都在该进程的虚拟地址空间中,使用该进程的全局变量和系统资源。操作系统给每个线程分配不同的
CPU
时间片,在某一个时刻,
CPU
只执行一个时间片内的线程,多个时间片中的相应线程在
CPU
内轮流执行,由于每个时间片时间很短,所以对用户来说,仿佛各个线程在计算机中是并行处理的。操作系统是根据线程的优先级来安排
CPU
的时间,优先级高的线程优先运行,优先级低的线程则继续等待。
线程被分为两种:用户界面线程和工作线程(又称为后台线程)。用户界面线程通常用来处理用户的输入并响应各种事件和消息,其实,应用程序的主执行线程
CWinAPP
对象就是一个用户界面线程,当应用程序启动时自动创建和启动,同样它的终止也意味着该程序的结束,进城终止。工作者线程用来执行程序的后台处理任务,比如计算、调度、对串口的读写操作等,它和用户界面线程的区别是它不用从
CwinThread
类派生来创建,对它来说最重要的是如何实现工作线程任务的运行控制函数。工作线程和用户界面线程启动时要调用同一个函数的不同版本;最后需要读者明白的是,一个进程中的所有线程共享它们父进程的变量,但同时每个线程可以拥有自己的变量。
二、
线程的管理和操作
1
.
线程的启动
创建一个用户界面线程,首先要从类
CwinThread
产生一个派生类,同时必须使用
DECLARE_DYNCREATE
和
IMPLEMENT_DYNCREATE
来声明和实现这个
CwinThread
派生类。
第二步是根据需要重载该派生类的一些成员函数如:
ExitInstance()
;
InitInstance()
;
OnIdle();PreTranslateMessage()
等函数,最后启动该用户界面线程,调用
AfxBeginThread()
函数的一个版本:
CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass, int nPriority = THREAD_PRIORITY_NORMAL, UINT nStackSize = 0, DWORD dwCreateFlags = 0, LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
其中第一个参数为指向定义的用户界面线程类指针变量,第二个参数为线程的优先级,第三个参数为线程所对应的堆栈大小,第四个参数为线程创建时的附加标志,缺省为正常状态,如为
CREATE_SUSPENDED
则线程启动后为挂起状态。
对于工作线程来说,启动一个线程,首先需要编写一个希望与应用程序的其余部分并行运行的函数如
Fun1()
,接着定义一个指向
CwinThread
对象的指针变量
*pThread,
调用
AfxBeginThread(Fun1,param,priority)
函数,返回值付给
pThread
变量的同时一并启动该线程来执行上面的
Fun1()
函数,其中
Fun1
是线程要运行的函数的名字,也既是上面所说的控制函数的名字,
param
是准备传送给线程函数
Fun1
的任意
32
位值,
priority
则是定义该线程的优先级别,它是预定义的常数,读者可参考
MSDN
。
2
.线程的优先级
以下的
CwinThread
类的成员函数用于线程优先级的操作:
int GetThreadPriority();
BOOL SetThradPriority()(int nPriority);
上述的二个函数分别用来获取和设置线程的优先级,这里的优先级,是相对于该线程所处的优先权层次而言的,处于同一优先权层次的线程,优先级高的线程先运行;处于不同优先权层次上的线程,谁的优先权层次高,谁先运行。至于优先级设置所需的常数,自己参考
MSDN
就可以了,要注意的是要想设置线程的优先级,这个线程在创建时必须具有
THREAD_SET_INFORMATION
访问权限。对于线程的优先权层次的设置,
CwinThread
类没有提供相应的函数,但是可以通过
Win32 SDK
函数
GetPriorityClass()
和
SetPriorityClass()
来实现。
3
.线程的悬挂、恢复
CwinThread
类中包含了应用程序悬挂和恢复它所创建的线程的函数,其中
SuspendThread()
用来悬挂线程,暂停线程的执行;
ResumeThread()
用来恢复线程的执行。如果你对一个线程连续若干次执行
SuspendThread()
,则需要连续执行相应次的
ResumeThread()
来恢复线程的运行。
4
.结束线程
终止线程有三种途径,线程可以在自身内部调用
AfxEndThread()
来终止自身的运行;可以在线程的外部调用
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode )
来强行终止一个线程的运行,然后调用
CloseHandle
()函数释放线程所占用的堆栈;第三种方法是改变全局变量,使线程的执行函数返回,则该线程终止。下面以第三种方法为例,给出部分代码:
////////////////////////////////////////////////////////////////
//////CtestView message handlers
/////Set to True to end thread
Bool bend=FALSE;//
定义的全局变量,用于控制线程的运行
//The Thread Function
UINT ThreadFunction(LPVOID pParam)//
线程函数
{
while(!bend)
{Beep(100,100);
Sleep(1000);
}
return 0;
}
CwinThread *pThread;
HWND hWnd;
/////////////////////////////////////////////////////////////
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
pThread=AfxBeginThread(ThradFunction,hWnd);//
启动线程
pThread->m_bAutoDelete=FALSE;//
线程为手动删除
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{ bend=TRUE;//
改变变量,线程结束
WaitForSingleObject(pThread->m_hThread,INFINITE);//
等待线程结束
delete pThread;//
删除线程
Cview::OnDestroy();
}
三、
线程之间的通信
通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。
1
.
利用用户定义的消息通信
在
Windows
程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:
#define WM_USERMSG WMUSER+100
;在需要的时候,在一个线程中调用
::
PostMessage((HWND)param,WM_USERMSG,0,0)
或
CwinThread::PostThradMessage()
来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数
WPARAM
和
LPARAM
。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:
UINT ThreadFunction(LPVOID pParam)
{
while(!bend)
{
Beep(100,100);
Sleep(1000);
}
::
PostMessage(hWnd,WM_USERMSG,0,0)
;
return 0;
}
////////WM_USERMSG
消息的响应函数为
OnThreadended(WPARAM wParam,LPARAM lParam)
LONG CTestView::OnThreadended(WPARAM wParam,LPARAM lParam)
{
AfxMessageBox("Thread ended.");
Retrun 0;
}
上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::
GetMessage()
这个
SDK
函数进行消息分检和处理,自己实现一个消息循环。
GetMessage()
函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用
CPU
的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。
2
.用事件对象实现通信
在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用
MFC
的
Cevent
类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:
////////////////////////////////////////////////////////////////////
Cevent threadStart,threadEnd;
////////////////////////////////////////////////////////////////////
UINT ThreadFunction(LPVOID pParam)
{
::
WaitForSingleObject(threadStart.m_hObject,INFINITE);
AfxMessageBox("Thread start.");
while(!bend)
{
Beep(100,100);
Sleep(1000);
Int result=::WaitforSingleObject(threadEnd.m_hObject,0);
//
等待
threadEnd
事件有信号,无信号时线程在这里悬停
If(result==Wait_OBJECT_0)
Bend=TRUE;
}
::
PostMessage(hWnd,WM_USERMSG,0,0)
;
return 0;
}
/////////////////////////////////////////////////////////////
Void CtestView::OninitialUpdate()
{
hWnd=GetSafeHwnd();
threadStart.SetEvent();//threadStart
事件有信号
pThread=AfxBeginThread(ThreadFunction,hWnd);//
启动线程
pThread->m_bAutoDelete=FALSE;
Cview::OnInitialUpdate();
}
////////////////////////////////////////////////////////////////
Void CtestView::OnDestroy()
{ threadEnd.SetEvent();
WaitForSingleObject(pThread->m_hThread,INFINITE);
delete pThread;
Cview::OnDestroy();
}
运行这个程序,当关闭程序时,才显示提示框,显示
"Thread ended"
四、
线程之间的同步
前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。
Visual C++
中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,
MFC
支持的七个多线程的同步类可以分成两大类:同步对象(
CsyncObject
、
Csemaphore
、
Cmutex
、
CcriticalSection
和
Cevent
)和同步访问对象(
CmultiLock
和
CsingleLock
)。本节主要介绍临界区(
critical section
)、互斥(
mutexe
)、信号量(
semaphore
),这些同步对象使各个线程协调工作,程序运行起来更安全。
1
.
临界区
临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个
CcriticalSection
对象,当线程需要访问保护数据时,调用临界区对象的
Lock()
成员函数;当对保护数据的操作完成之后,调用临界区对象的
Unlock()
成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为
WriteThread()
和
ReadThread()
,用以对公共数组组
array[]
操作,下面的代码说明了如何使用临界区对象:
#include "afxmt.h"
int array[10],destarray[10];
CCriticalSection Section;
////////////////////////////////////////////////////////////////////////
UINT WriteThread(LPVOID param)
{Section.Lock();
for(int x=0;x<10;x++)
array[x]=x;
Section.Unlock();
}
UINT ReadThread(LPVOID param)
{
Section.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
Section.Unlock();
}
上述代码运行的结果应该是
Destarray
数组中的元素分别为
1-9
,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。
2
.
互斥
互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与
Cmutex
类的对象相对应,使用互斥对象时,必须创建一个
CSingleLock
或
CMultiLock
对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用
CSingleLock
对象,该对象的
Lock()
函数用于占有互斥,
Unlock()
用于释放互斥。实现代码如下:
#include "afxmt.h"
int array[10],destarray[10];
CMutex Section;
/////////////////////////////////////////////////////////////
UINT WriteThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
for(int x=0;x<10;x++)
array[x]=x;
singlelock.Unlock();
}
UINT ReadThread(LPVOID param)
{ CsingleLock singlelock;
singlelock (&Section);
singlelock.Lock();
For(int x=0;x<10;x++)
Destarray[x]=array[x];
singlelock.Unlock();
}
3
.
信号量
信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用
Csemaphore
类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个
CsingleLock
或
CmltiLock
对象,然后用该对象的
Lock()
函数减少这个信号量的计数值,
Unlock()
反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后
10
秒后第三个消息框才得以显示。
/////////////////////////////////////////////////////////////////
Csemaphore *semaphore;
Semaphore=new Csemaphore(2,2);
HWND hWnd=GetSafeHwnd();
AfxBeginThread(threadProc1,hWnd);
AfxBeginThread(threadProc2,hWnd);
AfxBeginThread(threadProc3,hWnd);
//////////////////////////////////////////////////////////////////////
UINT ThreadProc1(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread1 had access","Thread1",MB_OK);
return 0;
}
UINT ThreadProc2(LPVOID param)
{CSingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread2 had access","Thread2",MB_OK);
return 0;
}
UINT ThreadProc3(LPVOID param)
{CsingleLock singelLock(semaphore);
singleLock.Lock();
Sleep(10000);
::MessageBox((HWND)param,"Thread3 had access","Thread3",MB_OK);
return 0;
}
30
.程序的自动运行:
内容提要
在工作中经常遇到一些程序,当计算机启动时会自动将该程序加载,以实现对计算机的监控等特殊的目的。本文就针对这个问题,阐述了系统加载特定程序的原理和方法,同时利用
VC++ 6.0
编程实现这种特定的功能的,并对其中的关键代码进行了分析。
文章正文
工作中经常遇到一些程序,它们在系统启动的过程中,自动打开并运行,以便实现对系统的监控或者病毒的检测等特定的目的,典型的例子就是常用的一些杀毒软件如:
KV300
及瑞星杀毒软件等。笔者在此,以自己的编程实践为基础,说明这些这些程序自动启动的原理和方法,同时对一些典型程序代码进行分析,以便读者在今后的编程过程中使用。
一、
程序自动启动的原理及方法:
1.
利用
WIN.INI
文件实现相关程序的自动启动
WIN.INI
是系统保存在
C:WINDOWS
目录下的一个系统初始化文件。系统在起动时会检索该文件中的相关项,以便对系统环境的初始设置。
在该文件中的
"[windows]"
数据段中,有两个数据项
"load="
和
"run=",
它们的作用就是在系统起动之后自动得装入和运行相关的程序。如果我们需要在系统起动之后装入并运行一个程序,只将需要运行文件的全文件名添加在该数据项的后面系统起动后就会自动运行该程序,系统也会进入特定的操作环境中去。
2.
利用注册表实现相关程序的自动启动
系统注册表保存着系统的软件、硬件及其他与系统配置有关的重要信息,一个计算机系统的系统注册表一旦遭到破坏,整个系统将无法运行。
在计算机的系统注册表中的子目录中有一个目录的名称为
HKEY_LOCAL_MACHINESoftware MicrosoftWindowsCurrent_VersionRun
,如果你想让程序在系统起动的过程中启动该程序,就可以向该目录添加一个子项,具体的过程是在注册表中右击该项,选中其中的
"
新建
"
项目,然后选中其中的
"
串值
"
,建立新的串值后将它的名称改成相应的名称,双击新建的串值,输入新的数值,自动启动程序的过程就设置完成。
二、
利用
VC++
编程实现程序自动启动的编程实例。
微软公司提供的
VC++ 6.0
程序开发工具功能非常强大。在
VC++ 6.0
中同时具有对注册表和
*.INI
文件操作的函数。笔者经过一段时间的摸索,成功的利用
VC++ 6.0
开发成功了一个小软件,该软件利用系统自动启动程序的原理,将原来需要的繁琐的手动工作转变成成计算机的自动设置工作,使系统自动启动相关程序的设置工作变的非常简单可靠。
1.
程序功能概述:
程序的主界面是对话框,在主界面对话框中有编辑框(
EDIT BOX
),圆形按钮(
RADIO BUTTON
)和普通按钮(
COMMON BUTTON
)组成。操作者通过向编辑框中添加需要自动加载的程序的全文件名(也可以通过浏览按钮来完成),然后通过对两个
RADIO BUTTON
的选择,进而完成对加载方式的选择(是选用注册表方式还是选者修改
WIN.INI
文件的方式),最后操作者通过点击
"
应用
"
按钮实现程序的自动加载功能,同时系统会提示操作者是否将操作计算机重新启动,以便观察程序的具体功能完成情况。程序在
WIN98
中调试运行正常。
2.
编码说明:
①
浏览按钮的功能代码:
void CAutoloadDlg::OnLiulan()
{
// TODO: Add your control notification handler code here
CFileDialog fileDlg(TRUE,_T("EXE"),_T("*.exe"),OFN_HIDEREADONLY|OFN_OVERWRITEPROMPT,(_T("Executable Files (*.exe) |*.exe ||")));//
显示打开文件的对话框
if(fileDlg.DoModal()==IDOK)//
当操作者选择
OK
时,程序,取得选择文
//
件的全路径名(包括文件的路径及文件名称),并将相应的数值传输给相
//
关的控件变量
{
m_filename=fileDlg.GetPathName();//m_filename
是
EDIT BOX
控件的相应的变量。
UpdateData(FALSE);//
向将变量中的数值传输给控件显示出来。
}
②
应用按钮的功能代码:
void CAutoloadDlg::OnOK()
{
// TODO: Add extra validation here
LPCTSTR title;
UpdateData(TRUE);
if(m_title.IsEmpty())//
如果操作者没有填写要设置项的标题,程序显示对话框,提示操作者进行相关的填写。
{
MessageBox(_T("Please input the title name"));
return;
}
title=m_title;
if(m_filename.IsEmpty())//
如果操作者没有选择要设置的程序的全路径文
//
件名,程序显示对话框,提示操作者进行相关的选择。
{
MessageBox(_T("Please input the programe file name"));
return;
}
if(IsDlgButtonChecked(IDC_RADIO1))//
如果操作者选择注册表方式,程序修改系统的注册表。
{
HKEY hKey;
LPCTSTR data_Set="SoftwareMicrosoftWindowsCurrentVersionRun";//
设置注册表中相关的路径
Longret0=(::RegOpenKeyEx(HKEY_LOCAL_MACHINE,data_Set,0,KEY_WRITE,&hKey));//
打开注册表中的相应项
if(ret0!=ERROR_SUCCESS)
{
MessageBox("
错误
0");
}
int length=m_filename.GetLength()+1;//
将控件中的内容进行转换,以达到注册表修改函数的参数调用需求。
for(int i=0;i){
if(m_filename[i]==92)
length=length+1;
}
DWORD cbData=length;
LPBYTE lpb=new BYTE[length];
int j=0;
for(i=0;i{
if(m_filename[i]==92)
{
lpb[j]=92;
j++;
lpb[j]=92;
j++;
}
else
{
lpb[j]=m_filename[i];
j++;
}
}
lpb[j]=0;
long ret1=(::RegSetValueEx(hKey,title,NULL,REG_SZ,lpb,cbData));//
将相关的信息写入注册表。
if(ret1!=ERROR_SUCCESS)//
判断系统的相关注册是否成功?
{
MessageBox("
错误
1");
}
delete lpb;
::RegCloseKey(hKey);//
关闭注册表中的相应的项
}
if(IsDlgButtonChecked(IDC_RADIO2))//
如果操作者选择用修改
WIN.INI
文件的方式
{
LPCTSTR filename;
filename=m_filename;
WritePrivateProfileString(_T("windows"),_T("load"),filename,_T("c:windowswin.ini"));
WritePrivateProfileString(_T("windows"),_T("run"),filename,_T("c:windowswin.ini"));
}
yzdlg.DoModal();//
显示对话框,提示操作者是否需要重新启动计算机,以便验证程序的功能。
CDialog::OnOK();
}
③
重新启动按钮的功能代码:
void yanzheng::OnOK()
{
OSVERSIONINFO OsVerInfo;//
保存系统版本信息的数据结构
OsVerInfo.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&OsVerInfo);//
取得系统的版本信息
if(OsVerInfo.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS)
{
ExitWindowsEx(EWX_REBOOT,0);//
重新启动计算机
}
CDialog::OnOK();
}
31
.获得本机的地址,机器名,
IP
……:
#include <winsock.h>
#include <wsipx.h>
#include <wsnwlink.h>
#include <stdio.h>
int main()
{
////////////////
//
初始化
Windows sockets API.
要求版本为
version 1.1
//
WORD wVersionRequested = MAKEWORD(1, 1);
WSADATA wsaData;
if (WSAStartup(wVersionRequested, &wsaData)) {
printf("WSAStartup failed %s\n", WSAGetLastError());
return -1;
}
//////////////////
//
获得主机名
.
//
char hostname[256];
int res = gethostname(hostname, sizeof(hostname));
if (res != 0) {
printf("Error: %u\n", WSAGetLastError());
return -1;
}
printf("hostname=%s\n", hostname);
////////////////
//
根据主机名获取主机信息
.
//
hostent* pHostent = gethostbyname(hostname);
if (pHostent==NULL) {
printf("Error: %u\n", WSAGetLastError());
return -1;
}
//////////////////
//
解析返回的
hostent
信息
.
//
hostent& he = *pHostent;
printf("name=%s\naliases=%s\naddrtype=%d\nlength=%d\n",
he.h_name, he.h_aliases, he.h_addrtype, he.h_length);
sockaddr_in sa;
for (int nAdapter=0; he.h_addr_list[nAdapter]; nAdapter++) {
memcpy ( &sa.sin_addr.s_addr, he.h_addr_list[nAdapter],he.h_length);
//
输出机器的
IP
地址
.
printf("Address: %s\n", inet_ntoa(sa.sin_addr)); //
显示地址串
}
//////////////////
//
终止
Windows sockets API
//
WSACleanup();
return 0;
}
32
.一个对系统进行“恶作剧”的注册表应用程序段:
//////////////////////////////////////////////////////////////////////////
//
// Author: hongweijin(robocode_java@163.com)
UpdateData(true);
HKEY hKey;
//
//
设置注册表中相关的路径
//
LPCTSTR data_Set = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run\\";
//
// RegOpenKeyEx:
的函数参数的设置
,
对
read
方式没有进行出错检查,
//
在下面可以看到出错检查
//
DWORD Souce_Data = 1024;
LPBYTE data_Set2 = new BYTE[100];
DWORD typeClient = REG_SZ;
RegOpenKeyEx(HKEY_LOCAL_MACHINE, data_Set, 0, KEY_READ, &hKey);
//
//
查询当前的键值有没有存在,若存在讲不写注册表
//
long retSec = (::RegQueryValueEx(hKey,
"LTSMMSGL", NULL, &typeClient, data_Set2, &Souce_Data));
//
//
假如
ERROR_SUCCESS
成立,即找到有这样的值,那么就不去执行下面的程序块
//
若不成立,那么下面是如何去写一个自动执行的注册表实现
//
if (retSec != ERROR_SUCCESS)
{
//
//
先关掉当前以
KEY_READ
方式打开的注册表
//
RegCloseKey(hKey);
//
//
打开注册表中的相应项
//
long ret0 = (::RegOpenKeyEx(HKEY_LOCAL_MACHINE, data_Set, 0, KEY_WRITE, &hKey));
if(ret0 != ERROR_SUCCESS)
MessageBox("
我靠
!
啥机子这么强
!
注册表也禁掉了
?");
//
//
设置的键名
//
LPCTSTR title = "LTSMMSGL";
//
//
路径,反正接下来是注销,管它是不是分配的太多。
//
取当前路径,并对路径进行更改
//
char name[3000];
GetCurrentDirectory(3000, name);
//////////////////////////////////////////////////////////////////////////
//_getcwd(name, 1024);
strcat(name, "\\LTSMMSGL.EXE");
int strLen = strlen(name);
DWORD cbData = (DWORD)strLen;
//////////////////////////////////////////////////////////////////////////
//
//
将
char
类型的数据转换为
BYTE
的格式
//
LPBYTE lpb = new BYTE[strLen + 1];
for(int i = 0; i < strLen; i++)
lpb[i] = name[i];
lpb[strLen] = 0;
//////////////////////////////////////////////////////////////////////////
//
//
将相关的信息写入注册表。
//
long ret1 = (::RegSetValueEx(hKey, title, NULL, REG_SZ, lpb, cbData));
if (ret1 != ERROR_SUCCESS)//
判断系统的相关注册是否成功?
MessageBox("
不会吧
!
牛比……
!");
}
RegCloseKey(hKey);
//////////////////////////////////////////////////////////////////////////
//
调用系统函数,对系统注销
//
BOOL ReStartON = FALSE;
if (ReStartON == ExitWindowsEx(EWX_LOGOFF, NULL))
MessageBox("
不会吧
,
怎么回事啊
,
到底出现了什么问题
,
快重新启动啊
,
不然后果俺不负责
!");
33.
对自己的程序服务进行提升:
不用了,
BCB
有自己的
“
服务向导
”
。
以下的找来的代码。
Windows NT
与
Windows 9x
有一个非常重要的区别,即
Windows NT
提供了很多功能强大的
Service(
服务
)
。这些
Service
可以随着
NT
的启动而自启动,也可以让用户通过控制面板启动,还可以被
Win32
应用程序起停。甚至在没有用户登录系统的情况下,这些
Service
也能执行。许多
FTP
、
WWW
服务器和数据库就是以
Service
的形式存在于
NT
上,从而实现了无人值守。就连最新版的
“
黑客
”
程序
Back Orifice 2000
也是以
Service
形式在
NT
上藏身的。由于
Service
的编程较复杂,许多开发者想开发自己的
Service
但往往都望而却步。鉴于此,下面我们就从头到尾来构造一个全新的
Service
,读者只要在程序中注明的地方加上自己的代码,那么就可以轻松拥有一个自己的
Service
。在编写
Service
之前,先介绍一下几个重要的函数:
---- 1. SC_HANDLE OpenSCManager( LPCTSTR lpMachineName,
LPCTSTR lpDatabaseName, DWORD dwDesiredAccess)
---- OpenSCManager
函数打开指定计算机上的
service control
manager database
。其中参数
lpMachineName
指定计算机名,若为空则指定为本机。
LpDatabaseName
为指定要打开的
service control manager database
名
,
默认为空。
dwDesiredAccess
指定操作的权限
,
可以为下面取值之一:
---- SC_MANAGER_ALL_ACCESS //
所有权限
---- SC_MANAGER_CONNECT //
允许连接到
service control manager database
---- SC_MANAGER_CREATE_SERVICE //
允许创建服务对象并把它加入
database
---- SC_MANAGER_ENUMERATE_SERVICE //
允许枚举
database
中的
Service
---- SC_MANAGER_LOCK //
允许锁住
database
---- SC_MANAGER_QUERY_LOCK_STATUS //
允许查询
database
的封锁信息
----
函数执行成功则返回一个指向
service control manager
database
的句柄,失败则返回
NULL
。注意:
WINNT
通过一个名为
service control manager database
的数据库来管理所有的
Service
,因此对
Service
的任何操作都应打开此数据库。
---- 2. SC_HANDLE CreateService(SC_HANDLE
hSCManager,
LPCTSTR lpServiceName,
LPCTSTR lpDisplayName,
DWORD dwDesiredAccess,
DWORD dwServiceType,
DWORD dwStartType,
DWORD dwErrorControl,
LPCTSTR lpBinaryPathName,
LPCTSTR lpLoadOrderGroup,
LPDWORD lpdwTagId,
LPCTSTR lpDependencies,
LPCTSTR lpServiceStartName,
LPCTSTR lpPassword)
---- CreatService
函数产生一个新的
SERVICE
。其中参数
hSCManager
为指向
service
control manager database
的句柄,由
OpenSCManager
返回。
LpServiceName
为
SERVICE
的名字,
lpDisplayName
为
Service
显示用名,
dwDesiredAccess
是访问权限,本程序中用
SERVICE_ALL_ACCESS
。
wServiceType,
指明
SERVICE
类型,本程序中用
SERVICE_WIN32_OWN_PROCESS| SERVICE_INTERACTIVE_PROCESS
。
dwStartType
为
Service
启动方式,本程序采用自启动,即
dwStartType
等于
SERVICE_AUTO_START
。
dwErrorControl
说明当
Service
在启动中出错时采取什么动作,本程序采用
SERVICE_ERROR_IGNORE
即忽约错误,读者可以改为其他的。
LpBinaryPathName
指明
Service
本体程序的路径名。剩下的五个参数一般可设为
NULL
。如函数调用成功则返回这个新
Service
的句柄,失败则返回
NULL
。与此函数对应的是
DeleteService( hService)
,它删除指定的
Service
。
---- 3. SC_HANDLE OpenService(SC_HANDLE hSCManager,LPCTSTR lpServiceName, DWORD dwDesiredAccess )
---- OpenService
函数打开指定的
Service
。其中参数
hSCManager
为指向
service
control manager database
的句柄,由
OpenSCManager
返回。
LpServiceName
为
Service
的名字,
dwDesiredAccess
是访问权限,其可选值比较多,读者可以参看
SDK
Help.
函数调用成功则返回打开的
Service
句柄,失败则返回
NULL
。
---- 4. BOOL StartService( SC_HANDLE hService, DWORD dwNumServiceArgs,LPCTSTR *lpServiceArgVectors )
---- StartService
函数启动指定的
Service
。其中参数
hService
为指向
Service
的句柄,由
OpenService
返回。
dwNumServiceAr
为启动服务所需的参数的个数。
lpszServiceArgs
为
启
动
服务所需的参数。函数执行成功则返回
True,
失败则返回
False
。
---- 5. BOOL ControlService(SC_HANDLE hService DWORD dwControl,LPSERVICE_STATUS lpServiceStatus )
---- Service
程序没有专门的停止函数,而是用
ControlService
函数来控制
Service
的暂停、继续、停止等操作。参数
dwControl
指定发出的控制命令,可以为以下几个值:
SERVICE_CONTROL_STOP //
停止
Service
SERVICE_CONTROL_PAUSE //
暂停
Service
SERVICE_CONTROL_CONTINUE //
继续
Service
SERVICE_CONTROL_INTERROGATE //
查询
Service
的状态
SERVICE_CONTROL_SHUTDOWN //
让
ControlService
调用失效
----
参数
lpServiceStatus
是一个指向
SERVICE_STATUS
的指针。
SERVICE_STATUS
是一个比较重要的结构,它包含了
Service
的各种信息,如当前状态、可接受何种控制命令等等。
---- 6. BOOL QueryServiceStatus( SC_HANDLE hService,LPSERVICE_STATUS lpServiceStatus )
---- QueryServiceStatus
函数比较简单,它查询并返回当前
Service
的状态。
----
编制一个
Service
一般需要两个程序,一个是
Service
本体,一个是用于对
Service
进行控制的控制程序。通常
Service
本体是一个
console
程序,而控制程序则是一个普通的
Win32
应用程序(当然,用户不用控制程序而通过控制面板也可对
Service
进行启、停,但不能进行添加、删除操作。)
----
首先,我们来编写
Service
本体。对于
Service
本体来说,它一般又由以下三部分组成:
main()
、
ServiceMain
()、
Handler
(),下面是
main()
的源代码:(注:由于篇幅的关系,大部分程序都没进行错误处理,读者可以自己添上)
int main(int argc, char **argv)
{
SERVICE_TABLE_ENTRY ste[2];
//
一个
Service
进程可以有多个线程,这是每个
//
线程的入口表
ste[0].lpServiceName="W.Z.SERVICE"; //
线程名字
ste[0].lpServiceProc=ServiceMain;
//
线程入口地址
ste[1].lpServiceName=NULL;
//
最后一个必须为
NULL
ste[1].lpServiceProc=NULL;
StartServiceCtrlDispatcher(ste);
return 0;
}
---- main()
是
Service
的主线程。当
servie control manager
开始一个
Service
进程时,它总是等待这个
Service
去调用
StartServiceCtrlDispatcher
()函数。
main(
)
作为这个进程的主线程应该在程序开始后尽快调用
StartServiceCtrlDispatcher
()。
StartServiceCtrlDispatcher
()在被调用后并不立即返回,它把本
Service
的主线程连接到
service
control manager
,从而让
service control manager
通过这个连接发送开始、停止等控制命令给主线程。主线程在这时就扮演了一个命令的转发器的角色,它或者调用
Handle( )
去处理停止、继续等控制要求,或者产生一个新线程去执行
ServiceMain
。
StartServiceCtrlDispatcher
()在整个
Service
结束时才返回。
---- ServiceMain
()是
Service
真正的入口点,必须在
main()
中进行了正确的定义。
ServiceMain( )
的两个参数是由
StartService
()传递过来的。下面是
ServiceMain()
的源代码:
void WINAPI ServiceMain(DWORD dwArgc,LPTSTR *lpszArgv)
{
ssh=RegisterServiceCtrlHandler
("W.Z.SERVICE",Handler);
ss.dwServiceType=SERVICE_WIN32_OWN
_PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_START_PENDING;
//
如用户程序的代码比较多
(执行时间超过
1
秒),这儿要设成
SERVICE_
START_PENDING
,待用户程序完成后再设为
SERVICE_RUNNING
。
ss.dwControlsAccepted=SERVICE_ACCEPT_
STOP;//
表明
Service
目前能接受的命令是停止命令。
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh, &ss);
//
必须随时更新数据库中
Service
的状态。
Mycode(); //
这儿可放入用户自己的代码
ss.dwServiceType=SERVICE_WIN32_OWN_
PROCESS|SERVICE_INTERACTIVE_PROCESS;
ss.dwCurrentState=SERVICE_RUNNING;
ss.dwControlsAccepted=SERVICE_ACCEPT_STOP;
ss.dwWin32ExitCode=NO_ERROR;
ss.dwCheckPoint=0;
ss.dwWaitHint=0;
SetServiceStatus(ssh,&ss);
Mycode();//
这儿也可放入用户自己的代码
}
在
ServiceMain
()中应该立即调用
RegisterServiceCtrlHandler
()注册一个
Handler
去处理控制程序或控制面板对
Service
的控制要求。
Handler
()被转发器调用去处理要求,
下面是
Handler()
的源代码
:
void WINAPI Handler(DWORD Opcode)
{
switch(Opcode)
{
case SERVICE_CONTROL_STOP: //
停止
Service
Mycode
();
//
这儿可放入用户自己的相关代码
ss.dwWin32ExitCode = 0;
ss.dwCurrentState =SERVICE_STOPPED;
//
把
Service
的当前状态置为
STOP
ss.dwCheckPoint = 0;
ss.dwWaitHint = 0;
SetServiceStatus (ssh,&ss);
/
必须随时更新数据库中
Service
的状态
break;
case SERVICE_CONTROL_INTERROGATE:
SetServiceStatus (ssh,&ss);
/
必须随时更新数据库中
Service
的状态
break;
}
}
----
好了,
Service
本体程序已基本完成,我们接着来看一下
Service
的控制程序:
----
控制程序是一个标准的
window
程序,上面主要有四个按纽:
Create Service
、
Delete
Service
、
start
、
stop
,分别用来产生、删除、开始和停止
Service
。下面是它们的部分源代码:
1.
产生
Service
void __fastcall TForm1::CreateBtnClick
(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,
SC_MANAGER_CREATE_SERVICE);
if (scm!=NULL){
svc=CreateService(scm,
"W.Z.SERVICE","W.Z.SERVICE",//Service
名字
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS |SERVICE_INTERACTIVE_PROCESS,
SERVICE_AUTO_START,
//
以自动方式开始
SERVICE_ERROR_IGNORE,
"C:\\ntservice.exe", //Service
本体程序路径,
必须与具体位置相符
NULL,NULL,NULL,NULL,NULL);
if (svc!=NULL)
CloseServiceHandle(svc);
CloseServiceHandle(scm);
}
}
2.
删除
Service
void __fastcall TForm1::DeleteBtnClick
(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,
SC_MANAGER_CONNECT);
if (scm!=NULL){
svc=OpenService(scm,"W.Z.SERVICE",
SERVICE_ALL_ACCESS);
if (svc!=NULL){
QueryServiceStatus(svc,&ServiceStatus);
if (ServiceStatus.dwCurrentState==
SERVICE_RUNNING)//
删除前,先停止此
Service.
ControlService(svc,
SERVICE_CONTROL_STOP,&ServiceStatus);
DeleteService(svc);
CloseServiceHandle(svc);
//
删除
Service
后,最好再调用
CloseServiceHandle
}
//
以便立即从数据库中移走此条目。
CloseServiceHandle(scm);
}
}
3.
开始
Service
void __fastcall TForm1::StartBtnClick(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,SC_MANAGER_CONNECT);
if (scm!=NULL){
svc=OpenService(scm,"W.Z.SERVICE",SERVICE_START);
if (svc!=NULL){
StartService(svc,0,NULL);//
开始
Service
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
}
}
4.
停止
Service
void __fastcall TForm1::StopBtnClick
(TObject *Sender)
{
scm=OpenSCManager(NULL,NULL,
SC_MANAGER_ALL_ACCESS);
if (scm!=NULL){
svc=OpenService(scm,"W.Z.SERVICE",
SERVICE_STOP|SERVICE_QUERY_STATUS);
if (svc!=NULL){
QueryServiceStatus(svc,&ServiceStatus);
if (ServiceStatus.dwCurrentState==
SERVICE_RUNNING)
ControlService(svc,
SERVICE_CONTROL_STOP,&ServiceStatus);
CloseServiceHandle(svc);
}
CloseServiceHandle(scm);
}
}
34
.预读功能影响媒体文件的删除:
预读机制:
某些媒体播放中断或正在预览时会造成无法删除。在
“
运行
”
框中输入:
REGSVR32 /U SHMEDIA.DLL
,注销掉预读功能。或删除注册表中下面这个键值:
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{87D62D94-71B3-4b9a-9489-5FE6850DC73E}\InProcServer32]
。
35
.数据类型的转换:
目录
一.
VC
常用数据类型列表
二.
常用数据类型转化
2.1
数学类型变量与字符串相互转换
2.2 CString
及
string,char *
与其他数据类型的转换和操作
●CString,string,char*
的综合比较
●
数学类型与
CString
相互转化
●CString
与
char*
相互转换举例
●CString
与
BSTR
型转换
●VARIANT
型转化成
CString
型
2.3 BSTR
、
_bstr_t
与
CComBSTR
2.4 VARIANT
、
_variant_t
与
COleVariant
附录
CString
及字符串转及操作详解
参考书籍
:CSDN,<<MFC
深入浅出
(Second Edit)>>
一.
VC
常用数据类型列表
|
|
|
|
|
说明
:
这些基础数据类型对于
MFC
还是
API
都是被支持的
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Win32
API
常
用
数
据
类
型
全
大
写
|
说明
:
这些
Win32API
支持的简单数据类型主要是用来定义函数返回值,消息参数,结构成员。这类数据类型大致可以分为五大类:字符型、布尔型、整型、指针型和句柄型(
?
)
.
总共大概有
100
多种不同的类型,
|
|
|
|
|
|
|
|
|
BSTR
是指向字符串的
32
位指针
是对
BSTR
的封装
是对
BSTR
的封装
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
其他相似类型
: LPHANDLE
、
LPINT
、
LPLONG
、
LPWORD
、
LPRESULT
PBOOL
、
PBOOLEAN
、
PBYTE
、
PCHAR
、
PDWORD
、
PFLOAT
、
PHANDLE
、
PINT
、
PLONG
、
PSHORT
……
说明
:(1)
在
16
位系统中
LP
为
16bit,P
为
8bit,
在
32
位系统中都是
32bit(
此时等价
)
(2)LPCSTR
等
中的
C
指
Const,T
表示
TCHAR
模式即可以工作在
ANSI
下也可
UNICODE
|
|
|
|
其他
UCHAR
、
UINT
、
ULONG
、
ULONGLONG
、
USHORT
为无符号相应类型
|
|
|
|
|
|
|
VARIANT
_variant_t
COleVariant
|
|
一个结构体参考
OAIDL.H
_variant_t
是
VARIANT
的封装类
COleVariant
也是
VARIANT
的封装类
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
标记集合中一个元素的位置的值
,
被
MFC
中的集合类所使用
|
|
指向一个
RECT
结构体常量(不能修改)的
32
位指针
|
|
|
|
|
|
说明
:
(1)-------
表示省略
(2)1Byte=8Bit,
字与机器有关
,
在
8
位系统中
:
字
=1
字节
,16
位系统中
,1
字
=2
字节,
32
位中
:1
字
=4
字节
,
64
位中
1
字
=8
字节
.
不要搞混这些概念
.
二.常用数据类型转化及操作
2
.
1
数学类型变量与字符串相互转换
(
这些函数都在
STDLIB.H
里
)
(
1
)将数学类型转换为字符串可以用以下一些函数
:
举例
: _CRTIMP char * __cdecl _itoa(int, char *, int);//
这是一个将数字转换为一个字符串类型的函数
,
最后一个
int
表示转换的进制
如以下程序
:
int iTyep=3;
char *szChar;
itoa(iType,szChar,2);
cout<<szChar;//
输出为
1010
类似函数列表
:
_CRTIMP char * __cdecl _itoa(int, char *, int);//
为了完整性
,
也列在其中
_CRTIMP char * __cdecl _ultoa(unsigned long, char *, int);
_CRTIMP char * __cdecl _ltoa(long, char *, int);
_CRTIMP char * __cdecl _i64toa(__int64, char *, int);
_CRTIMP char * __cdecl _ui64toa(unsigned __int64, char *, int);
_CRTIMP wchar_t * __cdecl _i64tow(__int64, wchar_t *, int);
_CRTIMP wchar_t * __cdecl _ui64tow(unsigned __int64, wchar_t *, int);
_CRTIMP wchar_t * __cdecl _itow (int, wchar_t *, int);//
转换为长字符串类型
_CRTIMP wchar_t * __cdecl _ltow (long, wchar_t *, int);
_CRTIMP wchar_t * __cdecl _ultow (unsigned long, wchar_t *, int);
还有很多
,
请自行研究
(
2
)将字符串类型转换为数学类型变量可以用以下一些函数
:
举例
: _CRTIMP int __cdecl atoi(const char *);//
参数一看就很明了
char *szChar=”88”;
int temp(0);
temp=atoi(szChar);
cout<<temp;
类似的函数列表
:
_CRTIMP int __cdecl atoi(const char *);
_CRTIMP double __cdecl atof(const char *);
_CRTIMP long __cdecl atol(const char *);
_CRTIMP long double __cdecl _atold(const char *);
_CRTIMP __int64 __cdecl _atoi64(const char *);
_CRTIMP double __cdecl strtod(const char *, char **);//
_CRTIMP long __cdecl strtol(const char *, char **, int);//
_CRTIMP long double __cdecl _strtold(const char *, char **);
_CRTIMP unsigned long __cdecl strtoul(const char *, char **, int);
_CRTIMP double __cdecl wcstod(const wchar_t *, wchar_t **);//
长字符串类型转换为数学类型
_CRTIMP long __cdecl wcstol(const wchar_t *, wchar_t **, int);
_CRTIMP unsigned long __cdecl wcstoul(const wchar_t *, wchar_t **, int);
_CRTIMP int __cdecl _wtoi(const wchar_t *);
_CRTIMP long __cdecl _wtol(const wchar_t *);
_CRTIMP __int64 __cdecl _wtoi64(const wchar_t *);
还有很多
,
请自行研究
2
.
2
.
CString
及
string,char *
与其他数据类型的转换和操作
(
1
)
CString,string,char*
的综合比较(这部分
CSDN
上的作者
joise
的文章
<< CString,string,char*
的综合比较
>>
写的很详细
,
请大家在仔细阅读他的文章
.
地址
: http://blog.csdn.net/joise/
或参考附录
:
(2)
转换
:
●
数学类型与
CString
相互转化
数学类型转化为
CString
可用
Format
函数
,
举例
:
CString s;
int i = 64;
s.Format("%d", i)
CString
转换为数学类型
:
举例
CString strValue("1.234");
double dblValue;
dblValue = atof((LPCTSTR)strValue);
●CString
与
char*
相互转换举例
CString strValue(“Hello”);
char *szValue;
szValue=strValue.GetBuffer(szValue);
也可用
(LPSTR)(LPCTSTR)
对
CString//
进行强制转换
.
szValue=(LPSTR)(LPCTSTR)strValue;
反过来可直接赋值
:
char *szChar=NULL;
CString strValue;
szChar=new char[10];
memset(szChar,0,10);
strcpy(szChar,”Hello”);
strValue=szChar;
●CString
与
BSTR
型转换
CString
型转化成
BSTR
型
当我们使用
ActiveX
控件编程时,经常需要用到将某个值表示成
BSTR
类型
.BSTR
是一种记数字符串,
Intel
平台上的宽字符串(
Unicode
),并且可以包含嵌入的
NULL
字符。
可以调用
CString
对象的
AllocSysString
方法将
CString
转化成
BSTR
:
CString str;
str = .....; // whatever
BSTR bStr = str.AllocSysString();
BSTR
型转换为
CString
如果你在
UNICODE
模式下编译代码,你可以简单地写成:
CString convert(BSTR bStr)
{
if(bStr == NULL)
return CString(_T(""));
CString s(bStr); // in UNICODE mode
return s;
}
如果是
ANSI
模式
CString convert(BSTR b)
{
CString s;
if(b == NULL)
return s; // empty for NULL BSTR
#ifdef UNICODE
s = b;
#else
LPSTR p = s.GetBuffer(SysStringLen(b) + 1);
::WideCharToMultiByte(CP_ACP, // ANSI Code Page
0, // no flags
b, // source widechar string
-1, // assume NUL-terminated
p, // target buffer
SysStringLen(b)+1, // target buffer length
NULL, // use system default char
NULL); // don''t care if default used
s.ReleaseBuffer();
#endif
return s;
}
●VARIANT
型转化成
CString
型
VARIANT
类型经常用来给
COM
对象传递参数,或者接收从
COM
对象返回的值。你也能自己编写返回
VARIANT
类型的方法,函数返回什么类型
依赖可能(并且常常)方法的输入参数(比如,在自动化操作中,依赖与你调用哪个方法。
IDispatch::Invoke
可能返回(通过其一个参数)一个
包含有
BYTE
、
WORD
、
float
、
double
、
date
、
BSTR
等等
VARIANT
类型的结果,(详见
MSDN
上的
VARIANT
结构的定义)。在下面的例子中,假设
类型是一个
BSTR
的变体,也就是说在串中的值是通过
bsrtVal
来引用,其优点是在
ANSI
应用中,有一个构造函数会把
LPCWCHAR
引用的值转换为一个
CString
(见
BSTR-to-CString
部分)。在
Unicode
模式中,将成为标准的
CString
构造函数,参见对缺省
::WideCharToMultiByte
转换的告诫,以及你觉得是否可以接受(大多数情况下,你会满意的)。
VARIANT vaData;
vaData = m_com.YourMethodHere();
ASSERT(vaData.vt == VT_BSTR);
CString strData(vaData.bstrVal);
你还可以根据
vt
域的不同来建立更通用的转换例程。为此你可能会考虑:
CString VariantToString(VARIANT * va)
{
CString s;
switch(va->vt)
{ /* vt */
case VT_BSTR:
return CString(vaData->bstrVal);
case VT_BSTR | VT_BYREF:
return CString(*vaData->pbstrVal);
case VT_I4:
s.Format(_T("%d"), va->lVal);
return s;
case VT_I4 | VT_BYREF:
s.Format(_T("%d"), *va->plVal);
case VT_R8:
s.Format(_T("%f"), va->dblVal);
return s;
...
剩下的类型转换由读者自己完成
default:
ASSERT(FALSE); // unknown VARIANT type (this ASSERT is optional)
return CString("");
} /* vt */
}
2
.
3 BSTR
、
_bstr_t
与
CComBSTR
CComBSTR
、
_bstr_t
是对
BSTR
的封装
,BSTR
是指向字符串的
32
位指针。
char *
转换到
BSTR
可以这样
:
BSTR b=_com_util::ConvertStringToBSTR("
数据
");///
使用前需要加上头文件
comutil.h
反之可以使用
char *p=_com_util::ConvertBSTRToString(b);
2
.
4(
引
)VARIANT
、
_variant_t
与
COleVariant
VARIANT
的结构可以参考头文件
VC98\Include\OAIDL.H
中关于结构体
tagVARIANT
的定义。
对于
VARIANT
变量的赋值:首先给
vt
成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4;///
指明整型数据
va.lVal=a; ///
赋值
对于不马上赋值的
VARIANT
,最好先用
Void VariantInit(VARIANTARG FAR* pvarg);
进行初始化
,
其本质是将
vt
设置为
VT_EMPTY,
下表我们列举
vt
与常用数据的对应关系
:
unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF
_variant_t
是
VARIANT
的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;
COleVariant
的使用与
_variant_t
的方法基本一样,请参考如下例子:
COleVariant v3 = "
字符串
", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;
36
.匈牙利命名法
MFC
、句柄、控件及结构的命名规范
Windows
类型
样本变量
MFC
类
样本变量
HWND hWnd
;
CWnd* pWnd
;
HDLG hDlg
;
CDialog* pDlg
;
HDC hDC
;
CDC* pDC
;
HGDIOBJ hGdiObj
;
CGdiObject* pGdiObj
;
HPEN hPen
;
CPen* pPen
;
HBRUSHhBrush
;
CBrush*pBrush
;
HFONT hFont
;
CFont* pFont
;
HBITMAP hBitmap
;
CBitmap* pBitmap
;
HPALETTE hPaltte
;
CPalette* pPalette
;
HRGN hRgn
;
CRgn* pRgn
;
HMENU hMenu
;
CMenu*pMenu
;
HWND hCtl
;
CState* pState
;
HWND hCtl
;
CButton* pButton
;
HWND hCtl
;
CEdit* pEdit
;
HWND hCtl
;
CListBox* pListBox
;
HWND hCtl
;
CComboBox* pComboBox
;
HWND hCtl
;
CScrollBar* pScrollBar
;
HSZ hszStr
;
CString pStr
;
POINT pt
;
CPoint pt
;
SIZE size
;
CSize size
;
RECT rect
;
CRect rect
;
一般前缀命名规范
前缀
类型
实例
C
类或结构
CDocument
,
CPrintInfo
m_
成员变量
m_pDoc
,
m_nCustomers
变量命名规范
前缀
类型
描述
实例
ch char 8
位字符
chGrade
ch TCHAR
如果
_UNICODE
定义,则为
16
位字符
chName
b BOOL
布尔值
bEnable
n int
整型(其大小依赖于操作系统)
nLength
n UINT
无符号值(其大小依赖于操作系统)
nHeight
w WORD 16
位无符号值
wPos
l LONG 32
位有符号整型
lOffset
dw DWORD 32
位无符号整型
dwRange
p *
指针
pDoc
lp FAR*
远指针
lpszName
lpsz LPSTR 32
位字符串指针
lpszName
lpsz LPCSTR 32
位常量字符串指针
lpszName
lpsz LPCTSTR
如果
_UNICODE
定义,则为
32
位常量字符串指针
lpszName
h handle Windows
对象句柄
hWnd
lpfn callback
指向
CALLBACK
函数的远指针
应用程序符号命名规范
前缀
符号类型
实例
范围
IDR_
不同类型的多个资源共享标识
IDR_MAIINFRAME1
~
0x6FFF
IDD_
对话框资源
IDD_SPELL_CHECK 1
~
0x6FFF
HIDD_
对话框资源的
Help
上下文
HIDD_SPELL_CHECK 0x20001
~
0x26FF
IDB_
位图资源
IDB_COMPANY_LOGO 1
~
0x6FFF
IDC_
光标资源
IDC_PENCIL 1
~
0x6FFF
IDI_
图标资源
IDI_NOTEPAD 1
~
0x6FFF
ID_
来自菜单项或工具栏的命令
ID_TOOLS_SPELLING 0x8000
~
0xDFFF
HID_
命令
Help
上下文
HID_TOOLS_SPELLING 0x18000
~
0x1DFFF
IDP_
消息框提示
IDP_INVALID_PARTNO 8
~
0xDEEF
HIDP_
消息框
Help
上下文
HIDP_INVALID_PARTNO 0x30008
~
0x3DEFF
IDS_
串资源
IDS_COPYRIGHT 1
~
0x7EEF
IDC_
对话框内的控件
IDC_RECALC 8
~
0xDEEF
Microsoft MFC
宏命名规范
名称
类型
_AFXDLL
唯一的动态连接库(
Dynamic Link Library
,
DLL
)版本
_ALPHA
仅编译
DEC Alpha
处理器
_DEBUG
包括诊断的调试版本
_MBCS
编译多字节字符集
_UNICODE
在一个应用程序中打开
Unicode
AFXAPI MFC
提供的函数
CALLBACK
通过指针回调的函数
库标识符命名法
标识符
值和含义
u ANSI
(
N
)或
Unicode
(
U
)
d
调试或发行:
D =
调试;忽略标识符为发行。
静态库版本命名规范
库
描述
NAFXCWD.LIB
调试版本:
MFC
静态连接库
NAFXCW.LIB
发行版本:
MFC
静态连接库
UAFXCWD.LIB
调试版本:具有
Unicode
支持的
MFC
静态连接库
UAFXCW.LIB
发行版本:具有
Unicode
支持的
MFC
静态连接库
动态连接库命名规范
名称
类型
_AFXDLL
唯一的动态连接库(
DLL
)版本
WINAPI Windows
所提供的函数
Windows.h
中新的命名规范
类型
定义描述
WINAPI
使用在
API
声明中的
FAR PASCAL
位置,如果正在编写一个具有导出
API
人口点的
DLL
,则可以在自己的
API
中使用该类型
CALLBACK
使用在应用程序回叫例程,如窗口和对话框过程中的
FAR PASCAL
的位置
LPCSTR
与
LPSTR
相同,只是
LPCSTR
用于只读串指针,其定义类似(
const char FAR*
)
UINT
可移植的无符号整型类型,其大小由主机环境决定(对于
Windows NT
和
Windows 9x
为
32
位);它是
unsigned int
的同义词
LRESULT
窗口程序返回值的类型
LPARAM
声明
lParam
所使用的类型,
lParam
是窗口程序的第四个参数
WPARAM
声明
wParam
所使用的类型,
wParam
是窗口程序的第三个参数
LPVOID
一般指针类型,与(
void *
)相同,可以用来代替
LPSTR