基于Qt的多窗口编程-界面的设计

25 3 月, 2011 by edsionte 5 comments »

对于应用程序中的多窗体切换,我们已经习以为常。通常一个应用程序中,不同的窗口代表不同功能的工作区。本文将详细描述基于Qt的多窗体程序的设计方法。在阅读本文之前,你最好了解面向对象的基本思想以及Qt的基本使用方法。

接下来我们以编写一个客户端为例来具体说明多窗体程序的编程方法。该客户端包括多个子系统,每个子系统对应一个窗口;在客户端的主界面,通过点击相应的按钮实现多个窗体之间的切换。主界面图如下:

 

如图所示,该客户端主界面包含顶部的标签、右方的按钮区、中间的主窗口区以及底部的状态栏。通过点击不同的按钮则进入不同的子系统。配置信息子系统和日志信息管理子系统界面设计如下:

布局管理

上述三个界面都可以通过Qt Designer轻松设计完成,这里只对窗体部件的布局管理作说明。通常我们可以显示指定窗体中各个部件的大小以及位置,但是这样麻烦而且缺少灵活性。因此在实际的应用中,使用布局管理器对窗体中的各个子部件进行布局管理。布局管理器会为每个窗体部件提供合理的默认值,并随着个别部件大小的变化而调整整体的窗体布局。

常用的布局管理器有QHBoxLayout、QVBoxLayout和QGridLayout,即依次为水平布局管理、垂直布局管理器和网格布局管理器。结合上面的主界面图示,其对应的布局管理图如下:

使用布局管理器的一般方法为:先创建相应布局管理器的对象;再将要进行布局管理窗体部件加入到布局管理器对象中;为当前窗体设置该布局管理器。除了可以将窗体部件加入到当前的布局管理器中,还可以将另外一个布局管理器对象加入到当前的布局管理器中;此外,设置布局管理器只需在所有布局管理器都添加好之后进行一次的设置即可。具体使用方法可参考随后给出的示例代码。

在topLayout这个顶部的布局管理器中,我们仅加入一个标签。正如上面所述,我们首先创建一个水平布局管理器对象,再将这个mainlabel标签加入其内。

    QHBoxLayout *topLayout = new QHBoxLayout;
    topLayout->addWidget(ui->mainLabel);

右边的按钮区的布局管理rightBtnLayout和上述类似,只不过我们此时使用的是垂直管理器。接下来重点说一下主窗口区布局管理器widgetLayout的设置。两个子系统和主界面的功能不同,因此将他们分别封装成类configUI、logUI和frontPage,我们依次创建三个类的对象。接下来将这三个部件加入到widgetLayout中。由于客户端初始显示主界面,则我们将其他两个窗体暂时隐藏。

    QVBoxLayout *widgetLayout = new QVBoxLayout;
    configWidget = new configUI(this);
    logWidget = new logUI(this);
    mainMenu = new frontPage(this);
    widgetLayout->addWidget(ui->introTextBrowser);
    widgetLayout->addWidget(configWidget);
    widgetLayout->addWidget(logWidget);
    configWidget->hide();
    logWidget->hide();

我们现在已经设置好了rightBtnLayout和widgetLayout,接下来将这两个布局管理器加入到mainWindowLayout中。

    QHBoxLayout *mainWindowLayout = new QHBoxLayout;
    mainWindowLayout->addLayout(widgetLayout);
    mainWindowLayout->addLayout(rightBtnLayout);

这样mainWindowLayout布局管理器就设置好了。最后我们还需要设置bottomLayout布局管理器,只是简单的添加一个标签和一个水平分割线。

至此,我们拥有三个二级布局管理器:topLayout、mainWindowLayout和bottomLayout,我们将这三个布局管理器全部添加到顶级布局管理器mainLayout中:

   QVBoxLayout *mainLayout = new QVBoxLayout;
    mainLayout->addLayout(topLayout);
    mainLayout->addLayout(mainWindowLayout);
    mainLayout->addLayout(bottomLayout);
    this->adjustSize();

    setLayout(mainLayout);

此时,我们就设置好了主界面的布局。

Linux下的socket编程-基于Qt的客户端

20 3 月, 2011 by edsionte 23 comments »

