0

字节对齐详解(x86 + ARM)


一.什么是字节对齐,为什么要对齐?
   现代计算机中内存空间都是按照byte划分的,从理论上讲似乎对任何类型的变量的访问可以从任何地址开始,但实际情况是在访问特定类型变量的时候经常在特 定的内存地址访问,这就需要各种类型数据按照一定的规则在空间上排列,而不是顺序的一个接一个的排放,这就是对齐。
   对齐的作用和原因:各个硬件平台对存储空间的处理上有很大的不同。一些平台对某些特定类型的数据只能从某些特定地址开始存取。比如有些架构的CPU在访问 一个没有进行对齐的变量的时候会发生错误,那么在这种架构下编程必须保证字节对齐.其他平台可能没有这种情况,但是最常见的是如果不按照适合其平台要求对 数据存放进行对齐,会在存取效率上带来损失。比如有些平台每次读都是从偶地址开始,如果一个int型(假设为32位系统)如果存放在偶地址开始的地方,那 么一个读周期就可以读出这32bit,而如果存放在奇地址开始的地方,就需要2个读周期,并对两次读出的结果的高低字节进行拼凑才能得到该32bit数 据。显然在读取效率上下降很多。

二.字节对齐对程序的影响:
   先让我们看几个例子吧(32bit,x86环境,gcc编译器):
设结构体如下定义:
struct A
{
   int a;
   char b;
   short c;
};
struct B
{
   char b;
   int a;
   short c;
};
现在已知32位机器上各种数据类型的长度如下:
char:1(有符号无符号同)    
short:2(有符号无符号同)    
int:4(有符号无符号同)    
long:4(有符号无符号同)    
float:4    double:8
那么上面两个结构大小如何呢?
结果是:
sizeof(strcut A)值为8
sizeof(struct B)的值却是12

结构体A中包含了4字节长度的int一个,1字节长度的char一个和2字节长度的short型数据一个,B也一样;按理说A,B大小应该都是7
之所以出现上面的结果是因为编译器要对数据成员在空间上进行对齐。上面是按照编译器的默认设置进行对齐的结果,那么我们是不是可以改变编译器的这种默认对齐设置呢,当然可以.例如:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
   char b;
   int a;
   short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct C)值是8。
修改对齐值为1:
#pragma pack (1) /*指定按1字节对齐*/
struct D
{
   char b;
   int a;
   short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
sizeof(struct D)值为7。
后面我们再讲解#pragma pack()的作用.

三.编译器是按照什么样的原则进行对齐的?
   先让我们看四个重要的基本概念:
1.数据类型自身的对齐值:
 对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
有 了这些值,我们就可以很方便的来讨论具体数据结构的成员和其自身的对齐方式。有效对齐值N是最终用来决定数据存放地址方式的值,最重要。有效对齐N,就是 表示“对齐在N上”,也就是说该数据的"存放起始地址%N=0".而数据结构中的数据变量都是按定义的先后顺序来排放的。第一个数据变量的起始地址就是数 据结构的起始地址。结构体的成员变量要对齐排放,结构体本身也要根据自身的有效对齐值圆整(就是结构体成员变量占用总长度需要是对结构体有效对齐值的整数 倍,结合下面例子理解)。这样就不能理解上面的几个例子的值了。
例子分析:
分析例子B;
struct B
{
   char b;
   int a;
   short c;
};
假 设B从地址空间0×0000开始排放。该例子中没有定义指定对齐值,在笔者环境下,该值默认为4。第一个成员变量b的自身对齐值是1,比指定或者默认指定 对齐值4小,所以其有效对齐值为1,所以其存放地址0×0000符合0×0000%1=0.第二个成员变量a,其自身对齐值为4,所以有效对齐值也为4, 所以只能存放在起始地址为0×0004到0×0007这四个连续的字节空间中,复核0×0004%4=0,且紧靠第一个变量。第三个变量c,自身对齐值为 2,所以有效对齐值也是2,可以存放在0×0008到0×0009这两个字节空间中,符合0×0008%2=0。所以从0×0000到0×0009存放的 都是B内容。再看数据结构B的自身对齐值为其变量中最大对齐值(这里是b)所以就是4,所以结构体的有效对齐值也是4。根据结构体圆整的要求, 0×0009到0×0000=10字节,(10+2)%4=0。所以0x0000A到0x000B也为结构体B所占用。故B从0×0000到0x000B 共有12个字节,sizeof(struct B)=12;其实如果就这一个就来说它已将满足字节对齐了, 因为它的起始地址是0,因此肯定是对齐的,之所以在后面补充2个字节,是因为编译器为了实现结构数组的存取效率,试想如果我们定义了一个结构B的数组,那 么第一个结构起始地址是0没有问题,但是第二个结构呢?按照数组的定义,数组中所有元素都是紧挨着的,如果我们不把结构的大小补充为4的整数倍,那么下一 个结构的起始地址将是0x0000A,这显然不能满足结构的地址对齐了,因此我们要把结构补充成有效对齐大小的整数倍.其实诸如:对于char型数据,其 自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,这些已有类型的自身对齐值也是基于数组考虑的,只 是因为这些类型的长度已知了,所以他们的自身对齐值也就已知了.
同理,分析上面例子C:
#pragma pack (2) /*指定按2字节对齐*/
struct C
{
   char b;
   int a;
   short c;
};
#pragma pack () /*取消指定对齐,恢复缺省对齐*/
第 一个变量b的自身对齐值为1,指定对齐值为2,所以,其有效对齐值为1,假设C从0×0000开始,那么b存放在0×0000,符合0×0000%1= 0;第二个变量,自身对齐值为4,指定对齐值为2,所以有效对齐值为2,所以顺序存放在0×0002、0×0003、0×0004、0×0005四个连续 字节中,符合0×0002%2=0。第三个变量c的自身对齐值为2,所以有效对齐值为2,顺序存放
在0×0006、0×0007中,符合 0×0006%2=0。所以从0×0000到0×00007共八字节存放的是C的变量。又C的自身对齐值为4,所以C的有效对齐值为2。又8%2=0,C 只占用0×0000到0×0007的八个字节。所以sizeof(struct C)=8.

四.如何修改编译器的默认对齐值?
1.在VC IDE中,可以这样修改:[Project]|[Settings],c/c++选项卡Category的Code Generation选项的Struct Member Alignment中修改,默认是8字节。
2.在编码时,可以这样动态修改:#pragma pack .注意:是pragma而不是progma.

五.针对字节对齐,我们在编程中如何考虑?

   如果在编程的时候要考虑节约空间的话,那么我们只需要假定结构的首地址是0,然后各个变量按照上面的原则进行排列即可,基本的原则就是把结构中的变量按照 类型大小从小到大声明,尽量减少中间的填补空间.还有一种就是为了以空间换取时间的效率,我们显示的进行填补空间进行对齐,比如:有一种使用空间换时间做 法是显式的插入reserved成员:
        struct A{
          char a;
          char reserved[3];//使用空间换时间
          int b;
}

reserved成员对我们的程序没有什么意义,它只是起到填补空间以达到字节对齐的目的,当然即使不加这个成员通常编译器也会给我们自动填补对齐,我们自己加上它只是起到显式的提醒作用.

六.字节对齐可能带来的隐患:
   代码中关于对齐的隐患,很多是隐式的。比如在强制类型转换的时候。例如:
unsigned int i = 0×12345678;
unsigned char *p=NULL;
unsigned short *p1=NULL;

p=&i;
*p=0×00;
p1=(unsigned short *)(p+1);
*p1=0×0000;
最后两句代码,从奇数边界去访问unsignedshort型变量,显然不符合对齐的规定。
在x86上,类似的操作只会影响效率,但是在MIPS或者sparc上,可能就是一个error,因为它们要求必须字节对齐.

七.如何查找与字节对齐方面的问题:
如果出现对齐或者赋值问题首先查看
1. 编译器的big little端设置
2. 看这种体系本身是否支持非对齐访问
3. 如果支持看设置了对齐与否,如果没有则看访问时需要加某些特殊的修饰来标志其特殊访问操作。

八.相关文章:转自http://.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx

ARM下的对齐处理
from DUI0067D_ADS1_2_CompLib

3.13 type  qulifiers

有部分摘自ARM编译器文档对齐部分

对齐的使用:
1.__align(num)
  这个用于修改最高级别对象的字节边界。在汇编中使用LDRD或者STRD时
  就要用到此命令__align(8)进行修饰限制。来保证数据对象是相应对齐。
  这个修饰对象的命令最大是8个字节限制,可以让2字节的对象进行4字节
  ,但是不能让4字节的对象2字节对齐。
  __align是存储类修改,他只修饰最高级类型对象不能用于结构或者函数对象。
 
2.__packed
 __packed是进行一字节对齐
 1.不能对packed的对象进行对齐
 2.所有对象的读写访问都进行非对齐访问
 3.float及包含float的结构联合及未用__packed的对象将不能字节对齐
 4.__packed对局部整形变量无影响
 5.强制由unpacked对象向packed对象转化是未定义,整形指针可以合法定
 义为packed。
    __packed int* p;  //__packed int 则没有意义
 6.对齐或非对齐读写访问带来问题
 __packed struct STRUCT_TEST
{
 char a;
 int b;
 char c;
}  ;    //定义如下结构此时b的起始地址一定是不对齐的
        //在栈中访问b可能有问题,因为栈上数据肯定是对齐访问[from CL]
//将下面变量定义成全局静态不在栈上
static char* p;
static struct STRUCT_TEST a;
void Main()
{
__packed int* q;  //此时定义成__packed来修饰当前q指向为非对齐的数据地址下面的访问则可以

p = (char*)&a;          
q = (int*)(p+1);      

*q = 0×87654321;
/*  
得到赋值的汇编指令很清楚
ldr      r5,0×20001590 ; = #0×12345678
[0xe1a00005]   mov      r0,r5
[0xeb0000b0]   bl       __rt_uwrite4  //在此处调用一个写4byte的操作函数
     
[0xe5c10000]   strb     r0,[r1,#0]   //函数进行4次strb操作然后返回保证了数据正确的访问
[0xe1a02420]   mov      r2,r0,lsr #8
[0xe5c12001]   strb     r2,[r1,#1]
[0xe1a02820]   mov      r2,r0,lsr #16
[0xe5c12002]   strb     r2,[r1,#2]
[0xe1a02c20]   mov      r2,r0,lsr #24
[0xe5c12003]   strb     r2,[r1,#3]
[0xe1a0f00e]   mov      pc,r14
*/

/*
如果q没有加__packed修饰则汇编出来指令是这样直接会导致奇地址处访问失败
[0xe59f2018]   ldr      r2,0×20001594 ; = #0×87654321
[0xe5812000]   str      r2,[r1,#0]
*/

//这样可以很清楚的看到非对齐访问是如何产生错误的
//以及如何消除非对齐访问带来问题
//也可以看到非对齐访问和对齐访问的指令差异导致效率问题
}

0

Shell编程 – 傻瓜教程1


