配置Pro*C/C++,以及链接到oracle

1,一般安装oracle时候都已经安装了pro*,所以一般不需要再配置/etc/profile,如果是后来单独安装,看先这个文文件里是否有pro*的目录,否则在执行proc的时候可能会提示报错。

vi /etc/profile

加入

LD_LIBRARY_PATH=$ORACLE_HOME/lib:/usr/lib:/usr/local/lib;
export LD_LIBRARY_PATH

2,修改$ORACLE_HOME/precomp/admin/pcscfg.cfg(红色字为新加入内容)

sys_include=(/software/oracle/precomp/public,/usr/include,/usr/lib/gcc/i386-redhat-linux/4.1.1/include,/usr/lib/gcc/i386-redhat-linux/3.4.5/include,/usr/lib/gcc-lib/i386-redhat-linux/3.2.3/include,/usr/lib/gcc/i586-suse-linux/4.1.0/include)
ltype=short
code=cpp
cpp_suffix=cc
parse=none
SQLCHECK=SEMANTICS

至此,我们可以开始写pc源程序了

以下是一个链接oracle数据库的程序。

/*

login.pc

用户名:soft;密码soft;数据服务器地址:192.168.0.2:1521/orasoft

*/

#include<iostream>
using namespace std;
#include "sqlca.h"

EXEC SQL BEGIN DECLARE SECTION;
char *uid="
soft/soft@192.168.0.2:1521/orasoft";
EXEC SQL END DECLARE SECTION;

int main()
{
EXEC SQL CONNECT :uid;
if(sqlca.sqlcode==0)
   cout<<"connect success…"<<endl;
else
   cout<<sqlca.sqlerrm.sqlerrmc<<endl;
}

1,proc预编译

命令:proc login.cp

2,通过第一步生产login.cc文件,现在就要通过g++编译源文件

命令:g++ -o login login.cc -I /software/oracle/precomp/public/ -L /software/oracle/lib/ -l clntsh

3,通过上一步生成login可执行文件。执行login

命令:

chown 755 login

./login

结果显示connect success…,成功。。。。

=============================end==============================

双重指针

双重指针」也有人称为「指针的指针」,其作用为「间接参照」,但无论是哪一个名词,都是令人困惑的,其实指针就是指针,所谓的多重指针,其实还是指针,它们的作用单纯来说,都是用以储存记忆体位址。

思考一个问题,当您要取得int变数的记忆体位址时,会使用int*来宣告指针,要取得double变数的记忆体位址时,会使用double*来宣告指针,这是因为它们在进行加减法运算时,所位移的单位并不相同,而是根据它们的资料型态而定,而如果您只是要储存一个记忆体位址,您就宣告指针为void* 型态。

指针可以用来储存(某变数的)记忆体位址,所以指针本身就是一个变数,也要占有记忆体空间才能储存资讯,那么指针的记忆体空间位址在哪呢?同样的使用 &运算子就可以得知了,例如:
#include <iostream>
using namespace std;

int main() {
    int p = 10;
    int *ptr = &p;

    cout << "p的值:" << p
         << endl;
    cout << "p的记忆体位置: " << &p
         << endl;
    cout << "*ptr参照的值: " << *ptr
         << endl;

    cout << "ptr储存的位址值: " << ptr
         << endl;
    cout << "ptr的记忆体位置: " << &ptr
         << endl;

    return 0;
}

执行结果:

p的值:10
p
的记忆体位置
: 0x22ff74
*ptr
参照的值
: 10
ptr
储存的位址值
: 0x22ff74
ptr
的记忆体位置: 0x22ff70

由以上的范例,您知道ptr在记忆体中的0x22ff70占据空间,并储存了0x22ff74这个值,0x22ff74也就是p在记忆体中的位置,该位置储存了10这个值。

如果在上例中,您要储存ptr的记忆体位址,也就是0x22ff70这个值,那么如何作?由于ptr是个int*型态变数,如同int变数必须宣告 int*指针,所以int*型态变数就必须宣告int**型态的指针,例如:
int **ptr2 = &ptr;

下面这个程式可得仔细看看:
#include <iostream>
using namespace std;

int main() {
    int p = 10;
    int *ptr1 = &p;
    int **ptr2 = &ptr1;

    cout << "p的值:" << p << endl;
    cout << "p的记忆体位置: " << &p << endl;

    cout << endl;

    cout << "*ptr1 = " << *ptr1 << endl;
    cout << "ptr1 = " << ptr1 << endl;
    cout << "ptr1的记忆体位置: " << &ptr1 << endl;

    cout << endl;

    cout << "**ptr2 = " << **ptr2 << endl;
    cout << "*ptr2 = " << *ptr2 << endl;
    cout << "ptr2 = " << ptr2 << endl;

    cout << endl;

    cout << "整理(谁储存了谁?):" << endl;
    cout << "&p = " << &p << "\t\t" << "ptr1 = " << ptr1 << endl;
    cout << "&ptr1 = " << &ptr1 << "\t"
         << "ptr2 = " << ptr2
         << endl;

    return 0;
}

执行结果:

p的值:10
p
的记忆体位置
: 0x22ff74

*ptr1 = 10
ptr1 = 0x22ff74
ptr1的记忆体位置
: 0x22ff70

**ptr2 = 10
*ptr2 = 0x22ff74
ptr2 = 0x22ff70

整理(谁储存了谁?)
&p = 0x22ff74           ptr1 = 0x22ff74
&ptr1 = 0x22ff70        ptr2 = 0x22ff70