上文中对面向连接的C/S模型中的服务器进行了简单描述,本文将说明如何编写一个客户端应用程序。与服务器程序不同,客户端程序要求有友好的交互界面,因此本文所示的客户端程序采用Qt和linux C共同完成。客户端的界面部分采用Qt完成,而与服务器间具体的通信部分则通过linux C语言完成。

1.界面设计

通过Qt Designer可快速设计好客户端界面,并对四个按钮所对应的信号和槽函数进行连接。客户端所对应的类为ClientUI。

2.与服务器间通信的编程

由于Qt是对C++的一种扩展,因此我们必须将使用Linux C编写的客户端通信程序封装成类,这里我们将其定义为ClientConnection。

我们已经对客户端的界面和通信部分分别定义了两个类,但是如何将两者结合在一起?方法很简单。将ClientConnection类的对象cli作为ClientUI类的成员,然后在具体的槽函数中对应相应的通信函数。和上文分析服务器编程的方法不同,本文将以分析客户端对应的槽函数的方式来说明如何编写客户端程序。

2.1连接按钮的槽函数

在连接按钮对应的槽函数中,首先创建客户端套接字描述符;然后从输入框中获得服务器的IP地址;再通过connect函数创建一个连接。

connect函数的原型如下:

     #include < sys/socket.h >
     int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

connect函数用于在sockfd套接字上创建一个连接,sockfd即客户端套接字;addr即为服务器端的套接字地址;addrlen为addr的长度。我们将connect函数封装成ClientConnection类中的connectingSocket函数,该成员函数在连接按钮所对应的槽函数中被调用。:

int ClientConnection::connectingSocket()
{
    if (::connect(sockfd, (struct sockaddr *)&serv_addr,
                          sizeof(struct sockaddr)) == -1) {
        return 0;
    }

    return 1;
}

在该槽函数中,可以这样使用连接函数:

    if (cli.connectingSocket() == 1) {
        ui->statusLabel->setText("connecting to server success!");
    } else {
        ui->statusLabel->setText("ERROR:connecting to server fail!");
        //exit(1);
    }

当客户端连接至服务器成功后,客户端就可以跟服务器端进行数据的传送。

2.2 修改按钮的槽函数

该槽函数的工作比较简单,使得文本编辑区可被编辑。

2.3 应用按钮的槽函数

在该槽函数中,将文本编辑区的数据发送至服务器端;或从服务器接受数据显示在这个文本编辑区中。发送和接受数据的API仍然是send和recv函数,不过这里我们将这两个函数封装成ClientConnection类中的sendData成员和recvData成员:

int ClientConnection::recvData(char *buf)
{
    if ((recvbytes = recv(sockfd, buf, MAX_DATA_NUM, 0)) == -1) {
        return 0;
    }

    buf[recvbytes] = '\0';
    return 1;
}

int ClientConnection::sendData(char *msg, int len)
{
    if (send(sockfd, msg, len, 0) == -1) {
        printf("send error!\n");
        return 0;
    }

    return 1;
}

这两个成员函数在该槽函数的适当位置被调用。

2.4退出按钮的槽函数

退出按钮的槽函数中只需断开客户端和服务器之间的连接即可。

参考:

1.Qt Assistant

2.Linux C编程实战;童永清 著;人民邮电出版社;

Linux下的socket编程-服务器

15 3 月, 2011 by edsionte 18 comments »

我们都知道,同一台计算机上的进程可以通过IPC(进程间通信)机制进行通信;而不同机算计上运行的进程则通过网络IPC,即套接字(socket)进行通信。Linux下的socket API是基于BSD套接口而是实现的,通过这些统一的API就可以轻松实现进程间的网络通信。此外,socket API即可用于面向连接(TCP)的数据传输,又可用于无连接(UDP)的数据传输。一般使用Client/Server交互模型进行通信。

本文以及下文将实现一个面向连接的C/S通信模型。本文首先介绍服务器端的实现。

1.创建套接字

#include < sys/socket.h >
int socket(int domain, int type, int protocol);

通过socket函数可以创建一个套接字描述符,这个描述符类似文件描述符。通过这个套接字描述符就可以对服务器进行各种相关操作。

