一、安装
1
、
linux
安装好后已经装好了
cvs
,可用
rpm -qa|grep cvs
查看。
如果没有安装你可以在
Redhat
第
2
张光盘上找到,另外你也可以在网上下载到最新的
rpm
包。很容易找,其实不存在什么
linux
版本。
2
、创建
cvs
使用的目录:
mkdir /home/mycvstest
3
、创建
cvs
使用的用户和组
groupadd cvs
useradd -g cvs -G cvs –d /home/mycvstest cvs
passwd cvs
4
、修改
mycvstest
的用户:
chown -R cvs:cvs /home/mycvstest
chmod 755 /home/mycvstest
5
、切换用户:
su cvs
6
、创建源码仓库:
mkdir /home/mycvstest/drcls drclgw
7
、初始化源码仓库:
cvs -d /home/mycvstest/drcls init
cvs -d /home/mycvstest/drclgw init
chmod 755 /home/mycvstest/drcls drclgw
初始化后可以在目录下见到新增加的
CVSROOT
目录,
cvs
所有的控制信息都在这个目录里。
8
、退回到
root
用户,建立
CVS
服务启动文件,
CVS
使用
xinetd
方式启动。
vi /etc/xinetd.d/cvspserver
service cvspserver
{
disable = no
flags = REUSE
socket_type = stream
wait = no
user = root
server= /usr/bin/cvs
server_args= -f --allow-root=/home/mycvstest/drcls
--allow-root=/home/mycvstest/drclgw pserver
log_on_failure += USERID
}
注:红色的参数很重要,由于
xinetd
的
server_args
长度限制
,
当你想运行很多的单个仓库的时候
(
就是有很多个模块时,比如
drcrgw),
但在
server_args
中无法输入这么多单个的仓库,
可以采取以下方式解决(现在实验室
90
上就用的这种方式):
#> vi cvspserver
service cvspserver
{
disable = no
socket_type = stream
wait = no
user = root
server = /usr/bin/cvs.run
server_args =""
}
编写
cvs.run
脚本
vi /cvsroot/cvs.run
#!/bin/bash
/usr/bin/cvs -f
--allow-root=/home/mycvstest/drcls
--allow-root= /home/mycvstest/drclgw
pserver
chmod a+x cvs.run
9
、加入
cvs
服务:
#>vi /etc/services
cvspserver 2401/tcp #pserver cvs service
cvspserver 2401/udp #pserver cvs service
10
、启动
cvs
服务:
#> service xinetd restart
11
、检查
cvspserver
服务是否已经启动:
#> netstat -l |grep cvspserver
应该有如下结果:
tcp 0 0 *:cvspserver *:* LISTEN
二、使用和管理
1
、创建
cvs
用户:
cd /home/mycvstest/drcls/CVSROOT
htpasswd –c passwd zhangcan (htpasswd
命令要安装
apache
后才有,此命令创建一个
passwd
文件,里面内容为新建
cvs
用户的用户名和密码
)
vi passwd (
在新建的用户后手工加入用户属于的组
cvs)
例如:
zhangcan:dgeagasdgasdr:cvs
蓝色字符串表示加密后的密码。
2
、赋予用户读写权限
手工在
CVSROOT
目录中建立
readers
和
writers
文件。
Readers
文件中的用户只有读权限,
writers
中的用户具有读写权限,一行只写一个用户名。
3
、登录
cvs
在客户机
Linux
下面用命令:
export CVSROOT=:pserver:zhangcan@192.168.100.197:/home/mycvstest/drcls
cvs login
4
、向源码仓库中导入源码
首先进入你本机上安装源码的目录,然后使用以下命令:
cvs import –m “this is my soure code” drcls NISEC test start
-m
表示在
cvs
历史文件显示的消息,
drclst
为你指定的源码目录名,
NISEC
为供应商标签,
test
为发行标签(这两项可以不要),
start
必须要。
5
、
checkout
出需要修改的源代码
cvs co drcls
在你的当前目录下会发现多了一个
drcls
目录,要修改的源码都在里面
co
为
checkout
的简写
6
、提交修改
假设刚才对
readme
文件进行了修改,现在提交
cvs commit –m “modify a few of wrong words” readme
命令执行后会提示版本已经改为
1.2
。
7
、
checkout
出以前的版本
如果想检出以前的版本可以用以下命令:
cvs co –r 1.1 drcls/readme
或者
cvs –D yesterday drcls/readme
8
、删除文件
若想从源码仓库中删除
readme
文件,首先应把客户机工作目录中的
readme
文件删除,然后使用
cvs
的删除命令,最后提交删除,流程如下:
rm readme
cvs rm readme
cvs commit –m “
现在不需要这个
readme
文件
” readme
如果系统类似错误:
cannot remove file `INSTALL' which has a numeric sticky tag of `1.1'
可以用命令
cvs update –A readme
后再删除文件。
以上为火山哥提供的,以下是我添加的部份:
1.在reader和writes文件中添加使用用户时要注意,当在reader中添加了某一只读用户后就不要在writers中添加此用户,如果在两个文件中都添加同一用户的话,在使用cvs时,CVS服务器会把此用户当做只读用户看待,当使用一些命令如import时会产生权限问题,以下是linux关于此问题的说明:
/* This command has the potential to modify the repository, so
* we check if the user have permission to do that.
*
* (Only relevant for remote users -- local users can do
* whatever normal Unix file permissions allow them to do.)
*
* The decision method:
*
* If $CVSROOT/CVSADMROOT_READERS exists and user is listed
* in it, then read-only access for user.
*
* Or if $CVSROOT/CVSADMROOT_WRITERS exists and user NOT
* listed in it, then also read-only access for user.
*
* Else read-write access for user.
*/
刚学习ICE时,在VC++6.0下配置ICE工程简直是个恶梦,死活配不来DEBUG版本的,开发全在RELEASE版本下.很痛苦.最近研究了下,成功的配置成功了,方法如下:
1.Project Settings >> C/C++(Tab) >> Category:Code Generation >> User run-time library:Debug Multithreaded DLL
2.Project Settings >> C/C++(Tab) >> Category:Preprocessor >> Additional include directories:(在此项中填入一个点".",表示根目录)
3.Project Settings >> C/C++(Tab) >> Category:C++ Language >> 勾选"Enable Run-Time Type Information(RTTI)"项
4.Project Settings >> Link(Tab) >> Category:General>>在"Object/library modules:"框中的未首加入两个包"iced.lib"和"iceutild.lib"
这样,整个ICE工程的DEBUG版本就建立完成了.
以上方法在 VC++6.0 SP6 和 ICE3.0.0 下通过
对动态链接库的概念其实还很模糊,自己的理解是:
把一些常用的代码,如函数,类等,编译成一个"包"即DLL(WINDOWS下)或者SO(LINUX下)文件,
然后供其它程序使用时直接调用里面封闭的函数即可,实现的代码的重用,也节省了
硬盘空间(这点可能是次要的吧).在WIDOWS下利用VC++可方便的生成DLL,在LINUX下则需要通过
各种编译命令来实现,对于像我这种菜鸟级程序员来说是个不小的挑战.
下面用个简单的例子来说明生成一个.SO文件和如何使用它:
1.我这有几个文件:
ConfigMap.cpp ConfigMap.h (读配置文件类)GetWinState.cpp GetWinState.h(ICE接口文件,由SLICE生成) GetWinSysState.cpp GetWinSysState.h (远程接口实现文件)
SocDbInfo.cpp(封装的一个类,用于调用ICE接口实现相关操作)
说明:此程序是为了获得远程主机(WINDOWS)上一些系统信息,比如:CPU占用率,硬盘使用情况,数据
库连接状态,内存使用情况等.
我想利用这些文件生成动态链接库.SO,主要是调用SocDbInfo.cpp里的类.然后可以拿到任意一个系统中(LINUX)去用.
2.开始编译:$c++ -I. -I$ICE_HOME/include -c *.cpp
编译后生成连接文件(我一直这样叫,可能不对哈),即以.O结尾的
3.生成动态链接库:$c++ -shared -o libMyApp.so *.o -L$ICE_HOME/lib -lIce -lIceUtil
这样就生成了libMyApp.so文件,即我们所要的
4.使用动态链接库:
新建两个文件Demo.cpp Demo.h(使用libMyApp.so提供的一些函数),
在Demo.h中声明了libMyApp.so中提供的函数和结构体,
Demo.h:代码如下:
struct MemoryInf
{
int TotalMem;//×ÜÄÚ´æ´óС
int ValidMem;//¿ÉʹÓÃÄÚ´æ´óС
int VirtualMem;//ÐéÄâÄÚ´æ´óС
};
struct DiskInf
{
int TotalSpace;//Ó²ÅÌ´óС
int FreeSpace;//Ê£Óà¿Õ¼ä´óС
};
struct DbInf
{
int DbStat;//Êý¾Ý¿â״̬:"0"±íʾÊý¾Ý¿â´¦ÔڹرÕ״̬,"1"±íʾÊý¾Ý¿âÕý³£´ò¿ª,"2"±íʾÊý¾Ý¿â´¦ÔÚ¹ÒÆð״̬
int DbConnNum;//Êý¾Ý¿âÁ¬½ÓÊý
};
bool Inital( char *ResHostIP);//³õʼ»¯Í¨ÐÅÆ÷
bool DesIceCom();//Ïú»ÙICEͨÐÅÆ÷
MemoryInf GetMemInf();//µÃµ½ÄÚ´æÐÅÏ¢
int GetCpuInf();//µÃµ½CPUÕ¼ÓÃÂÊ
DbInf GetDbStat();//µÃµ½Êý¾Ý¿âÐÅÏ¢
DiskInf GetDiskStat();//µÃµ½Ó²ÅÌÐÅÏ¢
注:乱码是由于我的LINUX下不支持中文哈,是注释不用管它
Demo.cpp:代码如下:
#include <string.h>
#include <iostream>
#include "Demo.h"
int main( int argc , char* argv[])
{
MemoryInf mymem;
DiskInf mydisk;
DbInf mydb;
Inital( argv[1]);
mydisk = GetDiskStat();
mymem = GetMemInf();
mydb = GetDbStat();
printf("disk total space:%d\n",mydisk.TotalSpace);
printf("disk FreeSpace space:%d\n",mydisk.FreeSpace);
printf("Memory TotalMem:%d\n",mymem.TotalMem);
printf("ValidMem:%d\n",mymem.ValidMem);
printf("VirtualMem:%d\n",mymem.VirtualMem);
printf("DbConnNum:%d\n",mydb.DbConnNum);
printf("DbStat:%d\n",mydb.DbStat);
printf("cpu:%d\n",GetCpuInf());
DesIceCom();
return 1;
}
5.编译文件生成可执行程序:
用以下命令:
$c++ -lMyApp -o Demo Demo.cpp
说明:-lMyApp参数表示,用动态链接库libMyApp.so一起进行编译,对了libMyApp.so最好放在/usr/lib目录下哈
如不出意外刚会生成名为Demo的可执行文件
#include <Ice/Ice.h>
#include <iostream>
#include <GetWinSysState.h>
#include <Winbase.h>
#include <conio.h>
#include <stdio.h>
#include <fstream>
#include <iostream>
#include <string>
#include <direct.h>
#define SystemBasicInformation 0
#define SystemPerformanceInformation 2
#define SystemTimeInformation 3
#define Li2Double(x) ((double)((x).HighPart) * 4.294967296E9 + (double)((x).LowPart))
//ICE预编译语句
#ifdef _DEBUG
#pragma comment(lib, "iced.lib")
#pragma comment(lib, "iceutild.lib")
#else
#pragma comment(lib, "ice.lib")
#pragma comment(lib, "iceutil.lib")
#endif
typedef struct
{
DWORD dwUnknown1;
ULONG uKeMaximumIncrement;
ULONG uPageSize;
ULONG uMmNumberOfPhysicalPages;
ULONG uMmLowestPhysicalPage;
ULONG uMmHighestPhysicalPage;
ULONG uAllocationGranularity;
PVOID pLowestUserAddress;
PVOID pMmHighestUserAddress;
ULONG uKeActiveProcessors;
BYTE bKeNumberProcessors;
BYTE bUnknown2;
WORD wUnknown3;
} SYSTEM_BASIC_INFORMATION;
typedef struct
{
LARGE_INTEGER liIdleTime;
DWORD dwSpare[76];
} SYSTEM_PERFORMANCE_INFORMATION;
typedef struct
{
LARGE_INTEGER liKeBootTime;
LARGE_INTEGER liKeSystemTime;
LARGE_INTEGER liExpTimeZoneBias;
ULONG uCurrentTimeZoneId;
DWORD dwReserved;
} SYSTEM_TIME_INFORMATION;
// ntdll!NtQuerySystemInformation (NT specific!)
//
// The function copies the system information of the
// specified type into a buffer
//
// NTSYSAPI
// NTSTATUS
// NTAPI
// NtQuerySystemInformation(
// IN UINT SystemInformationClass, // information type
// OUT PVOID SystemInformation, // pointer to buffer
// IN ULONG SystemInformationLength, // buffer size in bytes
// OUT PULONG ReturnLength OPTIONAL // pointer to a 32-bit
// // variable that receives
// // the number of bytes
// // written to the buffer
// );
typedef LONG (WINAPI *PROCNTQSI)(UINT,PVOID,ULONG,PULONG);
PROCNTQSI NtQuerySystemInformation;
/*
功能:得到CPU使用状态
参数:无
返回值:内存占用率
作者:牵牛散步
*/
int GetCpuStat()
{
SYSTEM_PERFORMANCE_INFORMATION SysPerfInfo;
SYSTEM_TIME_INFORMATION SysTimeInfo;
SYSTEM_BASIC_INFORMATION SysBaseInfo;
double dbIdleTime;
double dbSystemTime;
LONG status;
LARGE_INTEGER liOldIdleTime = {0,0};
LARGE_INTEGER liOldSystemTime = {0,0};
int UsageCpu = 0;
NtQuerySystemInformation = (PROCNTQSI)GetProcAddress(
GetModuleHandle("ntdll"),
"NtQuerySystemInformation"
);
if (!NtQuerySystemInformation)
return 0;
status = NtQuerySystemInformation(SystemBasicInformation,&SysBaseInfo,sizeof(SysBaseInfo),NULL);
if (status != NO_ERROR)
return 0;
for( int t = 0 ; t < 2 ; t++ )
{
status = NtQuerySystemInformation(SystemTimeInformation,&SysTimeInfo,sizeof(SysTimeInfo),0);
if (status!=NO_ERROR)
return 0;
status = NtQuerySystemInformation(SystemPerformanceInformation,&SysPerfInfo,sizeof(SysPerfInfo),NULL);
if (status != NO_ERROR)
return 0;
if (liOldIdleTime.QuadPart != 0)
{
dbIdleTime = Li2Double(SysPerfInfo.liIdleTime) - Li2Double(liOldIdleTime);
dbSystemTime = Li2Double(SysTimeInfo.liKeSystemTime) - Li2Double(liOldSystemTime);
dbIdleTime = dbIdleTime / dbSystemTime;
dbIdleTime = 100.0 - dbIdleTime * 100.0 / (double)SysBaseInfo.bKeNumberProcessors + 0.5;
UsageCpu = (int)dbIdleTime;
}
// store new CPU's idle and system time
liOldIdleTime = SysPerfInfo.liIdleTime;
liOldSystemTime = SysTimeInfo.liKeSystemTime;
// wait one second
Sleep(500);
}
return UsageCpu;
}
/*
功能:得到内存使用状态
参数:无
返回值:内存信息结构体[包括总的物理内存,还可使用内存,虚拟内存,单位为K]
作者:牵牛散步
*/
MemoryInf MemorySta()
{
MemoryInf tmp;//在ICE SLICE里定义的信息结构体
MEMORYSTATUS memStatus;
GlobalMemoryStatus(&memStatus);
DWORD tom=memStatus.dwTotalPhys/1024;
DWORD mem=memStatus.dwAvailPhys/1024;
DWORD res=memStatus.dwAvailVirtual/1024;
tmp.TotalMem = (int)tom;
tmp.ValidMem = (int)mem;
tmp.VirtualMem = (int)res;
return tmp;
}
/*
功能:得到硬盘使用情况
参数:无
返回值:硬盘信息结构体
作者:牵牛散步
*/
DiskInf GetDiskSta()
{
ULARGE_INTEGER FreeAvailable,TotalNum,TotalFreeNum;
char p[3];
bool b_flag;
DiskInf tmp;//ICE SLICE里定义的硬盘信息结构体
tmp.TotalSpace = 0;
tmp.FreeSpace = 0;
//得到有效的驱动器名,即盘符
for( int drive = 1; drive <= 26; drive++ )
{
if( !_chdrive( drive ) )
{
memset( p , 0 , sizeof(p));
p[0] = drive + 'A' - 1;
p[1] = ':';
p[2] = '\0';
b_flag = GetDiskFreeSpaceEx( p ,&FreeAvailable,&TotalNum,&TotalFreeNum );
if( b_flag )
{
tmp.TotalSpace += (int)(TotalNum.QuadPart/(1024*1024));
tmp.FreeSpace += (int)(FreeAvailable.QuadPart/(1024*1024));
}
}
}
return tmp;
}
int main()
{
return 1;
}
文 CSDN 孟岩 转载自2005年第11期《程序员》杂志
不知不觉,C++迎来了自己20岁的生日。20年来,C++从一个实验室语言成长为软件产业一流主导语言,在实际应用中取得了巨大的成功,同时也催生了大量为技术人员耳熟能详的经典技术著作,比如Bjarne Stroustrup的《TC++PL》和《D&E》,Stan Lippman的《C++ Primer》,Scott Meyers的《Effective C++》,GoF的《Design Patterns》,Andy Koenig的《Ruminations on C++》,Herb Sutter的《Exceptional C++》,Andrei Alexandrescu的《Modern C++ Design》,Addison Wesley的“C++ in Depth”系列等。C++领域的一些经典图书不但对于C++语言的发展起到了巨大的推动作用,而且对于其他相关技术领域也起到了指导和促进作用。例如Scott Meyers的“Effective系列”,开辟了技术图书写作的新风格,而“Design Patterns”的影响,更是远远超出C++的范畴。这些经典的好书,已经成为C++辉煌历史的一部分而被人们铭记。
20年后的今天,软件产业的规模和环境已经发生了深刻的变化。如今企业级应用整合与开发的任务主要由Java、C#、Visual Basic以及各种新型动态语言来承担,而C++的应用场合也有所收缩,不再是像1990年代中期那样从上到下包打天下,而是呈现出鲜明的应用领域特色。相应的,近期的C++技术图书也更加注重在特色领域的发挥。下面我们分别从几个角度来了解近期C++图书的热点,并且展望未来一年中C++技术图书中值得注意的选题。
经典著作全面翻新
这一两年,一大批经典技术图书都经历了一次更新换代,C++经典图书自然也不例外。第一个要说的就是Scott Meyers的《Effective C++》。这本书在1991年推出第一版,1998年推出第二版,在C++技术的传播与教育方面居功至伟。包括我在内的很多C++开发者都是通过阅读这本书而寻得升堂入室的门径。今年,《Effective C++》推出了第三版。这一版决不是第二版的简单修订,而是根据八年来C++所发生的巨大变化而进行的一次全面改写,几乎是一本全新的书。其内容涵盖了旧版的精髓,也体现了诸如Boost库、TR1标准等C++领域最新成果。如果说这本书的内容发生了重大的变化,那么可以说不变的是这本书的地位——它仍然是每个严肃的C++开发者都应当反复阅读领悟的重要作品。
另一本翻新的著作是Stan Lippman的《C++ Primer》第四版。这本书的历史地位无需赘言,迄今为止仍然是对C++介绍最全面的一本著作。第四版进行了大幅度的删修,篇幅减少近300页,而内容也大幅修改,以反映C++近年来的变化。对于希望系统学习C++的新手,或者希望拥有一本手册以备查的老手来说,这本书的地位是不可取代的。
有传言说GoF打算翻新“Design Patterns”。该书第一版于1995年初版,此后十年,设计模式领域的理论探讨和实践基本上仍然围绕这本书中的观点和内容进行,少有突破。已经有不少开发者对于这种情况表示了不满,认为初版中的一些观点已经过时,甚至对现在的一些技术应用构成思想上的羁绊。但是此书实在声名太盛,真正的突破恐怕只能由GoF自己完成,因此本书新版如果推出,必将是技术界内的一桩大事。不过传言亦称,GoF对于新版“Design Patterns”是否继续使用C++作为示范语言存在分歧。不管怎样,相信广大读者会对这本书抱有高度的关注。
新锐佳作剑走偏锋
近年来,一批C++技术新锐崛起,给我们带来了一批令人耳目一新的C++好书。在技术上,这批图书偏重于C++模板风格的巧妙运用,富于智趣而对实践的关注略显不足。这类图书以2001年Andrei Alexandrescu的《Modern C++ Design》为开端,之后又有《Boost Graphic Library》,《C++ Template Metap- rogramming》等。这些书一度以其新颖的思想和奇妙的技巧吸引了大家的目光,但由于缺乏实践的支撑,逐渐褪去光环。这两年,这类题材逐渐缩减。即使像Andrei Alexandrescu那样的模板技术奇才,最近与Herb Sutter合作的《C++ Coding Standard》也并没有过多地炫耀模板技巧,而是中规中矩地对C++编码的经验做了贴近实践的总结。这本书与Steve McConnell的经典著作《Code Complete II》相配合,应成为每一位C++开发者必读的基本著作,将帮助读者奠定坚实的编码和微观设计技术基础。
Boost是C++社群尽人皆知的“准标准库”,其中大量的组件已经基本成熟并可供应用。最近Bjorn Karlsson撰写的《Beyond C++ Standard Library: An Introduction to Boost》是市面上第一本全面介绍Boost的著作。对于那些勇于吃螃蟹的C++先锋开发者来说,这本书无疑是值得一读的。不过在C++社群,对Boost库还存在争议。大部分C++实践者比较保守,这本书对他们的吸引力恐怕将打折扣。
Stephen Dewhurst是近年来比较活跃的新锐作家,他的两本书《C++ Gotchas》和《C++ Common Knowledge》,单独来看都是值得一读的好书,可惜现在不是1995年,而是2005年,Scott Meyers、Herb Sutter煌煌巨著已然危急九五,Dewhurst只好自叹施手略慢。不过我们希望他能够再接再厉,找到创新点,突破前人。
相比之下,中文版即将问世的《Imperfect C++》就比较有新意,很值得一读,以至于Bjarne Stroustrup都相当赞赏。这位C++之父表示,如果不是因为该书太厚,他一定会将其纳入著名的“C++ in Depth”系列。这在事实上肯定了本书的高质量。我个人认为,这本书是一年来出版的最值得精读的C++著作,其中对于不少实践中经常遇到的难题进行了深入的分析,给出了实实在在的解决方案。作者Matt Wilson凭借此书以及其系列模板程序库崛起为C++社群中引人注目的新星,实在可喜可贺。
关键领域期待佳作
未来C++将主要在系统级复杂应用程序、高性能、实时中间件及嵌入式领域施展,同时,随着多核CPU的的普及和网络安全重要性的空前提升,在并发程序设计和安全程序设计方面,C++也将获得新的应用空间。因此,在这些具体领域的C++著作更值得关注。
1996年,John Lakos出版了《Large Scale C++》一书,该书与 “C++ In Depth”系列中的《Applied C++》一样,是C++著作中极少数实战派佳作,其中字字句句都来自于作者丰富的实际项目经验,对于一线的C++开发者来说是特别值得咀嚼体味的好书。可惜这本书在国内一直没有得到应有的重视,这可能跟本书中译本出版时间过晚,且翻译质量不佳有关。好消息是,John Lakos即将在2006年推出其新作《Scalable C++》。我本人将此书视为未来一年中C++领域最令人期待的作品。这一方面是出于我对于作者的高度信任,另一方面是由于该书题材的极端重要性。该书副标题为“基于组件的软件开发”,而“组件化”这一主题,是C++十几年来的一块心病。在未来,无论C++应用在何种场合,“组件化”是必不可少的基本要求。而COM技术虽然在组件化方面比较成熟,但是不具有可移植性,而且对于无须跨语言的C++开发项目来说过于复杂。因此,C++社群需要自己探讨经济适用的组件化实用方案。John Lakos本人从事大型复杂应用软件开发多年,在这方面的经验无人能敌,由他来对这个主题进行深入剖析,并且给出实际解决方案,毫无疑问是再合适不过的了。虽然这本书还在写作过程中,但是我们有理由对其抱有充分信心。在此我也呼吁本书中文版未来的出版者认真对待此书的翻译制作,不要重蹈《Large Scale C++》的覆辙。
说到COM,自从.NET推出,COM的书几乎一夜之间绝了种,几乎无人再勇于炒这碗冷饭。但这次出版界的茶实在凉得快了些,事实上直到今天,COM开发仍然是Windows平台上应用开发的一个重要方向。特别是在前两年微软Windows核心向.NET全面转型的左倾冒险主义的尝试以失败告终后,在可见的未来,Windows操作系统将继续构建在C/C++和COM的基础之上——这就确保了COM在Windows平台上的重要地位将至少延续到2011年之后。因此,COM、ATL和WTL开发技术在未来几年都还是颇具意义的出版题材。特别是近年来ATL/WTL的发展之快,相比之下,技术出版在这方面出现了一个空白点。此外,开源的Mozilla项目提供了一个可移植到所有主流平台的COM实现,这对于C++开发者来说是很具有吸引力的,却一直没有出版资源的关注,令人遗憾。
C++近年来的一个应用热点是复杂网络应用的开发,ACE在这方面已经成为越来越流行的选择,而ICE作为目前可用的最先进的高性能中间件产品,崛起的势头很猛。这两方面目前都有一些好书,特别是最近出版的《ACE Programmer’s Guide》,对于ACE编程入门很有好处。而ICE 1.3版的手册早已由马维达先生译成中文,可在网上自由获得。不过坦率地说,这方面的图书还远远不够,ACE原作者Doug Schmidt所著的两卷本《C++ Network Programming》可读性和实用性不足,而ICE手册深度和广度都显不够,我们期望这方面能有更好的著作出现。
随着多核CPU的普及,并发程序设计将成为C++技术上的一个新热点。这方面目前的好书几乎没有,不知道这个巨大的空白将由何方神圣来填补。
同样,网络安全重要性的空前提升对C++开发提出了很多新的具体要求,很多C++老手面临一个“再教育”的问题。这方面Microsoft Press的《Writing Safe Code》,O’Reilly的《Secure Programming Cookbook for C and C++》,以及最近Pearson出版的《Secure Coding in C and C++》都是不错的参考。不过我发现目前C++开发者并未普遍重视这个问题。也许管理层还需要更加“血淋淋的教训”来刺激一下,才会有革新的动力。
在嵌入式方面,由于应用复杂度的逐渐提升和Symbian OS/Windows CE等面向消费的高级嵌入式操作系统的广泛应用,给C++提供了一个广阔的发挥空间。可惜在这方面,真正堪称经典的好书还是凤毛麟角,看来还需要时间和经验的积淀。不过有一本书特别值得一提,北航出版社去年引进的《嵌入式系统的微模块化设计》被国际嵌入式开发领域公推为数年来年度最重要的嵌入式软件开发技术著作,其中含有一些意义深远的创新思想,非嵌入式开发者也有必要了解此书的大致思想。
总结
程序设计语言数以千计,能够广为流传的不过几十种,而能够风光20年的更是屈指可数。Fortran已经问世50年,仍然是科学计算首选,C语言辉煌30年,至今老当益壮。C++顺利地度过了自己的20岁生日,相信属于它的日子还很长很长。作为C++开发者,我们也希望看到优秀的C++技术图书不断涌现。历史证明,C++领域内的技术创新,不但对于C++开发具有重大意义,而且对于整个软件开发技术都具有重大意义。我们有理由相信,在下一个十年里,以高水平C++技术专家和作家为代表的C++技术社群能够继续为软件技术做出突出的贡献。
|
在所有的预处理指令中,#Pragma 指令可能是最复杂的了,它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C ++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。 其格式一般为: #Pragma Para 其中Para 为参数,下面来看一些常用的参数。
(1)message 参数。 Message 参数是我最喜欢的一个参数,它能够在编译信息输出窗 口中输出相应的信息,这对于源代码信息的控制是非常重要的。其使用方法为: #Pragma message(“消息文本”) 当编译器遇到这条指令时就在编译输出窗口中将消息文本打印出来。 当我们在程序中定义了许多宏来控制源代码版本的时候,我们自己有可能都会忘记有没有正确的设置这些宏,此时我们可以用这条指令在编译的时候就进行检查。假设我们希望判断自己有没有在源代码的什么地方定义了_X86这个宏可以用下面的方法 #ifdef _X86 #Pragma message(“_X86 macro activated!”) #endif 当我们定义了_X86这个宏以后,应用程序在编译时就会在编译输出窗口里显示“_ X86 macro activated!”。我们就不会因为不记得自己定义的一些特定的宏而抓耳挠腮了 。
(2)另一个使用得比较多的pragma参数是code_seg。格式如: #pragma code_seg( ["section-name"[,"section-class"] ] ) 它能够设置程序中函数代码存放的代码段,当我们开发驱动程序的时候就会使用到它。
(3)#pragma once (比较常用) 只要在头文件的最开始加入这条指令就能够保证头文件被编译一次,这条指令实际上在VC6中就已经有了,但是考虑到兼容性并没有太多的使用它。
(4)#pragma hdrstop表示预编译头文件到此为止,后面的头文件不进行预编译。BCB可以预编译头文件以加快链接的速度,但如果所有头文件都进行预编译又可能占太多磁盘空间,所以使用这个选项排除一些头文件。 有时单元之间有依赖关系,比如单元A依赖单元B,所以单元B要先于单元A编译。你可以用#pragma startup指定编译优先级,如果使用了#pragma package(smart_init) ,BCB就会根据优先级的大小先后编译。
(5)#pragma resource "*.dfm"表示把*.dfm文件中的资源加入工程。*.dfm中包括窗体 外观的定义。
(6)#pragma warning( disable : 4507 34; once : 4385; error : 164 ) 等价于: #pragma warning(disable:4507 34) // 不显示4507和34号警告信息 #pragma warning(once:4385) // 4385号警告信息仅报告一次 #pragma warning(error:164) // 把164号警告信息作为一个错误。 同时这个pragma warning 也支持如下格式: #pragma warning( push [ ,n ] ) #pragma warning( pop ) 这里n代表一个警告等级(1---4)。 #pragma warning( push )保存所有警告信息的现有的警告状态。 #pragma warning( push, n)保存所有警告信息的现有的警告状态,并且把全局警告 等级设定为n。 #pragma warning( pop )向栈中弹出最后一个警告信息,在入栈和出栈之间所作的 一切改动取消。例如: #pragma warning( push ) #pragma warning( disable : 4705 ) #pragma warning( disable : 4706 ) #pragma warning( disable : 4707 ) //....... #pragma warning( pop ) 在这段代码的最后,重新保存所有的警告信息(包括4705,4706和4707)。 (7)pragma comment(...) 该指令将一个注释记录放入一个对象文件或可执行文件中。 常用的lib关键字,可以帮我们连入一个库文件。 (8)用pragma导出dll中的函数
传统的到出 DLL 函数的方法是使用模块定义文件 (.def),Visual C++ 提供了更简洁方便的方法,那就是“__declspec()”关键字后面跟“dllexport”,告诉连接去要导出这个函数,例如:
__declspec(dllexport) int __stdcall MyExportFunction(int iTest);
把“__declspec(dllexport)”放在函数声明的最前面,连接生成的 DLL 就会导出函数“_MyExportFunction@4”。
上面的导出函数的名称也许不是我的希望的,我们希望导出的是原版的“MyExportFunction”。还好,VC 提供了一个预处理指示符“#pragma”来指定连接选项 (不仅仅是这一个功能,还有很多指示功能) ,如下:
#pragma comment(linker,"/EXPORT:MyExportFunction=_MyExportFunction@4")
这下就天如人愿了:)。如果你想指定导出的顺序,或者只将函数导出为序号,没有 Entryname,这个预处理指示符 (确切地说是连接器) 都能够实现,看看 MSDN 的语法说明:
/EXPORT:entryname[,@ordinal[,NONAME]][,DATA]
@ordinal 指定顺序;NONAME 指定只将函数导出为序号;DATA 关键字指定导出项为数据项。
每个编译程序可以用#pragma指令激活或终止该编译程序支持的一些编译功能。例如,对循环优化功能: #pragma loop_opt(on) // 激活 #pragma loop_opt(off) // 终止 有时,程序中会有些函数会使编译器发出你熟知而想忽略的警告,如“Parameter xxx is never used in function xxx”,可以这样: #pragma warn —100 // Turn off the warning message for warning #100 int insert_record(REC *r) { /* function body */ } #pragma warn +100 // Turn the warning message for warning #100 back on 函数会产生一条有唯一特征码100的警告信息,如此可暂时终止该警告。 每个编译器对#pragma的实现不同,在一个编译器中有效在别的编译器中几乎无效。可从编译器的文档中查看。
|
基于TCP/IP的多线程通信及其在远程监控系统中的应用 |
|
|
传统的应用程序都是单线程的,即在程序运行期间,由单个线程独占CPU的控制权,负责执行所有任务。在这种情况下,程序在执行一些比较费时的任务时,就无法及时响应用户的操作,影响了应用程序的实时性能。在监控系统,特别是远程监控系统中,应用程序往往不但要及时把监控对象的最新信息反馈给监视客户(通过图形显示),还要处理本地机与远程机之间的通信以及对控制对象的实时控制等任务,这时 ,仅仅由单个线程来完成所有任务,显然无法满足监控系统的实时性要求。在DOS系统下,这些工作可以由中断来完成。而在Windows NT下,中断机制对用户是不透明的。为此,可引进多线程机制,主线程专门负责消息的响应,使程序能够响应命令和其他事件。辅助线程可以用于完成其他比较费时的工作,如通信、图形显示和后台打印等,这样就不至于影响主线程的运行。
1 Windows NT 多线程概述
Windows NT是一个真正的抢占式多任务操作系统。在 Windows NT中,启动一个应用程序就是启动该应用程序的一个实例,即进程。进程由一个或多个线程构成,拥有内存和资源,但自己不能执行自己,而是进程中的线程被调度执行。进程至少要有一个线程,当创建一个进程时,就创建了一个线程,即主线程。主线程可以创建其他辅助线程,由主线程创建的线程又可创建线程。每个线程都可指定优先级,操作系统根据线程的优先级调度线程的执行。
Windows NT中使用多线程的方法有三种:
· 使用C多线程库函数;
· 使用CreateThread() 等Win32函数;
· 使用MFC类。
本文采用第三种方法。在Visual C++5.0 中,MFC应用程序用CWinThread 对象表示线程。基本操作如下:
· 创建新线程:调用MFC全局函数AfxBeginThread ()创建新线程。AfxBeginThread()启动新线程并返回控制,然后,新线程和调用AfxBeginThread()的线程同时运行。它的返回值为指向CWinThread对象的指针;
· 暂停/恢复线程:调用CWinThread类成员函数SuspendThread()暂停线程的运行,调用ResumeThread()成员函数恢复线程的运行;
· 终止线程:在线程内部可调用全局函数AfxBeginThread()终止线程的运行,否则,线程执行结束后,线程自动从线程函数返回并释放线程占有的资源。
2 基于TCP/IP的多线程编程
TCP/IP是lnternet上广泛使用的一种协议,可用于异种机之间的互联。TCP/IP协议本身是非常复杂的,然而在网络编程中,程序员不必考虑TCP/IP的实现细节,只需利用协议的网络编程接口Socket(亦称套接字)即可。在 Windows 中,网络编程接口是 Windows Socket它包含标准的Berkley Sockets的功能调用的集合,以及为 Windows 所做的一些扩展。TCP/IP协议的应用一般采用客户/服务器模式,面向连接的应用调用如图1所示。
根据上述顺序调用函数建立连接后,通信双方便可交换数据。然而,在调用带*号的函数时,操作常会阻塞,特别是当套接字工作在同步阻塞模式(Blocking Mode)时。这时,程序无法响应任何消息。为了避免出现这种情况,本文引进辅助线程。在执行含有可能阻塞的函数的任务时,动态创建新的线程,专门处理该任务。主线程把任务交给辅助线程后,不再对辅助线程加以控制与调度。本文分别针对connect()、accept()、receive()、send()等可能阻塞的函数创建了相应的线程,如表1所示。
多线程编程常常还要考虑线程间的通信。线程间的通信可以采用全局变量、指针参数和文件映射等方式。本文采用指针参数方式。在调用AfxBeginThread()函数时,通过传递指针参数的方式在主线程与辅助线程间通信。
AfxBeginThread()函数的用法如下:
CWinThread*AfxBeginThread (AFX_THREADPROC pfnThreadproc,
LPVOID pParam,
int nPriority=THREAD_PRIORITY_NORMAL,
UINT nStackSixe=0,
DWORD dwCreateFlags=0,
LPSECURITY_ATTRIBUTES pSecurityAttrs=NULL);
参数pfnThreadProc指定线程函数必须如下定义:
UINT MyControllingFunction(LPVOID pParam);
参数pParam 是调用线程传递给线程函数pfThreadProc的参数;
其他参数一般只需采用缺省值。
指针参数通信方式就是通过参数pParam在线程间通信的,它可为指向任何数据类型的指针。本文中,定义了一个名叫EXCHANGE_INFO的结构如下:
typedef struct
{ SOCKET sServerSocket;
SOCKET *pcCoientSocket;
SOCKADDR_IN *pClientAddr;
BOOL *pbConnected;
unsigned char *pucBuffer;
int *pnMessageLen;
} EXCHANGE_INFO;
在需要通信时,先声明一个结构变量,再把变量的指针作为pParam参数,调用AfxBeginThread((AFX_THREADPROC) CSocketThread::WaitFor ConnectThread,(LPVOID)& m_Exchangeinfo)函数即可。
为了利用面向对象技术编程所具有的模块性强、便于修改、可移植性好等优点,本文还把表1中的线程封装为父类为CWinThread的自定义类CSocketThread中。还自定义了一个叫CSocketComm的新类,封装了一些函数,如CreateSocket、ConnectToServer、WaitForClient、ReadMessage、SendMessage等,这些函数屏蔽了面向连接的通信程序的实现细节,如创建、连接、发送和接收等,在这些函数里,动态创建辅助线程。
下面以CSocketComm类中的等待客户连接请求的函数WaitForClient()为例,注释说明多线程编程的具体细节。
BOOL CSocketComm::WaitForClient
{
if(m_bConnected)return( TRUE );
//配置bind函数的参数即服务器的套接字地址结构
SOCKADDR_IN Addr;
memset(&Addr,0,sizeof(SOCKADDR_IN));
Addr.sin_family=AF_INET;
ADDR.SIN_port= htonl(m_nPort);
Addr.sin_addr.s_addrr = htonl(INADDR_ANY);
//将套接字地址结构赋予套接字(绑定),以指定本地半相关
int nReturnValue;
nReturnValue =::bind( m_sSserverSocket,( LPSOCKADDR)&Addr,sizeof (SOCKADDR_IN ));
if(nReturnValue == SOCKET_ERROR) returu( FALSE );
//配置传给WaitForConnectThread线程函数的参数m_Exchangeinfo
m_Exchangeinfo.sServerSocket = m_serverSocket;
m_Exchangeinfo.psClientSocket = &m_sClientSocket;
m_Exchangeinfo.pClientAddr = &m_lientAddr;
m_Exchangeinfo.pbConnected = &m_bConnected;
//以m_Exchangeinfo的指针为参数调用WaitforConnectThread线程等待客户端连接
AfxBeginThread((AFX_THREADPROC)CSocketThread::
WaitForConnectThread,(LPVOID) &m_Exchanginfo);
returi( TRUE )
}
//等待连接线程
UINT CSocketThread::WaitForConnectThread(LPVOIDpParam)
{
EXCHANGE_INFO*pExchangelnfo=(EXCHANGE_INFO*) pParam;
int nReturnValue, nClientAddrSize= Sizeof( SOCKADDR_IN);
//侦听连接
nReturnValue=:: listen(pExchangelnfo ->sServerSocket, 1);
if( nReturnValue == SOCKET_ERROR )return(0);
//阻塞调用accept,直至有客户连接请求
*pExchangelnfo->psClitentSocket=:: accept(pExchangelnfo->sServerSocket, (LPSOCKADDR) pEchangelnfo ->pClientAddr,&nClientAddrSize);
if(( *pExchangelnfo->psClitentSocket)!= INVALID_SOCKET)
//通过pExchangelnfo的指针在线程间通信
* pExchangelnfo->pbConnected TRUE;
return( 0 );
3 应用实例-高层协议的设计
在电厂和电站中,为了保证安全工作,保护系统必不可少。保护系统的电源供应通常使用两种方式。一般情况下,使用交流电系统对保护系统进行供电;当交流电系统出现故障时立即使用后备的蓄电池系统对保护系统进行供电。为了对蓄电池系统进行监控和管理,以保证蓄电池在关键时刻能正常工作,设计了在Windows NT环境下具有远程通讯功能和动态人机界面的智能蓄电池远程监控系统 。该系统由蓄电池智能管理、充电机控制、母线绝缘在线检测、声光报警、系统组态、远程通信等子系统组成,实现对蓄电池/充电机智能化远程管理和控制,对整个系统的运行状态进行实时监控,具有多媒体报警、事件处理、动态数据库、趋势画面和动态画面显示、操作提前提醒等功能。系统框图如图2所示。在远程通信模块中,远程监控机需把监控客户的操作命令及时传给本地机,本地机根据命令控制充电机,使之按照一定的方式工作,而本地机需定时向远程监控机反馈实时的充电机状态信息。它们之间的通信是基于TCP/IP的广域网通信,而且,我们引进了多线程机制以保证系统具有良好的实时性。
下面以其中的充电机控制系统为例谈谈如何使用CSocketComm类进行远程通信。为简单起见,假定本地机与远程监控机之间通信的信息仅有下面三种类型:
·本地机接收到该命令后,控制充电机按照稳压模式运行,输出电压为电压给定值;
·本地机接收到该命令后,控制充电机按照稳流定时模式运行,输出电流为电流给定值;
·本地机向远程监控机发送充电机的实时状态数据(包括输出电压、输出电流、状态指示和故障类型指示)。
在基于TCP/IP的面向连接的网络通信中,客户与服务器之间传送的是有序可靠的字节流(Byte Stream),所以程序员有必要在传输层TCP上定义自己的高层协议,设计帧结构,将字节流变成有意义的信息。在CSocketComm类中由AssembleMessage()函数把数据组合成一定的帧结构。帧结构为:
其中@为帧起始标志,#为帧终结标志
对应的结构定义如下:
typedef struct
{ int MessageType; //信息类型
int ChargerNo; //充电机编号
int DataNo; //数据类型
float Data; //数据
} MessageStruct;
需要通信时,先声明一个 MessageStruct变量,根据信息内容对各成员变量赋值,传给 AssembleMessage()函数组合成帧,再调用SendMessage()函数发送给接受方。接受方接到数据后,对数据内容的解释,是由CsocketComm类中的AnalyzeMessage()函数完成的。AnalyzeMessage()函数返回一个 MessageStruct变量。应用程序就可根据它的各成员变量控制充电机或动态显示充电机的状态。
总之,把多线程机制引进通信,有利于提高应用程序的实时性,充分利用系统资源。对于大型的工程应用来说,不同的线程完成不同的任务,也有利于提高程序的模块化,便于维护和扩展。本文给出了一种在Windows NT下基于TCP/IP协议的多线程通信的基本方法,根据该方法进行修改和扩充,便可设计出符合具体应用的高质量的多线程通信程序。 |
//string 转换为 char 型
char* str = strdup ( SendData.strSql.c_str() );
cout << str << endl;
char 转换为 string 型
char* str = "char 转换为 string 型";
SendData.strSql = str;
//SendData.strSql 为std::string型
首先,请大家读ICE中文手册中的Slice语言一章。 这一部分除了model(模块),在 ICE 1.3中文手册中都有描述
含有Slice 定义的文件必须以.ice 扩展名结尾,例如, Clock.ice就是一个有效的文件名。编译器拒绝接受其他扩展名。
Slice 支持#ifndef、#define、#endif,以及#include 预处理指令。它们的使用方式有严格的限制:你只能把#ifndef、#define,以及#endif 指令用于创建双包括(double-include)块。例如:
#ifndef _CLOCK_ICE
#define _CLOCK_ICE
// #include 文件 here...
//定义 here...
#endif _CLOCK_ICE
我们强烈建议你在所有的Slice 定义中使用双包括(double-include)块(所上),防止多次包括同一文件。
#include 指令只能出现在Slice 源文件的开头,也就是说,它们必须出现在其他所有Slice 定义的前面。此外,在使用#include 指令时,只允许使用<> 语法来指定文件名,不能使用""。例如:
#include <File1.ice> // OK
#include "File2.ice" // 不支持!
你不能把这些预处理指令用于其他目的,也不能使用其他的C++ 预处理指令 (比如用\ 字符来连接行、token 粘贴,以及宏展开,等等)。
在Slice 定义里,既可以使用C 的、也可以使用C++ 的注释风格:
Slice 关键字必须小写。例如, class 和dictionary 都是关键字,必须按照所示方式拼写。这个规则有两个例外:Object 和LocalObject 也是关键字,必须按照所示方式让首字母大写。
标识符以一个字母起头,后面可以跟任意数目的字母或数字。Slice 标识符被限制在ASCII 字符范围内,不能包含非英语字母,与C++ 标识符不同, Slice 标识符不能有下划线。这种限制初看上去显得很苛刻,但却是必要的:保留下划线,各种语言映射就获得了一个名字空间,不会与合法的Slice 标识符发生冲突。于是,这个名字空间可用于存放从Slice 标识符派生的原生语言标识符,而不用担心其他合法的Slice 标识符会碰巧与之相同,从而发生冲突 。
标识符(变量名等等)是大小写不敏感的,但大小写的拼写方式必须保持一致(看了后面的话,再理解一下)。例如,在一个作用域内, TimeOfDay 和TIMEOFDAY 被认为是同一个标识符。但是,Slice 要求你保持大小写的一致性。在你引入了一个标识符之后,你必须始终一致地拼写它的大写和小写字母;否则,编译器就会将其视为非法而加以拒绝。这条规则之所以存在,是要让Slice 既能映射到忽略标识符大小写的语言,又能映射到把大小写不同的标识符当作不同标识符的语言。(可以这样理解,变量名区分大小写,并且不可以是相同的单词)
是关键字的标识符:你可以定义在一种或多种实现语言中是关键字的Slice 标识符。例如,switch是完全合法的Slice标识符,但也是C++和Java的关键字。语言映射定义了一些规则来处理这样的标识符。要解决这个问题,通常要用一个前缀来使映射后的标识符不再是关键字。例如, Slice 标识符switch 被映射到C++ 的_cpp_switch ,以及Java 的_switch。对关键字进行处理的规则可能会产生难以阅读的源码。像native、throw,或export 这样的标识符会与C++ 或Java(或两者)的关键字发生冲突。为了让你和别人生活得更轻松一点,你应该避免使用是实现语言的关键字的Slice 标识符。要记住,以后Ice 可能会增加除C++ 和Java 以外的语言映射。尽管期望你总结出所有流行的编程语言的所有关键字并不合理,你至少应该尽量避免使用常用的关键字。使用像self、import,以及while 这样的标识符肯定不是好主意。
转义的标识符:在关键字的前面加上一个反斜线,你可以把Slice 关键字用作标识符,例如:
struct dictionary { // 错误!
// ...
};
struct \dictionary { // OK
// ...
};
反斜线会改变关键字通常的含义;在前面的例子中, \dictionary 被当作标识符dictionary。转义机制之所以存在,是要让我们在以后能够在Slice 中增加关键字,同时尽量减少对已有规范的影响:如果某个已经存在的规范碰巧使用了新引入的关键字,你只需在新关键字前加上反斜线,就能够修正该规范。注意,从风格上说,你应该避免用Slice 关键字做标识符(即使反斜线转义允许你这么做)。
保留的标识符:Slice 为Ice 实现保留了标识符Ice 及以Ice (任何大小写方式)起头的所有标识符。例如,如果你试图定义一个名为Icecream 的类型, Slice 编译器会发出错误警告3。以下面任何一种后缀结尾的Slice 标识符也是保留的:Helper、Holder、Prx,以及Ptr。Java 和C++ 语言映射使用了这些后缀,保留它们是为了防止在生成的代码中发生冲突。
(注:ICE 1.3的中文手册上没有“模块”这一部分)模块来组织一组相关的语句是为了解决名字冲突。模块可以包含所有合法的Slice语句和子模块。你可以用一些不常用的词来给最外层的模块命名,比如公司名、产品名等等。
module ZeroC {
module Client {
// Definitions here...
};
module Server {
// Definitions here...
};
};
Slice要求所有的定义都是模块的一部分,比如,下面的语句就是非法的。
interface I { // 错误:全局空间中只可以有模块
// ...
};
多个文件可以共享同一个模块,比如:
module ZeroC {
// Definitions here...
};
//另一个文件中 :
module ZeroC { // OK, reopened module
// More definitions here...
};
把一个大的模块放到几个文件中去可以方便编译(你只需重新编译被修改的文件,而没有必要编译整个模块)。
模块将映射的语言中的相应结构,比如 C++, C#, 和 Visual Basic, Slice的modules被映射为namespaces;java中被映射为package.
除了少数与特定的程序语言相关的调用之外,ice的绝大部分API(应用程序接口)都是用Slice来定义的 。这样做的好处是可以用一个ICE API定义文件来支持所有的程序语言。
|
注意 |
为了保证代码的简洁,以后文章中提及的Slice定义没有写出包含的模块,你要假定该语句是在一个模块中。 |
表 2.1. Slice的数据类型
类型 |
取值范围 |
大小(单位:bit) |
bool |
false or true |
≥ 1 |
byte |
-128-127或0-255 |
≥ 8 |
short |
2-15至215-1 |
≥ 16 |
int |
2-31至231-1 |
≥ 32 |
long |
2-63至263-1 |
≥ 64 |
float |
IEEE的单精度 |
≥ 32 bits |
double |
IEEE的双精度 |
≥ 64 bits |
string |
所有Unicode 字符,除了所有位为零的字符 |
变长 |
-
枚举:enum Fruit { Apple, Pear, Orange };
这个定义引入了一种名为Fruit 的类型,这是一种拥有自己权利的新类型。关于怎样把顺序值(ordinal values)赋给枚举符的问题, Slice 没有作出定义。例如,你不能假定,在各种实现语言中,枚举符Orange 的值都是2。Slice 保证枚举符的顺序值会从左至右递增,所以在所有实现语言中,Apple 都比Pear 要小。与C++ 不同, Slice 不允许你控制枚举符的顺序值(因为许多实现语言不支持这种特性):
enum Fruit { Apple = 0, Pear = 7, Orange = 2 }; // 出错
在实践中,只要你不在地址空间之间传送枚举符的顺序值,你就不用管枚举符使用的值是多少。例如,发送值0 给服务器来表示Apple 可能会造成问题,因为服务器可能没有用0 表示Apple。相反,你应该就发送值Apple 本身。如果在接收方的地址空间中, Apple 是用另外的顺序值表示的, Ice run time 会适当地翻译这个值。
与在C++ 里一样, Slice 枚举符也会进入围绕它的名字空间,所以下面的定义是非法的:
enum Fruit { Apple, Pear, Orange };
enum ComputerBrands { Apple, IBM, Sun, HP }; // Apple已经被定义!
Slice 不允许定义空的枚举。
-
结构
Slice 支持含有一个或多个有名称的成员的结构,这些成员可以具有任意类型,包括用户定义的复杂类型。例如: struct TimeOfDay {
short hour; // 0 - 23
short minute; // 0 - 59
short second; // 0 - 59
};
与在 C++ 里一样,这个定义引入了一种叫作TimeOfDay 的新类型。结构定义会形成名字空间,所以结构成员的名字只需在围绕它们的结构里是唯一的。在结构内部,只能出现数据成员定义,这些定义必须使用有名字的类型。例如,你不可能在结构内定义结构: struct TwoPoints {
struct Point { //错误!
short x;
short y;
};
Point coord1;
Point coord2;
};
这个规则大体上适用于Slice:类型定义不能嵌套(除了模块支持嵌套)。其原因是,对于某些目标语言而言,嵌套的类型定义可能会难以实现,而且,即使能够实现,也会极大地使作用域解析规则复杂化。对于像Slice 这样的规范语言而言,嵌套的类型定义并无必要——你总能以下面的方式编写上面的定义(这种方式在风格上也更加整洁): struct Point {
short x;
short y;
};
struct TwoPoints { // Legal (and cleaner!)
Point coord1;
Point coord2;
}
-
序列
序列是变长的元素向量:
sequence<Fruit> FruitPlatter;
序列可以是空的——也就是说,它可以不包含元素;它也可以持有任意数量的元素,直到达到你的平台的内存限制。
序列包含的元素自身也可以是序列。这种设计使得你能够创建列表的列表:
sequence<FruitPlatter> FruitBanquet;
序列可用于构建许多种collection,比如向量、列表、队列、集合、包(bag),或是树(次序是否重要要由应用决定;如果无视次序,序列充当的就是集合和包)。
序列的一种特别的用法已经成了惯用手法,即用序列来表示可选的值。例如,我们可能拥有一个Part 结构,用于记录小汽车的零件的详细资料。这个结构可以记录这样的资料:零件名称、描述、重量、价格,以及其他详细资料。 备件通常都有序列号,我们用一个long 值表示。但有些零件,比如常用的螺丝钉,常常没有序列号,那么我们在螺丝钉的序列号字段里要放进什么内容?要处理这种情况,有这样一些选择:
-
用一个标记值,比如零,来指示“没有序列号”的情况。
这种方法是可行的,只要确实有标记值可用。尽管看起来不大可能有人把零用作零件的序列号,这并非是不可能的。而且,对于其他的值,比如温度值,在其类型的范围中的所有值都可能是合法的,因而没有标记值可用。
-
把序列号的类型从long 变成string。
串自己有内建的标记值,也就是空串,所以我们可以用空串来指示.“没有序列号”的情况。这也是可行的,但却会让大多数人感到不快:我们不应该为了得到一个标记值,而把某种事物自然的数据类型变成string
-
增加一个指示符来指示序列号的内容是否有效.
struct Part {
string name;
string description;
// ...
bool serialIsValid; // true if part has serial number
long serialNumber;
};
对于大多数人而言,这也让人讨厌,而且最终肯定会让你遇到麻烦:迟早会有程序员忘记在使用序列号之前检查它是否有效,从而带来灾难性的后果。
-
用序列来建立可选字段
这种技术使用了下面的惯用手法:
sequence<long> SerialOpt;
struct Part {
string name;
string description;
// ...
SerialOpt serialNumber; // optional: zero or one element
};
按照惯例, Opt 后缀表示这个序列是用来建立可选值的。如果序列是空的,值显然就不在那里;如果它含有一个元素,这个元素就是那个值。这种方案明显的缺点是,有人可能会把不止一个元素放入序列。为可选值增加一个专用的Slice 成分可以纠正这个问题。但可选值并非那么常用,不值得为它增加一种专门的语言特性(我们将看到,你还可以用类层次来建立可选字段)。
-
词典
词典是从键类型到值类型的映射。例如:
struct Employee {
long number;
string firstName;
string lastName;
};
dictionary<long, Employee> EmployeeMap;
这个定义创建一种叫作EmployeeMap 的词典,把雇员号映射到含有雇员详细资料的结构。你可以自行决定键类型(在这个例子中是long 类型的雇员号)是否是值类型(在这个例子中是Employee 结构)的一部分——就Slice 而言,你无需让键成为值的一部分。
词典可用于实现稀疏数组,或是具有非整数键类型的任何用于查找的数据结构。尽管含有键-值对的结构的序列可用于创建同样的事物,词典要更为适宜:
-
词典明确地表达了设计者的意图,也就是,提供从值的域(domain)到值的范围(range)的映射(含有键-值对的结构的序列没有如此明确地表达同样的意图)。
-
在编程语言一级,序列被实现成向量(也可能是列表),也就是说,序列不大适用于内容稀疏的域,而且要定位具有特定值的元素,需要进行线性查找。而词典被实现成支持高效查找的数据结构(通常是哈希表或红黑树),其平均查找时间是O(log n),或者更好。词典的键类型无需为整型。例如,我们可以用下面的定义来翻译一周每一天的名称:
dictionary<string, string> WeekdaysEnglishToGerman;
服务器实现可以用键-值对Monday–Montag、Tuesday–Dienstag,等等,对这个映射表进行初始化。
-
词典的值类型可以是用户定义的任何类型。但词典的键类型只能是以下类型之一:
复杂的嵌套类型,比如嵌套的结构或词典,以及浮点类型(float和double),不能用作键类型。之所以不允许使用复杂的嵌套类型,是因为这会使词典的语言映射复杂化;不允许使用浮点类型,是因为浮点值在跨越机器界线时,其表示会发生变化,有可能导致成问题的相等语义。
-
常量定义与直接量
Slice 允许你定义常量。常量定义的类型必须是以下类型中的一种:
下面有一些例子:
const bool AppendByDefault = true;
const byte LowerNibble = 0x0f;
const string Advice = "Don't Panic!";
const short TheAnswer = 42;
const double PI = 3.1416;
enum Fruit { Apple, Pear, Orange };
const Fruit FavoriteFruit = Pear;
直接量(literals)的语法与C++ 和Java 的一样(有一些小的例外):
-
布尔常量只能用关键字false和true初始化(你不能用0和1来表示false和true)。
-
和C++ 一样,你可以用十进制、八进制,或十六进制方式来指定整数直接量。例如:
const byte TheAnswer = 42;
const byte TheAnswerInOctal = 052;
const byte TheAnswerInHex = 0x2A; // or 0x2a
|
注意 |
如果你把byte 解释成数字、而不是位模式,你在不同的语言里可能会得到不同的结果。例如,在C++ 里, byte 映射到char,取决于目标平台, char 可能是有符号的,也可能是无符号的。 |
|
注意 |
用于指示长常量和无符号常量的后缀(C++ 使用的l、L、u、U)是非法的: const long Wrong = 0u; // Syntax error
const long WrongToo = 1000000L; // Syntax error
|
-
-
整数直接量的值必须落在其常量类型的范围内,否则编译器就会发出诊断消息。
-
浮点直接量使用的是C++语法,除了你不能用l或L后缀来表示扩展的浮点常量;但是, f 和F 是合法的(但会被忽略)。下面是一些例子:
const float P1 = -3.14f; // Integer & fraction, with suffix
const float P2 = +3.1e-3; // Integer, fraction, and exponent
const float P3 = .1; // Fraction part only
const float P4 = 1.; // Integer part only
const float P5 = .9E5; // Fraction part and exponent
const float P6 = 5e2; // Integer part and exponent
-
浮点直接量必须落在其常量类型(float 或double)的范围内;否则编译器会发出诊断警告。
-
串直接量支持与C++ 相同的转义序列。下面是一些例子:
const string AnOrdinaryString = "Hello World!";
const string DoubleQuote = "\"";
const string TwoSingleQuotes = "'\'"; // ' and \' are OK
const string Newline = "\n";
const string CarriageReturn = "\r";
const string HorizontalTab = "\t";
const string VerticalTab = "\v";
const string FormFeed = "\f";
const string Alert = "\a";
const string Backspace = "\b";
const string QuestionMark = "\?";
const string Backslash = "\\";
70 Slice 语言
const string OctalEscape = "\007"; // Same as \a
const string HexEscape = "\x07"; // Ditto
const string UniversalCharName = "\u03A9"; // Greek Omega
和在 C++ 里一样,相邻的串直接量会连接起来:
const string MSG1 = "Hello World!";
const string MSG2 = "Hello" " " "World!"; // Same message
/*
* Escape sequences are processed before concatenation,
* so the string below contains two characters,
* '\xa' and 'c'.
*/
const string S = "\xa" "c";
|
注意 |
Slice 没有null 串的概念 const string nullString = 0; // Illegal!
null 串在Slice 里根本不存在,因此,在Ice 平台的任何地方它都不能用作合法的串值。这一决定的原因是, null 串在许多编程语言里不存在 |
这一节大部分内容整理自ICE中文手册,在这里我特别感谢马维达同志的翻译给我们的学习带来了方便。
读服务端代码
文件server.cpp.
#include <Ice/Ice.h>
#include "../print.h"
using namespace std;
using namespace Demo;
//惯例,用后缀I 表示这个类实现一个接口
class PrinterI : public Printer {
public:
virtual void printString(const string& s, const Ice::Current&);
};
/*
打开print.h,看看PrinterI父类的定义
namespace Demo {
class Printer : virtual public Ice::Object {
public:
//纯虚函数,不能实例化
virtual void printString(const std::string&,
//第二个参数有缺省值,实现中可以不使用
const Ice::Current&= Ice::Current()) = 0;
};
};
*/
void PrinterI::printString(const string& s, const Ice::Current&)
{
cout << s << endl;
}
int main(int argc, char* argv[])
{
//程序的退出时的状态,就是否成功执行
int status = 0;
//来包含Ice run time 的主句柄 (main handle)
Ice::CommunicatorPtr ic;
try {
//初始化Ice run time (argc和argv是run time命令参数;
//就这个例子而言,服务器不需要任何命令行参数)。
//initialize 返回一个指向Ice::Communicator对象的智能指针,
//这个指针是Ice run time 的主句柄。
ic = Ice::initialize(argc, argv);
//调用Communicator 实例上的createObjectAdapterWithEndpoints,
//创建一个对象适配器(比如:网卡就是一种适配器)。
//参数是"SimplePrinterAdapter" (适配器的名字)
//和"default -p 10000"(用缺省协议(TCP/IP),侦听端口10000 的请求。)
//显然,在应用中硬编码对象标识和端口号,是一种糟糕的做法,
//但它目前很有效;我们将在以后看到在架构上更加合理的做法。
Ice::ObjectAdapterPtr adapter
= ic->createObjectAdapterWithEndpoints(
"SimplePrinterAdapter", "default -p 10000");
//服务器端run time 已经初始化,实例化一个PrinterI 对象,
//为我们的Printer 接口创建一个servant(serv 服务+-ant人,背一下单词)。
Ice::ObjectPtr object = new PrinterI;
//我们调用适配器的add,告诉它有了一个新的servant ;
//传给add 的参数是刚才实例化的servant,再加上一个标识符。
//在这里,"SimplePrinter" 串是servant 的名字
//(如果我们有多个打印机,每个打印机都可以有不同的名字,
//更正确的说法是,都有不同的对象标识)。
adapter->add(object,
Ice::stringToIdentity("SimplePrinter"));
//调用适配器的activate 方法激活适配器
//(适配器一开始是在暂停(holding)状态创建的;
//这种做法在下面这样的情况下很有用:
//我们有多个servant,它们共享同一个适配器,
//而在所有servant实例化之前我们不想处理请求)。
//一旦适配器被激活,服务器就会开始处理来自客户的请求。
adapter->activate();
//最后,我们调用waitForShutdown。
//这个方法挂起发出调用的线程直到服务器实现终止
//——或者是通过发出一个调用关闭run time,
ic->waitForShutdown();
}
catch (const Ice::Exception& e) {
cerr << e << endl;
status = 1;
} catch (const char* msg) {
cerr << msg << endl;
status = 1;
}
if (ic) {
try {
//必须调用Communicator::destroy结束Ice run time。
//destroy 会等待任何还在运行的操作调用完成。
//此外, destroy 还会确保任何还未完成的线程都得以汇合(joined),
//并收回一些操作系统资源,比如文件描述符和内存。
//决不要让你的main 函数不调用destroy 就终止,
//否则,后果无法想象。
ic->destroy();
} catch (const Ice::Exception& e) {
cerr << e << endl;
status = 1;
}
}
return status;
}
注意,尽管以上的代码不算少,但它们对所有的服务器都是一样的。你可以把这些代码放在一个辅助类里,然后就无需再为它费心了(Ice 提供了这样的辅助类,叫作Ice::Application,参见 10.3.1 节) 。就实际的应用代码而言,服务器只有几行代码:六行代码定义PrinterI 类,再加上三2 行代码实例化一个PrinterI 对象,并向对象适配器注册它。
读客户端代码
文件client.cpp.
#include <Ice/Ice.h>
#include "..\print.h"
using namespace std;
using namespace Demo;
int main(int argc, char* argv[])
{
int status = 0;
Ice::CommunicatorPtr ic;
try {
ic = Ice::initialize(argc, argv);
//stringToProxy 返回的代理(Proxy)类型是Ice::ObjectPrx,
//这种类型位于接口和类的继承树的根部(接口的基类)。
Ice::ObjectPrx base
=ic->stringToProxy( "SimplePrinter:default -p 10000");
//但要实际要与我们的打印机交谈,
//我们需要的是Printer 接口、不是Object 接口的代理。
//为此,需要调用PrinterPrx::checkedCast 进行向下转换(向下转型)。
//这个方法会发送一条消息给服务器,
//询问“这是Printer 接口的代理吗?”
//如果回答“是”,就会返回Printer 的一个代理;
//如果代理代表的是其他类型的接口,返回一个空代理
PrinterPrx printer = PrinterPrx::checkedCast(base);
//测试向下转型是否成功,若不成功,就抛出出错消息并终止客户。
if (!printer) throw "Invalid proxy";
//现在,我们在我们的地址空间里有了一个激活的代理,
//可以调用printString 方法,
//把享誉已久的 "Hello World!" 串传给它。
//服务器会在它的终端上打印这个串。
printer->printString("Hello World!");
}
catch (const Ice::Exception& ex) {
cerr << ex << endl;
status = 1;
} catch (const char* msg) {
cerr << msg << endl;
status = 1;
}
if (ic)
ic->destroy();
return status;
}
如果出现任何错误,客户会打印一条出错消息。例如,如果我们没有先启动服务器就运行客户,我们会得到:
Network.cpp:471: Ice::ConnectFailedException:
connect failed: Connection refused
(由于windows下的命令行窗口在出错后会一闪就消失,不过我们可以在client.cpp的main函数的return status;之前加上system("PAUSE");然后再在VS2003.net中把client设置为启动项目,重新编译,运行。OK,可以看到结果了。)