Archive for the ‘c/c++’ Category

GDB怎么调试运行着的程序

Posted by 机器人 on 10th 九月 2009 in c/c++, linux/server

对于GDB这里就不作介绍了,随便找个搜索引擎一搜GDB,介绍就已经是很详细了,这篇文章主要是来谈怎么使用GDB来调试一个运行着的程序,或者说怎么调试一个进程,似乎标题有些拗口,其次也会对fork()分离出现的多子进程的调试加以说明。
下面是一段测试代码。
test.c

#include < stdio.h >
#include < unistd.h >
 
static void PrintMessage(int i);
static void GoToSleep(void);
 
 
int main(void)
{
	int i = 100000;
 
	while ( 1 )
	{
		PrintMessage( i );
		GoToSleep();
		i -= 1;
	}
 
	return 0;
}
 
 
 
void PrintMessage(int i)
{
		char buf[1024];
		sprintf(buf,"%d bottles of beer on the wall.\n", i);
		printf("%s",buf);
 
}
 
 
 
static void GoToSleep(void)
{
	sleep(3);
}

接下来是编译时使用的Makefile文件.

TARGET = test
SRC    =  $(TARGET).c
OBJ    =  $(TARGET).o
CC     =  gcc
CFLAGS = -g3 -W -Wall -std=c99
 
$(TARGET): $(OBJS)
 
 
.PHONY: clean
 
clean:
	$(RM) $(TARGET) $(OBJS)

此程序是一个服务程序,程序一旦启动,将作为一个进程永驻内存,可以通过

~@hqlong ps -ef | grep "test"

来查看该进程的信息。
此程序主要实现每3秒钟向墙上打印一瓶啤酒。对于这样的一个启动就作为一个进程进驻内存的程序应该怎么来进行调试呢?接下来的事情就是要来回来这个问题,
通过make来对源文件进行编译。

~@hqlong make

这里会在当前目录下产生一个test的可执行文件。
在对程序进行正式调试之前来回忆一个使用GDB调试一个非服务程序的步骤。假设test这个可执行文件是一个非服务程序,那么一般是通过如下几步方式来进行调试的。

hqlong@ubuntu:/tmp$ gdb test
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http ://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...
(gdb) b 1
Breakpoint 1 at 0x80483f4: file beer-process.c, line 1.
(gdb) r
Starting program: /tmp/test 
 
Breakpoint 1, main () at beer-process.c:9
warning: Source file is more recent than executable.
9	{
(gdb) n
main () at beer-process.c:10
10		int i = 100000;
(gdb) q
The program is running.  Exit anyway? (y or n) y
</http>

首先是通过gdb test来调试程序。然后使用b(break) 1在第一行设置断点,然后使用r(run) 来运行程序,最后使用n来单步运行程序,如果想要查看运行中某变量的值,可能通过p(print)来打印。如查看i的值,就可以通过p i。最后使用q(quit)来退出程序。

由于服务程序一旦启动,就以进程的方式进驻内存,不退出,所以和非服务程序的调试方式有一些区别。
服务一旦启动后,系统会分配一个pid,然后使用gdb绑定上这个pid,最后就可以通过通用方式进行调试了。
绑定进程的方式有下几种。

hqlong@ubuntu:/tmp$ ./test &
100000 bottles of beer on the wall.
[1] 25292

方式一
通过–pid参数来绑定指定的进程程序。

~@hqlong gdb --pid 25552

方式二
通过程序和进程号来绑定。

~@hqlong gdb test 25552

方式二
先启动gdb后,通过attach来绑定pid

~@hqlong gdb
gdb) attach 25552

将pid和gdb绑定后,就可以来对每一段代码进行调试。
下面是对上面例子的完整调试过程。
1. 启动进程

hqlong@ubuntu:/tmp$ ./test &
[1] 25615
hqlong@ubuntu:/tmp$ 100000 bottles of beer on the wall.
99999 bottles of beer on the wall.
99998 bottles of beer on the wall.

可以看见,启动test后,系统所分配的pid(进程号)为25615,然后每隔3秒钟就打印出一条信息。
2.使用gdb来绑定test进程。
这里需要重新启动一个新的shell来进行调试,也就是新开一个窗口,然后使用

hqlong@ubuntu:/tmp$ gdb --pid 25615
GNU gdb 6.8-debian
Copyright (C) 2008 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http ://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "i486-linux-gnu".
Attaching to process 25615
Reading symbols from /tmp/test...done.
Reading symbols from /lib/tls/i686/cmov/libc.so.6...done.
Loaded symbols for /lib/tls/i686/cmov/libc.so.6
Reading symbols from /lib/ld-linux.so.2...done.
Loaded symbols for /lib/ld-linux.so.2
0xb7ffb430 in __kernel_vsyscall ()
(gdb) 
</http>

来将gdb与已启动的test进行绑定,从而进入调试状态,这里会发现,当进入gdb调试状态后,先前启动程序的窗口就不再每隔3秒钟打印信息了,而是将控制权将给了gdb.
3.使用bt来显示当前程序的函数调用栈结构(也就是函数的调用顺序)。

(gdb) bt
#0  0xb7ffb430 in __kernel_vsyscall ()
#1  0xb7f23780 in nanosleep () from /lib/tls/i686/cmov/libc.so.6
#2  0xb7f235be in sleep () from /lib/tls/i686/cmov/libc.so.6
#3  0x0804851e in GoToSleep () at beer-process.c:35
#4  0x080484ac in main () at beer-process.c:15
(gdb)