该函数包含三个参数,domain参数用于指定所创建套接字的协议类型。通常选用AF_INET,表示使用IPv4的TCP/IP协议;如果只在本机内进行进程间通信,则可以使用AF_UNIX。参数type用来指定套接字的类型,SOCK_STREAM用于创建一个TCP流的套接字,SOCK_DGRAM用于创建UDP数据报套接字。参数protocol通常取0。对于本文所描述的服务器,创建套接字的示例代码如下:

	if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {

		printf("socket error!\n");
		exit(1);
	}

2.绑定套接字

对于服务器而言,它的IP地址和端口号一般是固定的。服务器的IP即为本地IP,而服务器的端口号则需要显示的指定。通过bind函数可将服务器套接字和一个指定的端口号进行绑定。

在具体介绍绑定函数之前,先说明一下socket中的套接字地址结构。由于套接字是通过IP地址和端口号来唯一确定的,因此socket提供了一种通用的套接字地址结构:

   struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
           }

sa_family指定了套接字对应的协议类型,如果使用TCP/IP协议则改制为AF_INET;sa_data则用来存储具体的套接字地址。不过在实际应用中,每个具体的协议族都有自己的协议地址格式。比如TCP/IP协议组对应的套接字地址结构体为:

struct sockaddr_in {
	short int sin_family; /* Address family */
	unsigned short int sin_port; /* Port number */
	struct in_addr sin_addr; /* Internet address */
	unsigned char sin_zero[8]; /* Same size as struct sockaddr */
};

struct in_addr {
	unsigned long s_addr;
};

该地址结构和sockaddr结构均为16字节,因此通常在编写基于TCP/IP协议的网络程序时,使用sockaddr_in来设置具体地址,然后再通过强制类型转换为sockaddr类型。

绑定函数的函数原型如下:

       #include < sys/socket.h >
       int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数sockfd即服务器的套接字描述符;addr参数指定了将socket绑定到的本地地址;addrlen则为所使用的地址结构的长度。示例代码如下:

	memset(&my_addr, 0, sizeof(struct sockaddr_in));
	my_addr.sin_family = AF_INET;
	my_addr.sin_port = htons(SERV_PORT);
	my_addr.sin_addr.s_addr = htonl(INADDR_ANY);

	if(bind(sockfd, (struct sockaddr *)&my_addr,
				sizeof(struct sockaddr_in)) == -1) {

		printf("bind error!\n");
		exit(1);
	}

注意在上述代码中,将IP地址设置为INADDR_ANY,这样就既适合单网卡的计算机又适合多网卡的计算机。

3.在套接字上监听

对于C/S模型来说,通常是客户端主动的对服务器端发送连接请求,服务器接收到请求后再具体进行处理。服务器只有调用了listen函数才能宣告自己可以接受客户端的连接请求,也就是说,服务器此时处于被动监听状态。listen函数的原型如下:

       #include < sys/socket.h >
       int listen(int sockfd, int backlog);

sockfd为服务器端的套接字描述符,backlog指定了该服务器所能连接客户端的最大数目。超过这个连接书目后,服务器将拒绝接受客户端的连接请求。示例代码如下:

        #define BACKLOG 10
	if(listen(sockfd, BACKLOG) == -1) {

		printf("listen error!\n");
		exit(1);
	}

4.接受连接

listen函数只是将服务器套接字设置为等待客户端连接请求的状态,真正接受客户端连接请求的是accept函数:

      #include < sys/socket.h >
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

accept函数中所使用的参数都在上面的API中有所描述。accept函数执行成功后将返回一个代表客户端的套接字描述符,服务器进程通过该套接字描述符就可以与客户端进行数据交换。

5.数据传输

由于socket适用多个数据传输协议,则不同的协议就对应不同的数据传输函数。与TCP协议对应的发送数据和接受数据的函数如下:

       #include < sys/socket.h >
       #include < sys/types.h >
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);

从这两个函数的原型可以看书,socket中的数据传输函数与普通文件的读写函数类似,只不过第一个参数需要传递套接字描述符;buf指定数据缓冲区;len为所传输数据的长度;flag一般取0。示例代码如下:

	while (1) {
		sin_size = sizeof(struct sockaddr_in);
		if ((client_fd = accept(sockfd, (struct sockaddr *)&remote_addr, &sin_size)) == -1) {
			printf("accept error!\n");
			continue;
		}
		/*
                 *进行相应的数据处理;
                 */
	}