在执行结果中,您可以看到最后的整理中,ptr1储存了p变数所占有的位址,而ptr2则储存了ptr1所占有的位址,所以当您使用*取值运算子时, *ptr2取出的是ptr1所储存的值,也就是&p,而再使用一次*运算子时,也就是**ptr2时,因为*ptr2 == ptr1,所以*(*ptr2 ) == *ptr1,而*ptr1 == p,所以也就是取出了p的值了。

gcc和g++的区别

gcc和g++都是GNU(组织)的一个编译器。

误区一:gcc只能编译c代码,g++只能编译c++代码
两者都可以,但是请注意:
1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

误区二:gcc不会定义__cplusplus宏,而g++会
实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

误区三:编译只能用gcc,链接只能用g++
严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。
gcc和g++的区别
我们在编译c/c++代码的时候,有人用gcc,有人用g++,于是各种说法都来了,譬如c代码用gcc,而c++代码用g++,或者说编译用 gcc,链接用g++,一时也不知哪个说法正确,如果再遇上个extern "C",分歧就更多了,这里我想作个了结,毕竟知识的目的是令人更清醒,而不是更糊涂。

误区一:gcc只能编译c代码,g++只能编译c++代码

两者都可以,但是请注意:
1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的,例如:
#include <stdio.h>
int main(int argc, char* argv[]) {
   if(argv == 0) return;
   printString(argv);
   return;
}
int printString(char* string) {
sprintf(string, "This is a test.\n");
}
如果按照C的语法规则,OK,没问题,但是,一旦把后缀改为cpp,立刻报三个错:“printString未定义”;
“cannot convert `char**’ to `char*”;
”return-statement with no value“;
分别对应前面红色标注的部分。可见C++的语法规则更加严谨一些。
2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

误区二:gcc不会定义__cplusplus宏,而g++会

实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

误区三:编译只能用gcc,链接只能用g++

严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。

误区四:extern "C"与gcc/g++有关系

实际上并无关系,无论是gcc还是g++,用extern "c"时,都是以C的命名方式来为symbol命名,否则,都以c++方式命名。试验如下:
me.h:
extern "C" void CppPrintf(void);

me.cpp:
#include <iostream>
#include "me.h"
using namespace std;
void CppPrintf(void)
{
     cout << "Hello\n";
}

test.cpp:
#include <stdlib.h>
#include <stdio.h>
#include "me.h"       
int main(void)
{
    CppPrintf();
    return 0;
}

1. 先给me.h加上extern "C",看用gcc和g++命名有什么不同

[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   CppPrintf, @function
[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   CppPrintf, @function
完全相同!
              
2. 去掉me.h中extern "C",看用gcc和g++命名有什么不同

[root@root GCC]# gcc -S me.cpp
[root@root GCC]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   _Z9CppPrintfv, @function
[root@root G++]# g++ -S me.cpp
[root@root G++]# less me.s
.globl _Z9CppPrintfv        //注意此函数的命名
        .type   _Z9CppPrintfv, @function
完全相同!
【结论】完全相同,可见extern "C"与采用gcc/g++并无关系,以上的试验还间接的印证了前面的说法:在编译阶段,g++是调用gcc的。

指针把我弄糊涂了

指针有点吧我弄糊涂了。。

指针 函数指针 指针函数 数组指针 指针数组 双重指针

单脑袋很混乱的时候需要清洗下脑袋,吧所有关于指针的东西全部清理掉。然后重新开始学。

恩,就这么做吧。。一定可以的。

选择了C就爱上指针吧。。

常用的C变量的定义方式

a) 一个整型数(An integer)

  b) 一个指向整型数的指针(A pointer to an integer)

  c) 一个指向指针的的指针,它指向的指针是指向一个整型数(A pointer to a pointer to an integer)

  d) 一个有10个整型数的数组(An array of 10 integers)

  e) 一个有10个指针的数组,该指针是指向一个整型数的(An array of 10 pointers to integers)

  f) 一个指向有10个整型数数组的指针(A pointer to an array of 10 integers)

  g) 一个指向函数的指针,该函数有一个整型参数并返回一个整型数(A pointer to a function that takes an integer as an argument and returns an integer)

  h) 一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型数( An array of ten pointers to functions that take an integer argument and return an integer )

  答案是:

  a) int a; // An integer

  b) int *a; // A pointer to an integer

  c) int **a; // A pointer to a pointer to an integer

  d) int a[10]; // An array of 10 integers

  e) int *a[10]; // An array of 10 pointers to integers

  f) int (*a)[10]; // A pointer to an array of 10 integers

  g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer

  h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer

Makefile文件实例

#以下四句是对标识符赋值,类似C/C++中变量的赋值,应该比较好理解
CC=g++
CFLAGS= -c -g
LDFLAGS= -g
target = ex_3_1
# 目标对象:源对象,定义目标对象依赖的源对象,如果Malefle不指出目标对象,则第一个被定义的目标对象就是要生成的对象。
all: $(target)
#赋值
target_objects=ex_3_1.o nouse.o
#定义目标对象ex_3_1依赖的源对象
ex_3_1 :$(target_objects)
#在上句定义后,定义生产目标对象的命令,把标识符代入下句为 g++ -g -o ex_3_1 ex_3_1.o nouse.o 意思为通过编译生产的目标文件(.o文件)链接生产可执行文件ex_3_1(可以这么理解)
#下句执行的
前提是依赖的源对象比目标对象日期要新,否则不会执行
#源对象和目标对象的生成命令会一直搜索下去,直至结束或遇到Makefile里的其它类型的语句,注意命令前要tab字符而不是多几个空格
       $(CC) $(LDFLAGS) -o $@ $^
