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 常用数据类型列表
 
 
Type
Default Size
Description
 
 
 
 
 
 
说明 : 这些基础数据类型对于 MFC 还是 API 都是被支持的
boolean
unsigned 8 bit ,
取值 TRUE/FALSE
byte
unsigned 8 bit,
整数 , 输出按字符输出
char
unsigned 8 bit,
字符
double
signed 64 bit
浮点型
float
signed32 bit
浮点型
handle_t
 
Primitive handle type
hyper
signed 64 bit
整型
int
signed 32 bit
整型
long
signed 32 bit
整型
short
signed 16 bit
整型
small
signed 8 bit
整型
void *
32-bit
指向未知类型的指针
wchar_t
unsigned 16 bit
16 位字符 , char 可容纳更多的字符
 
 
 
Win32
API
 
说明 : 这些 Win32API 支持的简单数据类型主要是用来定义函数返回值,消息参数,结构成员。这类数据类型大致可以分为五大类:字符型、布尔型、整型、指针型和句柄型( ? . 总共大概有 100 多种不同的类型,
BOOL/BOOLEAN
8bit,TRUE/FALSE
布尔型
BYTE
unsigned 8 bit
 
BSTR
CComBSTR
_bstr_t
32 bit
BSTR 是指向字符串的 32 位指针
是对 BSTR 的封装
是对 BSTR 的封装
CHAR
8 bit
(ANSI )字符类型
COLORREF
32 bit
RGB 颜色值 整型
DWORD
unsigned 32 bit
整型
FLOAT
float
float
HANDLE
 
Object 句柄
HBITMAP
 
bitmap 句柄
HBRUSH
 
brush 句柄
HCURSOR
 
cursor 句柄
HDC
 
设备上下文句柄
HFILE
 
OpenFile 打开的 File 句柄
HFONT
 
font 句柄
HHOOK
 
hook 句柄
HKEY
 
注册表键句柄
HPEN
 
pen 句柄
HWND
 
window 句柄
INT
--------
--------
LONG
--------
---------
LONGLONG
 
64 位带符号整型
LPARAM
32 bit
消息参数
LPBOOL
 
BOOL 型指针
LPBYTE
 
BYTE 型指针
LPCOLOREF
 
COLORREF 型指针
LPCSTR/LPSTR/PCSTR
 
指向 8 位( ANSI )字符串类型指针
LPCWSTR/LPWSTR/PCWSTR
 
指向 16 Unicode 字符串类型
LPCTSTR/LPTSTR/PCTSTR
 
指向一 8 位或 16 位字符串类型指针
LPVOID
 
指向一个未指定类型的 32 位指针
LPDWORD
 
指向一个 DWORD 型指针
其他相似类型 : 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
SHORT
usigned
整型
其他 UCHAR UINT ULONG ULONGLONG USHORT 为无符号相应类型
TBYTE
 
WCHAR 型或者 CHAR
TCHAR
 
ANSI unicode 均可
VARIANT
_variant_t
COleVariant
 
一个结构体参考 OAIDL.H
_variant_t VARIANT 的封装类
COleVariant 也是 VARIANT 的封装类
 
 
 
 
 
 
WNDPROC
 
指向一个窗口过程的 32 位指针
WCHAR
 
16 Unicode 字符型
WORD
 
16 位无符号整型
WPARAM
 
消息参数
MFC
独有
数据
类型
下面两个数据类型是微软基础类库中独有的数据类型
POSITION
标记集合中一个元素的位置的值 , MFC 中的集合类所使用
LPCRECT
指向一个 RECT 结构体常量(不能修改)的 32 位指针
CString
其实是 MFC 中的一个类
 
 
 
说明 :
(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* 的综合比较 >> 写的很详细 , 请大家在仔细阅读他的文章 .
或参考附录 :
(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
HBRUSH hBrush        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_MAIINFRAME 1 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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值