可知函数的调用过程为:main调用GoToSleep,GoToSleep调用sleep等。
使用frame来选择程序的调试起点,也可以使用break来选择指定的行或者函数来作为断点。

(gdb) frame 4
#4  0x080484ac in main () at beer-process.c:15
15			GoToSleep();
(gdb)

这里我们选择最底层的栈,也就是整个程序的入口main函数来作为起点。
4. 调试跟踪过程
使用n(next)来一步一步的跟踪。

(gdb) n
Single stepping until exit from function sleep, 
which has no line number information.
GoToSleep () at beer-process.c:36
36	}
(gdb) 
main () at beer-process.c:16
16			i -= 1;
(gdb) 
14			PrintMessage( i );
(gdb)

这里有个技巧,如果要重复执行上一次执行的操作,可以直接回车。
通过上面的调试,发现程序运行到了14行,也就是PrintMessage函数处,如果这个时候我们继续n(next),程序就直接跳过函数,运行到函数的下一行,但如果想进入函数内部去调试,可以使用s(step),来进入函数体内进行调试。
进入函数体

(gdb) 
14			PrintMessage( i );
(gdb) s
PrintMessage (i=99897) at beer-process.c:25
25	{
(gdb) n
27	    sprintf(buf,"%d bottles of beer on the wall.\n", i);
(gdb) n
28		printf("%s",buf);
(gdb)

以上是进入PrintMessage体内,然后继续使用n(next)来一步一步的运行。进行到了28行,发现有两个变量,一个为i,另一个为buf,可以通过p(print)来打印该变量的值。

(gdb) p i
$1 = 99897
(gdb) p buf
$2 = "99897 bottles of beer on the wall.\n\000\000R\000�0u������$u���\227\001�\000\000\000\000p���\000\000\000\000\000\000\000\000\001\000\000\000��DB�x��8���\000\000\000\000�\217\001�p\226\001�0u��$u���\226\000�\001\000\000\000\000\000\000\000�v���\222\001�\205���", '\0' <repeats 12 times>, "�\226\000��\217\001�\000\000\000\000\000u���t���\020\001�\b\000\000\000\f\000\000\000\000����v��b\221\000�\000���\020���\f\000\000\000�\217"...
(gdb) 
</repeats>

发现通过p i 来打印i的值,结果是正确的,但通过p buf来打印buf 的值,却显示了很多不可读的乱码。这里因为buf代表这个变量在内存中的首地址,所以p不知道变量在什么时候结束,而i则不同,因为i是整型,在内存中一般占4个字节,所以p直接从首地址向后数4位就行了。所以这里打印字符指针或者数组时,需要指定一个长度。语法为 p *buf@len. 这里的len可以通过strlen(buf)来取得。

(gdb) p *buf@strlen(buf)
$3 = "99897 bottles of beer on the wall.\n"
(gdb)

这一下子就对了。
接下来继续使用n(next)来单步运行。

(gdb) 
28		printf("%s",buf);

发现运行到这一步时,启动程序的那个窗口打印出了一条信息。

99897 bottles of beer on the wall.

这说明,程序正好运行到了打印字符的地方。
如果这个时候想退出函数,直接返回,可能通过 finish.

(gdb) finish 
Run till exit from #0  PrintMessage (i=99895) at beer-process.c:28
main () at beer-process.c:15
15			GoToSleep();
(gdb)
退出PrintMessage函数,接着运行下一行代码GoToSleep.
如果调试完毕,或能通过q(quit)来退出GDB。
(gdb) q
The program is running.  Quit anyway (and detach it)? (y or n) y
Detaching from program: /tmp/test, process 25615

以上是调试一个进程的过程。

下面附带说明一下怎么调试一个需要通过fork()分离进程的程序。下面是一段一个Web服务器的在接受请求时的代码片断。

for (;;) {
	if ((connfd = accept(listenfd, (struct sockaddr *) &clientaddr,
					&clientaddrlen)) == -1) {
		perror("http server: accept error");
		continue;
	}
	if (fork() == 0)
		accept_request(connfd);
	else close(connfd);
 
	int statloc;
	waitpid(-1, &statloc, WNOHANG);
}

当单步运行到if(fork() == 0)这一步时,那怕是分离进程成功,也不会运行accept_request(connfd);而是直接跳到跳过,运行下面的代码。在调试这样的程序里,需要在进入gdb控制台时,需要将follow-fork-mode的值设成child.

(gdb) set follow-fork-mode child

本文主要是体验一下使用GDB的调试过程,对一些指令和俗语没有作过多的解释,如果有不明白可直接留言,或者参考相关资料。
参数资料
http://www.gnu.org/software/gdb/
http://dirac.org/linux/gdb/06-Debugging_A_Running_Process.php

结束!

机器人 2009-09-10 19:00 于 北京 晴

对于c/c++二次封装第三方类库的使用

Posted by 机器人 on 24th 八月 2009 in c/c++, linux/server

设计模块里中的适配器模式就谈到了为了实现接口的统一,需要对现有的类库进行第二次封装,进而达到接口统一的目的。
在 c/c++中,同样我们会采用这一策略,对接口不友好的或者需要统一的接口类库进行第二次封装。
一般来讲,会将二次封装后的库同样也做成一个静态或者动态库,但在对自己的库进行使用时,一定要加裁所封装的第三方库。
假如现在手上有一个第三方库叫libxx.a,二次封装后的库叫libself.a,在使用自己的库时,一般会采用如下编译参数选项编译。
假设:头文件目录为/inc 类库目录为/lib

g++ .... -lself -I/inc -L/lib

但这样会提示xx这个库未定义,正确的做法是同样需要加裁所封装的第三方库。

g++ .... -lself -lxx -I/inc -L/lib

结束!

机器人 2009年08月24日 17:28 于 北京 阴

vim执行php或者c/c++的快捷键

Posted by 机器人 on 23rd 六月 2009 in c/c++, linux/server, php/javascript, phpoo, vim

经常我们会编写一些测试代码,这些代码一般只是临时测试某一个函数,测试过后基本上就没什么用了。
如果使用IDE,这好办,基本上都有一些快速运行的键,比如在ZendStudio里,将代码编写好了以后,只需要按一下F5,就可以马上出来执行结果,而且这个文件都可以不用保存。

如果使用VIM来编写代码,可能很多朋友就会想了,有没有什么方法来建立一个快捷键,能快速的运行我们所编写的代码呢?!!
答案当然是肯定了的,不然也不会写这篇日志来讨论这个问题了。

在介绍方法之前,先来谈一下自己以前的执行方式吧!

先拿PHP来讲吧,最传统的方式就是将编写好的代码放在apache的根目录,然后通过浏览器来运行,或者使用php命令来运行php.可以使用php的r参数

hqlong@~$ php -r "echo 'hqlong';"

来快速测试,或者使用q参数

hqlong@~$ php -q test.php

来运行。

接下来讲怎么将vim支持一键执行。
大致原理是使用键盘映射,将快捷键映射到执行的命令。

在.vimrc中添加

nmap < F5 > < ESC >:!clear && php -q < C-R >%< cr >

即当我们按下F5时,就相当于执行了如下操作。
1.按下ESC,然后再按下:,进入vim命令行模式
2.执行clear清屏操作。
3.然后运行当前脚本。
4.最后执行回车操作.
下面有几个指令需要解释一下:
< C - R >%:得到当前文件的名字,包括路径.(按ctrl+r,然后再按%)

接下来可以测试一下.
vim /tmp/test.php

< ? php
    echo "hqlong";
?>

保存后,按F5,
就是快速的得出执行结果。

基于c/c++也同样可以做出映射来,比如我们用F6来执行C++代码。
那么就只需在.vimrc中添加

nmap < F6 > < ESC >:!clear && g++ < C-R >% -o test && ./test< cr >

这里如果我们测试如下代码。
vim test.cpp

# include < iostream >
using namespace std;
 
int main() {
     cout << "hqlong";
    return 0;
}

将会在当前上当下生成一个test可执行文件,并执行它。
所以的这一切都是由于nmap的强大映射功能。

希望对大家有所帮助,欢迎对不明白的地方进行讨论。
机器人 2009-06-23 22:59 于 北京 晴

几步搞定makefile文件的编写

Posted by 机器人 on 16th 六月 2009 in c/c++, linux/server

对于一个简单的程序。如下:
hello.c

#include < stdio.h>
int main() {
    printf("hello world\n");
    return 0;
}

可通过

~@ubuntu:~$ gcc hello.c -o hello

来进行编译。
当一个程序有3个文件时。假设有如下几个文件。
hello.c global.h global.c。
源码如下
hello.c

#include < stdio.h>
#include "global.h"
 
int main() {
    echo("hello\n");
    return 0;
}

global.h

void echo (char* str);

global.c

#include < stdio.h>
#include "global.h"
 
void echo (char* str) {
    printf("%s",str);
}

一般的编译方式如下

~@ubuntu:~$ gcc global.c hello.c -o hello

或者先编译再连接

~@ubuntu:~$ gcc -o global.c
~@ubuntu:~$ gcc global.o hello.c -o hello

我们也可以使用make命令来对它进行编译,不过这里需要一个makefile文件来描述make为何种方式来编译源代码和链接程序。
makefile文件的缩写规则如下:
TARGET… : PREREQUISITES…
COMMAND


target:规则的目标
prerequisites:规则的依赖
command:规则的命令行。值得注意的是每一个命令必需是以[TAB]开始。
上面的程序的makefile简单编写如下
Makefile

hello:global.o hello.o
    gcc hello.o global.o -o hello
hello.o:hello.c global.h
    gcc -c hello.c -o hello.o
global.o:global.c global.h
    gcc -c global.c -o global.o
clean:
    rm *.o hello

使用make编译

~@ubuntu:~$ make
gcc -c global.c -o global.o
gcc -c hello.c -o hello.o
gcc hello.o global.o -o hello

这里我们可以发现当前目录下生成了hello这个可执行文件。

从上面的Makefile文件中,能看出有三条规则,第一条的目标就生成可执行文件hello,第二条目标就生成hello.o文件,第三条则是生成global.o文件。而每个冒号后台的部分为生成达到该目标所需要的依赖文件,比如:第一条需要依赖hello.o和global.o这两个文件,第二条需要依赖hello.c和 global.h这两上文件,第三条需要依赖global.c和global.h这两上文件。接下来下面每条规则下面的一行就是需要执行的命令了。

最后还有一个clean,当用户键入make clean时,就清除所有的*.o文件。

关于make使用上面Makefile文件的工作原理如下:
默认情况下,make执行的是Makefile文件中的第一个规则,此规则的目标也就是整个程序的终极目标,我们上面的文件中生成hello这个可执行文件就是我们的最终目标,但要生成hello,又必需要依赖于hello.o和global.o这两个链接文件,make在处理这个规则之前,需要先处理hello所依赖文件的更新规则。
对这些.o(链接文件)规则的处理有三种情况。
1. 目标.o文件不存在,使用其描述规则创建它。如:如果hello.o不存在,则使用gcc -c hello.c -o hello.o来创建它。
2. 目标.o文件存在,但它所信赖的源文件中的任何一个比它都要新,则使用其描述规则创建它。如:hello.c或者global.h有更新时,gcc -c hello.c -o hello.o就得重新执行一次。
3. 目标.o文件存在 ,但它比所依赖的所有源文件都要新时,则什么也不做。

完成了对所有的.o文件的创建和更新后,make将处理第一条规则(终极目标),同样也分三种情况下进行处理。
1. 目标文件”hello”不存在,则执行描述规则创建hello.
2. 目标文件”hello”存在,但它所信赖的文件中的任何一个比它都要新,则根据规则重新链接生成hello文件.
3. 目标文件 “hello”存在,它比它的任何一个链接文件都要新,则什么也不做.

更多细节可参考:
GNU make 中文手册
http://www.diybl.com/course/3_program/c++/cppjs/2008622/127546.html

机器人 2009-06-16 14:17 于 北京 雨

linux c/c++多线程程序的编写

Posted by 机器人 on 8th 六月 2009 in c/c++, linux/server

第一次尝试编写linux下多线程程序,也是颇废一番周折。深知初学者对能快速的成功运行一段程序的渴望,所以本文专门用了“linux c/c++多线程程序的编写“这个醒目的标题,方便大家能即使从搜索引擎搜到本文,从而提升大家的信心。
首先先让代码进行起来再说为什么,见了效果再回过来头来看原因似乎会容易明白和理解一些。
测试代码如下:

#include[stdio.h]
#include[unistd.h]
#include[stdlib.h]
#include[pthread.h]
#include[string.h]
void *thread_function(void* arg);
char message[] = "Hello world!";
 
int main() {
    int res;
    pthread_t a_thread;
    void *thread_result;
 
    res = pthread_create(&a_thread, NULL, thread_function, (void*)message);
    if (0 != res) {
        perror("Thread creation faied");
        exit(EXIT_FAILURE);
    }
 
    printf("Waiting for thread to finish...\n");
    res = pthread_join(a_thread, &thread_result);
    if (0 != res) {
        perror("Thread join failed");
        exit(EXIT_FAILURE);
    }
    printf("Thread joined, it returned %s\n", (char*)thread_result);
    printf("Message is now %s\n",message);
    exit(EXIT_FAILURE);
}
void *thread_function(void* arg) {
    printf("thread_function is running. argument was %s\n",(char*)arg);
    sleep(3);
    strcpy(message,"bye!");
    pthread_exit("Thank you for the cpu time");
}

请将包含头文件的[替换成< ,]替换成>,代码高亮插件会认为尖括号是html标签,所以这里用中括号暂时替代。
编译:

hqlong@ubuntu:~/code/c$ gcc test.c -o test -lpthread

运行

hqlong@ubuntu:~/code/c$ ./test

结果

thread_function is running. argument was Hello world!
Waiting for thread to finish...
Thread joined, it returned Thank you for the cpu time
Message is now bye!

创建进程主要是通过pthread_create()这个函数来创建,函数的定义如下:

int pthread_create
(
	pthread_t *restrict tidp,
	const pthread_attr_t *restrict attr,
	void *(*start_rtn)(void),
	void *restrict arg
);

返回值:若是成功建立线程返回0,否则返回错误的编号
形式参数:
pthread_t *restrict tidp 要创建的线程的线程id指针
const pthread_attr_t *restrict attr 创建线程时的线程属性
void* (start_rtn)(void) 返回值是void类型的指针函数
vodi *restrict arg start_rtn的行参

注意上面还用到了pthread_join()这个函数,那么这个函数有什么用呢?接下来还是先看看它的原形定义:

int pthread_join
(
	pthread_t thread,
	void **value_ptr
);

各参数说明如下:
thread 等待退出线程的线程号
value_ptr 退出线程的返回值。
该函数的作用使得当前进程挂起,等待另一个进程返回才继续执行。也就是说当程序运行到这个地方时,程序会先停止,然后等线程id为thread的这个线程返回,然后程序才会断续执行。

在上面的例子中,如果把

  res = pthread_join(a_thread, &thread_result);
    if (0 != res) {
        perror("Thread join failed");
        exit(EXIT_FAILURE);
    }
    printf("Thread joined, it returned %s\n", (char*)thread_result);

注释掉,那么主程序根据不会等待线程返回,就退出,当然主进行退出了,他所创建的子进程也就会跟着退出,所以在上面的例子中,程序还在睡眠时,就被主进程强行退出了。

在上面例子进对源码进行编译时,加了-lpthread,如果不加-lprhead,会出现如下编译错误。

tmp/ccnPVTWo.o: In function `main':
mthread.c:(.text+0x30): undefined reference to `pthread_create'
mthread.c:(.text+0x6f): undefined reference to `pthread_join'
collect2: ld returned 1 exit status

thread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a。

更多细节请参考:
http://hi.baidu.com/wenlongren/blog/item/4615450f498006eaaa645759.html
http://hi.baidu.com/wenlongren/blog/item/998f04dd7e866de877c6383e.html
http://blog.sina.com.cn/s/blog_5dcf190f0100dbcm.html
http://hi.baidu.com/beisika/blog/item/8ced51cea7ac9c3eb600c8ea.html

机器人 2009-06-08 18:00 于 北京 阴
机器人 2009-06-09 10:48 于 北京 晴 更新

apache cgi程序的简单配置

Posted by 机器人 on 5th 五月 2009 in c/c++, linux/server

首先添加虚拟主机

<virtualhost 127.0.0.1:50001>
</virtualhost>

这里新添加了50001端口来进行监听,所以还需要添加监听端口号

Listen 50001

要让程序能正常运行,还得通过配置ScriptAlias来允许服务器在指定的情况下,以CGI方式运行。

<virtualhost 127.0.0.1:50001>
ScriptAlias /cgi-bin/ /usr/local/ebserver/apache/cgi-bin/
</virtualhost>

所以上述的配置会告诉apache,所以以/cgi-bin/开头的资源都会被映射到/usr/local/webserver/apache/cgi-bin/目录下,并被认为是cgi程序。
然后重启服务器
在apache/cgi-bin/目录里有一些测试例子,我们可能用它来测试下,看看配置是否能正常运行。
比如说apache/cgi-bin/目录下有个test-cgi程序,源码如下:

#!/bin/sh
 
# disable filename globbing
set -f
 
echo "Content-type: text/plain; charset=iso-8859-1"
echo
 
echo CGI/1.0 test script report:
echo
 
echo argc is $#. argv is "$*".
echo
 
echo SERVER_SOFTWARE = $SERVER_SOFTWARE
echo SERVER_NAME = $SERVER_NAME
echo GATEWAY_INTERFACE = $GATEWAY_INTERFACE
echo SERVER_PROTOCOL = $SERVER_PROTOCOL
echo SERVER_PORT = $SERVER_PORT
echo REQUEST_METHOD = $REQUEST_METHOD
echo HTTP_ACCEPT = "$HTTP_ACCEPT"
echo PATH_INFO = "$PATH_INFO"
echo PATH_TRANSLATED = "$PATH_TRANSLATED"
echo SCRIPT_NAME = "$SCRIPT_NAME"
echo QUERY_STRING = "$QUERY_STRING"
echo REMOTE_HOST = $REMOTE_HOST
echo REMOTE_ADDR = $REMOTE_ADDR
echo REMOTE_USER = $REMOTE_USER
echo AUTH_TYPE = $AUTH_TYPE
echo CONTENT_TYPE = $CONTENT_TYPE
echo CONTENT_LENGTH = $CONTENT_LENGTH

然后在浏览器里输入http://127.0.0.1/cgi-bin/test-cgi
如果设置正确就能显示如下信息

CGI/1.0 test script report:
 
argc is 0. argv is .
 
SERVER_SOFTWARE = Apache/2.2.10 (Unix) PHP/5.2.8
SERVER_NAME = 127.0.0.1
GATEWAY_INTERFACE = CGI/1.1
SERVER_PROTOCOL = HTTP/1.1
SERVER_PORT = 50001
REQUEST_METHOD = GET
HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
PATH_INFO = 
PATH_TRANSLATED = 
SCRIPT_NAME = /cgi-bin/test-cgi
QUERY_STRING = 
REMOTE_HOST =
REMOTE_ADDR = 127.0.0.1
REMOTE_USER =
AUTH_TYPE =
CONTENT_TYPE =
CONTENT_LENGTH =

当然你也可以选择用其它语言来写一个cgi程序,其中apache提供了一个用pear写的例子。
下面是用C语言写的一个简单的例子。

#include <stdlib .h>
#include <stdio .h>
int main (int argc, char** argv) {
    char a[] = "-100";
    char b[] = "456";
    int c;
    c = atoi(a) + atoi(b);
    char *p;
    p = getenv("QUERY_STRING");
    printf("Content-type: text/html\n\n");
    printf("c=%d",c);
    printf("%s",p);
    return 0;
}
</stdio></stdlib>

实现功能为:打印打印a+b的值,如果有get参数,同时打印get参数
编译

hqlong@ubuntu:/usr/local/webserver/apache/cgi-bin$ gcc test.c -o test

这时我们可以通过http://127.0.0.1:50001/test来访问了。
如果通过http://127.0.0.1:50001/test?name=hqlong
我们的程序就会打印name=hqlong这个查询参数。
参考资料:http://doc.chinahtml.com/Manual/ApacheManual/howto/cgi.html
机器人 2009-05-05 17:52 于 北京

阿里大牛重拳出击,首创saas架构新著

Posted by 机器人 on 13th 十二月 2008 in c/c++, 读书

今天拿到了刚发布不久的,而且是国内少见的一本关于SaaS架构设计的书--<<互联网时代的软件革命 SaaS架
构设计>>。一看书名就很有吸引力。这是一本阿里软件大牛们一起创作的一本书。大致谈了目前最为先前的互联网技术已经软件和互联网的结合,趋势,已经高可靠性服务器,群集,缓存共享、分布式文件系统以及云计算等时会遇到的问题和相应的解决方案等。国内可算是唯独的技术资料哟!!强烈推荐大家能读一下,虽然我目前还没开始读,但大致的浏览了下,就感觉到了这本书的分量。

详细信息
互联网时代的软件革命
作者: 叶伟 / 赵进 / 叶军 / 闻波 / 黄晓龙 / 龙良 / 曾义 / 李战 / 莫建祥

副标题: SaaS架构设计
ISBN: 9787121077364
页数: 348
出版社: 电子工业出版社
出版年: 2008

简介 · · · · · ·
国内第一本完整介绍SaaS应用设计的书籍。.
具有丰富SaaS实践经验的一线架构师的经验总结。..
用创业故事贯穿全书,以一种活泼的风格来描述SaaS应用逐步成熟的过程。…
作者简介 · · · · · ·
叶伟,阿里软件研发中心总监.曾先后就职于金仕达卫宁.IBM.金蝶,在HIS.ERP.SaaS领域领导开发了多个大型成功产品.复旦大学计算机科学学士.硕士,1993年获高级程序员,2000年被评为高级工程师.15年软件开发经验,专长面向对象分析&设计,以及SaaS应用架构设计.
赵进,阿里软件首席架构师,在管理软件领域和SaaS领域都有多年的开发和架构设计经验.现负责阿里巴巴软件互联平台的技术规划和架构设计工作,对于云计算.PaaS.OpenAPI.MultiTenant架构.SOA.MDA等领域都具有浓厚的兴趣.
叶军,计算机博士,阿里软件架构师.10年Web应用开发经验,对网站设计和互联网前沿技术有广泛的研究.现负责阿里软件互联平台的系统架构设计.
闻波,阿里软件桌面平台架构师.一直致力于Windows应用软件开发,对面向对象程序设计和Windows系统底层的研究有丰富的经验,对驱动程序开发.软件加密/解密等有较深入的研究.
黄晓龙,阿里软件高级架构师,先后在金蝶.腾讯等多家著名IT公司任职,在企业管理软件.架构设计.OOAD.敏捷开发.项目管理等方面积累了多年经验.
龙良,阿里软件架构师,先后在金蝶.中兴等多家著名IT公司担任架构师.系统分析师(2005年),清华大学软件工程硕士.在Web架构设计和企业管理软件等方面积累了多年经验.
曾义,阿里软件Web平台技术经理,四川大学计算机科学硕士.专长于MDA.Web前端组件设计.SOA,目前领导SaaS应用开发平台XPlatform的研发.
李战,阿里软件架构师,从事SaaS研究多年.在SaaS数据库.Web架构.前端框架以及数据库全文检索方面都有丰富的经验.
莫建祥,阿里软件高级架构师.擅长大规模即时通信系统.分布式存储和数据库系统.分布式计算.高性能计算.网络通信的设计开发.现负责阿里旺旺(IM产品)的整体架构设计.

机器人 2008-12-13 16:12 于 北京

几步创建您的php扩展

Posted by 机器人 on 16th 十一月 2008 in c/c++, linux/server

下面以开发hello world这个简单的应用扩展为例,来说明开发的步骤,这个扩展提供一个hello_world()函数,该函数会返回一个”hello world”字符串,在php中的调用方式如下:

< ?php
    echo hello_world();
?>

首先进入php源码中的ext目录,php的所有扩展库的源码都放在这里。
然后创建hello目录,这个目录里就存放我们helloworld扩展的源码和相关文件.
Read the rest of this entry »

怎么让你的对话框保持统一的风格

Posted by 机器人 on 2nd 八月 2007 in c/c++

1.前言

我们在做窗口程序时,难免有时候会软件添加一些背景颜色或者图片,来对其进行美化,如果只有一个窗口,可能这还比较好办.如果窗口很多,而且我们又想让它们的界面风格保持一致,那么我们应该怎么去做呢?或许有朋友会讲,对每个对话框窗口进行美化不就行了吗?如果要求它们的界面保持一致的话,使用相同的代码对各自的对话框进行美化,这不就能达到风格统一的效果吗?

是的,的确,这样确实也能达到我们的要求,但不知道各位有没有想过,当我们的对话框足够多,就意味着我们需要将同一份代码复制足够多份到足够多的对话框程序中,如果那天我们对风格不满意了,需要对它们进行修改,而且风格依然要求统一起来,如果遇到这样的情况,我们再使用前面 的方法去做,或许这会儿我们就会感到该方法的的弊端,因为我们需要去修改每一个对话框程序中的代码.那么究竟用什么方法才能避免这种事情呢?

答案是:充分利用面向对象的思想,采用继承机制来帮我们完成这一工作.

2.设计思想:

平常情况下, 我们创建一个对话框,那么我们会从CDialog类中派生一个类来与之相关联,如果 我们需要更改对话框的风格,我们就需要在该派生类中来完成对该对话框风格的更改.

当需要定义多个对话框时,我们可以先从CDialog类中派生一个自己的类CMyDialog,然后再为对话框创建关联类时,我们只需要把对话框关联类的基类修改成CMyDialog,那么在CMyDialog中定义的所有方法都会被所有继承它的子类所享有,当我们需要对对话框的界面风格进行统一制定时,我们只需要在CMyDialog里对风格进行设置,其它的对话框所风格则会自己继承.当需要对风格进行更改时,我们只需要修改CMyDialog中定义风格的代码即可完成对所有对话框的界面风格的更改.

3.举例说明

1.首先我们创建一个基于对话框的MFC应用程序.工程名字为phpoo.

2.然后我们新建一个类,该类从CDialog中派生,类名为CMyDig.(注意:当我们点击确定时,这里会出现一个提示信息,提示说"没有为该类创建一个资源对话框,希望我们先创建资源对话框后再创建该类,否则将会为其关联一个不合法的资源ID,是否断续",我们选择是).

3.修改CPhpooDlg类的基类为CMyDig,同时修改CPhpooDlg构造函数的初始化列表为 CMyDlg(CPhpooDlg::IDD, pParent)(因为现在CPhpooDlg已经不再从CDialog派生,而是从CMyDlg类派生而来,所以这时我们需要向它的基类CMyDlg传递资源相关的参数.

代码如下:

class CPhpooDlg : public CMyDlg//将CPhpooDlg基类修改为CMyDlg

 

CPhpooDlg::CPhpooDlg(CWnd* pParent /*=NULL*/)
	: CMyDlg(CPhpooDlg::IDD, pParent)//修改CPhpooDlg构造函数的初始化列表

4.将CMyDlg类头文件MyDlg.h加入到CPhpooDlg类的头文件中.

5.将CMyDlg中enum { IDD = _UNKNOWN_RESOURCE_ID_ };去掉。并重载一个构造函数,将设置和先前默认构造函数相同的初始化列表,用来接受派生类传递过来的ID。同时将默认构造函数的初始化列表去掉。因为这里只使用我们所重载的构造函数。向基类的构造函数的初始化由现在的重载构造函数来完成。

代码如下:

/**
 * 重载构造函数,用来接收派生类所传递的资源ID等参数,
 */
CMyDlg::CMyDlg(UINT nIDTemplate, CWnd *pParentWnd)
 	: CDialog(nIDTemplate, pParentWnd)//设置初始化列表对CDialog函数进行初始化
{

}
//去掉初始化列表
CMyDlg::CMyDlg(CWnd* pParent /*=NULL*/)

 6.接下来为CMyDlg定义背景,这里我们将设置一张图片作为该对话框的背景。

首先找一张图片,拷入到资源文件夹下,并将其导入到资源列表中。

接下来在CMyDlg类中添类型为CBrush的成员变量,用来保存我们的背景。

 

CBrush m_brush;//定义画刷句柄

在构造函数中创建位图画刷.

 

/**
 * 重载构造函数,用来接收派生类所传递的资源ID等参数,
 */
CMyDlg::CMyDlg(UINT nIDTemplate, CWnd *pParentWnd)
 	: CDialog(nIDTemplate, pParentWnd)//设置初始化列表对CDialog函数进行初始化
{
	CBitmap bmp;
	bmp.LoadBitmap(IDB_BITMAP1);//load pic
	m_brush.CreatePatternBrush(&bmp);//使用指定的位图来创建一把画刷
	bmp.DeleteObject();
}

添加ON_WM_CTLCOLOR事件响应函数,将返回值改为m_brush.

 

HBRUSH CMyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
	// TODO: Change any attributes of the DC here
	// TODO: Return a different brush if the default is not desired
	return m_brush;//返回我们定义的画刷
}

 由于在子类中CMyDlg的OnCtlColor不会自动被调用,所以我们需要在子类中显示的调用该函数。

创建CPhpooDlg的ON_WM_CTLCOLOR事件响应函数。并将返回CMyDlg所拥有的画刷。代码如下:

 

HBRUSH CPhpooDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor)
{
	HBRUSH hbr = CMyDlg::OnCtlColor(pDC, pWnd, nCtlColor);//返回CMyDlg而不是CDialog的画刷

	// TODO: Change any attributes of the DC here

	// TODO: Return a different brush if the default is not desired
	return hbr;
}

 

我们这里编译运行,就会发现,这里的对话框背景已经变成了我们所指定的背景了。

上面我们只创建了一个对话框,如果我们需要创建更多的对话框,我们只需要简简单单的修改几处,就能很方便的将这些对话框的风格进行统一进来。这几处是:对话框的基类,修改对话框构造函数的初始化列表,为ON_WM_CTLCOLOR添加响应函数,并返回CMyDlg的画刷。

 

机器人 2007年8月2日于 北京。

Visual C++ 6.0编程实现打印功能

Posted by 机器人 on 1st 八月 2007 in c/c++

Visual C++6.0是开发Windows应用程序的强大工具,但是要通过它实现程序的打印功能,一直是初学者的一个难点,经常有朋友询问如何在VC中实现打印功能,他们往往感到在MFC提供的框架内实现这个问题很复杂,不知道如何下手。本例针对这个问题,介绍一种简单的方法实现文字串的打印功能,读者朋友可以在此基础上稍微改动一下,就可以实现文件、图像的打印功能。

一、实现方法

在Windows操作系统下,显示器、打印机和绘图仪都被视为输出设备,正常情况下,系统默认的输出设备是显示器。要使用打印机,首先需要创建一个指向打印机的设备环境句柄,然后通过该句柄调用相关的绘图函数把所需的文字和图形输出至打印机上。当打印结束后,删除这个设备环境句柄即可。

当Windows系统中安装好打印机后,系统总是自动设置一个打印机为系统的默认打印机,在Windows的启动配置文件Win.ini中的[window]段中列出了带有关键字device的默认打印机。下面是某一机器中Win.ini中的[Windows]字段的内容:

[windows]
load=
run=
NullPort=None
device=HP LaserJet 4050(computer000),HPBFDB1,LPT1

在上述关键字device后的字符串中,包含了系统中默认打印机的三个重要属性,它们依次是打印机的设备名HP LaserJet 4050(computer000),驱动程序名是HPBFDB1,输出端口为LPT1。

为了操纵系统默认的打印机,实现程序的打印功能,在程序中可调用API函数GetProfileString()从Win.ini文件中获得 device这个设备字符串,该函数的原型为:DWORD GetProfileString( LPCTSTR lpAppName, LPCTSTR lpKeyName, LPCTSTR lpDefault, LPTSTR lpReturnedString, DWORD nSize)。函数中lpAppName参数为所要检索的Win.ini文件中的字段名;lpKeyName为字段中的关键字名;lpDefault为默认的字符串;lpReturnedString为检索到的字符串,如果该函数没有从lpKeyName关键字中检索到相应的字符串,则 kpRetrunedString返回默认字符串lpDefault;nSize为返回字符串的长度。

获取上述字符串后,再使用 strtok()函数将该字符串进行分解,获得与打印机相关的三个属性,作为API函数CreateDC()创建打印机设备环境句柄的参数, CreateDC()函数如果调用成功,则为默认打印机创建一个设备环境句柄,否则返回一个空值(NULL)。该函数的原形为:HDC CreateDC(LPCTSTR lpszDriver,LPCTSTR lpszDevice,LPCTSTR lpszOutput,CONST DEVMODE *lpinitData)。该函数的前三个参数恰好对应打印机的三个属性,最后一个参数为初始化打印机驱动程序的数据,一般情况下该参数设置为NULL就可以了。

在具体打印的过程中,调用int StartDoc( HDC hdc, CONST DOCINFO *lpdi )函数来开始一个打印任务,其中参数lpdi为一个指向DOCINFO结构的指针,该结构如下:

typedef struct {
 int cbSize; //结构的尺寸大小;
 LPCTSTR lpszDocName; //文档的名字;
 LPCTSTR lpszOutput; //输出文档名,一般情况下为NULL;
 LPCTSTR lpszDatatype;//用来记录打印过程的数据类型,一般情况下为NULL;
 DWORD fwType; //用来支持打印工作的额外信息,一般情况下为NULL;
} DOCINFO, *LPDOCINFO;

开始一个打印任务后,再调用StartPage(hdcprint)函数让打印机走纸,通知打印机有文档将要打印;接下来的工作就是输出数据了,这部分工作对于开发人员来说就象往计算机屏幕上输出文字、图像一样容易,只不过是计算机根据当前的设备环境句柄自动将数据输出到打印机罢了。数据打印完后,需要作一些善后处理工作,使用RestoreDC(hdcprint,-1)函数恢复打印机设备句柄、EndPage(hdcprint)函数让打印机停止打印,最后调用EndDoc(hdcprint)函数结束上述的打印作业。

 二、编程步骤

1、启动Visual C++6.0,新建一个基于对话框的应用程序Test,在程序的对话框窗体中加入一个按钮(Button),设置这个Button的属性:ID=IDC_PRINT,CAPTION="打印";

2、使用Class Wizard类向导为该按钮添加一个鼠标单击处理函数OnPrint()

3、修改TestDlg.cpp文件中的OnPrint()函数;

4、添加代码,编译运行程序。

三、程序代码

 

////////////////////////////////////////////
void CTestDlg::OnPrint()
{
 char szprinter[80];
 char *szDevice,*szDriver,*szOutput;
 HDC hdcprint; // 定义一个设备环境句柄
 //定义一个打印作业
 static DOCINFO di={sizeof(DOCINFO),"printer",NULL};
 // 得到设备字符串存入数组szprinter中
 GetProfileString("windows","device",",,,",szprinter,80);
 // 将设备字符串分解
 if(NULL!=(szDevice=strtok(szprinter,","))&&NULL!=(szDriver=strtok(NULL,","))&&
NULL!=(szOutput=strtok(NULL,",")))
  // 创建一个打印机设备句柄
  if((hdcprint=CreateDC(szDriver,szDevice,szOutput,NULL))!=0)
  {
   if(StartDoc(hdcprint,&di)>0) //开始执行一个打印作业
   {
    StartPage(hdcprint); //打印机走纸,开始打印
    SaveDC(hdcprint); //保存打印机设备句柄
    // 输出一行文字
    TextOut(hdcprint,1,1,"热烈祝贺编程实例出版发行!",16);
    RestoreDC(hdcprint,-1); //恢复打印机设备句柄
    EndPage(hdcprint); //打印机停纸,停止打印
    EndDoc(hdcprint); //结束一个打印作业
    MessageBox("打印完毕!","提示",MB_ICONINFORMATION);
   }
   // 用API函数DeleteDC销毁一个打印机设备句柄
   DeleteDC(hdcprint);
  }else
  {
   MessageBox("没有默认打印机,或者没有安装打印机!");
   return;
  }
 }

 四、小结

上面的例子非常简单,笔者主要是通过它说明如何实现打印功能,而不是说明如何实现复杂的打印效果,因为它们已经不属于我们这里所要讨论的范畴了,相信读者朋友真正掌握了上面实现打印功能的方法后,通过灵活的设置设备环境的各种对象(如字体对象、画刷等),一定可以打印出各种满意的效果来。

源码下载

机器人 2007-8-1 整理于 北京.