#显示$@ Build OK.$@=ex_3_1,所以即显示:ex_3_1 Build OK.
      @echo $@ Build OK.
#扩展名对象生成规则,由.SUFFIXES来定义如何生成一类扩展名文件。语法是:“.SUFFIXES:.源扩展名 .目标扩展名”,例如下句,当处理Malefile时,发现一个扩展名为.o会去查找同名而扩展名为.cpp的文件。
.SUFFIXES:.cpp .o
#重定义了扩展名对应规则后,要定义如何生成,规则为:“.源扩展名 .目标扩展名<br>(换行后要加tab字符)执行命令”,例如下句,如果目标文件是nouse.o,则会去查找nouse.cpp文件,如果找到,则经过标识符替换,执行g++ -c -g -o nouse.o nouse.cpp
.cpp.o :
      $(CC) $(CFLAGS) -o $@ $<
#delete all object删除所有对象
clean:
@rm -rf $(target) $(target_objects)
###########################################################################################
#以上14Makefile文件,其格式可以应用到非常多的程序中,自己可以通过添加修改来完成要求的目标

使用:吧该文件放入根目录,命令行输入make就可以了

GNU-Gcc编译器一些基本的选项

Gcc编译器能将C、C++语言源程序、汇程式化序和目标程序编译、连接成可执行文件,如果没有给出可执行文件的名字,gcc将生成一个名为a.out的文件。在Linux系统中,可执行文件没有统一的后缀,系统从文件的属性来区分可执行文件和不可执行文件。而gcc则通过后缀来区别输入文件的类别,下面我们来介绍gcc所遵循的部分约定规则。 注意:-Wall可以查看编译时的错误警告。

  .c为后缀的文件,C语言源代码文件;

  .a为后缀的文件,是由目标文件构成的档案库文件;

  .C,.cc或.cxx 为后缀的文件,是C++源代码文件;

  .h为后缀的文件,是程序所包含的头文件;

  .i 为后缀的文件,是已经预处理过的C源代码文件;

  .ii为后缀的文件,是已经预处理过的C++源代码文件;

  .m为后缀的文件,是Objective-C源代码文件;

  .o为后缀的文件,是编译后的目标文件;

  .s为后缀的文件,是汇编语言源代码文件;

  .S为后缀的文件,是经过预编译的汇编语言源代码文件。

linux下socket编程

什么是Socket
   Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。
   Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

Socket建立
  为了建立Socket,程序可以调用Socket函数,该函数返回一个类似于文件描述符的句柄。socket函数原型为:
   int socket(int domain, int type, int protocol);
   domain指明所使用的协议族,通常为PF_INET,表示互联网协议族(TCP/IP协议族);type参数指定socket的类型:SOCK_STREAM 或SOCK_DGRAM,Socket接口还定义了原始Socket(SOCK_RAW),允许程序使用低层协议;protocol通常赋值"0"。Socket()调用返回一个整型socket描述符,你可以在后面的调用使用它。
   Socket描述符是一个指向内部数据结构的指针,它指向描述符表入口。调用Socket函数时,socket执行体将建立一个Socket,实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。
  两个网络程序之间的一个网络连接包括五种信息:通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。

Socket配置
  通过socket调用返回一个socket描述符后,在使用socket进行网络传输以前,必须配置该socket。面向连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用bind函数来配置本地信息。
Bind函数将socket与本机上的一个端口相关联,随后你就可以在该端口监听服务请求。Bind函数原型为:
   int bind(int sockfd,struct sockaddr *my_addr, int addrlen);
   Sockfd是调用socket函数返回的socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针;addrlen常被设置为sizeof(struct sockaddr)。
   struct sockaddr结构类型是用来保存socket信息的:
   struct sockaddr {
   unsigned short sa_family; /* 地址族, AF_xxx */
char sa_data[14]; /* 14 字节的协议地址 */
};
   sa_family一般为AF_INET,代表Internet(TCP/IP)地址族;sa_data则包含该socket的IP地址和端口号。
   另外还有一种结构类型:
   struct sockaddr_in {
   short int sin_family; /* 地址族 */
   unsigned short int sin_port; /* 端口号 */
   struct in_addr sin_addr; /* IP地址 */
   unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */
   };
  这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度,可以用bzero()或memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换,这意味着如果一个函数所需参数类型是sockaddr时,你可以在函数调用的时候将一个指向sockaddr_in的指针转换为指向sockaddr的指针;或者相反。
  使用bind函数时,可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号:
   my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */
   my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */
通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。同样,通过将my_addr.sin_addr.s_addr置为INADDR_ANY,系统会自动填入本机IP地址。
注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序;而sin_addr则不需要转换。
  计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输,所以对于在内部是以低位字节优先方式存储数据的机器,在Internet上传输数据时就需要进行转换,否则就会出现数据不一致。
   下面是几个字节顺序转换函数:
·htonl():把32位值从主机字节序转换成网络字节序
·htons():把16位值从主机字节序转换成网络字节序
·ntohl():把32位值从网络字节序转换成主机字节序
·ntohs():把16位值从网络字节序转换成主机字节序
   Bind()函数在成功被调用时返回0;出现错误时返回"-1"并将errno置为相应的错误号。需要注意的是,在调用bind函数时一般不要将端口号置为小于1024的值,因为1到1024是保留端口号,你可以选择大于1024中的任何一个没有被占用的端口号。

连接建立
  面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接,其函数原型为:
   int connect(int sockfd, struct sockaddr *serv_addr,int addrlen);
