揭开SVCHOST.exe进程之谜
[转]http://www.cppblog.com/jerysun0818/archive/2006/06/02/8088.html
svchost.exe是nt核心系统的非常重要的进程,对于2000、xp来说,不可或缺。很多病毒、木马也会调用它。所以,深入了解这个程序,是玩电脑的必修课之一。
大家对windows操作系统一定不陌生,但你是否注意到系统中“svchost.exe”这个文件呢?细心的朋友会发现windows中存在多个 “svchost”进程(通过“ctrl+alt+del”键打开任务管理器,这里的“进程”标签中就可看到了),为什么会这样呢?下面就来揭开它神秘的面纱。
发现
在基于nt内核的windows操作系统家族中,不同版本的windows系统,存在不同数量的“svchost”进程,用户使用“任务管理器”可查看其进程数目。一般来说,win2000有两个svchost进程,winxp中则有四个或四个以上的svchost进程(以后看到系统中有多个这种进程,千万别立即判定系统**毒了哟),而win2003 server中则更多。这些svchost进程提供很多系统服务,如:rpcss服务(remote procedure call)、dmserver服务(logical disk manager)、dhcp服务(dhcp client)等。
如果要了解每个svchost进程到底提供了多少系统服务,可以在win2000的命令提示符窗口中输入“tlist -s”命令来查看,该命令是win2000 support tools提供的。在winxp则使用“tasklist /svc”命令。
svchost中可以包含多个服务
深入
windows系统进程分为独立进程和共享进程两种,“svchost.exe”文件存在于“%systemroot% system32”目录下,它属于共享进程。随着windows系统服务不断增多,为了节省系统资源,微软把很多服务做成共享方式,交由 svchost.exe进程来启动。但svchost进程只作为服务宿主,并不能实现任何服务功能,即它只能提供条件让其他服务在这里被启动,而它自己却不能给用户提供任何服务。那这些服务是如何实现的呢?
原来这些系统服务是以动态链接库(dll)形式实现的,它们把可执行程序指向 svchost,由svchost调用相应服务的动态链接库来启动服务。那svchost又怎么知道某个系统服务该调用哪个动态链接库呢?这是通过系统服务在注册表中设置的参数来实现。下面就以rpcss(remote procedure call)服务为例,进行讲解。
从启动参数中可见服务是靠svchost来启动的。
实例
以windows xp为例,点击“开始”/“运行”,输入“services.msc”命令,弹出服务对话框,然后打开“remote procedure call”属性对话框,可以看到rpcss服务的可执行文件的路径为“c:\windows\system32\svchost -k rpcss”,这说明rpcss服务是依靠svchost调用“rpcss”参数来实现的,而参数的内容则是存放在系统注册表中的。
在运行对话框中输入“regedit.exe”后回车,打开注册表编辑器,找到[hkey_local_machine systemcurrentcontrolsetservicesrpcss]项,找到类型为“reg_expand_sz”的键“magepath”,其键值为“%systemroot%system32svchost -k rpcss”(这就是在服务窗口中看到的服务启动命令),另外在“parameters”子项中有个名为“servicedll”的键,其值为“% systemroot%system32rpcss.dll”,其中“rpcss.dll”就是rpcss服务要使用的动态链接库文件。这样 svchost进程通过读取“rpcss”服务注册表信息,就能启动该服务了。
解惑
因为svchost进程启动各种服务,所以病毒、木马也想尽办法来利用它,企图利用它的特性来迷惑用户,达到感染、入侵、破坏的目的(如冲击波变种病毒“w32.welchia.worm”)。但windows系统存在多个svchost进程是很正常的,在受感染的机器中到底哪个是病毒进程呢?这里仅举一例来说明。
假设windows xp系统被“w32.welchia.worm”感染了。正常的svchost文件存在于“c:\windows\system32”目录下,如果发现该文件出现在其他目录下就要小心了。“w32.welchia.worm”病毒存在于“c:\windows\system32wins”目录中,因此使用进程管理器查看svchost进程的执行文件路径就很容易发现系统是否感染了病毒。windows系统自带的任务管理器不能够查看进程的路径,可以使用第三方进程管理软件,如“windows优化大师”进程管理器,通过这些工具就可很容易地查看到所有的svchost进程的执行文件路径,一旦发现其执行路径为不平常的位置就应该马上进行检测和处理。
posted @
2006-06-02 17:06 Jerry Cat 阅读(418) |
评论 (0) |
编辑 收藏
网络基础知识讲座之三:理解OSI网络分层
[转]http://www.cppblog.com/jerysun0818/archive/2006/06/01/8040.html
在上一次课程里我们介绍了子网的概念和子网的划分。现在该谈到OSI(开放系统互连)参考模型了。网络协议栈具有重要的意义。但是,还没有重要到你应该首先学习的程度。许多所谓的网络课程都是从教你记住OSI模型中的每一个层的名字和这个模型中包含的每一个协议开始的。这样做是不必要的。甚至第5层和第6层是完全可以忽略的。
国际标准组织(ISO)制定了OSI模型。这个模型把网络通信的工作分为7层。1至4层被认为是低层,这些层与数据移动密切相关。5至7层是高层,包含应用程序级的数据。每一层负责一项具体的工作,然后把数据传送到下一层。
物理层(也即OSI模型中的第一层)在课堂上经常是被忽略的。它看起来似乎很简单。但是,这一层的某些方面有时需要特别留意。物理层实际上就是布线、光纤、网卡和其它用来把两台网络通信设备连接在一起的东西。甚至一个信鸽也可以被认为是一个1层设备(参见RFC 1149)。网络故障的排除经常涉及到1层问题。我们不能忘记用五类线在整个一层楼进行连接的传奇故事。由于办公室的椅子经常从电缆线上压过,导致网络连接出现断断续续的情况。遗憾的是,这种故障是很常见的,而且排除这种故障需要耗费很长时间。
第2层是以太网等协议。请记住,我们要使这个问题简单一些。第2层中最重要的是你应该理解网桥是什么。交换机可以看成网桥,人们现在都这样称呼它。网桥都在2层工作,仅关注以太网上的MAC地址。如果你在谈论有关MAC地址、交换机或者网卡和驱动程序,你就是在第2层的范畴。集线器属于第1层的领域,因为它们只是电子设备,没有2层的知识。第2层的相关问题在本网络讲座中有自己的一部分,因此现在先不详细讨论这个问题的细节。现在只需要知道第2层把数据帧转换成二进制位供1层处理就可以了。
在往下讲之间,你应该回过头来重新阅读一下上面的内容,因为经验不足的网络管理员经常混淆2层和3层的区别。
如果你在谈论一个IP地址,那么你是在处理第3层的问题,这是“数据包”问题,而不是第2层的“帧”。IP是第3层问题的一部分,此外还有一些路由协议和地址解析协议(ARP)。有关路由的一切事情都在第3层处理。地址解析和路由是3层的重要目的。
第4层是处理信息的传输层。第4层的数据单元也称作数据包(packets)。但是,当你谈论TCP等具体的协议时又有特殊的叫法,TCP的数据单元称为段(segments)而UDP协议的数据单元称为“数据报(datagrams)”。这个层负责获取全部信息,因此,它必须跟踪数据单元碎片、乱序到达的数据包和其它在传输过程中可能发生的危险。理解第4层的另一种方法是,第4层提供端对端的通信管理。像TCP等一些协议非常善于保证通信的可靠性。有些协议并不在乎一些数据包是否丢失,UDP协议就是一个主要例子。
现在快要到7层了,我们很想知道第5层和第6层有些什么功能。可以说,它们都是没有用的。
有一些应用程序和协议在5层和6层。但是,对于理解网络问题来说,谈论这些问题没有任何益处。请大家注意,第7层是“一切”。7层也称作“应用层”,是专门用于应用程序的。如果你的程序需要一种具体格式的数据,你可以发明一些你希望能够把数据发送到目的地的格式,并且创建一个第7层协议。SMTP、DNS和FTP都是7层协议。
学习OSI模型中最重要的事情是它实际代表什么意思。
假如你是一个网络上的操作系统。在1层和2层工作的网卡将通知你什么时候有数据到达。驱动程序处理2层帧的出口,通过它你可以得到一个发亮和闪光的3层数据包(希望是如此)。作为操作系统,你将调用一些常用的应用程序处理3层数据。如果这个数据是从下面发上来的,你知道那是发给你的数据包,或者那是一个广播数据包(除非你同时也是一个路由器,不过,暂时不用担心这个问题)。如果你决定保留这个数据包,你将打开它,并且取出4层数据包。如果它是TCP协议,这个TCP子系统将被调用并打开这个数据包,然后把这个7层数据发送给在目标端口等待的应用程序。这个过程就结束了。
当要对网络上的其它计算机做出回应的时候,每一件事情都以相反的顺序发生。7层应用程序将把数据发送给TCP协议的执行者。然后,TCP协议在这些数据中加入额外的文件头。在这个方向上,数据每前进一步体积都要大一些。TCP协议在IP协议中加入一个合法的TCP字段。然后,IP协议把这个数据包交给以太网。以太网再把这个数据作为一个以太网帧发送给驱动程序。然后,这个数据通过了这个网络。这条线路中的路由器将部分地分解这个数据包以获得3层文件头,以便确定这个数据包应该发送到哪里。如果这个数据包的目的地是本地以太网子网,这个操作系统将代替路由器为计算机进行地址解析,并且把数据直接发送给主机。
这个过程确实简化了。但是,如果你能够按照这个进程来做,并且理解数据包在每一个阶段都会发生什么事情,你就征服了理解网络的相当大的一部分问题。当你开始讨论每一个协议实际上做什么的时候,一切都会变得非常复杂。如果你刚刚开始学习,在你理解复杂的事情在设法完成什么任务之前,请你先忽略这些复杂的事情。这样会提高你的学习热情。
在以后的文章里,我们将沿着网络栈向上旅行,通过讨论常用的协议和这些协议的工作原理来详细研究每一层。
小结
•与其苦钻OSI模型中的各协议不如好好理解路由器和主机如何利用网络栈传输数据
•2层数据称作帧,不包含IP地址。IP地址和数据包在3层,MAC地址在2层。
•除非你是一台路由器,通过网络栈向上发来的数据是给你的,通过网络栈向下发送的数据是你发送的。
posted @
2006-06-01 17:50 Jerry Cat 阅读(346) |
评论 (0) |
编辑 收藏
/********************************************\
| 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! |
\********************************************/
关于"Effective C++"中的new/delete的补充
作者: Jerry Cat
时间: 2006/06/01
链接: http://www.cppblog.com/jerysun0818/archive/2006/06/01/8009.html
在条款9中强调了要避免遮掩了new的正规形式
那么new有多少种正规形式? 对于一个完整的new它应该有下面四种类型:
void * operator new (size_t size)
{ return ::operator new (size); }
void operator delete(void * p)
{ ::operator delete (p); }
void * operator new[](size_t size)
{ return ::operator new (size * n); }
void operator delete[](void *p)
{ ::operator delete (p); }
void * operator new(size_t size, void *p)
{ return p; }
void operator delete(void *p, void *p2)
{ ::operator delete(p); }
void * operator new[](size_t size, void *p)
{ return ::operator new (size * n); }
void operator delete[](void *p, void *p2)
{ ::operator delete (p); }
尤其是对于[]的操作,要重点注意。
下面是new所对应的调用方法。
void foo();
new T;
new T[10]
new (foo)T;
new (foo)T[10];
所以对于类的设计者来说,如果要重载new操作,最好是实现所有的new操作,以防万一。
posted @
2006-06-01 02:08 Jerry Cat 阅读(772) |
评论 (1) |
编辑 收藏
/********************************************\
| 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! |
\********************************************/
标准C++ RTTI的仿MFC实现 - 只用2个宏来实现"根据对象名创建对象"
作者: Jerry Cat
时间: 2006/05/30
链接: http://www.cppblog.com/jerysun0818/archive/2006/05/30/7895.html
1. 起由:
C++ RTTI缺乏一些运行时信息即便是C++98这方面也不好使,无法根据对象名直接创建对象. 所以MFC为了实现序列化,从头构造了自己的RTTI信息,并定义了一整套宏. 具体可以参考MFC源代中一下类和宏的实现:
CRuntimeClass, CObject, DECLARE_DYNAMIC, IMPLEMENT_DYNAMIC
2. 需求:
本人正在写一个做集成测试的工具,有一个配置哪些case需要做测试的配置文件,类似结构如下:
<ut>
<case name="case1"/>
<case name="case2"/>
</ut>
因此当测试工具导入改配置信息时候,必须能够根据 "case1", "case2"等名字创建相应的Case对象. 显然C++没有提供类似的功能.
3. 实现:
仿照MFC Serialize的实现,定义一个基类和2个宏. 需要被动态创建的对象从该基类派生,并将2个宏注册到对象工厂(Factory对象),然后就
可以用该对象工厂根据对象名创建对象.
基类和宏的定义:
class CUTObject
{
public:
virtual ~CUTObject(){}
virtual void SetUTName(CFString name);
virtual CFString GetUTName();
virtual CUTObject* Clone(){ return NULL; }
private:
CFString m_strUTName;
};
#define DECLARE_UTOBJECT(className) \
public: \
virtual CUTObject* Clone() \
{ return new className(); }\
static CUTObject* CreateObject() \
{ return new className(); } \
static bool RegisterObject(CFString utName) \
{ \
className *pObj = (className*)CreateObject(); \
CUTFactory *pFactory = CUTFactory::Instance(); \
pFactory->RegisterObject(utName, pObj); \
return true; \
}
#define REGISTER_UTOBJECT(utName, className) bool b##className = className::RegisterObject(utName);
类工厂是一个标准的Singleton模式,提供2个方法 - RegisterObject和CreateObject:
class CUTFactory
{
public:
static CUTFactory* Instance();
private:
CUTFactory();
CUTFactory(const CUTFactory& other){}
CUTFactory& operator=(const CUTFactory& other){}
public:
bool RegisterObject(CFString name, CUTObject *pObj);
CUTObject* CreateObject(CFString name);
private:
static CUTFactory* m_pInstance;
static std::map<CFString, CUTObject*> m_mapObjects;
};
4. 小结:
最关键的代码是:
#define REGISTER_UTOBJECT(utName, className) bool b##className = className::RegisterObject(utName);
这行代码被调用时,对象className将会以utName的名字注册到对象工厂.
实际上是简单调用className::RegisiterObject方法.
创建一个className的实例,然后将该实例放置到Factory的数组中.
因为DECLARE_UTOBJECT宏为每个对象定义了Clone方法,所以类工厂可以从该数组中的实例来创建该对象的其他实例.
至于REGISTER_UTOBJECT为什么要定义一个bool b##className对象,完全是因为这样写才能通过编译器检查. 编译器读到className::RegisterObejct(utName)时候,会认为这是在进行一个函数定义而不是进行一个函数调用. 只有明确指定返回值的情况下,才被认为是函数调用.
posted @
2006-05-30 19:43 Jerry Cat 阅读(1280) |
评论 (0) |
编辑 收藏
/********************************************\
| 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! |
\********************************************/
VC学习小技巧一: 说明使用一个非模态对话框应注意问题
作者: Jerry Cat
时间: 2006/05/30
链接: http://www.cppblog.com/jerysun0818/archive/2006/05/30/7894.html
使用一个非模态对话框应该注意一定要在样式中包含WS_VISIBLE才能正常显示;创建对话框使用CreateDialog() 函数;消息循环部分应该使用IsDialogMessage过滤消息;关闭对话框使用函数 DestroyWindow()
posted @
2006-05-30 18:45 Jerry Cat 阅读(1333) |
评论 (0) |
编辑 收藏
/********************************************\
| 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! |
\********************************************/
C++异常处理一例
作者: Jerry Cat
时间: 2006/05/30
链接: http://www.cppblog.com/jerysun0818/archive/2006/05/30/7851.html
#include<fstream.h>
#include<iostream.h>
#include<stdlib.h>
void main()
{
ifstream source("c:\abc.txt"); //打开文件
char line[128];
try //定义异常
{
if (source.fail())
throw "txt"; //抛掷异常
}
catch(char * s) //定义异常处理
{
cout<<"error opening the file "<<s<<endl;
exit(1);
}
while(!source.eof())
{
source.getline(line, sizeof(line));
cout<<line<<endl;}
source.close();
}
}
posted @
2006-05-30 01:38 Jerry Cat 阅读(657) |
评论 (1) |
编辑 收藏
[转]网络基础知识讲座之二:理解子网和CIDR
http://www.cppblog.com/jerysun0818/archive/2006/05/28/7782.html
欢迎光临此网络讲座第二讲。这一次,我们将学习有关子网和CIDR(无类域间路由)的知识。我们希望能够以比某些图书提供的更容易管理的方式学习这些知识。
让我们先搞清楚一件事情:在子网中是没有“类别”的。在以前,网络分为A类、B类和C类。这些网络只能分为相等的几部分,因此现在我们引入了可变长度子网掩码(VLSM)来解决这个问题。老类别的C类网络是一个24位网络地址,B类网络是一个16位网络地址,A类网络是一个8位网络地址(如果你不清楚这个含义,请参阅“理解IP地址”那一讲)。这就是你需要了解的有关网络类别的全部内容。这些网络类别现在已经不存在了。
一个IP地址由一个主机部分和一个网络部分组成。与子网掩码配合使用,你可以确定这个地址中哪一个部分是网络部分、这个网络有多大和网络开始的地方在哪里。操作系统需要知道这些信息,以便确定本地子网使用什么IP地址以及哪些地址属于外网并需要一台路由器来访问那些地址。相邻的路由器也需要知道这个子网有多大,以便这些路由器仅向这个方向发送合适的通信。一个网络地址中主机部分与网络部分的划分完全是由子网掩码确定的。
CIDR(发音为“cider”的网络地址使用网络/子网掩码的风格。这个IP地址/子网掩码的组合能告诉你很多信息:
网络部分/主机部分
0000000000000000/0000000000000000
上述32位字符串代表一个16位网络,因为这个地址中的16位被掩盖了。
在本文中的例子(和现实世界)中,某些子网掩码被反复提及。这些子网掩码本身并不特殊。子网地址就是一个简单的32位字符串,其中有任何位数被掩盖(MASK)。不过,使用一个常用的子网掩码(如24位网络地址)开始研究对于记忆和理解关于子网的概念和划分非常有利。
让我们先看一下个标准的子网划分表,这个表中也包括了一些有趣的其它信息:
子网掩码位数
|
24位子网数量
|
一个子网的地址数量
|
对主机部分的位挪用
|
/24
|
1
|
256
|
0
|
/25
|
2
|
128
|
1
|
/26
|
4
|
64
|
2
|
/27
|
8
|
32
|
3
|
/28
|
16
|
16
|
4
|
/29
|
32
|
8
|
5
|
/30
|
64
|
4
|
6
|
/31
|
128
|
2
|
7
|
由于是二进制数字,这使一个31位网络有两个可用的IP地址。设想一下这个子网:2.2.2.0/31。如果我们以二进制来表达这个网络地址,这个地址看起来是这样的:
00000010.00000010.00000010.00000000 (2.2.2.0)
11111111.11111111.11111111.11111110 (31)
子网掩码“掩盖”被网络部分使用的位数。这意味着被掩盖的位数将用于网络地址部分。可供主机地址使用的位数等于1。这个数字可以是一个0或者一个1。这就导致了两个可用的IP地址,就像上面的表格显示的一样。另外,从上面的表格中可以看到,子网掩码(从主机部分挪用的)位数每增加一,子网中可用的地址数量就被削减一半。
现在让我们来分析“192.168.0.200/26”的广播地址、网络地址和掩码。这个IP地址的掩码很简单:为255.255.255.192(26位子网掩码的含义是主机用6位,2的6次方等于64,255减去64减1等于192)。你能够在网络上查到子网地址表。这个表还能为你列出所有的信息。但是,我们更感兴趣地是教人们理解这里所发生的事情。这个子网掩码可以告诉你,这个网络地址中惟一需要我们关心的部分是最后一个字节:广播地址和网络地址的开头都是192.168.0。
搞清楚这最后一个字节的含义很像是为一个划分一个24位网络。但是,如果这个提示对你没有帮助,你甚至不需要考虑这个问题。每一个26位地址的网络都有64台主机。这个网络的地址范围是从.0至.63、从.64至.127,从.128至.191,以及从.192至.255。我们的地址192.168.0.200/26在.192至.255网段中。因此,这个网络的地址是192.168.0.192/26。这个广播地址就更简单:192用二进制表示是11000000。取最后的6位数(这些字节被掩码“关闭”了),把这些字节“打开”,你得到了什么?192.168.0.255。来看一下你是否已经理解了这一切,现在计算192.168.0.44/26的网络地址和广播地址。(网络地址:192.168.0.0/26;广播地址:192.168.0.63)。
一开始这些地址是很难一下子就看出来。这时制作一个表格会很有帮助。如果你计算出你要每一个子网有6台主机(包括不能使用的网络和广播地址是8台主机),你就可以制作下面这个表格。下面是2.2.2.0/29、2.2.2.8/29、2.2.2.16/29以及最后一个子网是2.2.2.249/29。
子网编号
|
网络地址
|
第一个IP
|
最后一个IP
|
广播地址
|
1
|
2.2.2.0
|
2.2.2.1
|
2.2.2.6
|
2.2.2.7
|
2
|
2.2.2.8
|
2.2.2.9
|
2.2.2.14
|
2.2.2.15
|
3
|
2.2.2.16
|
2.2.2.17
|
2.2.2.22
|
2.2.2.23
|
32
|
2.2.2.249
|
2.2.2.250
|
2.2.2.254
|
2.2.2.255
|
实际上,你很可能偶尔发现这样的网络。这种网络划分为三个26位网络地址,并且最后一个26位网络地址分为两个27位网络地址。如果你已经能够制作上述表格将会更容易理解这个问题。
这就是你需要知道的全部东西。在16位网络地址和24位网络地址范围内使用更大的子网是比较复杂的。但是,原则是一样的。都是一个32位地址和一个子网掩码。然而,一定要认识到子网的使用是受到某些限制的。我们不能分配以10.1.0.32开头的26位网络地址。如果我们把10.1.0.32/26的IP地址和子网掩码发送给大多数操作系统,操作系统只会认为我们发送的起始地址是10.1.0.0/26。这是因为26位地址空间需要64个地址,而子网划分会从这个位数的自然分界线开始。因些,如果在上述表格中,你把某子网从2.2.2.3/29开始?实际的结果却是2.2.2.0/29。
这些复杂的问题确实需要一个简明的例子。请记住,当你从这个网络主机部分提取另一位以便创建一个更大的子网掩码时,IP地址数量在一个子网内是如何被减少一半的。这个原则在相反的情况下也发挥作用。如果我们有一个拥有128台主机的25位网络地址,并且从网络(掩码)部分挪用一位,我们现在就有一个拥有256台主机的24位网络地址。使用搜索引擎Google在网络上搜索“subnet table”(子网表),可以立即看到子网掩码与网络大小的关系。如果一个16位网络地址拥有65535个地址,一个17位网络地址拥有的网络地址将减少一半,一个15位网络地址拥有的网络地址将提高一倍。这是非常令人激动的。实践,实践,再实践。这是让你理解这个原理的好方法。不要忘记,所有的问题都可以归结到网络的位数问题。
如果你要更多地了解子网,下一步应该是阅读一些路由协议。我们将很快介绍一些路由协议。不过,在这个教程的下一讲网络中,我们先开始我们的OSI模型之旅。
小结
•CIDR IP地址有一个主机部分和一个网络部分。而子网掩码指定网络部分使用的位数,地址中这些位将不会改变。
•子网是通过简单地在32位数字中上下移动网络与主机部分的分界来创建。
•如果你从已知的子网掩码开始学习,复杂的地址是很容易理解的。一个17位网络地址的数量是一个16位网络地址的一半。16位网络中有6.5万个地址。
posted @
2006-05-28 20:02 Jerry Cat 阅读(294) |
评论 (0) |
编辑 收藏
网络没有地址就不能工作:只要你发送某种东西,你就需要具体说明这个东西要发到哪里和从哪里发出来的。要做一个高效率的网络工程师或者管理员,你需要全面理解IP地址:你需要能够依靠自己思考。如果发生了某些问题,很可能是某些地址分配不正确。迅速查明故障所在位置对于英雄和普通人来说是有很大区别的。普通人需要很长时间才能修复这个问题。在下一篇文章介绍子网之前,我想以最原始的格式全面介绍一下IP地址。这对于理解子网是非常重要的。
IPv4地址和32位数字
IP地址只是32位二进制数字。但是,它们是重要的二进制数字:你需要知道如何处理这些数字。当处理子网掩码的时候,新的网络管理员一般会混淆他们没有记住的子网掩码。所有这些子网掩码的总和是移动代表一个“网络”的地址部分与代表一台“主机”的部分之间的界限。一旦你适应了这种关于IP地址和子网掩码的思维方式,你就掌握了IP地址的方法。
二进制非常简单。在二进制中,数码只有0和1。一个32位数码有32个0和1。我们都适应十进制数码。在十进制中,数码中的每一个位置都可以是0至9之间的任何一个数字。在二进制中,每一个位置或者是一个0,或者是一个1。下面是以二进制表示的255.255.255.0的地址。
11111111.11111111.11111111.00000000
为了方便,网络工程师一般把IP地址分为4个8位字段,或者称作8位字节。在8位数字中,如果所有的字节都设置为1,那么,这个数码等于255。在上面的地址中,11111111代表255,00000000代表0。
二进制发挥作用的方法是以二次方为基础的。每一个字节代表一个不同的二次方。从左手端开始,最有意义的数位,数字以下列方式组成:
2的次方
|
232
|
...
|
2
7
|
2
6
|
2
5
|
2
4
|
2
3
|
2
2
|
2
1
|
2
0
|
小数
|
4,294,967,296
|
...
|
128
|
64
|
32
|
16
|
8
|
4
|
2
|
1
|
这个结果是加法。这就是说,如果所有的字节不变,你可以为每一个位置简单地加2的次方值。例如,如果我们有一个8位数11111111,我们可以简单地加:27 + 2 6 +
2 5 + 2 4 + 2 3 + 2 2 + 2 1 + 2 0 = 255
现在我们设法理解一个不平凡的例子:11110000
我们可以看到,在上述8位数中,有4位数是“固定的”。把这些位置上的2的次方的值加起来,结果是:27 + 26 + 2 5 + 2 4 = 240
就是这样简单。如果你能把二进制数字转换为小数形式,你就很容易猜出子网掩码和网络地址。我们将在下一篇文章中介绍这个问题。
现在,把重点集中在32位IPv4地址本身,有一些不同的类型需要了解。所有的IP地址都可以在0.0.0.0至255.255.255.255的数字范围内。但是,有些地址有特殊用途。
环回地址(loopback):
不离开主机的数据包(也就是说,这些数据包不会通过外部网络接口)。例如:127.0.0.1
单播地址:
指定向一个IP地址发送的数据包。例如:2.2.2.2
多播地址:
被路由器复制并且最终由组播路由机制转发的数据包。例如:226.0.0.2
有限广播:
一个广播数据包,发送给每一台主机,仅限于本地子网。例如:255.255.255.255
定向广播:
发送到一个具体子网的数据包,然后进行广播。例如,假如我们不在这个子网,使用的地址是:1.1.1.255
还有一些IP地址的特殊情况,包括专用和组播地址。在224.0.0.0至239.255.255.255之间的地址范围是为组播保留的。在互联网上,任何低于这个范围的地址都可能成为被攻击的目标,除非为RFC 1918保留的地址和为一些其它特殊用途分配的地址。这些1918地址是专用地址,这就意味着互联网路由器不会发送这些地址。这个地址范围包括:
• 10.0.0.0 -10.255.255.255
• 172.16.0.0 - 172.31.255.255
• 192.168.0.0 - 192.168.255.255
这些IP地址能够分配给本地的许多计算机,你愿意分配给多少台计算机都可以。但是,在这些计算机访问互联网之前,这些地址必须翻译成能够全球路由的地址。这个工作通常由网络地址转换(NAT)完成。1918地址并不是惟一保留的地址空间。但是,这些地址的定义为“本地站点”。组播也有一个保留的地址范围。这个地址范围并不是连接到互联网的:224.0.0.0 至224.0.0.255是组播“本地连接”的地址。
为了提供这个讲座的下一篇文章的必要的背景知识,我们需要确认大家都理解一个本地子网的概念。一旦我们分配给一台计算机一个合法的地址,假如子网掩码设置正确,这台计算机就可以同本地网络对话。子网掩码告诉这个操作系统哪一个IP地址在本地子网上,哪一个IP地址不在本地子网上。我们希望与之对话的IP地址位于本地子网,那么,这个操作系统不用使用路由器就能够直接与它对话。换句话说,操作系统能够使用ARP协议获得目标系统的物理地址并且开始对话。IP地址和子网掩码的设置对于普通的24位网络来说是非常简单的。标准的255.255.255.0子网掩码的意思是前三个八位字节是网络地址,最后的部分是为主机保留的。例如,一台计算机被分配了10.0.0.1的IP地址和255.255.255.0的子网掩码(如果你用二进制书写的话是24位),这台计算机能够同10.0.0.1至10.0.0.255地址范围内的任何人对话。
一定要消化这里讲的一切内容,因为下一讲我们将介绍使用CIDR(无类域间路由选择)设置子网的内容。
小结
•IP地址只是32位数字。子网掩码只是一个能够上下滑动这个IP地址字节的“盖子”,以便创建更大或者更小的网络。
•一个IP地址的网络部分告诉主机它的本地子网有多大,本地子网然后告诉主机它可以直接与谁对话。
•单向广播数据包发送到一台计算机,广播数据包发送到许多台计算机。
posted @
2006-05-27 20:58 Jerry Cat 阅读(651) |
评论 (3) |
编辑 收藏
[转]C程序设计的常用算法
算法(Algorithm):计算机解题的基本思想方法和步骤。算法的描述:是对要解决一个问题或要
完成一项任务所采取的方法和步骤的描述,包括需要什么数据(输入什么数据、输出什么结果)、采
用什么结构、使用什么语句以及如何安排这些语句等。通常使用自然语言、结构化流程图、伪代码等来描述算法。
一、计数、求和、求阶乘等简单算法
此类问题都要使用循环,要注意根据问题确定循环变量的初值、终值或结束条件,更要注意用来
表示计数、和、阶乘的变量的初值。
例:用随机函数产生100个[0,99]范围内的随机整数,统计个位上的数字分别为1,2,3,4,5,
6,7,8,9,0的数的个数并打印出来。
本题使用数组来处理,用数组a[100]存放产生的确100个随机整数,数组x[10]来存放个位上的数字
分别为1,2,3,4,5,6,7,8,9,0的数的个数。即个位是1的个数存放在x[1]中,个位是2的个数存
放在x[2]中,……个位是0的个数存放在x[10]。
void main()
{
int a[101],x[11],i,p;
for(i=0;i<=11;i++)
x[i]=0;
for(i=1;i<=100;i++)
{
a[i]=rand() % 100;
printf("%4d",a[i]);
if(i%10==0)printf("\n");
}
for(i=1;i<=100;i++)
{
p=a[i]%10;
if(p==0) p=10;
x[p]=x[p]+1;
}
for(i=1;i<=10;i++)
{
p=i;
if(i==10) p=0;
printf("%d,%d\n",p,x[i]);
}
printf("\n");
}
二、求两个整数的最大公约数、最小公倍数
分析:求最大公约数的算法思想:(最小公倍数=两个整数之积/最大公约数)
(1) 对于已知两数m,n,使得m>n;
(2) m除以n得余数r;
(3) 若r=0,则n为求得的最大公约数,算法结束;否则执行(4);
(4) m←n,n←r,再重复执行(2)。
例如: 求 m=14 ,n=6 的最大公约数. m n r
14 6 2
6 2 0
void main()
{
int nm,r,n,m,t;
printf("please input two numbers:\n");
scanf("%d,%d",&m,&n);
nm=n*m;
if (m<n)
{
t=n; n=m; m=t; }
r=m%n;
while (r!=0)
{
m=n; n=r; r=m%n;
}
printf("最大公约数:%d\n",n);
printf("最小公倍数:%d\n",nm/n);
}
三、判断素数
只能被1或本身整除的数称为素数 基本思想:把m作为被除数,将2—INT( )作为除数,如果都
除不尽,m就是素数,否则就不是。(可用以下程序段实现)
void main()
{
int m,i,k;
printf("please input a number:\n");
scanf("%d",&m);
k=sqrt(m);
for(i=2;i<k;i++)
if(m%i==0)
break;
if(i>=k)
printf("该数是素数");
else
printf("该数不是素数");
}
将其写成一函数,若为素数返回1,不是则返回0
int prime( m%)
{
int i,k;
k=sqrt(m);
for(i=2;i<k;i++)
if(m%i==0)
return 0;
return 1;
}
四、验证哥德巴赫猜想
(任意一个大于等于6的偶数都可以分解为两个素数之和)
基本思想:n为大于等于6的任一偶数,可分解为n1和n2两个数,分别检查n1和n2是否为素数,如都是,
则为一组解。如n1不是素数,就不必再检查n2是否素数。先从n1=3开始,检验n1和n2(n2=N-n1)是否
素数。然后使n1+2 再检验n1、n2是否素数,… 直到n1=n/2为止。
利用上面的prime函数,验证哥德巴赫猜想的程序代码如下:
#include "math.h"
int prime(int m)
{ int i,k;
k=sqrt(m);
for(i=2;i<k;i++)
if(m%i==0) break;
if(i>=k)
return 1;
else
return 0;
}
main()
{ int x,i;
printf("please input a even number(>=6):\n");
scanf("%d",&x);
if (x<6||x%2!=0)
printf("data error!\n");
else
for(i=2;i<=x/2;i++)
if (prime(i)&&prime(x-i))
{
printf("%d+%d\n",i,x-i);
printf("验证成功!");
break;
}
}
五、排序问题
1.选择法排序(升序)
基本思想:
1)对有n个数的序列(存放在数组a(n)中),从中选出最小的数,与第1个数交换位置;
2)除第1 个数外,其余n-1个数中选最小的数,与第2个数交换位置;
3)依次类推,选择了n-1次后,这个数列已按升序排列。
程序代码如下:
void main()
{
int i,j,imin,s,a[10];
printf("\n input 10 numbers:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(i=0;i<9;i++)
{
imin=i;
for(j=i+1;j<10;j++)
if(a[imin]>a[j])
imin=j;
if(i!=imin)
{
s=a[i];
a[i]=a[imin];
a[imin]=s;
}
printf("%d\n",a[i]);
}
}
2.冒泡法排序(升序)
基本思想:(将相邻两个数比较,小的调到前头)
1)有n个数(存放在数组a(n)中),第一趟将每相邻两个数比较,小的调到前头,经n-1次两两相邻比
较后,最大的数已“沉底”,放在最后一个位置,小数上升“浮起”;
2)第二趟对余下的n-1个数(最大的数已“沉底”)按上法比较,经n-2次两两相邻比较后得次大的数;
3)依次类推,n个数共进行n-1趟比较,在第j趟中要进行n-j次两两比较。
程序段如下
void main()
{
int a[10];
int i,j,t;
printf("input 10 numbers\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
printf("\n");
for(j=0;j<=8;j++)
for(i=0;i<9-j;i++)
if(a[i]>a[i+1])
{
t=a[i];
a[i]=a[i+1];
a[i+1]=t;
}
printf("the sorted numbers:\n");
for(i=0;i<10;i++)
printf("%d\n",a[i]);
}
3.合并法排序(将两个有序数组A、B合并成另一个有序的数组C,升序)
基本思想:
1)先在A、B数组中各取第一个元素进行比较,将小的元素放入C数组;
2)取小的元素所在数组的下一个元素与另一数组中上次比较后较大的元素比较,重复上述比较过程,
直到某个数组被先排完;
3)将另一个数组剩余元素抄入C数组,合并排序完成。
程序段如下:
void main()
{
int a[10],b[10],c[20],i,ia,ib,ic;
printf("please input the first array:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
for(i=0;i<10;i++)
scanf("%d",&b[i]);
printf("\n");
ia=0;ib=0;ic=0;
while(ia<10&&ib<10)
{
if(a[ia]<b[ib])
{
c[ic]=a[ia];
ia++;
}
else
{
c[ic]=b[ib];
ib++;
}
ic++;
}
while(ia<=9)
{
c[ic]=a[ia];
ia++;
ic++;
}
while(ib<=9)
{
c[ic]=b[ib];
b++;
ic++;
}
for(i=0;i<20;i++)
printf("%d\n",c[i]);
}
六、查找问题
1.①顺序查找法(在一列数中查找某数x)
基本思想:一列数放在数组a[1]---a[n]中,待查找的数放在x 中,把x与a数组中的元素从头到尾
一一进行比较查找。用变量p表示a数组元素下标,p初值为1,使x与a[p]比较,如果x不等于a[p],
则使p=p+1,不断重复这个过程;一旦x等于a[p]则退出循环;另外,如果p大于数组长度,循环也
应该停止。(这个过程可由下语句实现)
void main()
{
int a[10],p,x,i;
printf("please input the array:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
printf("please input the number you want find:\n");
scanf("%d",&x);
printf("\n");
p=0;
while(x!=a[p]&&p<10)
p++;
if(p>=10)
printf("the number is not found!\n");
else
printf("the number is found the no%d!\n",p);
}
思考:将上面程序改写一查找函数Find,若找到则返回下标值,找不到返回-1
②基本思想:一列数放在数组a[1]---a[n]中,待查找的关键值为key,把key与a数组中的元素从头到尾
一一进行比较查找,若相同,查找成功,若找不到,则查找失败。(查找子过程如下。index:存放
找到元素的下标。)
void main()
{
int a[10],index,x,i;
printf("please input the array:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
printf("please input the number you want find:\n");
scanf("%d",&x);
printf("\n");
index=-1;
for(i=0;i<10;i++)
if(x==a[i])
{
index=i;
break;
}
if(index==-1)
printf("the number is not found!\n");
else
printf("the number is found the no%d!\n",index);
}
2.折半查找法(只能对有序数列进行查找)
基本思想:设n个有序数(从小到大)存放在数组a[1]----a[n]中,要查找的数为x。用变量bot、
top、mid 分别表示查找数据范围的底部(数组下界)、顶部(数组的上界)和中间,
mid=(top+bot)/2,折半查找的算法如下:
(1)x=a(mid),则已找到退出循环,否则进行下面的判断;
(2)x<a(mid),x必定落在bot和mid-1的范围之内,即top=mid-1;
(3)x>a(mid),x必定落在mid+1和top的范围之内,即bot=mid+1;
(4)在确定了新的查找范围后,重复进行以上比较,直到找到或者bot<=top。
将上面的算法写成如下程序:
void main()
{
int a[10],mid,bot,top,x,i,find;
printf("please input the array:\n");
for(i=0;i<10;i++)
scanf("%d",&a[i]);
printf("please input the number you want find:\n");
scanf("%d",&x);
printf("\n");
bot=0;top=9;find=0;
while(bot<top&&find==0)
{
mid=(top+bot)/2;
if(x==a[mid])
{
find=1;
break;
}
else if(x<a[mid])
top=mid-1;
else
bot=mid+1;
}
if (find==1)
printf("the number is found the no%d!\n",mid);
else
printf("the number is not found!\n");
}
七、插入法
把一个数插到有序数列中,插入后数列仍然有序
基本思想:n个有序数(从小到大)存放在数组a(1)—a(n)中,要插入的数x。首先确定x插在数组
中的位置P;(可由以下语句实现)
#define N 10
void insert(int a[],int x)
{
int p, i;
p=0;
while(x>a[p]&&p<N)
p++;
for(i=N; i>p; i--)
a[i]=a[i-1];
a[p]=x;
}
main()
{
int a[N+1]={1,3,4,7,8,11,13,18,56,78}, x, i;
for(i=0; i<N; i++)
printf("%d,", a[i]);
printf("\nInput x:");
scanf("%d", &x);
insert(a, x);
for(i=0; i<=N; i++) printf("%d,", a[i]);
printf("\n");
}
八、矩阵(二维数组)运算
(1)矩阵的加、减运算
C(i,j)=a(i,j)+b(i,j) 加法
C(i,j)=a(i,j)-b(i,j) 减法
(2)矩阵相乘
(矩阵A有M*L个元素,矩阵B有L*N个元素,则矩阵C=A*B有M*N个元素)。矩阵C中任一元素
(i=1,2,…,m; j=1,2,…,n)
#define M 2
#define L 4
#define N 3
void mv(int a[M][L], int b[L][N], int c[M][N])
{
int i, j, k;
for(i=0; i<M; i++)
for(j=0; j<N; j++)
{
c[i][j]=0;
for(k=0; k<L; k++)
c[i][j]+=a[i][k]*b[k][j];
}
}
main()
{
int a[M][L]={{1,2,3,4},{1,1,1,1}};
int b[L][N]={{1,1,1},{1,2,1},{2,2,1},{2,3,1}}, c[M][N];
int i, j;
mv(a,b,c);
for(i=0; i<M; i++)
{
for(j=0; j<N; j++)
printf("%4d", c[i][j]);
printf("\n");
}
}
(3)矩阵传置
例:有二维数组a(5,5),要对它实现转置,可用下面两种方式:
#define N 3
void ch1(int a[N][N])
{
int i, j, t;
for(i=0; i<N; i++)
for(j=i+1; j<N; j++)
{
t=a[i][j];
a[i][j]=a[j][i];
a[j][i]=t;
}
}
void ch2(int a[N][N])
{
int i, j, t;
for(i=1; i<N; i++)
for(j= 0; j<i; j++)
{
t=a[i][j];
a[i][j]=a[j][i];
a[j][i]=t;
}
}
main()
{
int a[N][N]={{1,2,3},{4,5,6},{7,8,9}}, i, j;
ch1(a); /*或ch2(a);*/
for(i=0; i<N; i++)
{
for(j=0; j<N; j++)
printf("%4d", a[i][j]);
printf("\n");
}
}
(4)求二维数组中最小元素及其所在的行和列
基本思路同一维数组,可用下面程序段实现(以二维数组a[3][4]为例):
‘变量max中存放最大值,row,column存放最大值所在行列号
#define N 4
#define M 3
void min(int a[M][N])
{
int min, row, column, i, j;
min=a[0][0];
row=0;
column=0;
for(i=0; i<M; i++)
for(j=0; j<N; j++)
if(a[i][j]<min)
{
min=a[i][j];
row=i;
column=j;
}
printf("Min=%d\nAt Row%d,Column%d\n", min, row, column);
}
main()
{
int a[M][N]={{1,23,45,-5},{5,6,-7,6},{0,33,8,15}};
min(a);
}
九、迭代法
算法思想:对于一个问题的求解x,可由给定的一个初值x0,根据某一迭代公式得到一个新的值x1,
这个新值x1比初值x0更接近要求的值x;再以新值作为初值,即:x1→x0,重新按原来的方法求x1,重复
这一过和直到|x1-x0|<ε(某一给定的精度)。此时可将x1作为问题的解。
例:用迭代法求某个数的平方根。 已知求平方根的迭代公式为:
#include<math.h>
float fsqrt(float a)
{
float x0, x1;
x1=a/2;
do
{
x0=x1;
x1=0.5*(x0+a/x0);
}while(fabs(x1-x0)>0.00001);
return(x1);
}
main()
{
float a;
scanf("%f", &a);
printf("genhao =%f\n", fsqrt(a));
}
十、数制转换
将一个十进制整数m转换成 →r(2-16)进制字符串。
方法:将m不断除 r 取余数,直到商为零,以反序得到结果。下面写出一转换函数,参数idec为十
进制数,ibase为要转换成数的基(如二进制的基是2,八进制的基是8等),函数输出结果是字符串。
char *trdec(int idec, int ibase)
{
char strdr[20], t;
int i, idr, p=0;
while(idec!=0)
{
idr=idec % ibase;
if(idr>=10)
strdr[p++]=idr-10+65;
else
strdr[p++]=idr+48;
idec/=ibase;
}
for(i=0; i<p/2; i++)
{
t=strdr[i];
strdr[i]=strdr[p-i-1];
strdr[p-i-1]=t;
}
strdr[p]=’\0’;
return(strdr);
}
main()
{
int x, d;
scanf("%d%d", &x, &d);
printf("%s\n", trdec(x,d));
}
十一、字符串的一般处理
1.简单加密和解密
加密的思想是: 将每个字母C加(或减)一序数K,即用它后的第K个字母代替,变换式公式:c=c+k
例如序数k为5,这时 A→ F, a→f,B→?G… 当加序数后的字母超过Z或z则 c=c+k -26
例如:You are good→ Dtz fwj ltti
解密为加密的逆过程
将每个字母C减(或加)一序数K,即 c=c-k,
例如序数k为5,这时 Z→U,z→u,Y→T… 当加序数后的字母小于A或a则 c=c-k +26
下段程序是加密处理:
#include<stdio.h>
char *jiami(char stri[])
{
int i=0;
char strp[50],ia;
while(stri[i]!=’\0’)
{
if(stri[i]>=’A’&&stri[i]<=’Z’)
{
ia=stri[i]+5;
if (ia>’Z’)
ia-=26;
}
else if(stri[i]>=’a’&&stri[i]<=’z’)
{
ia=stri[i]+5;
if (ia>’z’)
ia-=26;
}
else
ia=stri[i];
strp[i++]=ia;
}
strp[i]=’\0’;
return(strp);
}
main()
{
char s[50];
gets(s);
printf("%s\n", jiami(s));
}
2.统计文本单词的个数
输入一行字符,统计其中有多少个单词,单词之间用格分隔开。
算法思路:
(1)从文本(字符串)的左边开始,取出一个字符;设逻辑量word表示所取字符是否是单词内的字符,
初值设为0
(2)若所取字符不是“空格”,“逗号”,“分号”或“感叹号”等单词的分隔符,再判断word是否为1,若
word不为1则表是新单词的开始,让单词数num = num +1,让word =1;
(3)若所取字符是“空格”,“逗号”,“分号”或“感叹号”等单词的分隔符, 则表示字符不是单词内字符,让word=0;
(4) 再依次取下一个字符,重得(2)(3)直到文本结束。
下面程序段是字符串string中包含的单词数
#include "stdio.h"
main()
{
char c,string[80];
int i,num=0,word=0;
gets(string);
for(i=0;(c=string[i])!='\0';i++)
if(c==' ') word=0;
else if(word==0)
{
word=1;
num++;
}
printf("There are %d word in the line.\n",num);
}
十二、穷举法
穷举法(又称“枚举法”)的基本思想是:一一列举各种可能的情况,并判断哪一种可能是符合要
求的解,这是一种“在没有其它办法的情况的方法”,是一种最“笨”的方法,然而对一些无法用解析法
求解的问题往往能奏效,通常采用循环来处理穷举问题。
例: 将一张面值为100元的人民币等值换成100张5元、1元和0.5元的零钞,要求每种零钞不少于1张,问有哪几种组合?
main()
{
int i, j, k;
printf(" 5元 1元 5角\n");
for(i=1; i<=20; i++)
for(j=1; j<=100-i; j++)
{
k=100-i-j;
if(5*i+1*j+0.5*k==100)
printf(" %3d %3d %3d\n", i, j, k);
}
}
十三、递归算法
用自身的结构来描述自身,称递归
VB允许在一个Sub子过程和Function过程的定义内部调用自己,即递归Sub子过程和递归Function
函数。递归处理一般用栈来实现,每调用一次自身,把当前参数压栈,直到递归结束条件;然后从栈
中弹出当前参数,直到栈空。
递归条件:(1)递归结束条件及结束时的值;(2)能用递归形式表示,且递归向终止条件发展。
例:编fac(n)=n! 的递归函数
int fac(int n)
{
if(n==1)
return(1);
else
return(n*fac(n-1));
}
main()
{
int n;
scanf("%d", &n);
printf("n!=%d\n", fac(n));
}
posted @
2006-05-27 04:56 Jerry Cat 阅读(560) |
评论 (0) |
编辑 收藏
/********************************************\ | 欢迎转载, 但请保留作者姓名和原文链接, 祝您进步并共勉! | \********************************************/
作者: Jerry Cat 时间: 2006/05/25 链接:http://www.cppblog.com/jerysun0818/archive/2006/05/25/7618.html
resource dll
|
|
许多细心的人也许已经注意到,在VC的集成编译环境 (IDE)中有一个功能,你可以选中一个资源,在上面点击鼠标右键,选 择Insert Copy,选择一个不同的Condition。然而我用VC十多年,一直没有用过这个特性,也从来不知道他是干吗的。前两天有 个网友问我这个东西是干吗的,才下决心去研究一下。仔细研究之后,才发现他是用于资源DLL的。
对于提供国际化解决方案的程序设计者而言,根据用户地区设置来显示对应的语言界面,将是非常有意义的。本文的目的是提 供一个step by step的建议,让没有做过资源dll的人可以从中受益。由于我也没做过资源dll,研究这个也就半天时间,因此如果 有什么错误,也很正常。请发现错误的朋友直接发邮件给我。对于你的帮助,我将非常感谢。
主工程的要求
对于一个需要使用资源dll实现国际化的工程,我们必须保证每个展现给用户的界面元素都必须从资源里获得。这主要指得是名 目繁多的字符串。程序设计者喜欢用AfxMessageBox显示信息。在单语言情况下,这当然没有问题。但是如果是多语言环境, 这样做就很难控制这个显示信息和当前的区域设置匹配。
另外,主工程的设计应该在创建资源dll之前完成。这样做的原因是,资源dll设计很简单,花不了多少时间。而如果资源的修 改,如果没有正确反映到代码上,有时候程序会莫名其妙的崩溃了,而你需要花很长的时间来找到原因。我自己的一个例子 是:我在icon中插入了一个65535色的图片,程序一直起不来,后来找了很久才找到原因。如果我们按照普通方式设计主工程, 并经过测试证明他工作正常,此时再做资源dll,就会避免在两个工程之间调试并找原因。
实际上对于增量开发情况,这种资源dll和主工程并存的情况可能难以避免,这时就需要增删资源特别小心,避免导致错误。
为了简化起见,我创建一个缺省的对话框工程来做测试,步骤如下:
-
启动Microsoft Visual Studio 6.0
-
点击File, New
-
选择Microsoft AppWizard(exe)工程,输入工程名test,点击OK
-
选择Dialog Based,并在What language would you like your resources in?下选择中文(此时选择自己最喜欢的语言即可,
我不熟悉英文,就选择中文了)
-
点击Finish创建工程
创建资源dll工程
我们可以按照下述步骤创建一个资源DLL:
-
创建一个普通的WIN32 Dynamic-Link Library(这里取名testdll)
-
把主工程中的所有资源相关的文件(rc,rc2,ico等)加入这个dll工程
-
点击菜单Build, Configurations,此时,我们应该有Release和Debug两个配置
-
点击Add按钮,以Debug为模板创建DebugEN和DebugCN
-
重复上述步骤,以Release为模板,创建ReleaseEN和ReleaseCN
-
删除原来的Debug和Release配置,然后点击Close退出配置对话框
-
按组合键ALT+F7弹出Project Settings对话框
-
选择Resources页
-
左侧选择All Configurations
-
在Preprocessor Definitions中添加如下宏AFX_RESOURCE_DLL
-
左侧选择Multiple Configurations,选中DebugEN和ReleaseEN
-
重复第10步,输入AFX_TARG_ENU宏(这是为美国英语而定义的宏English U.S)
-
重复11,12步,为DebugCN和ReleaseCN添加AFX_TARG_CHS宏(这是为简体中文的宏Chinese Simplified)
-
选择Link页,为每个配置指定输出文件名(我这里分别为testdll_end.dll, testdll_enr.dll, testdll_cnd.dll,testdll_cnr.dll,
分别表示英文debug,英文release和中文相关文件)
-
关闭Project Settings对话框
-
选中Resource View
-
在每个资源ID上右击,选择Insert Copy,改变Language为English(U.S)
-
如果为同一种语言你还希望提供根据某个自定义的宏而选择不同的界面(如UI模式和Service模式),在Condition
中输入你用以区分的宏名,如SERVICE_DLL。
-
打开每个新创建的资源,根据自己的需要修改资源(注意:这里只建议修改文字、字体,必须严格保证不删除任何资源)
-
保存修改
-
编译资源DLL
修改主工程
由于我们打算用资源dll来提供资源信息,我们必须对主工程做如下修改:
-
打开工程
-
选择File View,删除rc,rc2,ico等和资源密切相关的文件。resource.h请不要删除
-
在CWinApp中添加一个私有的HMODULE类型的变量m_hResource
-
在InitInstance函数开头添加如下代码:
LANGID id = ::GetUserDefaultLangID(); switch(id) { case 0x804://中文 #ifdef _DEBUG m_hResource = LoadLibrary(_T("D:\\projects\\testdll\\DebugCN\\testdllcnd.dll")); #else m_hResource = LoadLibrary(_T("D:\\projects\\testdll\\ReleaseCN\\testdllcnr.dll")); #endif break; default: #ifdef _DEBUG m_hResource = LoadLibrary(_T("D:\\projects\\testdll\\DebugEN\\testdllend.dll")); #else m_hResource = LoadLibrary(_T("D:\\projects\\testdll\\ReleaseCN\\testdllenr.dll")); #endif } if(m_hResource == NULL) { return FALSE; }else{ AfxSetResourceHandle((HINSTANCE)m_hResource); }
此后再做一点简单的调试,应该就可以了。具体调试,就已经超出我这篇文章所能讨论的范围了。
|
posted @
2006-05-25 10:17 Jerry Cat 阅读(1178) |
评论 (2) |
编辑 收藏