    外壳扩展( Extention)是一个能向外壳(资源管理器)添加一些功能的COM对象。这有很多的内容,但是却很少有关于它们的易懂的文档告诉我们如何去编写这些外壳(Shell)程序。如果你想做对外壳很深入的了解,我极力向你推荐Dino Esposito 的非常好的一本书《Visual C++ Windows Shell Programming》。但是对于那些没有这本书并且仅仅关心如何去编写外壳扩展的人,我写的一指南将会令你非常惊讶,如果并非如此的话也能给你理解如何编写外壳扩展提供很好的帮助。要阅读这一指南,确保你对COMATL要相当熟悉。

第一部分包括了对外壳扩展的概要的介绍,并提供了一个上下文菜单扩展的例程来使你对以后的部分中充满兴趣。

什么是外壳扩展呢?

这有两部分,外壳和扩展(extension)。外壳指的是资源管理器(Explorer),而扩展是指当一个预订的事件(如:右键单击一个.doc文档)发生时,被资源管理器调用的你写的代码。所以以个外壳扩展是一个向资源管理器添加特色的COM对象。

一个外壳扩展是一个进程中服务器,它实现了一些与资源管理器通信的借口。而在我看来,ATL是快速实现一个扩展并使它运行的最简单的方法,因为你不用为一遍又一遍的写QueryInterface()AddRef()而大伤脑筋。而且在Windows NT/2000下调试扩展也变得更为容易。

有很多种的扩展,每种扩展在不同的事件发生时被调用。下面是一些比较通用的类型和它们被调用的情况:

类型

什么时候被调用

可以做什么

上下文菜单

用户在文件或目录右键单击时。在外壳扩展4.71版本以上,在目录窗口的背景上右键单击也将被调用。

向上下文菜单添加项目。

属性单

文件的属性单被显示时。

向属性单添加一个属性页。

拖扔

用户右键拖动项目并把它扔在一个目录窗口活着桌面上时。

添加项目至上下文菜单。

用户拖一个项目并把它扔到一个文件上时。

任何你想做的事。

查询信息(外壳版本4.71+)

用户鼠标在一个文件或像我的电脑一样的其他外壳对象上悬停时。

返回一个资源管理器在工具条提示上的字符串。

到现在为止你可能为什么一个扩展看起来想在资源管理器里。如果你安装了WinZip(有谁没有吗?),它就包括了许多种的外壳扩展,其中一个就是上下文句柄。下面世WinZip 8 为了压缩文件添加到上下文菜单的截图:

WinZip包含了添加菜单项目的代码,并提供敏感帮助(显示在资源管理器状态条的文本),并在用户选择WinZip命令之一时起作用。

WinZip还包含了拖和扔的句柄。这个类型和上下文菜单扩展非常类似,但是它仅仅在用户通过鼠标右键拖动一个文件时才被调用。下面是WinZip的拖扔句柄如何添加上下文菜单:

还有很多种其他类型(微软一直往新版本的Windows里添加更多内容)。到现在,我们已经看到了上下文菜单扩展,因为它非常容易编写,我们将很容易的看到它的结果(很快就能满意)。

在我们开始编码以前,有一些提示,它将使我们做起来更加容易。当你促成一个外壳扩展被资源管理器调用的时候,它将在内存中呆上一小会儿,从而使它不能立即被重建(rebuild)。为了使资源管理器更加频繁的卸载这些扩展,创建这个注册表键:

HKLM\Software\Microsoft\Windows\CurrentVersion\Explorer\AlwaysUnloadDLL

并把它的默认值设为”1”。在Windows 9x系列中,这是最好的方法。在NT/2000,到如下的键:

HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer

创建一个叫做DesktopProcess的双字节值,使它的值为1。这使得桌面和任务栏运行在一个进程中,后发的资源管理器运行在它自己的进程里。这就意味着你可以使用一个单独的资源管理器窗口来调试,并且当你关掉它的时候,你的DLL也会自动的被卸载,避免了该文件仍然在使用得问题。要使得你的注册表修改生效的话,你必须注销并且重新登录。

我将稍晚一些解释如何在Win 9x下进行调试。

开始一个上下文菜单扩展它能做什么?

让我们开始简单的做一个扩展,它仅仅弹出一个消息框表示它已经在工作了。我们将对扩展名为.txt的文件设置一个钩子,这样当用户右键单击一个文本文件的时候,我们的扩展就能被调用了。

使用AppWizard 开始

好了,现在是我们开始的时候了。那是什么?我还没有告诉你如何使用神秘的外壳扩展接口?不要担心,我将在接下来的过程中为你解释。我发现如果一个概念被解释,有一个例子更容易明白,通过例子代码你很快就能理解。我将会先解释任何东西,然后给出代码,但是我发现还是不容易吸收。总之,启动你的MSVC吧,我们要开始了。

运行AppWizard ,做一个新的ATL COM wizard app。我们叫它SimpleExt 。在向导中保持所有默认选项,单击完成。我们现在就有了一个空的将会生成DLLATL项目,但是我们必须添加自己的外壳扩展COM对象。在ClassView树中,右键单击SimpleExt classes 项,选择New ATL Object

ATL Object 向导,第一面板已经选择了Simple Object ,只要单击下一步就行了。在第二面板中,在Short Name 编辑控件中输入SimpleShlExt ,然后单击确定(面板中的其它的编辑框将会自动完成)。这就创建了一个类名为CSimpleShlExt 的类,它包含了实现一个COM对象的基本代码。我们将向这个类添加我们的代码。

初始化接口

当我们的外壳扩展被装载的时候,资源管理器调用我们的QueryInterface()函数获取一个指向IShellExtInit 接口的指针。这个接口只有一个方法,Initialize(),它的原型如下:

HRESULT IShellExtInit::Initialize (
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID );

资源管理器使用这个方法给我们不同的信息。pidlFolder 是包含有正在被作用的文件的文件夹的PIDL(PIDL[pointer to an ID list]是唯一标志外壳中任一对象(无论是否是文件系统对象)的数据结构。pDataObj 是一个IDataObject 接口指针,通过它我们可以获得被作用的文件的文件名。hProgID 是一个打开的HKEY ,通过它我们可以访问包含有我们的DLL注册数据的注册表键。在这个简单的扩展中,我们只需要用到pDataObj参数。

添加这个接口方法到我们的COM 对象中,先打开SimpleShlExt.h 文件,并添加如下用红色书写的代码行:

#include <shlobj.h>
#include <comdef.h>
 
 ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>,
    public IShellExtInit
{
BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(ISimpleShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IShellExtInit)
END_COM_MAP()

这个COM_MAP是说明了ATL如何实现它的QueryInterface()。这个列表告诉ATL其他使用QueryInterface()的程序可以从我们这儿获得什么。

接着,在类的声明当中,添加Initialize()函数。此外,我们还需要一个保存文件名的变量:

protected:
    TCHAR m_szFile [MAX_PATH];
 
public:
    // IShellExtInit
    STDMETHOD(Initialize)(LPCITEMIDLIST, LPDATAOBJECT, HKEY);

接下来,在SimpleShlExt.cpp 文件中,添加该函数的定义:

HRESULT CSimpleShlExt::Initialize ( 
    LPCITEMIDLIST pidlFolder,
    LPDATAOBJECT pDataObj,
    HKEY hProgID )

我们所要做的就是获得被右键单击的文件的文件名,并把它显示在一个消息框中。如果有很多个文件被选中,你可以通过pDataObj 接口指针访问它们。但是为了保持该例子的简单性,我只要获得第一个文件的文件名。

文件名被保存为与你使用WS_EX_ACCEPTFILES样式拖和扔一个文件到窗口时用到的一个相同的格式。那就意味着,我们获得文件名使用了相同的API: DragQueryFile()。我们通过获得包含在IDataObject 中的数据的句柄开始这个函数:

{
FORMATETC fmt = { CF_HDROP, NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };
STGMEDIUM stg = { TYMED_HGLOBAL };
HDROP     hDrop;
 
    // Look for CF_HDROP data in the data object.
    if ( FAILED( pDataObj->GetData ( &fmt, &stg )))
        {
        // Nope! Return an "invalid argument" error back to Explorer.
        return E_INVALIDARG;
        }
 
    // Get a pointer to the actual data.
    hDrop = (HDROP) GlobalLock ( stg.hGlobal );
 
    // Make sure it worked.
    if ( NULL == hDrop )
        {
        return E_INVALIDARG;
        }

要注意,错误检查是极其重要的,尤其是指针。因为我们的扩展运行在资源管理器的进程空间当中,如果我们的程序毁坏的话,同样会让资源管理器也毁坏的。在Win 9x下,这可能就意味着重新启动。

现在,我们有了一个HDROP 句柄,我们可以获得我们需要的文件名了。

    // Sanity check – make sure there is at least one filename.
UINT uNumFiles = DragQueryFile ( hDrop, 0xFFFFFFFF, NULL, 0 );
 
    if ( 0 == uNumFiles )
        {
        GlobalUnlock ( stg.hGlobal );
        ReleaseStgMedium ( &stg );
        return E_INVALIDARG;
        }
 
HRESULT hr = S_OK;
 
    // Get the name of the first file and store it in our member variable m_szFile.
    if ( 0 == DragQueryFile ( hDrop, 0, m_szFile, MAX_PATH ))
        {
        hr = E_INVALIDARG;
        }
 
    GlobalUnlock ( stg.hGlobal );
    ReleaseStgMedium ( &stg );
 
    return hr;
}

如果我们返回E_INVALIDAR,在右键单击事件发生时,资源管理器将不会再调用我们的扩展。如果我们返回S_OK ,那么资源管理器将会再次调用QueryInterface()来获得我们将要添加的另一个接口指针:IContextMenu

和上下文菜单交互的接口

一旦资源管理器初始化了我们的扩展,它将会调用IContextMenu 的方法让我们添加菜单项目、敏感帮助并完成用户的选择。

向我们的扩展中添加IContextMenu 接口和添加IShellExtInit相类似。打开SimpleShlExt.h并添加一下红颜色的代码:

class ATL_NO_VTABLE CSimpleShlExt : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CSimpleShlExt, &CLSID_SimpleShlExt>,
    public IDispatchImpl<ISimpleShlExt, &IID_ISimpleShlExt, &LIBID_SIMPLEEXTLib>,
    public IShellExtInit,
    public IContextMenu
{
BEGIN_COM_MAP(CSimpleShlExt)
    COM_INTERFACE_ENTRY(ISimpleShlExt)
    COM_INTERFACE_ENTRY(IDispatch)
    COM_INTERFACE_ENTRY(IShellExtInit)
    COM_INTERFACE_ENTRY(IContextMenu)
END_COM_MAP()

接着添加IContextMenu 方法的原型:

public:
    // IContextMenu
    STDMETHOD(GetCommandString)(UINT, UINT, UINT*, LPSTR, UINT);
    STDMETHOD(InvokeCommand)(LPCMINVOKECOMMANDINFO);
    STDMETHOD(QueryContextMenu)(HMENU, UINT, UINT, UINT, UINT);

更改上下文菜单

IContextMenu 3个方法。第一个,QueryContextMenu(),让我们更改菜单。它的原型为:

HRESULT IContextMenu::QueryContextMenu (
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags );

hmenu 是上下文菜单的句柄。uMenuIndex 是我们开始添加我们的菜单项目的开始位置。 uidFirstCmd uidLastCmd 是我们可以给菜单项目使用的命令ID值的范围。uFlags 指出为什么资源管理器正在调用QueryContextMenu(),这我们将在以后看到。

有关它的返回值你将会得到不同的答案,如果你问不同的人的话。Dino Esposito 的书上说它使被QueryContextMenu()添加的菜单项目的号码。MSDN上关于VC 6部分说它是最后一个被添加的菜单项目的命令ID加上1。最新的MSDN文档有如下说明:

设置代码的值[HRESULT返回的]为被分配的最大的命令ID偏移加上1。例如,假定idCmdFirst被设置为5,你添加了3个菜单项目分别使用命令ID578。它的返回值将是MAKE_HRESULT(SEVERITY_SUCCESS, 0, 8 – 5 + 1)

到我现在所写的所有代码中,我接受了Dino的解释,这样工作的很好。事实上,他的制作返回值的方法和在线MSDN的方法是相同的,在你使用uidFirstCmd开始添加你的菜单项目时开始计数,每添加一个增加1

我们的简单的扩展将仅仅添加一个菜单项目,所以QueryContextMenu()函数相当简单:

HRESULT CSimpleShlExt::QueryContextMenu (
    HMENU hmenu,
    UINT  uMenuIndex, 
    UINT  uidFirstCmd,
    UINT  uidLastCmd,
    UINT  uFlags )
{
    // If the flags include CMF_DEFAULTONLY then we shouldn't do anything.
    if ( uFlags & CMF_DEFAULTONLY )
        {
        return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 0 );
        }
 
    InsertMenu ( hmenu, uMenuIndex, MF_BYPOSITION, uidFirstCmd, _T("SimpleShlExt Test Item") );
 
    return MAKE_HRESULT ( SEVERITY_SUCCESS, FACILITY_NULL, 1 );
}

我们首先要做的就是检查uFlags。你可以在MSDN查询所有的标志列表,但是对于上下文菜单扩展来说,只有一样是重要的:CMF_DEFAULTONLY。这个标志告诉名字空间扩展仅仅添加默认菜单项目。如果这个标志在的话,外壳扩展将不会添加任何菜单项目。这就是为什么当CMF_DEFAULTONLY存在的时候我们立即返回0的原因。如果该标志不存在,我们更改菜单(使用hmenu 句柄),然后返回1告诉外壳我们添加了一个菜单项目。

在状态条显示敏感帮助

IContextMenu 中下一个可以调用的方法是GetCommandString()。如果用户在资源管理器窗口中右键单击了一个文本文件的时候,或者选中一个文本文件,然后单击“文件”菜单,状态条上将显示敏感帮助。我们的GetCommandString() 函数将会返回一个让资源管理器显示得字符串。

GetCommandString()函数原型如下:

HRESULT IContextMenu::GetCommandString (
    UINT idCmd,
    UINT uFlags,
    UINT *pwReserved,
    LPSTR pszName,
    UINT cchMax );

idCmd 是一个基于0的指明哪个菜单项目被选中的数。因为我们仅仅添加了一个菜单项,idCmd 将总是为0。但是如果我们添加了,我是说,3个的话,idCmd 将会是0,1或者2uFlags是另一个标志组。我将会在后面进行描述。我们可以忽略pwReservedpszName 是一个指向一个被外壳所拥有的缓存的指针,该缓存保存被显示的帮助字符串。cchMax 是缓存的大小。返回值是HRESULT常量,例如S_OKE_FAIL

GetCommandString()同样能被用来获得菜单项的“动词”。“动词”是一个标志作用于文件的动作的字符串,它是独立于语言的。有关ShellExecute()的文档作了更多的说明,有关“动词”的主题更适合在另一篇文章说明,这儿简要说明的是列在注册表中的动词(比如说”open””print”),或者那些有上下文菜单扩展动态创建的“动词”。这使得在外壳扩展中实现的行为可以被ShellExecute()调用。

总之,我提及所有这些的原因是我们不得不确定为什么GetCommandString()被调用。如果资源管理器需要一个敏感帮助字符串的时候,我们就提供。如果资源管理器请求一个“动词”的话,我们将忽略它。这是uFlags起作用的地方。如果uFlags GCS_HELPTEXT 的位被设置的话,那么资源管理器将请求敏感帮助。附加的,如果GCS_UNICODE位被设置,我们必须返回一个Unicode字符串。

我们的GetCommandString() 的代码看起来应该像下面这样:

#include <atlconv.h>  // for ATL string conversion macros
 
HRESULT CSimpleShlExt::GetCommandString (
    UINT  idCmd,
    UINT  uFlags,
    UINT* pwReserved,
    LPSTR pszName,
    UINT  cchMax )
{
    USES_CONVERSION;
 
    // Check idCmd, it must be 0 since we have only one menu item.
    if ( 0 != idCmd )
        return E_INVALIDARG;
 
    // If Explorer is asking for a help string, copy our string into the
    // supplied buffer.
    if ( uFlags & GCS_HELPTEXT )
        {
        LPCTSTR szText = _T("This is the simple shell extension's help");
 
        if ( uFlags & GCS_UNICODE )
            {
            // We need to cast pszName to a Unicode string, and then use the
            // Unicode string copy API.
            lstrcpynW ( (LPWSTR) pszName, T2CW(szText), cchMax );
            }
        else
            {
            // Use the ANSI string copy API to return the help string.
            lstrcpynA ( pszName, T2CA(szText), cchMax );
            }
 
        return S_OK;
        }
 
    return E_INVALIDARG;
}

没什么奇特的;我只是把字符串编码并且把它转换为合适的字符集。如果你以前从来都没有使用过ATL变换宏,你干脆先看看它们,因为这将我使更容易理解传递一个Unicode字符串到COM方法和OLE函数中。在上面的代码中,我使用了T2CWT2CA 分别将TCHAR字符串转换为UnicodeANSI。在函数头部的USES_CONVERSION 宏声明了一个变换宏使用的局部变量。

一个需要注意的重要事项是lstrcpyn()API函数保证了目标字符串是以null结束的。这是它和CRT函数strncpy()的不同之处。如果源字符串的长度大于或等于cchMax时, strncpy() 并不添加结束符null。我建议你总是使用lstrcpyn(),这样你就不用不得不在strncpy()之后添加检查来保证字符串是null结束的。

执行用户的选择

最后一个IContextMenu方法是InvokeCommand()。这个方法将在用户单击我们添加的那个菜单项目时被调用。它的原型如下:

HRESULT IContextMenu::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo );

CMINVOKECOMMANDINFO 结构里有很多的信息,但是根据我们现在的意图,我们只需要关心lpVerbhwnd lpVerb 有双重的任务——它既可以是被调用的“动词”的名称,也可以是一个用以告诉我们哪个菜单项被选中地索引。hwnd 是资源管理器窗口的句柄,在那儿,用户调用了我们的扩展。

我们检查lpVerb ,因为我们只添加了一个菜单项,所以如果它为0,则我们的菜单被点击了。我能想到的最简单的事就是弹出一个消息框,所以我们就这么做。这个消息框显示了选中的文件的文件名,证明它的确是在工作。

HRESULT CSimpleShlExt::InvokeCommand ( LPCMINVOKECOMMANDINFO pCmdInfo )
{
    // If lpVerb really points to a string, ignore this function call and bail out.
    if ( 0 != HIWORD( pCmdInfo->lpVerb ))
        return E_INVALIDARG;
 
    // Get the command index - the only valid one is 0.
    switch ( LOWORD( pCmdInfo->lpVerb ))
        {
        case 0:
            {
            TCHAR szMsg [MAX_PATH + 32];
 
            wsprintf ( szMsg, _T("The selected file was:\n\n%s"), m_szFile );
 
            MessageBox ( pCmdInfo->hwnd, szMsg, _T("SimpleShlExt"),
                         MB_ICONINFORMATION );
 
            return S_OK;
            }
        break;
 
        default:
            return E_INVALIDARG;
        break;
        }
}

注册外壳扩展

到现在为止,我们已经实现了我们所有的COM接口。但是……如何使资源管理器使用我们的扩展呢?ATL自动生成了注册我们的DLL为一个COM服务器的代码,但是它仅仅是让其它程序来使用我们的DLL。为了告诉资源管理器我们的扩展存在,我们必须在保持文本文件的注册表键下注册它:

HKEY_CLASSES_ROOT\txtfile

在那个键下面,一个叫做ShellEx 的键保存了一个对于文本文件将被调用的外壳扩展列表。在ShellEx下,ContextMenuHandlers 键保存了一个上下文菜单扩展的列表。每一个扩展在ContextMenuHandlers下创建一个字键,并把他的默认值设置为它的GUID。所以,为我们的扩展,我们创建如下键:

HKEY_CLASSES_ROOT\txtfile\ShellEx\ContextMenuHandlers\SimpleShlExt

并把它的默认值设置为我们的GUID

"{5E2121EE-0300-11D4-8D3B-444553540000}"

然而,你不用自己做这件事。如果你在FileView页查看你得文件列表时,你会发现SimpleShlExt.rgs。这是一个由ATL解析的文本文件,它告诉ATL当这个服务器被注册时该添加什么键,当被反注册时又该删除什么键。下面我们指定了要添加的注册表入口:

HKCR
{
    NoRemove txtfile
    {
        NoRemove ShellEx
        {
            NoRemove ContextMenuHandlers
            {
                ForceRemove SimpleShlExt = s '{5E2121EE-0300-11D4-8D3B-444553540000}'
            }
        }
    }
}

它以"HKCR"——HKEY_CLASSES_ROOT的缩写——开头,每一行是注册表键名称。关键词NoRemove 意味着当该服务器被反注册时该键不能被删除。最后一行有点复杂。关键词ForceRemove 意思是如果该键存在,那么在该键被写之前先删除它。这一行剩下的部分指定了一个将被保存在SimpleShlExt键的默认值中的字符串(那就是”s”的意思)

在这儿,我需要说明一点。我们注册扩展时的键是HKCR\txtfile。然而,这个名称"txtfile" 并不是一个永久的或预先知道的。如果你查看一下HKCR\.txt ,那个键的默认值是这个名称被保存的地方。这就两个侧面效果:

  • 我们将不能可靠的使用RGS脚本,因为"txtfile"可能不是正确的键名。
  • 其他的一些文本编辑器可能被安装,它们同.TXT文件相关联。如果它们改变了 HKCR\.txt键的默认值,所有存在的外壳扩展都将会停止工作。

看起来,这的确是我设计的缺陷。我想微软也在考虑同样的事,因为最近创建的扩展,像QueryInfo扩展,是在.txt键下注册的。

好了,说明到这儿。有一个最终的注册细节。在Win NT/2000下,我们必须自己将我们的扩展方到一个“被认可的”扩展列表当中。如果我们不这么做的话,那些非管理员用户将不会壮在我们的扩展。这个列表被保存在:

HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Shell Extensions\Approved

在这个键下,我们创建一个字符串值它的名称是我们的GUID。字符串的内容可以是任何东西。做这些事情的代码在我们的DllRegisterServer()DllUnregisterServer()函数当中。我并不想把这些呆马列在这儿,因为这只是简单的注册表访问。你可以从本文的例子项目当中找到它们。

调试外壳扩展

最终,你写成了这个相当不容易的扩展,然后你将会调试它。打开你的项目设置(Project->Settings),到栏,在"Executable for debug session"编辑框中输入资源管理器的全路径,例如:"C:\windows\explorer.exe"。如果你使用的是NT2000,而且你已经设置过了DesktopProcess 注册表项,那么在你按F5开始调试的时候,会有一个新的资源管理器窗口打开。只要你在那个窗口工作,以后重建DLL时你将不会有问题,因为当你关掉窗口时,你的扩展也被卸载了。

Windows 9x下,恐怕你不得不在调试之前关闭你的外壳。单击“开始”->“关闭系统”。按住Ctrl+Alt+Shift然后点击“取消”。这将关闭资源管理器,然后你看见任务栏消失了。切换到MSVC然后按F5开始调试。按Shift+F5关闭资源管理器停止调试。当你做完调试的时候,你可以运行Explorer重新正常启动你的外壳。

它看起来是什么样的?

下面是我们添加的项目看起来的样子:

这就是我们的菜单!

下面是有敏感帮助时资源管理器的状态栏的样子:

而下面是消息框的样子,它显示了被选中的文件的文件名:

      本例程代码下载地址(11K)http://www.codeproject.com/shell/ShellExtGuide1/ShellExtGuide1_demo.zip

下一部分……

接着的第二部分,一个新的上下文菜单扩展将会告诉你如何同时对多个文件进行操作。

你可以从下面的网址获得这个和其他文章的最新版本:http://home.inreach.com/mdunn/code/

 

关于翻译:

      这是我第一次翻译文章,文章来自著名的http://www.codeproject.com/,翻译之前我看了csdn的开发文档,发现还是空白,所以就像把它翻译了,也许有对它感兴趣的人。文章总共有9个部分。我没有太多的时间,只翻译了第一部分,也许能起到抛砖引玉的作用,让那些对外壳扩展不了解的人入个门,入了门的多个参考。更多的文章大家可以从http://www.codeproject.com/shell/ 找到。例子代码,原文也可以从那儿找到。我的emailmefish@163.net,头一次翻译,做得不好,任何意见、建议、鲜花、掌声、石头、带酒的啤酒瓶都将受到热烈欢迎……

0

目前主流开发技术的分析和总结


转至: www.csdn.net

主流的程序设计语言:C++、Delphi(ObjectPascal)、、C#

  桌面应用程序框架:MFC、VCL、QT、JavaAWT\SWING、.Net

  企业应用程序框架:WindowsDNA(ASP、COM、COM+)、J2EE、.NetFramework

  开发工具:VisualBasic、Delphi、VisualC++、C++Builder、VisualC#
*程序设计语言:C++\Delphi(本来应该是ObjectPascal,但为了简单,我就语言和工具混为一谈吧)\Java\C#(虽然他刚刚推出,但因为微软为之倾注了大量心血,一定会成为一种重要的开发语言)

   *桌面应用程序框架:MFC\VCL

   *企业应用程序框架:WindowsDNA\J2EE\.Net

   *COM技术:我单独提出这项技术,是因为它无法简单的被视为语言、桌面应用程序框架或企业应用程序框架,它与这些都有关系。

  2.1 程序设计语言

  2.1.1 C++语言的演进

   最初要从二进制代码和汇编说起,但那太遥远了。我们就从面向过程的语言说起吧(包括Basic\C\Fortran\Pascal)。这种面向过程的高 级语言终于把计算机带入了寻常的应用领域。其中的C语言因为它的简单和灵活造就了Unix和Windows这样的伟大的软件。

  面向对象 的语言是计算机语言的一个合乎逻辑的进化,因为在没有过多的影响效率、简单性的前提下提供了一种更好的组织数据的方法,可使程序更容易理解,更容易管理 ——这一点可能会引出不同意见,但事实胜于雄辩,C++终于让C语言的领地越来越小,当今还活着的计算机语言或多或少的都具备面向对象的特征,所以这一点 并不会引起太多困惑。C++的成功很大程度要归因于C,C++成为它今天的样子是合乎逻辑的产物。因为在面向过程的时代,C几乎已经统一天下了。今天著名 的语言象Java\C#都从C借鉴了很多东西,C#本来的意思就是C++++。其实C++曾经很有理由统一面向对象程序设计语言的天下来着,但可惜的是, C++太复杂了。即使是一个熟练的程序员,要你很清楚的解释一些问题你也会很头痛。举几个还不是那么复杂的例子来说:

  对=的重载\成员转换函数\拷贝构造函数\转化构造函数之间有什么区别和联系呢?

  定义一个类成员函数private:virtualvoidMemFun()=0;是什么意义呢?

  int(*(*x(int))[4])(double);是什么意思?

   还有其他的特征,比如说可以用来制造一种新语言的typedef和宏(虽然宏不是C++的一部分,但它与C++的关系实在太密切了),让你一不小心就摔 跤的内存问题(只要new和delete就可以了吗?有没有考虑一个对象存放在容器中的情况?)……诸如此类,C++是如此的复杂以至于要学会它就需要很 长的时间,而且你会发现即使你用C++已经好几年了,你还会发现经常有新东西可学。你想解决一个应用领域的问题——比如说从数据库里面查询数据、更改数据 那样的问题,可是你却需要首先为C++头痛一阵子才可以,是的,你精通C++,你可以很容易的回答我的问题,可是你有没有想过你付出了多大的代价呢?我不 是想过分的谴责C++,我本人喜欢C++,我甚至建议一个认真的开发普通的应用系统的程序员也去学习一下C++,C++中的一些特性,比如说指针运算\模 板\STL几乎让人爱不释手,宏可以用几个字符代替很多代码,对系统级的程序员来说,C++的地位是不可替代的,Java的虚拟机就是C++写的。C++ 还将继续存在而且有旺盛的生命力。
2.1.2 Java和C#

  Java和C#相对于C++的不同最大的有两点:第一点是他们运 行在一个虚拟环境之中,第二点是语法简单。对于开发人员而言,在语法和语言机制的角度可以把Java和C#视为同一种语言。C#更多的是个政治的产物而不 是技术产物。如果不是Sun为难微软的话,我想微软不会费尽心力推出一个和Java差不多的C++++,记得Visual J++吗,记得WFC吗?看看那些东西就会知道微软为Java曾经倾注了多少心血。而且从更广泛的角度来说,两者也是非常相似的——C#和Java面对的 是同样的问题,面向应用领域的问题:事务处理、远程访问、Webservice、Web页面发布、图形界面。那么在这一段中,我暂且用Java这个名字指 代Java和C#两种语言——尽管两者在细节上确实有区别。Java是适合解决应用领域的问题的语言。最大的原因Java对于使用者来说非常简单。想想你 学会并且能够使用Java需要多长时间,学会并且能够使用C++要多长时间。由于Java很大程度上屏蔽了内存管理问题,而且没有那么多为了微小的性能提 升定义的特殊的内容(比如说,在Java里面没有virtual这个关键字,Java也不允许你直接在栈上创建对象,Java明确的区分bool和整型变 量),他让你尽量一致的方式操作所有的东西,除了基本数据类型,所有的东西都是对象,你必须通过引用来操作他们;除了这些之外,Java还提供了丰富的类 库帮助你解决应用问题——因为它是面向应用的语言,它为你提供了多线程标准、JDBC标准、GUI标准,而这些标准在C++中是不存在的,因为C++并不 是直接面向解决应用问题的用户,有人试图在C++中加入这些内容,但并不成功,因为C++本身太复杂了,用这种复杂的语言来实现这种复杂的应用程序框架本 身就是一件艰难的事情,稍后我们会提到这种尝试——COM技术。渐渐的,人们不会再用C++开发应用领域的软件,象MFC\QT\COM这一类的东西最终 也将退出历史舞台。

  2.1.3 Delphi

  Delphi是从用C++开发应用系统转向用Java开发应用系统的一 个中间产物。它比C++简单,简单的几乎象Java一样,因为它的简单,定义和使用丰富的类库成为可能,而且Delphi也这么做了,结果就是VCL和其 他的组件库。而另一方面,它又比运行于虚拟环境的Java效率要高一些,这样在简单性和效率的平衡之中,Delphi找到了自己的生存空间。而且预计在未 来的一段时间之内,这个生存空间将仍然是存在的。可以明显的看出,微软放弃了这个领域,他专注于两方面:系统语言C++和未来的Java(其实是. Net)。也许这对于Borland来说,是一件很幸运的事情。如果我能够给Borland提一些建议的话,那就是不要把Delphi弄得越来越复杂,如 果那样,就是把自己的用户赶到了C++或Java的领地。在虚拟机没有最终占领所有的应用程序开发领域之前,Delphi和Delphi的用户仍然会生存 得很好。

  2.2桌面应用程序框架

  目前真正成功的桌面应用程序框架只有两个,一个是MFC,一个是VCL,还有一些其他的,但事实上并未进入应用领域。遗憾的是我对两个桌面应用程序框架都不精通。但这不妨碍我对他做出正确的评价。

  2.2.1MFC

   MFC(还有曾经的OWL)是SDK编程的正常演化的结果,就象是C++是C的演化结果一样。MFC本身是一件了不起但不那么成功的作品,而且它过时 了。这就是我的结论。MFC凝聚了很多天才的智慧——当然,OWL和VCL也一样,侯捷的《深入浅出MFC》把这些智慧摆在了我们的面前。但是这件东西用 起来估计不会有人觉得很舒服,如果你一直在用Java、VB或者Delphi,再回过头来用MFC,不舒服的感觉会更加强烈。我不能够解释MFC为什么没 有能够最终发展成和VCL一样简单好用的桌面程序框架,也许是微软没精力或者没动力,总之MFC就是那个样子了,而且也不会再有发展,它已经被抛弃了。我 有时候想,也许基于C++这种复杂的语言开发MFC这样的东西本身就是错误的——可以开发这样的一个框架,但不应当要求使用它的人熟悉了整个框架之后才能 够使用这个系统,但很显然,如果你不了解MFC的内部机制,是不太可能把它用好的,我不能解释清楚为什么会出现这种现象。

  2.2.2VCL

   相比之下VCL要成功的得多。我相信很多使用VCL的人可能没有像MFC的用户研究MFC那样费劲的研究过VCL的内部机制。但这不妨碍他们开发出好用 好看的应用程序,这就足够了,还有什么好说的呢?VCL给你提供了一种简单一致的机制,让你可以写出复杂的应用程序。在李维的Borland故事那篇文章 中曾经说过,在Borland C++ 3.1推出之后Borland就有人提出开发类似C++ Builder一类的软件,后来竟未成行。是啊,如果C++ Builder是在那个时候出现的,今天的软件开发领域将会是怎么样的世界呢?真的不能想象。也许再过一段时间,这些都将不再重要。因为新生的语言如 Java和C#都提供了类似于VCL的桌面应用程序框架。那个时候,加上Java和C#本身的简单性,如果他们的速度有足够块,连Delphi这种语言也 要消失了,还有什么好争论的呢?只是对于今天的桌面程序开发人员来说,VCL确实是最好的选择。
2.3 企业应用程序框架

  2.3.1 DNA

   Windows DNA的起源无从探究了。随着.Net的推出,事实上Windows DNA将成为历史的陈迹。Windows DNA虽然是几乎所有的企业应用程序开发人员都知道的一个名词,但我相信Windows DNA事实上应用的最广泛的是ASP而不是COM+。真正的COM开发有多少人真正的掌握了呢,更不要提COM+(我有必要解释一下:COM+是COM的 执行环境,它提供了一系列如事务处理、安全等基础服务,让应用程序开发人员尽量少在基础架构设计上花精力)——当然我这里指的COM开发不是指VB下的 COM开发,所以要这么说,是因为我觉得如果不能理解用C++进行COM开发,也就不能真正的理解COM技术。如果以一种技术没有被广泛理解和应用作为失 败的标志,那么Windows DNA实际上是失败了,但这不是它本身的错,而是基于C++的COM技术的失败造成的。多层应用、系统开发角色分离的概念依然没有过时。
 2.3.2 J2EE

   J2EE是第一套成功的企业应用程序开发框架。因为它把事务处理、远程访问、安全这些概念带入了寻常百姓家。这种成功我认为要归因于Java的简单性。 Java的简单,对于J2EE容器供应商来说一样重要。开发一个基于Java的应用服务器要比基于C++的更容易。而且由于Java的简单性,应用系统开 发者出错的机会也会少一些,不会像C++的开发者那样受到那么多挫折。开发基于Java的企业应用系统的周期会更短,这恐怕是不容争辩的事实。不论如何, 是J2EE让事务处理、远程访问、安全这些原来几乎都是用在金融系统中的概念也被一般的企业用户使用并从中获得利益。

  2.3.3 .NET

   .Net有什么好说的呢?其实,它不过是微软的J2EE。事务处理、安全、远程访问,这些概念在.Net中都找得到。更有力的说明是,微软也利用了. Net实现了一个PetStore。所以,.Net与J2EE几乎是可以完全对等的。但微软确实是一家值得尊敬的公司——我指从技术上,象Web form这种东西,我相信很多Web应用开发者都梦想过甚至自己实现过,但Sun却一直无动于衷,而且似乎Borland也没有为此作过太多努力,好像有 过类似的东西,但肯定是不怎么成功——Borland已经很让人敬佩了,我们也许无法要求太多。
2.4 COM技术

  COM应当 是个更广泛的词汇,其实我觉得Axtive X、OLE、Auto mation、COM+都应当提及,因为如果你不理解COM,上面的东西你是无法理解的。而且,我只是想说明COM是一种即将消亡的技术,仅仅说说COM 的复杂性和他的问题就够了,所以不会提及那些东西。为什么会出现COM技术?COM的根本目标是实现代码的对象化的二进制重用,进而实现软件的组件化、软 件开发工作的分工。这要求他做到两点:第一,能够跨越不同的语言,第二,要跨越同一种语言的不同编译器。COM技术为这个目标付出了沉重的代价,尤其是为 了跨越不同的编译器,以至于无论对于使用者而言还是开发者而言,他都是一个痛苦的技术。但幸运的事,这一切终归要结束了。

  让我们从这个 目的出发看看COM为什么会成为它现在的样子。其实COM不是什么新玩意,最初的DLL就是重用二进制代码的技术。DLL在C的年代可能还不错,但到了C ++的年代就不行了。原因在于如果你在.h文件中改变了类定义(增加或者减少了成员变量),代码库用户的代码必须重新编译才可以,否则用户的代码会按你的 旧类的结构为你的新类分配内存,这将产生什么后果可想而知。这就是为什么通过接口继承和通过接口操作对象成为COM的强制规范的原因,能够通过对象的方式 组织代码是COM的重要努力。那么著名的IUnknown接口又是怎么回事呢?这是为了让使用不同编译器的C++开发人员能够共享劳动成果。

 

   首先看QueryInterface,因为COM是基于接口的,那么一个类可能实现了几个接口,而对于用户来说,你又只能通过操作接口来操作类,这样你 必须把类造型成某个特定的接口,使用Dynamic_cast吗?不行,因为这是编译器相关的,那么,就只好把它放在类的实现代码中了,这就是 QueryInterface的由来。至于AddRef和Release,他们产生的第一个原因是delete这个操作要求一个类具有虚析构函数(否则的 话,他的父类的析构函数将不会被调用),但不幸的是不同的编译器中析构函数在vtbl中的位置并不相同,这就是说你不能让用户直接调用delete,那么 你的COM对象必须提供自己删除自己的方法;另外的原因在于一个COM对象可能作为几个接口在被用户同时使用,如果用户粗暴的删掉了某个接口,那么其他的 接口也就不能用了,这样,只好要求用户在想用一个接口的时候通过AddRef通知COM对象“我要使用你了,你多了一个用户”,而在不用之后要调用 Release告诉COM对象“我不用你了,你少了一个用户”,如果COM对象发现自己没有用户了,它就把自己删掉。

  再看看诡异的 HRESULT,这是跨语言和跨编译器的代价。其实,异常处理是物竞天择的结果——连一直用效率作标榜的C++都肯牺牲效率来实现这个try- catch,可见它的意义,但COM为了照顾那些低级的语言居然抛弃了这个特征——产生的结果就是HRESULT。我们可以看看他是多么愚蠢的东西。首 先,我们要通过一个整数来传递错误信息,通过IErrorInfo来查找真正的错误描述,本来在现代语言中一个try-catch可以解决的问题,现在我 们却需要让用户和开发者都走很多路才能解决,你怎么评价这个结果?其次,由于这个返回计算结果的位置被占用了,我们不得不用怪异的out参数来返回结果。 想想一个简单的int add(intop1,intop2)在COM中竟然要变成HRESULT add(intop1,intop2,int* result),我不知道你对此作何感想。而且由于COM的方法无法返回一个对象而只能返回一个指针,为了完成一个简单的std::string GetName()这一类的操作,你要费多少周折——你需要先分配一块内存空间,然后在COM实现中把一个字符串拷贝到这个空间,用完了你要删掉这个空 间,不知道你是否觉得这种工作很幸福,反正我觉得很痛苦。还有COM为了照顾那些解释性的语言,又加入了Automation技术,你有没有为此觉得痛 苦?本来一个简单的方法调用,现在却需要传给你一个标志变量,然后让你根据这个标志变量去执行相应的操作。(这一点我现在仍然不明白,为什么解释性的语言 需要通过这个方式来执行一个方法)。“我受够了,我觉得头痛”,你会说,是啊,我想所有的人都受够了,所有这些因素实际上是把COM技术变成了一头让人无 法驾驭的怪兽。

  人对复杂事物的掌控能力终究是有限的,C++本身已经够复杂了,COM的复杂性已经超出了我们大部分人的控制能力,你需 要忍受种种痛苦得到的东西与你付出的代价相比是不是太不值得了?我们学习是为了解决问题,而现在我们却需要为了学习这件事情本身耗费太多的精力。这些痛苦 的东西太多了,我在这里说到的,其实只是一小部分而已。计算机技术是为人类服务的,而不是少数人的游戏(我想COM技术可能是他的设计者和一部分技术作者 的游戏),难道我们愿意成为计算机的奴隶吗?通过一种过于复杂的技术抛弃所有的人其实等于被所有的人抛弃,这么多年中选择J2EE的人我相信不乏高手,你 是不是因为COM的过于复杂才选择J2EE的?因为它可以用简单的途径实现差不多的目标——软件的“二进制”重用、组件化、专业分工(容器开发商和应用开 发商的分工)。事实上,你是被微软所抛弃的,同时,你也抛弃了微软。

  现在让我们回来吧,我把你带进了COM的迷宫,现在让我把你领回 来。再让我们看看COM到底想实现什么目标,其实很简单,不过是代码的二进制重用,也就是说你不必给别人源代码,而且你的组件可以象计算机硬件那样“即插 即用”。我们回过头来看看Java,其实,这种二进制重用的困难是C++所带来的(这不是C++本身的错,而是静态编译的错),当Java出现的时候,很 多问题已经不存在了。你不需要一个头文件,因为Java的字节码是自解释的,它说明了自己是什么样子的,可以做什么事情。不像C++那样需要一个头文件来 解释这些事情;也不需要事先了解对象的内存结构,因为内存是在运行的时候才分配的。如果我们现在再回过头来解决COM要解决的问题,你会怎么做呢?首先你 会不再选择C++这种语言来实现代码的“二进制”重用,其次,你会把所有的语言编译成同样的“二进制”代码(实际上,应当说是字节码),这显然是更聪明的 做法,从前COM试图在源代码的级别抹平二进制层次的差异,这实际上是让人在做本来应当由机器做的事情,很愚蠢是吗?但他们一直做了那么多年,而且把这个 痛苦带给了整个计算机世界——因为他们掌握着事实的标准,为了用户,为了利润,为了能够在Windows上运行,尽管你知道你在做着一个很不聪明的事情, 但你还是做了。

  COM技术为了照顾VB这个小兄弟和实现统一二进制世界的野心,实在浪费了太多的精力。首先,落后的东西的消亡是必然 的,就象C、Pascal在应用领域的消亡一样,Basic一点一点的改良运动是不符合历史潮流的做法,先进的东西和落后的东西在一起,要么先进的东西被 落后的东西拖住后腿,要么是同化落后的东西,显然我们更愿意看见后者,现在Basic终于被现代的计算机语言同化了。其次,统一二进制世界好像不是那么简 单的事情,而且也没什么必要,微软的COM技术奋战了10年,现在也只有他自己和Borland支持,.Net终于放弃了这个野心。这一切终于要结束了。

过去J2EE高歌猛进地占领着应用开发的领地,COM在这种进攻面前多少显得苍白无力。现在微软终于也有了可以和J2EE一较长短的.NET,对于开发 人员来讲,基于字节码的组件的二进制重用现在是天经地义的事情;你不用再为了能够以类方式发布组件做那么多乱七八糟的事情,因为现在所有的东西本来就是类 了;实现软件开发的分工也很自然,你是专业人员,就用C#吧,你是应用开发人员,你喜欢用VB.Net,你就用吧,反正你们写的东西最终都被翻译成了一样 的语言(其实我觉得这一点意义不大,因为一些不那么好用的语言被淘汰是正常的事情,C风格成为程序设计语言的主流风格,肯定是有它的原因的,语言的统一其 实是大势所趋,就象中国人民都要说普通话一样,我觉得Java不支持多语言算不上缺点——本来就是一种很好看很好用的语言了,为什么要把简单问题复杂化 呢?)。COM不会在短期内死去,因为我估计微软还不会马上用C#开发Office和Windows的图形界面部分,但我相信COM技术退出历史舞台的日 子已经不远了,作为一般的开发人员,忘了这段不愉快的历史吧——你将不会在你的世界里直接和COM打交道。若干年以后,我想COM也许会成为一场笑话,用 来讽刺那种野心过大、钻牛角尖的愚蠢的聪明人。

0

PHP5 效率优化


静态调用的成员一定要定义成 static  (5 ONLY)

PHP 5 引入了静态成员的概念,作用和 PHP 4 的函数内部静态变量一致,但前者是作为类的成员来使用。静态变量和 的类变量( variable)差不多,所有类的实例共享同一个静态变量。

<?php
class foo
{
    function
bar
() {
        echo
‘foobar’
;
    }
}

$foo = new foo;

// instance way

$foo->bar();

// static way

foo::bar();
?>

静态地调用非 static 成员,效率会比静态地调用 static 成员慢 50-60%。主要是因为前者会产生 E_STRICT 警告,内部也需要做转换。

使用类常量 (PHP5 ONLY)

PHP 5 新功能,类似于 C++ 的 const。

使用类常量的好处是:

- 编译时解析,没有额外开销
- 杂凑表更小,所以内部查找更快
- 类常量仅存在于特定「命名空间」,所以杂凑名更短
- 代码更干净,使除错更方便

(暂时)不要使用 require/include_once

require/include_once 每次被调用的时候都会打开目标文件!

- 如果用绝对路径的话,PHP 5.2/6.0 不存在这个问题
- 新版的 APC 缓存系统已经解决这个问题

文件 I/O 增加 => 效率降低

如果需要,可以自行检查文件是否已被 require/include。

不要调用毫无意义的函数

有对应的常量的时候,不要使用函数。

<?php
php_uname
(‘s’) == PHP_OS
;
php_version() == PHP_VERSION
;
php_sapi_name() == PHP_SAPI
;
?>
虽然使用不多,但是效率提升大概在 3500% 左右。

最快的 Win32 检查

<?php
$is_win
= DIRECTORY_SEPARATOR == ‘\\’
;
?>

- 不用函数
- Win98/NT/2000/XP/Vista/Longhorn/Shorthorn/Whistler…通用
- 一直可用

时间问题 (PHP>5.1.0 ONLY)

你如何在你的软件中得知现在的时间?简单,「time() time() again, you ask me…」。

不过总归会调用函数,慢。

现在好了,用 $_['REQUEST_TIME'],不用调用函数,又省了。

加速 PCRE

 对于不用保存的结果,不用 (),一律用 (?:)

这样 PHP 不用为符合的内容分配内存,省。效率提升 15% 左右。

- 能不用正则,就不用正则,在分析的时候仔细阅读手册「字符串函数」部分。有没有你漏掉的好用的函数?

加速 strtr

如果需要转换的全是单个字符的时候,用字符串而不是数组来做 strtr:

<?php
$addr
= strtr($addr, "abcd", "efgh");
// good
$addr = strtr($addr, array(‘a’ => ‘e’
,
                           
// …
                           
));
// bad
?>

效率提升:10 倍。

不要做无谓的替换

即使没有替换,str_replace 也会为其参数分配内存。很慢!解决办法:

- 用 strpos 先查找(非常快),看是否需要替换,如果需要,再替换

效率:

- 如果需要替换:效率几乎相等,差别在 0.1% 左右。
- 如果不需要替换:用 strpos 快 200%。

邪恶的 @ 操作符

不要滥用 @ 操作符。虽然 @ 看上去很简单,但是实际上后台有很多操作。用 @ 比起不用 @,效率差距:3 倍。

特别不要在循环中使用 @,在 5 次循环的测试中,即使是先用 error_reporting(0) 关掉错误,在循环完成后再打开,都比用 @ 快。

善用 strncmp

当需要对比「前 n 个字符」是否一样的时候,用 strncmp/strncasecmp,而不是 substr/strtolower,更不是 PCRE,更千万别提 ereg。strncmp/strncasecmp 效率最高(虽然高得不多)。

慎用 substr_compare (PHP5 ONLY)

按照上面的道理,substr_compare 应该比先 substr 再比较快咯。答案是否定的,除非:

- 无视大小写的比较
- 比较较大的字符串

不要用常量代替字符串

为什么:

- 需要查询杂凑表两次
- 需要把常量名转换为小写(进行第二次查询的时候)
- 生成 E_NOTICE 警告
- 会建立临时字符串

效率差别:700%。

不要把 count/strlen/sizeof 放到 for 循环的条件语句中

贴士:我的个人做法

<?php
for ($i = 0, $max = count($array);$i < $max; ++$i
);
?>

效率提升相对于:

- count 50%
- strlen 75%

短的代码不一定快

<?php
// longest
if ($a == $b
) {
   
$str .= $a
;
} else {
   
$str .= $b
;
}

// longer
if ($a == $b
) {
   
$str .= $a
;
}
$str .= $b
;

// short
$str .= ($a == $b ? $a : $b
);
?>

你觉得哪个快?

效率比较:

- longest: 4.27
- longer: 4.43
- short: 4.76

不可思议?再来一个:
<?php
// original
$d = dir(‘.’
);
while ((
$entry = $d->read()) !== false
) {
    if (
$entry == ‘.’ || $entry == ‘..’
) {
        continue;
    }
}

// versus
glob(‘./*’
);

// versus (include . and ..)
scandir(‘.’
);
?>

哪个快?

效率比较:

- original: 3.37
- glob: 6.28
- scandir: 3.42
- original without OO: 3.14
- SPL (PHP5): 3.95

画外音:从此也可以看出来 PHP5 的面向对象效率提高了很多,效率已经和纯函数差得不太多了。

提高 PHP 文件访问效率

需要包含其他 PHP 文件的时候,使用完整路径,或者容易转换的相对路径。

<?php

include ‘file.php’; // bad approach

incldue ‘./file.php’; // good

include ‘/path/to/file.php’; // ideal

?>

物尽其用

PHP 有很多扩展和函数可用,在实现一个功能的之前,应该看看 PHP 是否有了这个功能?是否有更简单的实现?

<?php
$filename
= "./somepic.gif"
;
$handle = fopen($filename, "rb"
);
$contents = fread($handle, filesize($filename
));
fclose($handle
);

// vs. much simpler

file_get_contents(‘./somepic.gif’);
?>

关于引用的技巧

引用可以:

- 简化对复杂结构数据的访问
- 优化内存使用
<?php
$a
['b']['c'
] = array();

// slow 2 extra hash lookups per access
for ($i = 0; $i < 5; ++$i
)
   
$a['b']['c'][$i] = $i
;

// much faster reference based approach
$ref =& $a['b']['c'
];
for (
$i = 0; $i < 5; ++$i
)
   
$ref[$i] = $i
;
?>

<?php
$a
= ‘large string’
;

// memory intensive approach
function a($str
)
{
    return
$str.‘something’
;
}

// more efficient solution
function a(&$str
)
{
   
$str .= ‘something’
;
}
?>

==============================================
参考资料
http://ilia.ws

Ilia 的个人网站,,他参与的开发以及出版的一些稿物链接等等。
http://ez.no

eZ components 官方网站,eZ comp 是针对 PHP5 的开源通用库,以效率为己任,Ilia 也参与了开发。
http://phparch.com

php|architect,不错的 php 出版商/培训组织。买不起或者买不到的话,网上可以下到很多经典的盗版。

http://talks.php.net

0

免费软件套装


在 CB 上看到的……

办公

OpenOffice - office suite
PC Suite 602 - office suite
AbiWord – text editor
Atlantis Nova – text editor
Microsoft PowerPoint Viewer - power point files viewer
Adobe Reader – pdf reader
Foxit PDF Reader – pdf reader
PDFCreator – create pdf documents
Doc Convertor - document convertor
Convert – unit convertor
Converber – unit convertor
Sunbird – calendar/organizer
EssentialPIM Free – calendar/organizer
PhraseExpress – speed up your writing
ATnotes – create notes on the desktop

解压缩

7-Zip - compression program
IZArc - compression program
TugZIP - compression program
CabPack – compression program
Universal Extractor – extract files from any type of archive

互联网

Firefox browser
Internet Explorer- web browser
Maxthon – web browser
Opera – web browser
Avant Browser – web browser
Thunderbird – email client
PopTray – check for emails
Free Download Manager – download manager
FlashGet - download manager
WellGet – download manager
Download Master – download manager
WGET – commandline download manager
HTTrack – offline browser
WebReaper – offline browser
Yeah Reader - RSS reader
GreatNews - RSS reader
RSSOwl – RSS reader

P2P

µTorrent – torrent client
Azureus – torrent client
BitComet – torrent client
ABC – torrent client
BitTornado – torrent client
eMule – p2p client
SoulSeek – p2p client
Shareaza – p2p client
DC++ – Direct Connect network client
PeerGuardian – IP blocker

聊天

Miranda – chat client
MSN Messenger – chat client
Yahoo Messenger – chat client
QIP – chat client
Gaim – chat client
JAJC – chat client
HydraIRC – IRC client
Talkative IRC – IRC client
IceChat – IRC client
Skype – VOIP client
Google Talk - VOIP client
VoipStunt – VOIP client
Gizmo – VOIP client
Wengo – VOIP client

安全

AVG Free – antivirus
Avast Home Free – antivirus
AntiVir PersonalEdition – antivirus
BitDefender Free – antivirus
ClamWin – antivirus
CyberDifender - Internet Security Suite
Ad-aware – anti-spyware
Spybot: Search & Destroy – anti-spyware
Windows Defender – anti-spyware
SpywareBlaster - anti-spyware
Spyware Terminator – anti-spyware
Tootkit Reveaker - rootkit detection utility
Winpooch - system protection
HiJack Free – system protection
HighJackThis – hijackers detector and remover
Kerio Personal Firewall – firewall
Sygate Personal Firewall - firewall
ZoneAlarm - firewall
AxCrypt – file encryption
Simple File Shredder – securely delete files
PuTTy - SSH client
KeePass – password manager
LockNote – password manager
nPassword – password manager
Microsoft Baseline Security Analyzer – identify security misconfigurations

网络

Hamachi – VPN client
RealVNC – remote control
UltraVNC – remote control
Ethereal – local area network administration
The Dude – network administration
Wireshark – network administration
Angry IP Scanner – IP scanner
IP-Tools - IP scanner
Free Port Scanner – IP scanner
NetMeter – network bandwidth monitoring

服务器

FileZilla – FTP client
FileZilla Server – FTP
EFTP – FTP client/server
XAMPP – integrated server package of Apache, mySQL, PHP and Perl
WAMP – Apache, PHP5 and MySQL server

音频

Foobar2000 – audio player
WinAmp – audio player
1by1 – audio player
JetAudio – audio player
XMPlay – audio player
Xion – audio player
Apollo – audio player
MediaMonkey – music organizer
The GodFather – music organizer
dBpowerAMP – audio converter
Audacity – audio converter
WavePad – audio converter
Kristal Audio Engine – audio editor
Exact Audio Copy – CD ripper
Audiograbber – CD ripper
CDex – CD ripper
Mp3 Tag Tools – tag editor
Mp3tag – tag editor
Taggin’ MP3 – tag editor
Monkey’s Audio – APE compressor/decompressor
mpTrim - mp3 editor
WavTrim - wave editor
EncSpot Basic – analyse mp3 files

视频

Windows Media Player – audio/video player
VLC – video player
Media Player Classic – video player
MV2Player – video player
CrystalPlayer 1.95 – video player
Zoom Player – video player
GOM Player – video player
viPlay – video player
DSPlayer – video player
VirtualDub – video editor
CamStudio – video screen recording
AviSplit – Avi splitter
Video mp3 Extractor – rip audio from video files
Free iPod Converter – convert all popular video formats to iPod video
MediaPortal – turning your PCinto a Media Center
The FilmMachine

图像

Gimp – image editor
PhotoFiltre – image editor
Paint.net – image editor
ArtRage - image editor
Artweaver – image editor
IrfanView - image viewer
Picasa - image viewer
XnView – image viewer
FastStone Image Viewer - image viewer
FuturixImager - image viewer
Easy Thumbnails – create thumbnails from images
JoJoThumb – create thumbnails from images
iWebAlbum - create web photo albums
JAlbum – create web photo albums
3D Box Shot Maker - design quality box shot
FastStone Capture - screen capture
WinSnap - screen capture

3D

Blender3D – 3D renderer
3Delight Free – 3D renderer
SketchUp – 3D modeling
Maya Learning Edition – 3D modeling

开发

AutoIt – task automation
SciTE4AutoIt3 - text editor for AutoIt
AutoHotkey - task automation
PHP Designer – PHP editor
Notepad++ – text editor
ConTEXT Editor – text editor
PSPad – text editor
FoxEditor - text editor
Crimson Editor - source code editor
Elfima Notepad - text editor
Notepad2 - text editor
Nvu editor
Alleycode - HTML editor
BlockNote - web page editor
Weaverslave – web page editor

CD/DVD

DeepBurner – CD/DVD burner
CDBurner XP Pro – CD/DVD burner
BurnAtOnce – CD/DVD burner
Express Burn – CD/DVD burner
Zilla CD-DVD Rip’n’Burn – CD/DVD
刻录
ImgBurn – ISO, BIN burner
Daemon tools – virtual CD/DVD
DVD Decrypter – DVD ripper
DVD Shrink – DVD ripper
Nero CD-DVD Speed - CD/DVD info and quality test
解码

GSpot - codec information
AC3Filter – audio codec
Xvid – video codec
QuickTime Alternative – video codec
Real Alternative – video codec
K-Lite Codec Pack – all codecs

系统工具

CCleaner – system cleaner
xp-AntiSpy – OS setup
jv16 Powertools – system utilities
XP SysPad – system monitoring utility
What’s Running – process guard
Registrar Lite – registry editor
WinIPConfig – replacement for “ipconfig.exe” and “route.exe”
Unlocker – file eraser
Eraser – secure file eraser
Undelete Plus – file recovery
freeCommander – file manager
ExplorerXP - file manager
Duplicate File Finder – find all duplicate files
Ant Renamer – file renaming
ReNamer – file renaming
Icons From File – icos extractor
Chaos MD5 – MD5 generator
HashTab - MD5, SHA1 and CRC-32 file hashes
Rainlendar Lite – desktop calendar
Weather Watcher – weather firecast
Subtitle Workshop – subtitles editor
Ant Movie Catalog – movie organizer
Disclib – CD organizer
Dexpot – virtual desktops
DriveImage XML – create partition images
MozBackup – backup and restore bookmarks, etc.
SyncBack – system backup
Atomic Cock Sync – syncronize your clock
Citrus Alarm Clock – alarm clock
TaskSwitchXP – Alt-Tab replacement
Launchy - application launcher
allSnap – make all snap
Sysinternals Tools – various system tools
StrokeIt - mouse gestures
Net Profiles – create profiles of your network settings
ResourceHacker – view, modify, rename, add, delete
Java Runtime Environment for Windows

UI设计

RocketDock - application launcher
AveDesk – desktop enhancer
IconPhile - customize windows’s system icons
CursorXP Free – change mouse cursors
MacSound – volume control
LClock - Windows Longhorn clock
Y’z Dock – application launcher
Y’z Shadow – shadow effect to the windows
Y’z Toolbar – change the toolbar icons in Explorer and Internet Explorer
Taskbar Shuffle – rearrange the programs on the taskbar by dragging
Visual Task Tips – thumbnail preview image for each task in the taskbar
Badges - put badges on any folder or file
Folderico - change icons of the folders
Folder Marker – mark your folders
Folder2MyPC – add favourite locations to My Computer
Microsoft TweakUI – system settings
BricoPacks packs
ShellPacks – shell packs
Tango Shell Patcher – shell patcher
XPize – GUI enhancer
Vista Transformation Pack - complete visual style
Vista Sound Scheme – Windows Vista sound scheme
Royale Theme - visual style

硬件检测

CPU-Z - cpu information
CrystalCPUID – cpu information
Central Brain Identifier – cpu information
Everest – system information
SiSoft Sandra - system information
SpeedFan - hardware monitor
Memtest86 – memory test
PowerMax – HDD test
3Dmark 06 - 3D game performance benchmark
Aquamark - performance benchmark
rthdribl – 3D benchmark
Fraps - 3D benchmark, fps viewer and screen recorder
Prime 95 - cpu benchmarking
SuperPI - cpu benchmarking
CPU Rightmark - cpu overclock
Core Temp - cpu temperature
ATiTool - video overclock
ATI Tray Tools – Radeon tweaker
aTuner - GeForce and Radeontweaker
RivaTuner – video overclock
Nokia Monitor Test - monitor adjustmets
UDPixel – fix dead pixels

游戏

123 Free Solitaire - solitaire games collection
Arcade Pack - classic arcade games
Live For Speed – online racing simulator
Enigma – puzzle game
Freeciv – multiplayer strategy game
Tux Racer – race down steep, snow-covered mountains

教育

SpeQ Mathematics - mathematics program
Dia – diagram creation program
Google Earth – explore the world
NASA World Wind – 3D virtual globe
Celestia – explore the space
Stellarium – planetarium

杂类

nLite – Build your own custom Windows disk.
VirtualPC – create virtual machines
grabMotion - webcam capture
iDailyDiary – simple page-for-a-day diary
Pivot Stickfigure Animator – create stick-figure animations
Wink – create presentations
Scribus – professional page layout
FreeMind – midn mapping software
Windows Live Writer – WYSIWYG authoring

墙纸

Michael Swanson - 1920 x 1200; 1600 x 1200; amazing wallpapers
Mikhail Arkhipov - 1920 x 1200; 1600 x 1200; amazing wallpapers

01

启发式评估


什么是启发式评估?
启发式评估是专家评审法的一种,就是让几个评审人员根据一些通用的可用性原则和自己的经验来发现系统内潜在的可用性问题。每一个评审人员可以发现35%的可用性问题,而5个评审人员能找到大约75%的可用性问题。

启发式评估该选择由什么样的人来进行?
启发式评估是专家评审法的一种,选用具有可用性知识或选用具有和被测试系统相关专业知识的“专家”,具有以上两种知识的人是最合适人选,他能多发现约20%的可用性问题。

启发式评估如何进行?
每一个评审人员进行1-2小时的使用系统,之后提供一份独立的报告,在报告中应包括可用性问题的描述,问题的严重性以及改进的建议。

启发式评估的通用准则
 •Visibility of system status.可视性原则
 •Match between system and real world.系统应符合用户的真实世界
 •User control and freedom.用户有自由控制权
 •Consistency.一致性原则
 •Error strategy.有预防用户出错的措施
 •Recognition rather than recall.要在第一时间让用户看到
 •Flexibility and efficiency of use.使用起来灵活且高效
 •Aesthetics and minialist design.易读性
 •Help users recognize, diagnose, and recover from errors.给用户明确的错误信息,并协助用户方便的从错误中恢复工作
•Help and Documentation.必要的帮助提示与说明文档

附:普渡大学可用性测试检查表
使用说明:本调查表共有100题,回答每一个问题时按照以后三个步骤:
(a)请评估每一个问题是否适用于所评审的系统。如果不适用,跳到下一题。如果适用,请继续回答。
(b)对于所评估的系统,请评价该问题的重要性(1是最不重要的,3是最重要的)
(c)评价系统在该问题上的表现(1是非常糟糕,7是非常好),如果不存在,请选择不存在项
1.兼容性
 1)光标的控制是否符合光标的移动?
 2)用户控制的结果是否符合用户的期望?
 3)所提供的控制是否符合用户的技能水平?
 4)界面的编码(例如,颜色、形状等)是否为用户所熟悉?
 5)用词是否为用户所熟悉?
2.一致性
 6)界面颜色的编码是否符合常规?
 7)编码是否在不同的显示及菜单上都保持一致?
 8)光标的位置是否一致?
 9)显示的格式是否一致?
 10)反馈信息是否一致?
 11)数据字段的格式是否一致?
 12)标号的格式是否一致?
 13)标号的位置是否一致?
 14)标号本身是否一致?
 15)显示的方向是否一致?(漫游或卷动)
 16)系统要求的用户动作是否一致?
 17)在不同的显示中用词是否一致?
 18)数据显示和数据输入的要求是否一致?
 19)数据显示是否符合用户的常规?
 20)图形数据的符号是否符合标准?
 21)菜单的用词和命令语言是否一致?
 22)用词是否符合用户指导的原则?
3. 灵活性
 23)是否可以使用命令语言而绕过菜单的选择?
 24)系统是否有直接操作的功能?
 25)数据输入的设计是否灵活?
 26)用户是否可以灵活地控制显示?
 27)系统是否提供了灵活的流程控制?
 28)系统是否提供了灵活的用户指导?
 29)菜单选项是否前后相关?
 30)用户是否可以根据他们的需要来命名显示和界面单元?
 31)系统是否为不同的用户提供了好的训练?
 32)用户是否可以自己改变视窗?
 33)用户是否可以自己命名系统命令?
 34)系统是否允许用户选择需要显示的数据?
 35)系统是否可以提供用户指定的视窗?
 36)为了扩展显示功能,系统是否提供放大的功能?
4. 可学习性
 37)用词是否清晰?
 38)数据是否有合理的分类,易于学习?
 39)命令语言是否有层次?
 40)菜单的分组是否合理?
 41)菜单的顺利是否合理?
 42)命令的名字是否有意义?
 43)系统是否提供了无惩罚的学习?
5. 极少化的用户动作
 44)系统是否为相关的数据提供了组合输入的功能?
 45)必要的数据是否只需要输入一次?
 46)系统是否提供了默认值?
 47)视窗之间的切换是否容易?
 48)系统是否为经常使用的控制提供了功能键?
 49)系统是否有全局搜索和替代的功能?
 50)菜单的选择是否可以使用点击的功能?(主要的流程控制方法)
 51)菜单的选择是否可以使用键入的功能?(辅助的控制方法)
 52)系统是否要求极少的光标定位?
 53)在选择菜单时,系统是否要求极少的步骤?
 54)系统是否要求极少的用户控制动作?
 55)为了退到更高一级菜单中,系统是否只需要一个简单的键入动作?
 56)为了退到一般的菜单中,系统是否只需要一个简单的键入动作?
6. 极小的记忆负担
 57)系统是否使用了缩写?
 58)系统是否为输入分层次的数据提供了帮助?
 59)指导信息是否总是可以得到的?
 60)系统是否为序列的选择提供了分层次的菜单?
 61)被选的数据是否有突出显示?
 62)系统是否为命令提供了索引?
 63)系统是否为数据提供了索引?
 64)系统是否提示在菜单结构中的当前位置?
 65)数据是否保存简短?
 66)为选择菜单使用的字母代码是否经过认真的设计?
 67)是否将长的数据分成不同的部分?
 68)先前的答案是否可以简便的再利用?
 69)字母大小写是否等同?
 70)系统是否使用短的代码而不使用长的代码?
 71)图符是否有辅助性的字符标号?
7. 知觉的有限性
 72)系统是否为不同的数据类别提供不同的编码?
 73)缩写是否清晰而相互不同?
 74)光标是否不同?
 75)界面单元是否清晰?
 76)用户指导的格式是否清晰?
 77)命令是否有清晰的意义?
 78)命令的拼写是否清晰?
 79)系统是否使用了易于分辨的颜色?
 80)目前活动的窗口是否有清楚的标识?
 81)为了直接比较,数据是否成对的摆在一起?
 82)是否限制语音信息使用的数量?
 83)系统是否提供了一系列相关信息?
 84)菜单是否和其他的显示信息有明显的区别?
 85)颜色的编码是否多余?
 86)系统是否提供了视觉上清晰可辨的数据字段?
 87)不同组的信息是否明显分开?
 88)屏幕的密度是否合理?
8. 用户指导
 89)系统反馈的错误信息是否有用?
 90)系统是否提供了“取消”的功能?
 91)错误的输入是否被显示出来?
 92)系统是否提供了明确的改正错误的方法?
 93)系统是否为控件输入提供了反馈?
 94)是否提供了“帮助”
 95)一个过程的结束是否标志清楚?
 96)是否对重复的错误有提示?
 97)错误信息是否具有建设性并提供有用的信息?
 98)系统是否提供了“重新开始”的功能?
 99)系统是否提供了“撤销”的功能?
 100)用户是否启动流程控制?

转自:http://www.dtell.net/article.asp?id=37

0

各系Rails大点兵


不知道明年今日,会不会出现一队只会用Ruby On Rails的毕业生,像当年的asp、jsp、php迅速剿了C++/perl的CGI那样,把我们给剿了。同好们劝我,根据大公司经济学,这基本不会发生。

在茫茫的框架之海认出一个Rails框架,基本上靠四个特征
1.一门动态语言
2.一个extreme simple to use的ORM框架
3.一个extreme simple to use的MVC框架
4.一些自动生成代码的命令、模版
其余ajax、 service、i18n等特性自由扩展

1.: on Rails
Rails系的旗手,一己之力搞得J2EE阵营鸡飞蛋打。
旗手的作用表现在:
*最接近1.0的版本,目前已出到最后一个RC版 1.0 rc4(0.14.3)
*拥有一本amazon超级畅销的《Agile Web Development with Rails》,而且这本书的组织也好,part I是一个渐进的sample application 让你快速入门,partII是几个重要领域的深入介绍。
*拥有自己的IDE: RadRails ,基于Eclipse但独立成军,目前出到0.5。(是不是怕惨了Eclipse的多变,现在这些IDE的发行版本开始反包含了Eclipse在内。)

2.: DJango、turbogears
认识不深,所以DJango请看limodou的blog,turbogears看xlp123的。

3.Groovy: grails
如果能顺产,绝对是J2EE阵营里感情分最高涨的项目。因为它语言用Groovy,ORM用annotation版Hibernate,MVC用Spring。不用担心它换汤不换药,因为经过extreme的封装,再结合Groovy,绝对不再是原来的Spring+Hibernate,而是和RoR差不多的一样东西了。不信可以看看他的Sample。
不过这个项目的源码目录树非一般的乱,也还没有0.1版释出。还有一样奇怪的事情,这个Groovy项目,大多数的class代码都是Java写的。

4.: symfony ,cake
据说cake比symfony弱非常多,所以没看。
symfonys是基于php5的项目,成功整合了Propel(ORM)、Mojavi3(MVC),再配合自动生成的脚本打造而成。看他的sample,最后整合出来的东西也很Rails了,除了ORM层的xml文件。
还有一个发现是Php项目现在可以用pear来安装,很像Ruby的gem,Java的Maven要努力了。
C: />pear channel-discover pear.symfony-project.com
C: />pear install symfony/symfony
这样就装完了symfonys和propel&croel, mojavi3,还有用来运行脚本命令的pake(php make),PHing(php ant?),一大堆东西。

from :http://www.blogjava.net/calvin/archive/2005/11/22/20938.
author:江南白衣

0

Reactor模式和NIO


转至 www.jdon.com

注: 理解ACE中的Reactor模式,  高效的响应IO等事件.

当前分布式计算  Services盛行天下,这些网络服务的底层都离不开对socket的操作。他们都有一个共同的结构:
1. Read request
2. Decode request
3. Process service
4. Encode reply
5. Send reply

经典的网络服务的设计如下图,在每个线程中完成对数据的处理:

但这种模式在用户负载增加时,性能将下降非常的快。我们需要重新寻找一个新的方案,保持数据处理的流畅,很显然,事件触发机制是最好的解决办法,当有事件发生时,会触动handler,然后开始数据的处理。

Reactor模式类似于AWT中的Event处理:

Reactor模式参与者

1.Reactor 负责响应IO事件,一旦发生,广播发送给相应的Handler去处理,这类似于AWT的thread
2.Handler 是负责非堵塞行为,类似于AWT ActionListeners;同时负责将handlers与event事件绑定,类似于AWT addActionListener

如图:

Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们,在此我们实现事件和handler的绑定。

我们来看看Reactor模式代码:

public Reactor implements Runnable{

  final Selector selector;
final ServerSocketChannel serverSocket;

  Reactor(int port) throws IOException {
selector = Selector.open();
serverSocket = ServerSocketChannel.open();
InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);
serverSocket.socket().bind(address);

serverSocket.configureBlocking(false);
//向selector注册该channel
SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);

logger.("–>Start serverSocket.register!");

//利用sk的attache功能绑定Acceptor 如果有事情,触发Acceptor
sk.attach(new Acceptor());
logger.debug("–>attach(new Acceptor()!");
}

public void run() { // normally in a new Thread
try {
while (!Thread.interrupted())
{
selector.select();
Set selected = selector.selectedKeys();
Iterator it = selected.iterator();
//Selector如果发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。
while (it.hasNext())
//来一个事件 第一次触发一个accepter线程
//以后触发SocketReadHandler
dispatch((SelectionKey)(it.next()));
selected.clear();
}
}catch (IOException ex) {
logger.debug("reactor stop!"+ex);
}
}

  //运行Acceptor或SocketReadHandler
void dispatch(SelectionKey k) {
Runnable r = (Runnable)(k.attachment());
if (r != null){
// r.run();

    }
}

  class Acceptor implements Runnable { // inner
public void run() {
try {
logger.debug("–>ready for accept!");
SocketChannel c = serverSocket.accept();
if (c != null)
//调用Handler来处理channel
new SocketReadHandler(selector, c);
}
catch(IOException ex) {
logger.debug("accept stop!"+ex);
}
}
}
}

以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起,当发生事件时,可以立即触发相应链接的Handler。

再看看Handler代码:

public class SocketReadHandler implements Runnable {

  public static Logger logger = Logger.getLogger(SocketReadHandler.class);

  private Test test=new Test();

  final SocketChannel socket;
final SelectionKey sk;

static final int READING = 0, SENDING = 1;
int state = READING;

  public SocketReadHandler(Selector sel, SocketChannel c)
throws IOException {

    socket = c;

    socket.configureBlocking(false);
sk = socket.register(sel, 0);

    //将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。
//参看dispatch(SelectionKey k)
sk.attach(this);

//同时将SelectionKey标记为可读,以便读取。
sk.interestOps(SelectionKey.OP_READ);
sel.wakeup();
}

  public void run() {
try{
// test.read(socket,input);
readRequest() ;
}catch(Exception ex){
logger.debug("readRequest error"+ex);
}
}

/**
* 处理读取data
* @param key
* @throws Exception
*/
private void readRequest() throws Exception {

  ByteBuffer input = ByteBuffer.allocate(1024);
input.clear();
try{

    int bytesRead = socket.read(input);

    ……

    //激活线程池 处理这些request
requestHandle(new Request(socket,btt));

    …..

}catch(Exception e) {
}

}

注意在Handler里面又执行了一次attach,这样,覆盖前面的Acceptor,下次该Handler又有READ事件发生时,将直接触发Handler.从而开始了数据的读 处理 写 发出等流程处理。

将数据读出后,可以将这些数据处理线程做成一个线程池,这样,数据读出后,立即扔到线程池中,这样加速处理速度:

更进一步,我们可以使用多个Selector分别处理连接和读事件。

一个高性能的Java网络服务机制就要形成,激动人心的集群并行计算即将实现。

0

什么是bootloader程序及其功能和特点


因工作需要, 在BLOG中记录一篇BOOTLOADER文章. 做为对BOOTLOADER概念的DEEPLY了解.
转至http://www.dz863.com

一、引言
在专用的嵌入式板子运行 GNU/ 系统已经变得越来越流行。一个嵌入式 系统从软件的角度看通常可以分为四个层次:
1. 引导加载程序。包括固化在固件(firmware)中的 boot 代码(可选),和 Boot Loader 两大部分。
2. Linux 内核。特定于嵌入式板子的定制内核以及内核的启动参数。
3. 文件系统。包括根文件系统和建立于 内存设备之上文件系统。通常用ram disk 来作为 root fs。
4. 用户应用程序。特定于用户的应用程序。有时在用户应用程序和内核层之间可能还会包括一个嵌入式图形用户界面。常用的嵌入式 GUI 有:MicroWindows 和 MiniGUI 等。

引导加载程序是系统加电后运行的第一段软件代码。回忆一下 PC 的体系结构我们可以知道,PC 机中的引导加载程序由 BIOS(其本质就是一段固件程序)和位于硬盘MBR中的OS Boot Loader(比如,LILO 和 GRUB 等)一起组成。BIOS 在完成硬件检测和资源分配后,将硬盘MBR中的 Boot Loader 读到系统的RAM 中,然后将控制权交给 OS Boot Loader。Boot Loader 的主要运行任务就是将内核映象从硬盘上读到RAM 中,然后跳转到内核的入口点去运行,也即开始启动操作系统。
而在嵌入式系统中,通常并没有像BIOS 那样的固件程序(注,有的嵌入式 CPU 也会内嵌一段短小的启动程序),因此整个系统的加载启动任务就完全由 Boot Loader 来完成。比如在一个基于 7TDMI core 的嵌入式系统中,系统在上电或复位时通常都从地址 0×00000000 处开始执行,而在这个地址处安排的通常就是系统的Boot Loader程序。

本文将从 Boot Loader 的概念、Boot Loader 的主要任务、Boot Loader 的框架结构以及Boot Loader 的安装等四个方面来讨论嵌入式系统的 Boot Loader。
二、 Boot Loader 的概念
简单地说,Boot Loader 就是在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作系统内核准备好正确的环境。

通常,Boot Loader 是严重地依赖于硬件而实现的,特别是在嵌入式世界。因此,在嵌入式世界里建立一个通用的 Boot Loader 几乎是不可能的。尽管如此,我们仍然可以对 Boot Loader 归纳出一些通用的概念来,以指导用户特定的 Boot Loader 设计与实现。

1. Boot Loader 所支持的 CPU 和嵌入式板

每种不同的 CPU 体系结构都有不同的Boot Loader。有些 Boot Loader 也支持多种体系结构的 CPU,比如 U-Boot 就同时支持 ARM 体系结构和MIPS 体系结构。除了依赖于 CPU的体系结构外,Boot Loader 实际上也依赖于具体的嵌入式板级设备的配置。这也就是说,对于两块不同的嵌入式板而言,即使它们是基于同一种 CPU 而构建的,要想让运行在一块板子上的 Boot Loader 程序也能运行在另一块板子上,通常也都需要修改 Boot Loader 的源程序。

2. Boot Loader 的安装媒介(Installation Medium)
系统加电或复位后,所有的CPU 通常都从某个由 CPU 制造商预先安排的地址上取指令。比如,基于 ARM7TDMI core 的 CPU 在复位时通常都从地址 0×00000000 取它的第一条指令。而基于CPU 构建的嵌入式系统通常都有某种类型的固态存储设备(比如:ROM、EEPROM 或 FLASH 等)被映射到这个预先安排的地址上。因此在系统加电后,CPU 将首先执行Boot Loader 程序。
下图1就是一个同时装有 Boot Loader、内核的启动参数、内核映像和根文件系统映像的固态存储设备的典型空间分配结构图。

图1 固态存储设备的典型空间分配结构

3. 用来控制 Boot Loader 的设备或机制
主机和目标机之间一般通过串口建立连接,Boot Loader 软件在执行时通常会通过串口来进行 I/O,比如:输出打印信息到串口,从串口读取用户控制字符等。

4. Boot Loader 的启动过程是单阶段(Single Stage)还是多阶段(Multi-Stage)

通常多阶段的 Boot Loader 能提供更为复杂的功能,以及更好的可移植性。从固态存储设备上启动的 Boot Loader 大多都是 2 阶段的启动过程,也即启动过程可以分为 stage 1和 stage 2 两部分。而至于在 stage 1 和 stage 2 具体完成哪些任务将在下面几篇讨论。

5. Boot Loader 的操作模式 (Operation Mode)

大多数 Boot Loader 都包含两种不同的操作模式:"启动加载"模式和"下载"模式,这种区别仅对于开发人员才有意义。但从最终用户的角度看,Boot Loader 的作用就是用来加载操作系统,而并不存在所谓的启动加载模式与下载工作模式的区别。

启动加载(Boot loading)模式:这种模式也称为"自主"(Autonomous)模式。也即 Boot Loader 从目标机上的某个固态存储设备上将操作系统加载到 RAM 中运行,整个过程并没有用户的介入。这种模式是 Boot Loader 的正常工作模式,因此在嵌入式产品发布的时侯,Boot Loader 显然必须工作在这种模式下。

下载(Downloading)模式:在这种模式下,目标机上的 Boot Loader 将通过串口连接或网络连接等通信手段从主机(Host)下载文件,比如:下载内核映像和根文件系统映像等。从主机下载的文件通常首先被 Boot Loader 保存到目标机的 RAM 中,然后再被 Boot Loader 写到目标机上的FLASH 类固态存储设备中。Boot Loader 的这种模式通常在第一次安装内核与根文件系统时被使用;此外,以后的系统更新也会使用 Boot Loader 的这种工作模式。工作于这种模式下的 Boot Loader 通常都会向它的终端用户提供一个简单的命令行接口。

像 Blob 或 U-Boot 等这样功能强大的 Boot Loader 通常同时支持这两种工作模式,而且允许用户在这两种工作模式之间进行切换。比如,Blob 在启动时处于正常的启动加载模式,但是它会延时 10 秒等待终端用户按下任意键而将 blob 切换到下载模式。如果在 10 秒内没有用户按键,则 blob 继续启动 Linux 内核。
6. BootLoader 与主机之间进行文件传输所用的通信设备及协议

最常见的情况就是,目标机上的 Boot Loader 通过串口与主机之间进行文件传输,传输协议通常是 xmodem/ymodem/zmodem 协议中的一种。但是,串口传输的速度是有限的,因此通过以太网连接并借助 TFTP 协议来下载文件是个更好的选择。
此外,在论及这个话题时,主机方所用的软件也要考虑。比如,在通过以太网连接和 TFTP 协议来下载文件时,主机方必须有一个软件用来的提供 TFTP 服务。在讨论了 BootLoader 的上述概念后,下面我们来具体看看 BootLoader 的应该完成哪些任务。

三、Boot Loader 的主要任务与典型结构框架
在继续本节的讨论之前,首先我们做一个假定,那就是:假定内核映像与根文件系统映像都被加载到 RAM 中运行。之所以提出这样一个假设前提是因为,在嵌入式系统中内核映像与根文件系统映像也可以直接在 ROM 或 Flash 这样的固态存储设备中直接运行。但这种做法无疑是以运行速度的牺牲为代价的。从操作系统的角度看,Boot Loader 的总目标就是正确地调用内核来执行。
另外,由于 Boot Loader 的实现依赖于 CPU 的体系结构,因此大多数 Boot Loader 都分为 stage1 和 stage2 两大部分。依赖于 CPU 体系结构的代码,比如设备初始化代码等,通常都放在 stage1 中,而且通常都用汇编语言来实现,以达到短小精悍的目的。而 stage2 则通常用C语言来实现,这样可以实现给复杂的功能,而且代码会具有更好的可读性和可移植性。

Boot Loader 的 stage1 通常包括以下步骤(以执行的先后顺序):
·硬件设备初始化。
·为加载 Boot Loader 的 stage2 准备 RAM 空间。
·拷贝 Boot Loader 的 stage2 到 RAM 空间中。
·设置好堆栈。
·跳转到 stage2 的 C 入口点。
Boot Loader 的 stage2 通常包括以下步骤(以执行的先后顺序):
·初始化本阶段要使用到的硬件设备。
·检测系统内存映射(memory map)。
·将 kernel 映像和根文件系统映像从 flash 上读到 RAM 空间中。
·为内核设置启动参数。
·调用内核。
3.1 Boot Loader 的 stage1
3.1.1 基本的硬件初始化

这是 Boot Loader 一开始就执行的操作,其目的是为 stage2 的执行以及随后的 kernel 的执行准备好一些基本的硬件环境。它通常包括以下步骤(以执行的先后顺序):
1.屏蔽所有的中断。为中断提供服务通常是 OS 设备驱动程序的责任,因此在 Boot Loader 的执行全过程中可以不必响应任何中断。中断屏蔽可以通过写 CPU 的中断屏蔽寄存器或状态寄存器(比如 ARM 的 CPSR 寄存器)来完成。
2.设置 CPU 的速度和时钟频率。
3.RAM 初始化。包括正确地设置系统的内存控制器的功能寄存器以及各内存库控制寄存器等。
4.初始化 LED。典型地,通过 GPIO 来驱动 LED,其目的是表明系统的状态是 OK 还是 Error。如果板子上没有 LED,那么也可以通过初始化 UART 向串口打印 Boot Loader 的 Logo 字符信息来完成这一点。
5. 关闭 CPU 内部指令/数据
3.1.2 为加载 stage2 准备 RAM 空间

为了获得更快的执行速度,通常把 stage2 加载到 RAM 空间中来执行,因此必须为加载 Boot Loader 的 stage2 准备好一段可用的 RAM 空间范围。

由于 stage2 通常是 C 语言执行代码,因此在考虑空间大小时,除了 stage2 可执行映象的大小外,还必须把堆栈空间也考虑进来。此外,空间大小最好是 memory page 大小(通常是 4KB)的倍数。一般而言,1M 的 RAM 空间已经足够了。具体的地址范围可以任意安排,比如 blob 就将它的 stage2 可执行映像安排到从系统 RAM 起始地址 0xc0200000 开始的 1M 空间内执行。但是,将 stage2 安排到整个 RAM 空间的最顶 1MB(也即(RamEnd-1MB) – RamEnd)是一种值得推荐的方法。

为了后面的叙述方便,这里把所安排的 RAM 空间范围的大小记为:stage2_size(),把起始地址和终止地址分别记为:stage2_start 和 stage2_end(这两个地址均以 4 字节边界对齐)。因此:
stage2_end=stage2_start+stage2_size
另外,还必须确保所安排的地址范围的的确确是可读写的 RAM 空间,因此,必须对你所安排的地址范围进行测试。具体的测试方法可以采用类似于 blob 的方法,也即:以 memory page 为被测试单位,测试每个 memory page 开始的两个字是否是可读写的。为了后面叙述的方便,我们记这个检测算法为:test_mempage,其具体步骤如下:

1.先保存 memory page 一开始两个字的内容。

2.向这两个字中写入任意的数字。比如:向第一个字写入 0×55,第 2 个字写入 0xaa。

3.然后,立即将这两个字的内容读回。显然,我们读到的内容应该分别是 0×55 和 0xaa。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

4.再向这两个字中写入任意的数字。比如:向第一个字写入 0xaa,第 2 个字中写入 0×55。

5.然后,立即将这两个字的内容立即读回。显然,我们读到的内容应该分别是 0xaa 和 0×55。如果不是,则说明这个 memory page 所占据的地址范围不是一段有效的 RAM 空间。

6.恢复这两个字的原始内容。测试完毕。

为了得到一段干净的 RAM 空间范围,我们也可以将所安排的 RAM 空间范围进行清零操作。

3.1.3 拷贝 stage2 到 RAM 中

拷贝时要确定两点:(1) stage2 的可执行映象在固态存储设备的存放起始地址和终止地址;(2) RAM 空间的起始地址。

3.1.4 设置堆栈指针 sp

堆栈指针的设置是为了执行 C 语言代码作好准备。通常我们可以把 sp 的值设置为(stage2_end-4),也即在 3.1.2 节所安排的那个 1MB 的 RAM 空间的最顶端(堆栈向下生长)。此外,在设置堆栈指针 sp 之前,也可以关闭 led 灯,以提示用户我们准备跳转到 stage2。经过上述这些执行步骤后,系统的物理内存布局应该如下图2所示。

3.1.5 跳转到 stage2 的 C 入口点
在上述一切都就绪后,就可以跳转到 Boot Loader 的 stage2 去执行了。比如,在 ARM 系统中,这可以通过修改 PC 寄存器为合适的地址来实现。

图2 bootloader 的 stage2 可执行映象刚被拷贝到 RAM 空间时的系统内存布局

3.2 Boot Loader 的 stage2
正如前面所说,stage2 的代码通常用 C 语言来实现,以便于实现更复杂的功能和取得更好的代码可读性和可移植性。但是与普通 C 语言应用程序不同的是,在编译和链接 boot loader 这样的程序时,我们不能使用 glibc 库中的任何支持函数。其原因是显而易见的。这就给我们带来一个问题,那就是从那里跳转进 main() 函数呢?直接把 main() 函数的起始地址作为整个 stage2 执行映像的入口点或许是最直接的想法。但是这样做有两个缺点:1)无法通过main() 函数传递函数参数;2)无法处理 main() 函数返回的情况。一种更为巧妙的方法是利用 trampoline(弹簧床)的概念。也即,用汇编语言写一段trampoline 小程序,并将这段 trampoline 小程序来作为 stage2 可执行映象的执行入口点。然后我们可以在 trampoline 汇编小程序中用 CPU 跳转指令跳入 main() 函数中去执行;而当 main() 函数返回时,CPU 执行路径显然再次回到我们的 trampoline 程序。简而言之,这种方法的思想就是:用这段 trampoline 小程序来作为 main() 函数的外部包裹(external wrapper)。

下面给出一个简单的 trampoline 程序示例(来自blob):

.text

.globl _trampoline
_trampoline:
bl main
/* if main ever returns we just call it again */
b _trampoline

可以看出,当 main() 函数返回后,我们又用一条跳转指令重新执行 trampoline 程序――当然也就重新执行 main() 函数,这也就是 trampoline(弹簧床)一词的意思所在。

3.2.1初始化本阶段要使用到的硬件设备

这通常包括:(1)初始化至少一个串口,以便和终端用户进行 I/O 输出信息;(2)初始化计时器等。在初始化这些设备之前,也可以重新把 LED 灯点亮,以表明我们已经进入 main() 函数执行。

设备初始化完成后,可以输出一些打印信息,程序名字字符串、版本号等。

3.2.2 检测系统的内存映射(memory map)

所谓内存映射就是指在整个 4GB 物理地址空间中有哪些地址范围被分配用来寻址系统的 RAM 单元。比如,在 SA-1100 CPU 中,从 0xC000,0000 开始的 512M 地址空间被用作系统的 RAM 地址空间,而在 Samsung S3C44B0X CPU 中,从 0x0c00,0000 到 0×1000,0000 之间的 64M 地址空间被用作系统的 RAM 地址空间。虽然 CPU 通常预留出一大段足够的地址空间给系统 RAM,但是在搭建具体的嵌入式系统时却不一定会实现 CPU 预留的全部 RAM 地址空间。也就是说,具体的嵌入式系统往往只把 CPU 预留的全部 RAM 地址空间中的一部分映射到 RAM 单元上,而让剩下的那部分预留 RAM 地址空间处于未使用状态。由于上述这个事实,因此 Boot Loader 的 stage2 必须在它想干点什么 (比如,将存储在 flash 上的内核映像读到 RAM 空间中) 之前检测整个系统的内存映射情况,也即它必须知道 CPU 预留的全部 RAM 地址空间中的哪些被真正映射到 RAM 地址单元,哪些是处于 "unused" 状态的。

(1) 内存映射的描述

可以用如下数据结构来描述 RAM 地址空间中的一段连续(continuous)的地址范围:

typedef struct memory_area_struct {
u32 start; /* the base address of the memory region */
u32 size; /* the byte number of the memory region */
int used;
} memory_area_t;

这段 RAM 地址空间中的连续地址范围可以处于两种状态之一:(1)used=1,则说明这段连续的地址范围已被实现,也即真正地被映射到 RAM 单元上。(2)used=0,则说明这段连续的地址范围并未被系统所实现,而是处于未使用状态。

基于上述 memory_area_t 数据结构,整个 CPU 预留的 RAM 地址空间可以用一个 memory_area_t 类型的数组来表示,如下所示:
memory_area_t memory_map[NUM_MEM_AREAS] = {
[0 ... (NUM_MEM_AREAS - 1)] = {
.start = 0,
.size = 0,
.used = 0
},
};

(2) 内存映射的检测

下面我们给出一个可用来检测整个 RAM 地址空间内存映射情况的简单而有效的算法:

/* 数组初始化 */
for(i = 0; i < NUM_MEM_AREAS; i++)
memory_map[i].used = 0;

/* first write a 0 to all memory locations */
for(addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE)
* (u32 *)addr = 0;

for(i = 0, addr = MEM_START; addr < MEM_END; addr += PAGE_SIZE) {
/*
* 检测从基地址 MEM_START+i*PAGE_SIZE 开始,大小为
* PAGE_SIZE 的地址空间是否是有效的RAM地址空间。
*/
调用3.1.2节中的算法test_mempage();
if ( current memory page isnot a valid ram page) {
/* no RAM here */
if(memory_map[i].used )
i++;
continue;
}

/*
* 当前页已经是一个被映射到 RAM 的有效地址范围
* 但是还要看看当前页是否只是 4GB 地址空间中某个地址页的别名?
*/
if(* (u32 *)addr != 0) { /* alias? */
/* 这个内存页是 4GB 地址空间中某个地址页的别名 */
if ( memory_map[i].used )
i++;
continue;
}

/*
* 当前页已经是一个被映射到 RAM 的有效地址范围
* 而且它也不是 4GB 地址空间中某个地址页的别名。
*/
if (memory_map[i].used == 0) {
memory_map[i].start = addr;
memory_map[i].size = PAGE_SIZE;
memory_map[i].used = 1;
} else {
memory_map[i].size += PAGE_SIZE;
}
} /* end of for (…) */

在用上述算法检测完系统的内存映射情况后,Boot Loader 也可以将内存映射的详细信息打印到串口。
3.2.3 加载内核映像和根文件系统映像
(1) 规划内存占用的布局

这里包括两个方面:(1)内核映像所占用的内存范围;(2)根文件系统所占用的内存范围。在规划内存占用的布局时,主要考虑基地址和映像的大小两个方面。

对于内核映像,一般将其拷贝到从(MEM_START+0×8000) 这个基地址开始的大约1MB大小的内存范围内(嵌入式 Linux 的内核一般都不操过 1MB)。为什么要把从 MEM_START 到 MEM_START+0×8000 这段 32KB 大小的内存空出来呢?这是因为 Linux 内核要在这段内存中放置一些全局数据结构,如:启动参数和内核页表等信息。
而对于根文件系统映像,则一般将其拷贝到 MEM_START+0×0010,0000 开始的地方。如果用 Ramdisk 作为根文件系统映像,则其解压后的大小一般是1MB。
(2)从 Flash 上拷贝
由于像 ARM 这样的嵌入式 CPU 通常都是在统一的内存地址空间中寻址 Flash 等固态存储设备的,因此从 Flash 上读取数据与从 RAM 单元中读取数据并没有什么不同。用一个简单的循环就可以完成从 Flash 设备上拷贝映像的工作:  

while(count) {
*dest++ = *src++; /* they are all aligned with word boundary */
count -= 4; /* byte number */
};

3.2.4 设置内核的启动参数
应该说,在将内核映像和根文件系统映像拷贝到 RAM 空间中后,就可以准备启动 Linux 内核了。但是在调用内核之前,应该作一步准备工作,即:设置 Linux 内核的启动参数。

Linux 2.4.x 以后的内核都期望以标记列表(tagged list)的形式来传递启动参数。启动参数标记列表以标记 ATAG_CORE 开始,以标记 ATAG_NONE 结束。每个标记由标识被传递参数的 tag_header 结构以及随后的参数值数据结构来组成。数据结构 tag 和 tag_header 定义在 Linux 内核源码的include/asm/setup.h 头文件中:

/* The list ends with an ATAG_NONE node. */
#define ATAG_NONE 0×00000000

struct tag_header {
u32 size; /* 注意,这里size是字数为单位的 */
u32 tag;
};
……
struct tag {
struct tag_header hdr;
union {
struct tag_core core;
struct tag_mem32 mem;
struct tag_videotext videotext;
struct tag_ramdisk ramdisk;
struct tag_initrd initrd;
struct tag_serialnr serialnr;
struct tag_revision revision;
struct tag_videolfb videolfb;
struct tag_cmdline cmdline;
/*
* Acorn specific
*/
struct tag_acorn acorn;
/*
* DC21285 specific
*/
struct tag_memclk memclk;
} u;
};

在嵌入式 Linux 系统中,通常需要由 Boot Loader 设置的常见启动参数有:ATAG_CORE、ATAG_MEM、ATAG_CMDLINE、ATAG_RAMDISK、ATAG_INITRD等。比如,设置 ATAG_CORE 的代码如下:

params = (struct tag *)BOOT_PARAMS;
params->hdr.tag = ATAG_CORE;
params->hdr.size = tag_size(tag_core);
params->u.core.flags = 0;
params->u.core.pagesize = 0;
params->u.core.rootdev = 0;
params = tag_next(params);

其中,BOOT_PARAMS 表示内核启动参数在内存中的起始基地址,指针 params 是一个 struct tag 类型的指针。宏 tag_next() 将以指向当前标记的指针为参数,计算紧临当前标记的下一个标记的起始地址。注意,内核的根文件系统所在的设备ID就是在这里设置的。

下面是设置内存映射情况的示例代码:

for(i = 0; i < NUM_MEM_AREAS; i++) {
if(memory_map[i].used) {
params->hdr.tag = ATAG_MEM;
params->hdr.size = tag_size(tag_mem32);
params->u.mem.start = memory_map[i].start;
params->u.mem.size = memory_map[i].size;
params = tag_next(params);
}
}

可以看出,在 memory_map[]数组中,每一个有效的内存段都对应一个 ATAG_MEM 参数标记。

Linux 内核在启动时可以以命令行参数的形式来接收信息,利用这一点我们可以向内核提供那些内核不能自己检测的硬件参数信息,或者重载(override)内核自己检测到的信息。比如,我们用这样一个命令行参数字符串"console=ttyS0,115200n8"来通知内核以 ttyS0 作为控制台,且串口采用 "115200bps、无奇偶校验、8位数据位"这样的设置。下面是一段设置调用内核命令行参数字符串的示例代码:

char *p;
/* eat leading white space */
for(p = commandline; *p == ‘ ‘; p++)
;
/* skip non-existent command lines so the kernel will still
* use its default command line.
*/
if(*p == ‘\0′)
return;
params->hdr.tag = ATAG_CMDLINE;
params->hdr.size = (sizeof(struct tag_header) + strlen(p) + 1 + 4) >> 2;
strcpy(params->u.cmdline.cmdline, p);
params = tag_next(params);

请注意在上述代码中,设置 tag_header 的大小时,必须包括字符串的终止符’\0′,此外还要将字节数向上圆整4个字节,因为 tag_header 结构中的size 成员表示的是字数。

下面是设置 ATAG_INITRD 的示例代码,它告诉内核在 RAM 中的什么地方可以找到 initrd 映象(压缩格式)以及它的大小:

params->hdr.tag = ATAG_INITRD2;
params->hdr.size = tag_size(tag_initrd);
params->u.initrd.start = RAMDISK_RAM_BASE;
params->u.initrd.size = INITRD_LEN;
params = tag_next(params);

下面是设置 ATAG_RAMDISK 的示例代码,它告诉内核解压后的 Ramdisk 有多大(单位是KB):

params->hdr.tag = ATAG_RAMDISK;
params->hdr.size = tag_size(tag_ramdisk);

params->u.ramdisk.start = 0;
params->u.ramdisk.size = RAMDISK_SIZE; /* 请注意,单位是KB */
params->u.ramdisk.flags = 1; /* automatically load ramdisk */

params = tag_next(params);

最后,设置 ATAG_NONE 标记,结束整个启动参数列表:

static void setup_end_tag(void)
{
params->hdr.tag = ATAG_NONE;
params->hdr.size = 0;
}

3.2.5 调用内核
Boot Loader 调用 Linux 内核的方法是直接跳转到内核的第一条指令处,也即直接跳转到 MEM_START+0×8000 地址处。在跳转时,下列条件要满足:

1. CPU 寄存器的设置:
·R0=0;
@R1=机器类型 ID;关于 Machine Type Number,可以参见 linux/arch/arm/tools/mach-types。
@R2=启动参数标记列表在 RAM 中起始基地址;

2. CPU 模式:
·必须禁止中断(IRQs和FIQs);
·CPU 必须 SVC 模式;
3. Cache 和 MMU 的设置:
·MMU 必须关闭;
·指令 Cache 可以打开也可以关闭;
·数据 Cache 必须关闭;
如果用 C 语言,可以像下列示例代码这样来调用内核:

void (*theKernel)(int zero, int arch, u32 params_addr)
= (void (*)(int, int, u32))KERNEL_RAM_BASE;
……
theKernel(0, ARCH_NUMBER, (u32) kernel_params_start);

注意,theKernel()函数调用应该永远不返回的。如果这个调用返回,则说明出错。

四、 关于串口终端
在 boot loader 程序的设计与实现中,没有什么能够比从串口终端正确地收到打印信息能更令人激动了。此外,向串口终端打印信息也是一个非常重要而又有效的调试手段。但是,我们经常会碰到串口终端显示乱码或根本没有显示的问题。造成这个问题主要有两种原因:(1) boot loader 对串口的初始化设置不正确。(2) 运行在 host 端的终端仿真程序对串口的设置不正确,这包括:波特率、奇偶校验、数据位和停止位等方面的设置。

此外,有时也会碰到这样的问题,那就是:在 boot loader 的运行过程中我们可以正确地向串口终端输出信息,但当 boot loader 启动内核后却无法看到内核的启动输出信息。对这一问题的原因可以从以下几个方面来考虑:

(1) 首先请确认你的内核在编译时配置了对串口终端的支持,并配置了正确的串口驱动程序。
(2) 你的 boot loader 对串口的初始化设置可能会和内核对串口的初始化设置不一致。此外,对于诸如 s3c44b0x 这样的 CPU,CPU 时钟频率的设置也会影响串口,因此如果 boot loader 和内核对其 CPU 时钟频率的设置不一致,也会使串口终端无法正确显示信息。
(3) 最后,还要确认 boot loader 所用的内核基地址必须和内核映像在编译时所用的运行基地址一致,尤其是对于 uClinux 而言。假设你的内核映像在编译时用的基地址是 0xc0008000,但你的 boot loader 却将它加载到 0xc0010000 处去执行,那么内核映像当然不能正确地执行了。

五、 结束语
Boot Loader 的设计与实现是一个非常复杂的过程。如果不能从串口收到那激动人心的内核启动信息,恐怕谁也不能说:"嗨,我的 boot loader 已经成功地转起来了!"。本文详细的介绍了bootloader的原理,回答了什么是bootloader

"uncompressing linux
……………… done,
booting the kernel……"

0

常见设计模式的解析和实现(C++) – 超强!!!!


最常用的设计模式, 及完全的代码, 及整洁的PDF文档. (全中文)
并在附件中带有全套的VC7.1 工程代码, 无Error可编译过.
经典啊!!!!!!!!~~~~~~~~~ 快下啊~~~

下载地址:
     http://www.cppblog.com/Files/converse/常见设计模式的解析和实现(C++).rar

转至www.cppblog.com

Previous Page Next Page

Random Posts Recent Comments