Sockfd是socket函数返回的socket描述符;serv_addr是包含远端主机IP地址和端口号的指针;addrlen是远端地质结构的长度。Connect函数在出现错误时返回-1,并且设置errno为相应的错误码。进行客户端程序设计无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候到打断口。
   Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接,它只是被动的在协议端口监听客户的请求。
   Listen函数使socket处于被动的监听模式,并为该socket建立一个输入数据队列,将到达的服务请求保存在此队列中,直到程序处理它们。
   int listen(int sockfd, int backlog);
Sockfd是Socket系统调用返回的socket 描述符;backlog指定在请求队列中允许的最大请求数,进入的连接请求将在队列中等待accept()它们(参考下文)。Backlog对队列中等待服务的请求的数目进行了限制,大多数系统缺省值为20。如果一个服务请求到来时,输入队列已满,该socket将拒绝连接请求,客户将收到一个出错信息。
当出现错误时listen函数返回-1,并置相应的errno错误码。
   accept()函数让服务器接收客户的连接请求。在建立好输入队列后,服务器就调用accept函数,然后睡眠并等待客户的连接请求。
   int accept(int sockfd, void *addr, int *addrlen);
   sockfd是被监听的socket描述符,addr通常是一个指向sockaddr_in变量的指针,该变量用来存放提出连接请求服务的主机的信息(某台主机从某个端口发出该请求);addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。
  首先,当accept函数监视的socket收到连接请求时,socket执行体将建立一个新的socket,执行体将这个新socket和请求连接进程的地址联系起来,收到服务请求的初始socket仍可以继续在以前的 socket上监听,同时可以在新的socket描述符上进行数据传输操作。

数据传输
   Send()和recv()这两个函数用于面向连接的socket上进行数据传输。
   Send()函数原型为:
   int send(int sockfd, const void *msg, int len, int flags);
Sockfd是你想用来传输数据的socket描述符;msg是一个指向要发送数据的指针;Len是以字节为单位的数据的长度;flags一般情况下置为0(关于该参数的用法可参照man手册)。
   Send()函数返回实际上发送出的字节数,可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时,应该对这种情况进行处理。
char *msg = "Hello!";
int len, bytes_sent;
……
len = strlen(msg);
bytes_sent = send(sockfd, msg,len,0);
……
   recv()函数原型为:
   int recv(int sockfd,void *buf,int len,unsigned int flags);
   Sockfd是接受数据的socket描述符;buf 是存放接收数据的缓冲区;len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数,当出现错误时,返回-1并置相应的errno值。
Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。
sendto()函数原型为:
   int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
  该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
   Recvfrom()函数原型为:
   int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
   from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。
如果你对数据报socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。

结束传输
  当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
close(sockfd);
  你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
   int shutdown(int sockfd,int how);
   Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
   ·0——-不允许继续接收数据
   ·1——-不允许继续发送数据
   ·2——-不允许继续发送和接收数据,
   ·均为允许则调用close ()
   shutdown在操作成功时返回0,在出现错误时返回-1并置相应errno。

代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件,在客户端运行客户软件,客户端就会收到该字符串。

该服务器软件代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#define SERVPORT 3333    /*服务器监听端口号 */
#define BACKLOG 10    /* 最大同时连接请求数 */

main()
{
    int sock_fd,client_fd;    /*sock_fd:监听socket;client_fd:数据传输socket */
    int sin_size;
    struct sockaddr_in my_addr;    /* 本机地址信息 */
    struct sockaddr_in remote_addr;    /* 客户端地址信息 */
    if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket创建出错!");
        exit(1);
    }
    my_addr.sin_family=AF_INET;
    my_addr.sin_port=htons(SERVPORT);
    my_addr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(my_addr.sin_zero),8);
    if(bind(sock_fd, (struct sockaddr *)&my_addr, sizeof(struct sockaddr)) == -1) {
        perror("bind出错!");
        exit(1);
    }
    if(listen(sock_fd, BACKLOG) == -1) {
        perror("listen出错!");
        exit(1);
    }
    while(1) {
        sin_size = sizeof(struct sockaddr_in);
        if((client_fd = accept(sock_fd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
            perror("accept出错");
            continue;
        }
        printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr));
        if(!fork()) {    /* 子进程代码段 */
            if(send(client_fd, "Hello, you are connected!\n", 26, 0) == -1) {
                perror("send出错!");
            }
            close(client_fd);
            exit(0);
        }
        close(client_fd);
    }
}

  服务器的工作流程是这样的:

  首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串"Hello,you are connected!"。最后关闭该socket。

  代码实例中的fork()函数生成一个子进程来处理数据传输部分,fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分,它与if语句后面的父进程代码部分是并发执行的。

客户端程序代码如下:

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define SERVPORT 3333
#define MAXDATASIZE 100    /*每次最大数据传输量 */