如示例代码所示,通过while循环使得服务器对客户端进行持续监听。如果客户端有连接请求则新建一个代表客户端的套接字描述符,进而进行对客户端数据的接受和发送。

上述的几个函数属于网络编程中最基本的也是最关键的几个API,依次通过上述的方法就可以完成服务器端的程序的编写,具体的过程还可以参考下图:

正如上图所示,在处理完客户端的数据传输请求后,必须通过close函数关闭客户端的连接。

参考:

1.Linux C编程实战;童永清 著;人民邮电出版社;

exec族函数

14 3 月, 2011 by edsionte 1 comment »

通常我们使用了fork函数创建了一个子进程后,子进程会调用exec族函数执行另外一个程序。由于fork函数新建的子进程和父进程几乎一模一样(不过,后来引入了写时复制技术避免了这一点),因此需要使用exec函数使得子进程执行一个新的可执行程序。

我们之所以称exec为族函数是因为它有6种不同的形式,主要区别体现在参数上:

#include <unistd.h>;
extern char **environ;
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg,
             ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execve(const char *filename, char *const argv[],
              char *const envp[]);

总体来说,exec族函数的参数分为三类:文件路径,命令行参数,环境变量。不论那个exec函数,最终都是将上述三类参数传递给可执行程序中的main函数。这里有一个新的概念,即环境变量,它定义了用户的工作环境,包括用户的主目录,终端的类型,当前用户,当前目录等信息。事实上,main函数的完整形式应该是:

int main(int argc, char *argv[], char **envp);

通常我们并没有显示指定环境变量,这是因为main函数每次都默认使用系统自定义的全局变量environ。另外,通过在终端输入env命令,也可查看当前的所有环境变量信息。可以看到,main函数中的argv[]和envp与exec函数的参数有对应关系。

初次接触exec族函数,易被这6种不同的调用形式搞得混乱。下面我们将依次从上述三个参数的角度来区分这些函数的细微区别。

1.可执行文件的路径

以p结尾的exec函数可以直接传递可执行程序的文件名,此时这类函数会自动在PATH环境变量所指定的目录下查找这个可执行文件;非p结尾的exec函数则必须显示指定可执行程序的完整路径;

2.命令行参数

以execl开始的exec函数必须以列表的形式传递所有的命令行参数,并且最终以NULL作为命令行参数的结束;而以execv开始的exec函数则以字符串指针数组的形式传递所有命令行参数;

3.环境变量

以e结尾的exec函数必须显示指定环境变量;而非p结尾的exec函数则使用默认的环境变量,也就是main函数中的环境变量;

虽然exec函数有6种不同的调用形式,但是每个函数实现的功能都是一样的,即完成新的可执行程序的装入。下面的这个程序很好的演示了不同exec函数的使用方法:

#include < unistd.h >
#include < stdio.h >

int main()
{
	char *envp[]={
		"PATH = /tmp",
	        "USER = edsionte",
		NULL
	};

	char *argv_execv[] = {"echo", "excuted by execv", NULL};
	char *argv_execvp[] = {"echo", "executed by execvp", NULL};
	char *argv_execve[] = {"env", NULL};

	if (fork() == 0)
		if(execl("/bin/echo", "echo", "executed by execl", NULL) < 0)
			perror("Err on execl");
	if(fork() == 0)
		if(execlp("echo", "echo", "executed by execlp", NULL) < 0)
			perror("Err on execlp");
	if(fork() == 0)
		if(execle("/usr/bin/env", "env", NULL, envp) < 0)
			perror("Err on execle");
	if(fork() == 0)
		if(execv("/bin/echo", argv_execv) < 0)
			perror("Err on execv");
	if(fork() == 0)
		if(execvp("echo", argv_execvp) < 0)
			perror("Err on execvp");
	if(fork() == 0)
		if(execve("/usr/bin/env", argv_execve, envp) < 0)
			perror("Err on execve");

	printf("goodbye!\n");
	return 0;
}
//结果:
edsionte@edsionte-desktop:~/edsionte$ ./sys_process
goodbye!
edsionte@edsionte-desktop:~/edsionte$ executed by execl
excuted by execv
executed by execvp
executed by execlp
PATH = /tmp
USER = edsionte
PATH = /tmp
USER = edsionte