  • Nouramohsen88 Says:

    http://goo.gl/vFWge لدينا ثلاجات عرض جديدة ومستعملة للبيع ولدينا ثلاجات عرض سوبر ماركت وحلويات في ست...

  • Nouramohsen88 Says:

    http://www.drdrahem.com/home دكتور رجيم دكتور تخسيس الكرش والارداف مركز تخسيس في مدينة نصر ...

  • Nouramohsen88 Says:

    شركه تصنيع صاعق ناموس http://www.grandelectronic-eg.com/...

  • Nouramohsen88 Says:

    شركة كشافات اضاءة في مصر http://www.grandelectronic-eg.com/ ...

  • Nouramohsen88 Says:

    http://www.grandelectronic-eg.com/ شركة كشافات طواريء في مصر...

  • Nouramohsen88 Says:

    anti-mosquitocompany.blogspot.com شركة جراند الكترونيك هي شركة مصرية متخصصة في تصنيع الكشافات الكهرب...

  • Nouramohsen88 Says:

    insect--killer.blogspot.com شركة جراند الكترونيك هي شركة مصرية متخصصة في تصنيع الكشافات الكهربية وك...

  • Nouramohsen88 Says:

    http://genius-square.com/ شركه للتدريب والاستشارات | متخصصون في التنمية البشرية...

  • Er Says:

    我了个去,我也是用的phpo ..... 看来大家的思绪差不多。。。。...

  • Fasf Says:

    SYM_TYPE * pType;改为SYM_TYPE pType;...

Tag Cloud

arm audio blog brew cache class debug flash google html j2me java javascript Joke linux lua mobile mtk php python ror ruby server shell stream unix web windows 优化 动态加载 女人 女生 平台 开发 手机 技术 流媒体 测试 漫画 生活 男人 男生 缓存 芯片