main(int argc, char *argv[])
{
    int sock_fd, recvbytes;
    char buf[MAXDATASIZE];
    struct hostent *host;
    struct sockaddr_in serv_addr;
    if(argc< 2) {
        fprintf(stderr,"Please enter the server’s hostname!\n");
        exit(1);
    }
    if((host=gethostbyname(argv[1])) == NULL) {
        herror("gethostbyname出错!");
        exit(1);
    }
    if((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        perror("socket创建出错!");
        exit(1);
    }
    serv_addr.sin_family=AF_INET;
    serv_addr.sin_port=htons(SERVPORT);
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
    bzero(&(serv_addr.sin_zero),8);
    if(connect(sock_fd, (struct sockaddr *)&serv_addr, sizeof(struct sockaddr)) == -1) {
        perror("connect出错!");
        exit(1);
    }
    if((recvbytes=recv(sock_fd, buf, MAXDATASIZE, 0)) == -1) {
        perror("recv出错!");
        exit(1);
    }
    buf[recvbytes] = ‘\0′;
    printf("Received: %s",buf);
    close(sock_fd);
}

  客户端程序首先通过服务器域名获得服务器的IP地址,然后创建一个socket,调用connect函数与服务器建立连接,连接成功之后接收从服务器发送过来的数据,最后关闭socket。

  函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写,所以为了方便,人们常常用域名来表示主机,这就需要进行域名和IP地址的转换。函数原型为:

   struct hostent *gethostbyname(const char *name);

   函数返回为hosten的结构类型,它的定义如下:

   struct hostent {
     char *h_name;          /* 主机的官方域名 */
char **h_aliases;          /* 一个以NULL结尾的主机别名数组 */
int h_addrtype;         /* 返回的地址类型,在Internet环境下为AF-INET */
int h_length;           /* 地址的字节长度 */
char **h_addr_list;           /* 一个以0结尾的数组,包含该主机的所有地址*/
};
#define h_addr h_addr_list[0]       /*在h-addr-list中的第一个地址*/

   当 gethostname()调用成功时,返回指向struct hostent的指针,当调用失败时返回-1。当调用gethostbyname时,你不能使用perror()函数来输出错误信息,而应该使用herror()函数来输出。

  无连接的客户/服务器程序,在原理上和连接的客户/服务器是一样的,两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接,而且在发送接收数据时,需要指定远端机的地址。

阻塞和非阻塞

  阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如,程序执行一个读数据的函数调用时,在此函数完成读操作以前将不会执行下一程序语句。当服务器运行到accept语句时,而没有客户连接服务请求到来,服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞(blocking)。而非阻塞操作则可以立即完成。比如,如果你希望服务器仅仅注意检查是否有客户在等待连接,有就接受连接,否则就继续做其他事情,则可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。

   #include <unistd.h>
#include <fcntl.h>
……
sockfd = socket(AF_INET,SOCK_STREAM,0);
fcntl(sockfd,F_SETFL,O_NONBLOCK);
……

  通过设置Socket为非阻塞方式,可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时,函数将立即返回,返回值为-1,并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式,从而降低性能,浪费系统资源。而调用select()会有效地解决这个问题,它允许你把进程本身挂起来,而同时使系统内核监听所要求的一组文件描述符的任何活动,只要确认在任何被监控的文件描述符上出现活动,select()调用将返回指示该文件描述符已准备好的信息,从而实现了为进程选出随机的变化,而不必由进程本身对输入进行测试而浪费CPU开销。select函数原型为:

   int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

   其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以从标准输入和某个socket描述符读取数据,你只需要将标准输入的文件描述符0和相应的sockdtfd加入到readfds集合中;numfds的值是需要检查的号码最高的文件描述符加1,这个例子中numfds的值应为sockfd+1;当select返回时,readfds将被修改,指示某个文件描述符已经准备被读取,你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试,它提供了一组宏:

   FD_ZERO(fd_set *set)—-清除一个文件描述符集;
FD_SET(int fd,fd_set *set)—-将一个文件描述符加入文件描述符集中;
FD_CLR(int fd,fd_set *set)—-将一个文件描述符从文件描述符集中清除;
FD_ISSET(int fd,fd_set *set)—-试判断是否文件描述符被置位。

   Timeout参数是一个指向struct timeval类型的指针,它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为:

  struct timeval {
int tv_sec; /* seconds */
int tv_usec; /* microseconds */
};

POP3客户端实例

  下面的代码实例基于POP3的客户协议,与邮件服务器连接并取回指定用户帐号的邮件。与邮件服务器交互的命令存储在字符串数组POPMessage中,程序通过一个do-while循环依次发送这些命令。

#include<stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <netdb.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define POP3SERVPORT 110
#define MAXDATASIZE 4096

main(int argc, char *argv[]){
int sock_fd;
struct hostent *host;
struct sockaddr_in serv_addr;
char *POPMessage[]={
"USER userid\r\n",
"PASS password\r\n",
"STAT\r\n",
"LIST\r\n",
"RETR 1\r\n",
"DELE 1\r\n",
"QUIT\r\n",
NULL
};
int iLength;
int iMsg=0;
int iEnd=0;
char buf[MAXDATASIZE];

if((host=gethostbyname("your.server"))==NULL) {
perror("gethostbyname error");
exit(1);
}
if ((sock_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1){
perror("socket error");
exit(1);
}
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(POP3SERVPORT);
serv_addr.sin_addr = *((struct in_addr *)host->h_addr);
bzero(&(serv_addr.sin_zero),8);
if (connect(sock_fd, (struct sockaddr *)&serv_addr,sizeof(struct sockaddr))==-1){
perror("connect error");
exit(1);
}

do {
send(sock_fd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0);
printf("have sent: %s",POPMessage[iMsg]);

iLength=recv(sock_fd,buf+iEnd,sizeof(buf)-iEnd,0);
iEnd+=iLength;
buf[iEnd]=’\0′;
printf("received: %s,%d\n",buf,iMsg);

iMsg++;
} while (POPMessage[iMsg]);

close(sockfd);
}

文章出处:DIY部落(http://www.diybl.com/course/3_program/c++/cppsl/200861/118803.html)

Linux下C和C++开发基础

Linux下C和C++开发基础 作者 阿江 日期 2009-3-23 4:57:00

基本编程概念
n 编程语言:C 、C++
n 编译(compile):源文件(.c)->目标文件(.o)
n 连接(link):目标文件(.o)->可执行文件
n 库(library):静态连接库(.a)、动态连接库(.so)

Linux下开发工具
n编辑器:vi、emacs、窗口编辑器
n编译器:GCC
n调试器:GDB
n可执行文件工具:Binutils
n连接器:ld
n汇编程序:as
n库管理工具:ar
n可执行文件符号管理:nm
n显示可执行文件信息:objdump

简单程序示例(C语言):
n hello.c
/***************************

C代码 复制代码
  1. #i nclude <stdio.h>   
  2. int main(int argc,char **argv)   
  3. {   
  4. printf("HelloWorld!\n");   
  5. return 0;   
  6. }   
  7. /***************************   
  8. n编译方法:gcc –o hello hello.c   
  9. n运行方法:./hello   
  10. 简单程序示例(C++语言):  

n hello.cpp
/*******************************

C代码 复制代码
  1. #i nclude <iostream>   
  2. using namespace std;   
  3. int main(int argc,char **argv)   
  4. {   
  5. cout << "Hello World!“ << endl;   
  6. return 0;   
  7. }  

/*******************************
n 编译方法:g++ –o hello hello.cpp
n 运行方法:./hello
GCC编译器
n GNU平台下主流编译器
n目前最新稳定版4.0
n官方网站:http://gcc.gnu.org
n支持编译语言:C、C++、Objective-C、
Objective-C++、Java、Fortran、Ada
n跨平台支持:支持几乎所有主流操作系统,如
Linux、UNIX、Windows等。支持多种硬件平
台,如X86、ARM、PPC、MIPS等
n交叉编译功能

编译相关文件路径
n头文件
n一般在/usr/include目录下
n库文件
n一般在/lib、/usr/lib目录下
n某些库在其他目录下,如X Window相关库一般在
/usr/X11R6/lib目录下
n编译器、连接器核心文件
n一般在/usr/lib/gcc-lib/<target>/<version>目录下

Linux下函数库
n静态库(.a)和动态库(.so)
n库命名:lib开头,库版本号
n库链接
n标准库:
n动态加载库:ld-linux.so(/lib)
n标准C库:libc.so(/lib)
n标准C++库:libstdc++.so(/usr/lib)
n数学库:libm.so(/lib)

GCC使用用法
n基本用法:
n gcc [options] file…
n示例:gcc –o hello hello.c,编译hello.c为可执行文件
hello(自动连接标准C库libc.so)
n示例:gcc –o hello hello.c –lm,编译hello.c为可执行
文件hello,连接数学库libm
n示例:g++ -o hello hello.cpp,编译hello.cpp为可执行
文件hello,自动连接标准C++库libstdc++.so

GCC常用选项
-v :显示gcc版本信息及其相关信息
n示例:gcc -v
n可用于查看gcc核心文件信息及其编译配置选项
n -o:生成可执行文件名
n示例:gcc -o hello hello.c
n -c:仅编译成中间目标文件(.o),不连接
n示例:gcc -c hello.c,将生成hello.o
n -S:由源程序生成汇编文件(.s)
n示例:gcc -S hello.c,将生成hello.s
-E:对源程序仅进行预处理,输出到标准输出上(可
用于分析预处理如define的问题)
n 示例:gcc -E hello.c> hellop.c
n -I:指定头文件所在路径
n 示例:gcc -Iinclude -o hello hello.c
n -L:指定库文件所在路径
n 示例:gcc –o hello hello.c -Llib -ltest
n -l:指定连接的库文件
n 示例:同上
n -D:定义宏
n 示例:gcc -D DEBUG=3 -o hello hello.c
-On:代码优化选项,以产生更小和更快的目标代码,
n表示优化级别,如-O1,-O2,-O3等
n 示例:gcc -O2 -o hello hello.c
n -m<arch>:针对特定处理器的优化,如-m386,-m586等
n 示例:gcc -m586 -o hello hello.c
n -g:产生调试代码,用于gdb调试工具
n 示例:gcc -g -o hello hello.c
n -pg:产生用于性能测试的附加信息,供gprof程序使用
n 示例:gcc -pg -o hello hello.c
n -Wall:显示所有警告信息
n 示例:gcc -Wall -o hello hello.c

GNU调试工具gdb
n基本功能:
n设置断点,暂停程序执行
n监视运行状态下变量值
n单步执行代码
n汇编、反汇编
n调试信息
n源程序编译时加上-g选项,保证目标程序内含调试
信息,方便gdb调试时显示代码行、变量名等。

GDB调试示例程序test.c

C代码 复制代码
  1. #i nclude <stdio.h>   
  2. int main()   
  3. {   
  4. int i,j;   
  5. j=0;   
  6. for(i=0;i<10;i++)   
  7. {   
  8. j+=2;   
  9. printf(“j=%d\n”,j);   
  10. }   
  11. }  

GDB调试命令
n 编译:gcc -g -o test test.c
n 运行gdb:gdb test
n gdb命令:
n help:可查看gdb命令帮助
n gdb命令可采用缩写,如list可缩写为l
n 列出源文件:list
n 设置断点:break
n 示例:break 6,在第6行设置断点
n 示例:break <filename>:<linenum>,在特定源文件的特定行设置断点
n 示例:break <>,在函数上设置断点
n 示例:break *<address>,在特定地址上设置断点
n 运行:run,在断点处程序暂停

GDB调试命令
n 打印变量值:print,printf
n 示例:print i,打印变量i的值
n 示例:print i=10,将变量i赋值为10
n 示例:printf “0x%x\n”,j+20,按格式打印变量值
n 设置表达式观察点:awatch、watch
n 功能:awatch当变量被读写时暂停程序运行,watch当变量发生改变时暂停程序运行
n 示例:awatch j,当j发生改变时暂停运行,显示原值和新值;j被读时显示当前值
n 继续运行:continue
n查看当前断点:info break
n清除断点:clear
n示例:clear 6,清除第6行设置的断点
n删除断点:delete
n示例:delete 2,删除编号为2的断点
n屏蔽断点:disable
n示例:disable 2,暂时使编号为2的断点失效
n激活断点:enable
n示例:enable 2,重新激活编号为2的断点
n条件断点:
n示例:break 8 if j==8,每执行到第8行检测j的值,如果j=8则程序暂停
n其他断点命令:
n ignore:忽略断点特定次数
n tbreak:设置临时断点,仅执行一次
n单步运行:step,跟踪到函数内部
n单步运行:next,不跟踪到函数内部
n显示表达式值:display,每运行到断点均显示值
n显示display的表达式:info display
n删除display的表达式:delete display <编号>
n设置变量值:set variable
n示例:set variable i=8
n打印当前堆栈信息:backtrace
n设置运行时参数:set args
n源程序编译时-l选项可自动连接相应动态库
n查看目标文件使用的动态库ldd
n示例:ldd hello
n动态库路径
n默认/lib,/usr/lib
n由/etc/ld.so.conf指定
n ldconfig程序根据/etc/ld.so.conf重建动态库cache
n ld-linux.so动态库负责完成动态链接
n环境变量LD_LIBRARY_PATH

n全称:executable and linkable format
n目标文件:可重定位文件(relocatable,.o),可执行文件,静态库,动态库等
n ELF文件组成
n文件头ELF header
n程序段,典型的段.text(代码段)、.bss(未初始化的数据段)、.data(初始化的数据段)
n重定位和位置无关代码(PIC)

二进制文件工具binutils
n 用于查看和操作二进制文件
n 包含工具:
n 连接器:ld
n 汇编器:as
n 转换地址到源程序行:addr2line
n 建立、修改函数库:ar
n 列出目标文件的符号表:nm
n 目标文件转换和拷贝:objcopy
n 显示目标文件信息:objdump
n 删除目标文件中的符号表:strip
n 显示elf文件信息:readelf

二进制文件工具使用示例
n查看目标文件基本信息
n示例:readelf –h hello
n查看目标文件中的符号
n示例:nm hello
n查看目标文件中的段信息
n示例:objdump –h hello
n反汇编目标文件
n示例:objdump –d hello
n删除目标文件中的符号,减小文件大小
n示例:strip hello
n显示可执行文件中的常量串
n示例:strings hello

编程帮助
n man:用于查看标准命令、系统调用和函数库等的用法
n示例:man sleep(查看标准命令sleep)
n示例:man 3 sleep(查看函数sleep的用法)
n Info:查看命令用法,类似Web页面
n示例:info gcc,查看gcc用法
n示例:info libc,查看标准C函数

一个最简单的MFC程序实例

尽管刚接触MFC不久,但其开发模式已初有感受,抛开复杂的应用不谈,想想我们通常怎样来写一个简单的MFC程序。也许浮现在你脑海里的是程序向导,类向导,资源编辑器,以及程序代码,此时你大概会灵光一现,说MFC程序=向导+资源+代码。看起来确实是这样,但有没有可能更简化一些呢,比如把向导去掉。想想是可以的,向导不过是简化MFC开发的一种手段,虽然没有类向导日子会很难过,但也使你有机会看看纯手工打造的MFC是怎么样的,这对于了解MFC的执行流程非常有帮助。

       我试图从一个最简单的MFC程序开始,慢慢丰富它,使它最终成为一个有点用处的程序。这个过程中我会尽力抵挡住使用类向导的诱惑,只使用资源以及程序代码。当然可能需要好几篇文章的篇幅,因为这个程序是我计划好的,用来学习MFC的每一个技术点的实验品。现在,它的另一个目的是证明:MFC程序=资源+代码。

一个最简单的MFC程序

       你一定有这样的经验,用MFC的程序向导生成的MFC代码满是宏,注释,条件定义,这些对于程序的健壮性很有必要,不过对于初学者却是可怕的怪兽。 软件开发网 www.mscto.com

       其实MFC程序可以写得非常简单,只用一个头文件和一个源文件就可以了,用程序向导新建一个程序,选择Win32 Application类型的程序,我的工程名是:MiniDraw;在下一步选择“一个空工程”并完成掉它。

       生成的工程仅仅是一个工作区,没有任何源文件,手工给它添加一个头文件和一个源文件,分别是:DrawApp.h; DrawApp.cpp。这是它们的代码:

DrawApp.h:

#ifndef LINZHENQUN_DRAWAPP_H_

#define LINZHENQUN_DRAWAPP_H_

class CDrawApp: public CWinApp

{

public:

    CDrawApp();

    BOOL InitInstance();

};

#endif //LINZHENQUN_DRAWAPP_H_

DrawApp.cpp:

#include <afxwin.h>

#include "DrawApp.h"

CDrawApp DrawApp;

CDrawApp::CDrawApp() 软件开发网 www.mscto.com

{    软件开发网 www.mscto.com

}

BOOL CDrawApp::InitInstance()

{

    return FALSE;   

}

       不管你信不信,这就是一个最简单的MFC程序,尽管它什么事情也做不了。在编译的时候会出现错误,这是因为没有添加MFC共享DLL,打开工程设置,在常规页将MFC下拉框选为“使用MFC作为共享DLL”。然后再编译看看,运行后它马上就返回了,我们得在InitInstance里面作点什么。

BOOL CDrawApp::InitInstance()

{

    MessageBox(0, "Hello world", "", MB_OK);

    return FALSE;   

}

       MFC版的Hello World完成了,可能你还觉得过于简陋,如果是这样的话,给程序加一个对话框怎么样呢。

对话框的界面通过资源来加,在新建向导的文件页里选“资源脚本”,输入文件名为resource,确定后便生成了resource.rc,并在IDE里可以见到它:

      在resource.rc处点击右键菜单,选插入,在插入资源对话框里选Dialog,点新建后,一个对话框模板就生成了。

       享受一下快速开发的感觉吧,接下来要将这个对话框资源与一个对话框类关联起来,使用类向导的话会非常简单,不过为了体现纯手工的意义,我决定还是用新建文件的方式。

新建AboutDialog.cpp和AboutDialog.h,代码如下:

AboutDialog.h

#ifndef LINZHENQUN_ABOUTDIALOG_H_

#define LINZHENQUN_ABOUTDIALOG_H_ 软件开发网 www.mscto.com

class CAboutDialog: public CDialog

{

public:

    CAboutDialog(CWnd* pParent = NULL);

protected:

    virtual OnInitDialog();

    DECLARE_MESSAGE_MAP();

};

#endif //LINZHENQUN_ABOUTDIALOG_H_

AboutDialog.cpp

#include <afxwin.h>

#include "AboutDialog.h"

#include "resource.h" 软件开发网 www.mscto.com

BEGIN_MESSAGE_MAP(CAboutDialog, CDialog)

END_MESSAGE_MAP()

CAboutDialog::CAboutDialog( CWnd* pParent /*== NULL*/ ):

CDialog(IDD_ABOUTDLG, pParent)

{

}

BOOL CAboutDialog::OnInitDialog()

{

    return CDialog::OnInitDialog();

}

       对话框资源的ID是IDD_ABOUTDLG,在resource.h里面声明,所以将这个头文件加进来,这个资源ID传给基类CDialog后,它会帮你处理对话框加载的事情,在上一篇文章提及过。

       现在,回到DrawApp,将InitInstance改成这样:

BOOL CDrawApp::InitInstance()

{

    CAboutDialog AboutDlg;

    AboutDlg.DoModal();

    return FALSE;   

}

       运行程序如下:

      看看我们写的代码,总共只有4个文件,并且代码非常简单,学过SDK编程的你可能看出WinMain被CDrawApp::InitInstance()代替了,整个程序流程可以从这里开始,但是那些曾经熟悉的东西却不见了,比如WinMain呢,消息循环呢,毕竟好奇是人类的天性,而要发现答案,必须到框架里去看看。

这一切是怎样发生的

       MFC与VCL有一个很大的区别,VCL刚开始用时觉得非常简单,你不需要理解SDK那一套东西,当你想一探究竟时,才发觉那里面的水很深;MFC则相反,一开始你会觉得摸不着头脑,这迫使你必须了解一点SDK的知识,当你理解了程序向导为你生成的这一堆代码之后,你会发现AFX的东西也变得一目了然。

程序的初始流程也是这样,CDrawApp声明了一个全局的类实例,它必须是一个单例,因为它代表着这个程序。而我们直接认为,在某个源文件里有一个WinMain函数,这个函数取得CDrawApp的类实例,然后调用CDrawApp::InitInstance(),然后调用CDrawApp::Run();

就象下面这样:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    LPTSTR lpCmdLine, int nCmdShow)

{

    int nReturnCode = -1;

    CWinApp* pApp = AfxGetApp();

    If (pApp->InitInstance())

        nReturnCode = pApp->Run();

    return nReturnCode;

}

实际的MFC代码与上面并没有相差太远,也许你又找回C++程序的那种顺序了。

当然作为一个框架,要考虑的东西远比上面的多,比如线程的信息,模块的信息,如何保存App单例等,实际的代码可以参考WINMAIN.CPP的AfxWinMain函数。

       AfxGetApp()取回一个App指针,这个指针就是上面代码定义的DrawApp,CWinApp的构造函数将This指针存放在“全局的地方”,使得用AfxWinApp可以拿到。

       我们的InitInstance()返回的是FALSE,因而Run不会执行到,直接就返回了,那么对话框的消息处理是怎么做到的呢,答案在上一篇文章中,就是对话框自己有一个消息循环叫RunModalLoop。

       结合MFC实际的代码,概括起来就是下面几个点:

1.         生成CDrawApp类实例,在构造函数中将This保存起来,让AfxGetApp可以拿到。

2.         WinMain开始,拿到App类,初始化程序pApp->InitApplication();

3.         初始化实例pThread->InitInstance(),pThread等于pApp。

4.         开始消息循环pThread->Run()。

5.         WinMain返回,程序结束。

3、4步是整个程序的生命周期,InitInstance是创建窗口,Run是消息循环。用窗口表现界面,以事件驱动程序的模型就这样形成了。

上面所列出来的是经过简化的代码,我认为这也有好处,太关注分枝别叶只会让你头晕目眩,把这些东西摘除,只剩下干干净净的支杆,整个骨架就清清楚楚了,等到实际项目应用的时候,再慢慢丰富这些知识,这种由简到繁的学习方法一直为我所坚持。