这个程序分别使用不同的exec函数调用了echo或env命令,并根据所传递的不同参数显示不同结果。该程序可能每次运行后的显示顺序都有所不同,这是进程的并发性所导致的。

值得注意的是,我们通常所说的命令行参数是以argv数组的第一个元素开始的,而第0号元素是可执行程序的名称。以第一个if语句为例,execl函数为/bin目录下的echo程序传递了两个参数:”echo”和”executed by execl”。由于我们通过execl函数已经装入了/bin/echo程序,因此在调用execl函数时,第一个参数”echo”可以替换成任意字符串,但是这并不代表就不需要传递该参数。

我们也可以让exec函数去执行我们自己编写的可执行程序。下面这个例子很好的演示了父进程和子进程的关系:

/*
 *Author: edsionte
 *Email:  edsionte@gmail.com
 *Time:   2011/03/14
*/

#include < stdio.h >
#include < unistd.h >
#include < stdlib.h >

int main()
{
	int pid;
	int val = 0;

	pid = fork();
	if (pid == 0) {
		//child proces;
		printf("[Child %d] I will be a new process!\n", getpid());
		if (execl("./sleeping","first arg", "20", NULL) == -1) {
			printf("execl error!\n");
			exit(1);
		}
		printf("[Child %d] I never go here!\n", getpid());
	} else if (pid > 0) {
		printf("[Parent %d] I am running!\n", getpid());
		/*
		wait(&val);
		printf("[Parent %d] All my child were leaving..\n", getpid());
		*/
	} else {
		printf("fork error!\n");
		exit(1);
	}

	printf("[process %d] I am leaving..\n", getpid());
	return 0;
}
//sleeping.c程序
#include < stdio.h >
#include < unistd.h >

int main(int argc, char** argv)
{
	int sec;

	sec = atoi(argv[1]);
	while (sec != 0) {
		sleep(1);
		printf("[New Child %d] I am running and my parent is %d\n", getpid(), getppid());
		sec--;
	}

	return 0;
}

该程序使用了与进程有关的四个最基本的系统调用函数:fork(),exec(),wait()和exit()。对于该程序的具体分析如下:

1.父进程通过fork函数创建子进程,然后父进程打印自己的pid;

2.子进程首先打印自己的pid,再通过execve函数装入可执行程序sleeping,并通过execve函数向sleeping传递了一个参数“20”;

3.在sleeping程序中,将主函数中传递的参数作为计数器,并依次循环睡眠20s。并且循环打印当前的sleeping进程的pid以及其父进程pid;

4.由于父进程先于子进程退出,则sleeping进程成为孤儿进程,因此在sleeping打印的父进程pid为1;如果去掉程序中的注释部分,则父进程等待子进程退出后再退出,那么sleeping所打印的父进程pid即为父进程的pid;

5.如果子进程执行exec函数成功,则主程序中最后一条语句就不会被子进程执行。说明子进程在执行了exec函数后就与父进程完全脱离。另外,在子进程执行过程中也可以通过ps命令查看当前的进程存在情况;

参考:

1.http://www.ibm.com/developerworks/cn/linux/kernel/syscall/part3/

2.Linux C编程实战;童永清 著;人民邮电出版社;

git与github快速入门

9 3 月, 2011 by edsionte 无评论 »

git是一个分布式的代码管理系统,可以对项目代码进行分布式的管理。不管项目组的各个成员身在何处,只要有网络环境,就可以一起对整个项目进行分布式开发。与你的项目有关的所有代码和文档等都将存放在git版本库中。本文以github托管库为例,简单说明git的使用方法。如果你需要建立一个git托管库,那么请从本文step1开始看起;如果你只需要了解如何使用git往git库中上传资料,那么直接看step2。

Setp 1:

1.安装git

使用下面的命令即可安装git:

sudo apt-get install git-core

2.创建项目的目录

为你的项目在本地创建一个专有目录,即本地的git版本库。这里我们以主目录下的xuptLinux目录为例。

3.创建github帐号

如果使用github对git版本库进行托管,就必须申请在github.com网站上申请帐号。具体申请办法很简单,此处不再赘述。

4.创建公共密钥

git通过ssh对远程资源库进行访问的,因此在使用之前必须创建一个用来验证身份的密钥。具体方法如下:

edsionte@edsionte-desktop:~$ ssh-keygen -t rsa -C "edsionte@163.com"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/edsionte/.ssh/id_rsa):

如果使用默认的文件来存储密钥,那么就直接按回车;否则,输入你想要保存密钥的文件名。接下来的输入信息直接按回车就可以得到默认配置。

Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/edsionte/.ssh/id_rsa.
Your public key has been saved in /home/edsionte/.ssh/id_rsa.pub.
The key fingerprint is:
6b:3a:f6:b4:29:85:fd:c8:96:ea:cc:08:6a:72:21:76 edsionte@163.com
The key's randomart image is:
+--[ RSA 2048]----+
|                 |
|                 |
|                 |
|                 |
|       oS        |
|...E  . o.       |
|..o.   o++       |
|.o.. +++=o.      |
|+.  .o**+        |
+-----------------+

5.上传密钥

将保存在id_rsa.pub文件中的密钥上传到github网站中,具体为:Account Settings> SSH Public Keys>Add another public key。

6.测试

使用下面的命令测试本地库到github上的服务器是否连接成功:

edsionte@edsionte-desktop:~$ ssh git@github.com
PTY allocation request failed on channel 0
Hi edsionte! You've successfully authenticated, but GitHub does not provide shell access.
Connection to github.com closed.

如果出现下面的提示,那么说名你已经验证成功了。

Step 2:

接下来将简单介绍如何向远程的git库提交文件。仍然以上面的xuptLinux目录为例,我们将向远程的git版本库中提交一个文件README。

1.建立文件

首先在本地的版本库中建立README文件。

edsionte@edsionte-desktop:~/xuptLinux$ touch README
edsionte@edsionte-desktop:~/xuptLinux$ echo "Hi, this is xuptLinux's wonderland" >> README

2.提交跟踪信息

使用git add命令可以将指定的文件加入到git库的文件索引当中,它更新了当前版本库所要跟踪文件的索引信息。换句话说,它并没有将文件的内容提交到版本库中。

edsionte@edsionte-desktop:~/xuptLinux$ git add README

3.提交文件内容

接下来就应该真正的将文件内容加入到版本库的跟踪信息中了。使用git commit命令即可完成。’v 0.2’是对本次提交的间断注释说明。

edsionte@edsionte-desktop:~/xuptLinux$ git commit -m 'v0.2'
[master 4b6c431] v0.2
 1 files changed, 1 insertions(+), 1 deletions(-)

4.提交到远程库中

上面的提交只是将文件提交到了本地的版本库中,对于分布式项目开发并没有多大意义。接下来,我们需要将README文件提交到了远程的库中。

如果你是第一次使用,必须先使用下面的命令连接远程的github。

edsionte@edsionte-desktop:~/xuptLinux$ git remote add origin git@github.com:edsionte/xuptLinux.git

其中,git@github.com:edsionte/xuptLinux.git是我们在github中注册后所产生的远程登录地址。

接下来就可以将刚才提交的文件添加到远程库中。

edsionte@edsionte-desktop:~/xuptLinux$ git push -u origin master

成功添加后,你可以在github网站上看到刚才提交的文件。

参考:

1.http://help.github.com/linux-set-up-git/

windows 7 ultimate product key

windows 7 ultimate product key

winrar download free

winrar download free

winzip registration code

winzip registration code

winzip free download

winzip free download

winzip activation code

winzip activation code

windows 7 key generator

windows 7 key generator

winzip freeware

winzip freeware

winzip free download full version

winzip free download full version

free winrar download

free winrar download

free winrar

free winrar

windows 7 crack

windows 7 crack

windows xp product key

windows xp product key

windows 7 activation crack

windows7 activation crack

free winzip

free winzip

winrar free download

winrar free download

winrar free

winrar free

download winrar free

download winrar free

windows 7 product key

windows 7 product key