2007年02月20日

wxPython窗口布局

wxPython包含了许多强大的技术,用于控制你的窗口和控件的布局。它提供了几种可以替换的机制和几种有效的方法来完成同一件事情。允许程序员在特别的环境下使用可以工作最好或最习惯的机制。

约束

有一个叫做wxLayoutConstraints的类,它允许一个窗口位置和大小的说明是相对于它的“兄弟”(同级控件)和它的“父亲”(父控件)。每一个wxLayoutContraints对象是由八个wxIndividualLayoutConstraint对象组成,这些对象定义了不同类型的关系,就象哪一个窗口在这个窗口的上面,这个窗口的相对宽度是什么,等等。你通常需要指出八个中的四个约束条件,以便窗口被完全限定。例如,这个按钮将定位于它的父控件的中间,并且将总是占距父控件宽度的50%:

b = wxButton(self.panelA, 100, ‘ Panel A `)
lc = wxLayoutConstraints()
lc.centreX.SameAs (self.panelA, wxCentreX)
lc.centreY.SameAs (self.panelA, wxCentreY)
lc.height.AsIs ()
lc.width.PercentOf (self.panelA, wxWidth, 50)
b.SetConstraints(lc);

布局算法

名为wxLayoutAlgorithm的类实现了在MDI或SDI框架中子窗口的布局。它向框架的子控件发送 wxCalculateLayoutEvent事件,向它们查询它们的大小的信息。因为使用了事件系统,因此这个技术可以应用于任何窗口,甚至那些不需要知道布局的类。然而你可能希望将wxSashLayoutWindow类用在你的子窗口上,因为这个类提供了用于请求事件的处理器,和可以指定窗口大小的存取器。可以有选择地使用基类中的框格行为(sash behavior)使窗口可以由用户调整大小。wxLayoutAlgorithm被典型地应用在IDE风格的应用程序中,这种应用程序除了有MDI客户窗口或其它主要编辑窗口之外,还有几个可调整的窗口。可调整窗口可能包括了工具条,工具窗口,显示错误或警告消息的窗口。

大小管理器(sizer)

为了简化简单布局的编程,一个wxSizer类家族被加入到wxPython库中。这些类纯粹是用Python实现的,代替了从 wxWindows中封装C++代码。它们有点象是Java中布局管理的再现,你选择要用的sizer,然后向它增加窗口或其它sizer,这样它们将全部遵从布局的相同规则。例如,这个代码片段创建了五个按钮,它们在一个框子中水平排列,并且最后一个按钮允许伸缩以便填充框子中所分配的剩余的空间:

box = wxBoxSizer(wxHORIZONTAL)
box.Add(wxButton(win, 1010, "one"), 0)
box.Add(wxButton(win, 1010, "two"), 0)
box.Add(wxButton(win, 1010, "three"), 0)
box.Add(wxButton(win, 1010, "four"), 0)
box.Add(wxButton(win, 1010, "five"), 1)

资源

wxWindows库有一个可用的简单对话框编辑器,它可以帮助你安排一个对话框中控件的布局,并且生一个可在交叉平台上移植的资源文件。这个文件可以在运行时装入到程序中,并且可以立即转化成一个带有特别控件在上面的窗口。这个方法唯一的缺陷是你没有机会实现所生成窗口的子类化,但是如果用存在的控件类型和事件处理能够做你需要的任何事情,它应该执行的很好。最后,将会出现一个为wxPython特别设计的应用程序生成工具,它将为你或者生成资源文件或者生成实际的Python源代码。

强制力

最后,有一种强制力(brute force)机制,用来通过编程来指明每一个组件的精确位置。有时候一个窗口的布局需要不能适应任何一种sizer,或者不能保证约束的复杂性,或者布局算法。对于这些情况,你可以重新回到手工方式去处理,但是对于比Edit Transaction对话框复杂多的东西,你可能不想去尝试它。

wxDialog and friends

  下一步是建立一个用来编辑交易的对话框。对象由日期,说明,和不定数目的交易记录组成,每条记录都有一个帐户名和一个余额。我们知道,所有的记录加起来应该为0,并且日期应该为一个有效日期。另外,为了编辑日期和说明,你需要能够增加,编辑,和删除记录。图7显示了对于这个对话框的一个可能的布局,并且是这个例子中所使用的。

图7. wxPython Doubletalk交易编辑器

  因为这里有很多的代码,让我们一步步地仔细检查这个类的初始化过程。下面是第一部分:

class EditTransDlg(wxDialog):
def __init__(self, parent, trans, accountList):
wxDialog.__init__(self, parent, -1, "")
self.item = -1
if trans:
self.trans = copy.deepcopy(trans)
self.SetTitle("Edit Transaction")
else:
self.trans = Transaction()
self.trans.setDateString(dates.ddmmmyyyy(self.trans.date))
self.SetTitle("Add Transaction")

  这是想当简单的东西。只是调用了父类的__init__方法,做一些初始化工作,并且判断是否你正在编辑一个存在的交易或创建一个新的交易。如果正在编辑一个存在的交易,使用Python拷贝模块来生成对象的拷贝。这样做因为你可能正好在编辑交易,并不想让被编辑的交易的任何部分留在 BookSet中。如果对话框被用来增加一条新交易,则创建一条,然后修改它的日期,通过从日期中截去时间。在交易中的缺省日期包括了当前的时间,但是这个对话框只具备了处理日期部分。

2007年02月13日

   我们经常会采用生产者/消费者关系的两个线程来处理一个共享缓冲区的数据。例如一个生产者线程接受用户数据放入一个共享缓冲区里,等待一个消费者线程对数据取出处理。但是如果缓冲区的太小而生产者和消费者两个异步线程的速度不同时,容易出现一个线程等待另一个情况。为了尽可能的缩短共享资源并以相同速度工作的各线程的等待时间,我们可以使用一个“队列”来提供额外的缓冲区。
        创建一个“队列”对象:

import Queue
myqueue = Queue.Queue(maxsize = 10)

Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

        将一个值放入队列中:

myqueue.put(10)

调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为1。如果队列当前为空且block1put()方法就使调用线程暂停,直到空出一个数据单元。如果block0put方法将引发Full异常。

        将一个值从队列中取出:

myqueue.get()

调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为1。如果队列为空且block1get()就使调用线程暂停,直至有项目可用。如果block为0,队列将引发Empty异常。

        我们用一个例子来展示如何使用Queue:

# queue_example.py
from Queue import Queue
import threading
import random
import time

# Producer thread
class Producer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),’adding’,i,’to queue’
self.sharedata.put(i)
time.sleep(random.randrange(10)/10.0)
print self.getName(),’Finished’

# Consumer thread
class Consumer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),’got a value:’,self.sharedata.get()
time.sleep(random.randrange(10)/10.0)
print self.getName(),’Finished’

# Main thread
def main():
queue = Queue()
producer = Producer(‘Producer’, queue)
consumer = Consumer(‘Consumer’, queue)

print ‘Starting threads …’
producer.start()
consumer.start()

producer.join()
consumer.join()

print ‘All threads have terminated.’

if __name__ == ‘__main__’:
main()

        示例代码中实现了两个类:生产者类Producer和消费者类Consumer。前者在一个随机的时间内放入一个值到队列queue中然后显示出来,后者在一定随机的时间内从队列queue中取出一个值并显示出来。

 多个执行线程经常要共享数据,如果仅仅读取共享数据还好,但是如果多个线程要修改共享数据的话就可能出现无法预料的结果。
        假如两个线程对象t1t2都要对数值num=0进行增1运算,那么t1t2都各对num修改10次的话,那么num最终的结果应该为20。但是如果当t1取得num的值时(假如此时num0),系统把t1调度为“sleeping”状态,而此时t2转换为“running”状态,此时t2获得的num的值也为0,然后他把num+1的值1赋给num。系统又把t2转化为“sleeping”状态,t1为“running”状态,由于t1已经得到num值为0,所以他也把num+1的值赋给了num1。本来是2次增1运行,结果却是num只增了1次。类似这样的情况在多线程同时执行的时候是有可能发生的。所以为了防止这类情况的出现就要使用线程同步机制。
        最简单的同步机制就是“锁”。锁对象用threading.RLock类创建:

    mylock = threading.RLock()

        如何使用锁来同步线程呢?线程可以使用锁的acquire() (获得)方法,这样锁就进入“locked”状态。每次只有一个线程可以获得锁。如果当另一个线程试图获得这个锁的时候,就会被系统变为“blocked”状态,直到那个拥有锁的线程调用锁的release() (释放)方法,这样锁就会进入“unlocked”状态。“blocked”状态的线程就会收到一个通知,并有权利获得锁。如果多个线程处于“blocked”状态,所有线程都会先解除“blocked”状态,然后系统选择一个线程来获得锁,其他的线程继续沉默(“blocked”)。

import threading
mylock = threading.RLock()
class mythread(threading.Thread)
    …
    def run(self …):
        …     #此处 不可以 放置修改共享数据的代码
        mylock.acquire()
        …     #此处 可以 放置修改共享数据的代码
        mylock.release()
        …    
#此处 不可以 放置修改共享数据的代码

        我们把修改共享数据的代码称为“临界区”,必须将所有“临界区”都封闭在同一锁对象的acquire()release()方法调用之间。
        锁只能提供最基本的同步级别。有时需要更复杂的线程同步,例如只在发生某些事件时才访问一个临界区(例如当某个数值改变时)。这就要使用“条件变量”。
        条件变量用threading.Condition类创建:

    mycondition = threading.Condition()

        条件变量是如何工作的呢?首先一个线程成功获得一个条件变量后,调用此条件变量的wait()方法会导致这个线程释放这个锁,并进入“blocked”状态,直到另一个线程调用同一个条件变量的notify()方法来唤醒那个进入“blocked”状态的线程。如果调用这个条件变量的notifyAll()方法的话就会唤醒所有的在等待的线程。
        如果程序或者线程永远处于“blocked”状态的话,就会发生死锁。所以如果使用了锁、条件变量等同步机制的话,一定要注意仔细检查,防止死锁情况的发生。对于可能产生异常的临界区要使用异常处理机制中的finally子句来保证释放锁。等待一个条件变量的线程必须用notify()方法显式的唤醒,否则就永远沉默。保证每一个wait()方法调用都有一个相对应的notify()调用,当然也可以调用notifyAll()方法以防万一。

2006年09月19日

一、BSTR、LPSTR和LPWSTR
   在Visual C++.NET的所有编程方式中,我们常常要用到这样的一些基本字符串类型,如BSTR、LPSTR和LPWSTR等。之所以出现类似上述的这些数据类型,是因为不同编程语言之间的数据交换以及对ANSI、Unicode和多字节字符集(MBCS)的支持。

  那么什么是BSTR、LPSTR以及LPWSTR呢?

  BSTR(Basic STRing,Basic字符串)是一个OLECHAR*类型的Unicode字符串。它被描述成一个与自动化相兼容的类型。由于操作系统提供相应的API函数(如SysAllocString)来管理它以及一些默认的调度代码,因此BSTR实际上就是一个COM字符串,但它却在自动化技术以外的多种场合下得到广泛使用。图1描述了BSTR的结构,其中DWORD值是字符串中实际所占用的字节数,且它的值是字符串中Unicode字符的两倍。

  LPSTR和LPWSTR是Win32和VC++所使用的一种字符串数据类型。LPSTR被定义成是一个指向以NULL(‘\0’)结尾的8位ANSI字符数组指针,而LPWSTR是一个指向以NULL结尾的16位双字节字符数组指针。在VC++中,还有类似的字符串类型,如LPTSTR、LPCTSTR等,它们的含义如图2所示。

  例如,LPCTSTR是指“long pointer to a constant generic string”,表示“一个指向一般字符串常量的长指针类型”,与C/C++的const char*相映射,而LPTSTR映射为 char*。

  一般地,还有下列类型定义:

#ifdef UNICODE
  typedef LPWSTR LPTSTR;
  typedef LPCWSTR LPCTSTR;
#else
  typedef LPSTR LPTSTR;
  typedef LPCSTR LPCTSTR;
#endif

二、CString、CStringA 和 CStringW

  Visual C++.NET中将CStringT作为ATL和MFC的共享的“一般”字符串类,它有CString、CStringA和CStringW三种形式,分别操作不同字符类型的字符串。这些字符类型是TCHAR、char和wchar_t。TCHAR在Unicode平台中等同于WCHAR(16位Unicode字符),在ANSI中等价于char。wchar_t通常定义为unsigned short。由于CString在MFC应用程序中经常用到,这里不再重复。

三、VARIANT、COleVariant 和_variant_t

  在OLE、ActiveX和COM中,VARIANT数据类型提供了一种非常有效的机制,由于它既包含了数据本身,也包含了数据的类型,因而它可以实现各种不同的自动化数据的传输。下面让我们来看看OAIDL.H文件中VARIANT定义的一个简化版:

struct tagVARIANT {
  VARTYPE vt;
  union {
   short iVal; // VT_I2.
   long lVal; // VT_I4.
   float fltVal; // VT_R4.
   double dblVal; // VT_R8.
   DATE date; // VT_DATE.
   BSTR bstrVal; // VT_BSTR.
   …
   short * piVal; // VT_BYREF|VT_I2.
   long * plVal; // VT_BYREF|VT_I4.
   float * pfltVal; // VT_BYREF|VT_R4.
   double * pdblVal; // VT_BYREF|VT_R8.
   DATE * pdate; // VT_BYREF|VT_DATE.
   BSTR * pbstrVal; // VT_BYREF|VT_BSTR.
  };
};

  显然,VARIANT类型是一个C结构,它包含了一个类型成员vt、一些保留字节以及一个大的union类型。例如,如果vt为VT_I2,那么我们可以从iVal中读出VARIANT的值。同样,当给一个VARIANT变量赋值时,也要先指明其类型。例如:

VARIANT va;
:: VariantInit(&va); // 初始化
int a = 2002;
va.vt = VT_I4; // 指明long数据类型
va.lVal = a; // 赋值

  为了方便处理VARIANT类型的变量,Windows还提供了这样一些非常有用的函数:

  VariantInit —— 将变量初始化为VT_EMPTY;

  VariantClear —— 消除并初始化VARIANT;

  VariantChangeType —— 改变VARIANT的类型;

  VariantCopy —— 释放与目标VARIANT相连的内存并复制源VARIANT。

  COleVariant类是对VARIANT结构的封装。它的构造函数具有极为强大大的功能,当对象构造时首先调用VariantInit进行初始化,然后根据参数中的标准类型调用相应的构造函数,并使用VariantCopy进行转换赋值操作,当VARIANT对象不在有效范围时,它的析构函数就会被自动调用,由于析构函数调用了VariantClear,因而相应的内存就会被自动清除。除此之外,COleVariant的赋值操作符在与VARIANT类型转换中为我们提供极大的方便。例如下面的代码:

COleVariant v1("This is a test"); // 直接构造
COleVariant v2 = "This is a test";
// 结果是VT_BSTR类型,值为"This is a test"
COleVariant v3((long)2002);
COleVariant v4 = (long)2002;
// 结果是VT_I4类型,值为2002

  _variant_t是一个用于COM的VARIANT类,它的功能与COleVariant相似。不过在Visual C++.NET的MFC应用程序中使用时需要在代码文件前面添加下列两句:

  #i nclude "comutil.h"

  #pragma comment( lib, "comsupp.lib" )

四、CComBSTR和_bstr_t

  CComBSTR是对BSTR数据类型封装的一个ATL类,它的操作比较方便。例如:

CComBSTR bstr1;
bstr1 = "Bye"; // 直接赋值
OLECHAR* str = OLESTR("ta ta"); // 长度为5的宽字符
CComBSTR bstr2(wcslen(str)); // 定义长度为5
wcscpy(bstr2.m_str, str); // 将宽字符串复制到BSTR中
CComBSTR bstr3(5, OLESTR("Hello World"));
CComBSTR bstr4(5, "Hello World");
CComBSTR bstr5(OLESTR("Hey there"));
CComBSTR bstr6("Hey there");
CComBSTR bstr7(bstr6);
// 构造时复制,内容为"Hey there"

  _bstr_t是是C++对BSTR的封装,它的构造和析构函数分别调用SysAllocString和SysFreeString函数,其他操作是借用BSTR API函数。与_variant_t相似,使用时也要添加comutil.h和comsupp.lib。

五、BSTR、char*和CString转换

  (1) char*转换成CString

  若将char*转换成CString,除了直接赋值外,还可使用CString::Format进行。例如:

char chArray[] = "This is a test";
char * p = "This is a test";

  或

LPSTR p = "This is a test";

  或在已定义Unicode应的用程序中

TCHAR * p = _T("This is a test");

  或

LPTSTR p = _T("This is a test");
CString theString = chArray;
theString.Format(_T("%s"), chArray);
theString = p;

  (2) CString转换成char*

  若将CString类转换成char*(LPSTR)类型,常常使用下列三种方法:

  方法一,使用强制转换。例如:

CString theString( "This is a test" );
LPTSTR lpsz =(LPTSTR)(LPCTSTR)theString;

  方法二,使用strcpy。例如:

CString theString( "This is a test" );
LPTSTR lpsz = new TCHAR[theString.GetLength()+1];
_tcscpy(lpsz, theString);

  需要说明的是,strcpy(或可移值Unicode/MBCS的_tcscpy)的第二个参数是 const wchar_t* (Unicode)或const char* (ANSI),系统编译器将会自动对其进行转换。

  方法三,使用CString::GetBuffer。例如:

CString s(_T("This is a test "));
LPTSTR p = s.GetBuffer();
// 在这里添加使用p的代码
if(p != NULL) *p = _T(‘\0′);
s.ReleaseBuffer();
// 使用完后及时释放,以便能使用其它的CString成员函数

  (3) BSTR转换成char*

  方法一,使用ConvertBSTRToString。例如:

#i nclude
#pragma comment(lib, "comsupp.lib")
int _tmain(int argc, _TCHAR* argv[]){
BSTR bstrText = ::SysAllocString(L"Test");
char* lpszText2 = _com_util::ConvertBSTRToString(bstrText);
SysFreeString(bstrText); // 用完释放
delete[] lpszText2;
return 0;
}

  方法二,使用_bstr_t的赋值运算符重载。例如:

_bstr_t b = bstrText;
char* lpszText2 = b;

  (4) char*转换成BSTR

  方法一,使用SysAllocString等API函数。例如:

BSTR bstrText = ::SysAllocString(L"Test");
BSTR bstrText = ::SysAllocStringLen(L"Test",4);
BSTR bstrText = ::SysAllocStringByteLen("Test",4);

  方法二,使用COleVariant或_variant_t。例如:

//COleVariant strVar("This is a test");
_variant_t strVar("This is a test");
BSTR bstrText = strVar.bstrVal;

  方法三,使用_bstr_t,这是一种最简单的方法。例如:

BSTR bstrText = _bstr_t("This is a test");

  方法四,使用CComBSTR。例如:

BSTR bstrText = CComBSTR("This is a test");

  或

CComBSTR bstr("This is a test");
BSTR bstrText = bstr.m_str;

  方法五,使用ConvertStringToBSTR。例如:

char* lpszText = "Test";
BSTR bstrText = _com_util::ConvertStringToBSTR(lpszText);

  (5) CString转换成BSTR

  通常是通过使用CStringT::AllocSysString来实现。例如:

CString str("This is a test");
BSTR bstrText = str.AllocSysString();

SysFreeString(bstrText); // 用完释放

  (6) BSTR转换成CString

  一般可按下列方法进行:

BSTR bstrText = ::SysAllocString(L"Test");
CStringA str;
str.Empty();
str = bstrText;

  或

CStringA str(bstrText);

  (7) ANSI、Unicode和宽字符之间的转换

  方法一,使用MultiByteToWideChar将ANSI字符转换成Unicode字符,使用WideCharToMultiByte将Unicode字符转换成ANSI字符。

  方法二,使用“_T”将ANSI转换成“一般”类型字符串,使用“L”将ANSI转换成Unicode,而在托管C++环境中还可使用S将ANSI字符串转换成String*对象。例如:

TCHAR tstr[] = _T("this is a test");
wchar_t wszStr[] = L"This is a test";
String* str = S”This is a test”;

  方法三,使用ATL 7.0的转换宏和类。ATL7.0在原有3.0基础上完善和增加了许多字符串转换宏以及提供相应的类,它具有如图3所示的统一形式:

  其中,第一个C表示“类”,以便于ATL 3.0宏相区别,第二个C表示常量,2表示“to”,EX表示要开辟一定大小的缓冲。SourceType和DestinationType可以是A、T、W和OLE,其含义分别是ANSI、Unicode、“一般”类型和OLE字符串。例如,CA2CT就是将ANSI转换成一般类型的字符串常量。下面是一些示例代码:

LPTSTR tstr= CA2TEX<16>("this is a test");
LPCTSTR tcstr= CA2CT("this is a test");
wchar_t wszStr[] = L"This is a test";
char* chstr = CW2A(wszStr);

2006年09月07日

-道德的起源- 
   
  把五只猴子关在一个笼子里,上头有一串香蕉实验人员装了一个自动装置。 
  一旦侦测到有猴子要去拿香蕉,马上就会有水喷向笼子,而这五只猴子都会一身湿。 
  首先有只猴子想去拿香蕉,当然,结果就是每只猴子都淋湿了。 
  之後每只猴子在几次的尝试后,发现莫不如此。 
  于是猴子们达到一个共识:不要去拿香蕉,以避免被水喷到。 
  后来实验人员把其中的一只猴子释放,换进去一只新猴子A。 
  这只猴子A看到香蕉,马上想要去拿。 
  结果,被其他四只猴子海K了一顿。 
  因为其他四只猴子认为猴子A会害他们被水淋到,所以制止他去拿香蕉,A尝试了几次,虽被打的满头包,依然没有拿到香蕉。 
  当然,这五只猴子就没有被水喷到。 
   
  后来实验人员再把一只旧猴子释放,换上另外一只新猴子B。 
  这猴子B看到香蕉,也是迫不及待要去拿。 
  当然,一如刚才所发生的情形,其他四只猴子海K了B一顿。 
  特别的是,那只A猴子打的特别用力(这叫老兵欺负新兵,或是媳妇熬成婆^O^)。 
  B猴子试了几次总是被打的很惨,只好作罢。 
   
  后来慢慢的一只一只的,所有的旧猴子都换成新猴子了,大家都不敢去动那香蕉。 
  但是他们都不知道为什么,只知道去动香蕉会被猴扁。 
   
  这就是道德的起源。 
   
  -阶级的起源- 
   
  实验人员继续他们的实验,不过这一次他们改变了喷水装置。 
  一旦侦测到有猴子要去拿香蕉,马上就会有水喷向拿香蕉的猴子,而不是全体。 
  然后实验人员又把其中的一只猴子释放,换进去一只新猴子C。 
  不同以往的是猴子C特别的孔武有力。 
  当然猴子C看到香蕉,也马上想要去拿。 
  一如以前所发生的情形,其他四只猴子也想海K猴子C一顿。 
  不过他们错误估计了C的实力,所以结果是反被C海K了一顿。 
  于是猴子C拿到了香蕉,当然也被淋了个透湿。 
  C一边打着喷嚏一边吃着香蕉,美味但是也美中不足。 
  A、B、D、E没有香蕉吃却也比较快乐,毕竟没有被淋到嘛。 
  后来C发现只有拿香蕉的那个才会被淋到,他就要最弱小的A替他去拿。 
  A不想被K,只好每天拿香蕉然后被水淋。 
  B、D、E越发的快乐了起来,这就叫比上不足,比下有余嘛 
   
  于是五只猴子有了三个阶级。 
  这下子阶级也随着道德起源了。 
   
  -道德的沦丧- 
   
  天变热了,笼子里的猴子们想冲凉却找不到地方。终于出现了一位反潮流英雄,猴子HERO。HERO在无意中碰到了香蕉,理所当然的引来了一顿饱打。但在挨打的过程中,猴子们享受到了冲凉的乐趣。等身上的水干了之后,猴子A在无意中碰撞了HERO,使HERO又一次接触到了香蕉,于是,猴子们享受了第二次冲凉,HERO遭到了第二次痛殴。 
   
  在此之后,只要大家有冲凉的需要,就会有一只猴子X挺身而出,对HERO进行合理冲撞。 
   
  大家对HERO的态度也有了明显的不同,在平时大家会对HERO异常温和,以弥补在冲凉时为维护规则而不得不对它进行的暴力举动。 
   
  一天,在大家冲凉时,饱受折磨的HERO闻到了香蕉的清香,生物本能使它在别的猴子心有旁鹜时将香蕉吃了。而且此后没有了新的香蕉来填补空缺。猴子们陷入了另一个尴尬境地:没有冲凉的水,也没有香蕉,只有HERO。 
   
  于是,另一个规则形成了。猴子在烦躁的时候会痛打HERO出气,HERO不得反抗。 
  当笼子里的旧猴子被新猴子换掉时,新猴子会在最快的时间内学会殴打HERO。 
   
  终于有一天,老天有眼,历尽沧桑的HERO被另一只猴子代替了。猴子们失去了发泄的对象,只能任意选取一个目标进行攻击。从此以后,笼子里的猴子们不吃不喝不冲凉,唯一的举动就是打架。 
   
  这就是道德的沦丧。 
   
  -道德的重建- 
   
  实验人员对猴子们的争斗不休感到不安。为了重建道德秩序,他们决定继续供应香蕉。 
   
  一天,正在混战的猴子们发现头顶多了一串香蕉,它们其中的一个A不顾身上的剧痛,把香蕉摘了下来。于是久违的甘露出现了,未曾尝过甜头的猴子们先是茫然失措,继而争先恐后的加入冲凉的行列。香蕉反而被遗忘了。当猴子B、C、D、E发现A在享受淋浴的同时还吃着美味的香蕉,嫉妒心使它们暂时团结起来,共同K了A一顿,将A吃剩的香蕉夺过来,但是,此刻的香蕉成了匹夫怀里的宝玉,得到它的猴子虽然可以享受美味,但付出的代价也是巨大的。 
   
  实验人员不断放入香蕉,却发现战斗比以前更激烈了。分析清楚原因后,他们用木头做了一个假香蕉扔进了笼子。此时猴子们已经学聪明了,它们知道触摸香蕉可以享淋浴,而试图独占香蕉则会遭到痛扁。于是,一个新的现象出现了,当猴子们有冲凉的需要时,会有一只猴子将香蕉拿起来,而当它发现有遭到攻击的可能时,它会马上放下香蕉逃到一边去。这样,猴子们都能冲凉,但是又不至于再象以前那样N败俱伤。 
   
  没有猴子发现那个香蕉是假的。 
   
  -信仰的起源- 
   
  五只猴子A、B、C、D、E三个阶级快乐地生活了很久。 
  他们精确的给出了三个阶级的定义,即吃香阶级、拿香阶级和干看着阶级。 
  可惜猴子A由于长期的水中作业无可避免地引发了它肺部功能的衰竭。 
  一天他在例行的拿香蕉作业中跌倒了就再也没有爬起来。 
  于是实验人员又送进了一只同样孔武有力的猴F。 
  当然他还是对屋顶的香蕉很有兴趣。 
  不幸的是他最终以微弱的劣势被以C为首的群猴再次海K。 
  第二天,又到了拿香蕉的时候。 
  猴子C很无所谓,反正他还要吃香蕉,反正他不会被水淋到。 
  真正恐慌的是B、D、E三猴。 
  F是那么的健壮,他们这些媳妇是熬不成婆了 
  他们将面临一个艰难的抉择,谁该去步A的后尘? 
  猴子B、D、E展开了激烈的争论,讨论谁最应该做下一个拿香阶级。 
  猴子F很奇怪也很好奇,什么叫“拿香阶级”呢? 
  猴子B、D、E解释道:所谓“拿香阶级”就是猴子界勇敢者的阶级。 
  需具备一不怕苦二不怕死的大无畏精神方能得此殊荣。 
  猴子F闻听不禁有些神往,有些跃跃欲试。 
  当然他最终达到了目的,作了唯一的拿香阶级。 
  再后来,B、D、E三猴陆续被换出局,换来的猴子个个健壮如C。 
  他们继续大大出手,不过目标不是香蕉,而是那个唯一的拿香阶级。 
   
  于是信仰也出现了 
   
  -迷信的起源- 
   
  后来A终于被好心的实验人员拉出了苦海。 
   
  新来了猴子F。 
   
  C觉得有必要维护自己的阶级地位,B、D、E则生怕自己顶了A的缸……在各种复杂心情的作用下,B、D、E在C的带领下爆扁了F一顿,然后强令F做拿香蕉阶级。 
   
  F开始不乐意,后来慢慢在B等的劝说下等“待多年的媳妇熬成婆”这一宿命。 
   
  慢慢的老资格的B、D、E猴子渐渐被淘汰,C发现自己在体力上不再占有优势,很难再通过武力让这一游戏规则继续下去,觉得十分苦恼。 
   
  这时,一只最有希望升级为吃香蕉阶级(暨C的理所当然接班人)也是C谋臣的H向C进言。于是君臣定计。 
   
  H开始依靠自己多懂几种猴语而在其他若干猴面前树立的权威形象向其他猴鼓吹:“每一只新来笼子的猴子都是有罪的,这种罪责来自血统。……只有摘香蕉的猴子才能被(实验人员)送到天堂。” 
   
  事实上,因为被水冲很容易得肺炎病倒而被实验人员淘汰掉,猴子们不知道反而以为被淘汰的猴子真的进了天堂。 
   
  渐渐,猴子都相信了这套理论,并且讲给每一只新猴子听。 
   
  然后就这么流传下去越传越神奇。以至于后来摘香蕉阶级的猴子都为了能摘香蕉而大打出手。…… 
   
  这些都是C没有想到,H没有看到的,那时他们都已经死了。 
   
  然而迷信就这么诞生了。

2006年08月24日

我们在使用和安装Windows程序时,有时会看到以“2052”、“1033”这些数字为名的文件夹(如Office),这些数字似乎和字符集有关,但它们究竟是什么意思呢?研究这个问题的同时,又会遇到其它问题。我们会谈到Windows的内部架构、Win32 API的A/W函数、Locale、ANSI代码页、与字符编码有关的编译参数、MBCS和Unicode程序、资源和乱码等,一起经历这段琐碎细节为主,间或乐趣点缀的旅程。

0 Where is Win32 API

Windows程序有用户态和核心态的说法。在32位地址空间中,0×80000000以下属于用户态(0×0~0×7FFFFFFF=2GB,0×0~0×10000是保留的),0×80000000以上属于核心态。所有硬件管理都在核心态。用户态程序的不能直接使用核心态的任何代码。所谓核心态其实只是CPU的一种保护模式。在x86 CPU上,用户态处于ring 3,核心态处于ring 0。

从用户态进入核心态的最常用的方法是在寄存器eax填一个功能码,然后执行int 2e。这有点像DOS时代的DOS和BIOS系统调用。在NT架构中这种机制被称作system service。

在核心态提供system service的有两个家伙:ntoskrnl.exe和win32k.sys。ntoskrnl.exe是Windows的大脑,它的上层被称为Executive,下层被称作Kernel。Win32k.sys提供与显示有关的system service。

在用户态一侧,有一个重要的角色叫作ntdll.dll,大多数system service都是它调用的。它封装这些system service,然后提供一个API接口。这个接口被称作native API。 native API的用户是各个子系统(subsystem),包括Win32子系统、OS/2子系统、POSIX子系统。各个子系统为Win32、OS2、POSIX程序提供了运行平台。

ntdll.dll由于提供了平台无关的API接口,所以被看作是NT系统的原生接口,由之得到了“native API”的匪号。其实它的主要工作是将调用传递到核心态。

Win32、OS/2、POSIX,听起来很庞大。其实真正做好的只有Win32子系统。OS2、POSIX都是Console UI,即只有字符界面。提供OS/2子系统,只因为在1988年,NT的主要设计目标就是与OS/2兼容,后来由于Windows 3.0卖得很好,所以设计目标被变更为与Windows兼容。提供POSIX子系统,是为了应付美国政府的一个编号为FIPS 151-2的标准。

Win32子系统的管理员是一个叫作csrss.exe的弟兄,它的全名是:Client/Server Run-Time Subsystem。它刚上任时,本来要分管所有的子系统,但后来POSIX和OS/2都被分别处理了,所以只管了一个Win32。即使这样也很了不起,所有的Win32程序的进程、线程们都要向它登记。

不过Win32程序用得最多的还是Win32子系统的DLL们,最核心的DLL包括:kernel32.dll、User32.dll、Gdi32.dll、Advapi32.dll(高级Win32应用程序接口)。这些DLL包装了ntdll.dll的native API。其中Gdi32.dll比较特殊,它与核心态的win32k.sys直接保持联系,以提高NT系统的图形处理能力。Win32子系统的DLL们提供的接口函数在MSDN文档中被详细介绍,它们就是Win32 API。

附录0 Windows的启动

计算机上电后,从BIOS的ROM开始运行。BIOS在做一些初始化后会将硬盘的第一个扇区的数据读入内存,然后将控制权交给它,这段数据被称作Master Boot Record(MBR)。

MBR包含一段启动代码和硬盘的主分区表。这段启动代码扫描主分区表,找到第一个可以启动的分区,然后将这个分区的第一个扇区读入内存并运行。这个扇区被称作引导扇区(boot sector)。

引导扇区的代码具备读文件系统根目录的能力,显然不同的文件系统需要不同的代码。引导扇区会从根目录中读出一个叫作ntldr的文件。顾名思义,这个文件是load NT的主要角色。它的业绩主要包括将CPU从实模式转入保护模式,启动分页机制,处理boot.ini等。

如果boot.ini中有一句:

C:\bootsect.rh=”Red Hat Linux”

bootsect.rh的内容是Linux引导扇区,用户又选择了“Red Hat Linux”,ntldr就会将执行Linux的引导扇区,开始Linux的引导。如果用户选择继续使用Windows,ntldr会装载并运行我们前面提到的ntoskrnl.exe。

ntoskrnl.exe会启动会话管理器smss.exe。smss.exe启动csrss.exe和winlogon.exe。smss.exe会永远等待csrss.exe和winlogon.exe返回。如果两者之一异常中止,就会导致系统崩溃。所以病毒们经常以打击csrss.exe为乐。

winlogon.exe负责用户登录,在完成登录后,它会启动注册表HKLM\SOFTWARE\Microsoft\Windows NT\Current Version\Winlogon项下Userinit值指定的程序。该值的缺省数据是userinit.exe。userinit.exe会装载个人设置,让硬盘响个不停,并考验我们的耐性,最后启动注册表同一项下Shell值指定的程序。该值的缺省数据是Explorer.exe。Explorer.exe运行后,我们就会看到熟悉的开始菜单和桌面。

1 Win32 API的A/W函数

要了解Win32子系统的DLL们提供了哪些API,最直接的方法就是用Win32dsm直接查看DLL们的导出表。这时我们会发现Win32 API中带字符串的API一般都有两个版本,例如CreateFileA和CreateFileW。当然也有例外,例如GetProcAddress函数。

A代表ANSI代码页,W是宽字符,即Unicode字符。Windows中的Unicode字符一般指UCS2的UTF16-LE编码。让我们通过几个实例观察A/W版本间的关系。

例1:用WIn32dsm查看gdi32.dll的汇编代码,可以看到TextOutA调用GdiGetCodePage获取当前代码页,再调用MultiByteToWideChar转换输入的字符串,然后调用一个内部函数。而TextOutW直接调用这个内部函数。

例2:用调试器跟踪一个使用了CreateFileA的程序,可以看到:CreateFileA在将输入字符串转换为Unicode后,会调用CreateFileW。假设输入文件名是“测试.txt”,对应的数据就是:“B2 E2 CA D4 2E 74 78 74 00”。
在调试器中可以看到传给CreateFileW的文件名数据是:“4B 6D D5 8B 2E 00 74 00 78 00 74 00 00 00”。 这是”测试.txt”对应的Unicdoe字符串。CreateFileW会接着调用ntdll.dll中的NtCreateFile。顺便看看NtCreateFile的代码:
mov eax, 00000020
lea edx, dword ptr [esp+04]
int 2E
ret 002C
可见这个native API只是简单地调用了核心态提供的0×20号system service。

例3:gdi32.dll中的GetGlyphOutline函数可以获取指定字符的字模。GetGlyphOutlineA和GetGlyphOutlineW函数都会调用同一个内部函数(记作F)。函数F在返回前将通过int 2E调用0×10B1号system service。
GetGlyphOutlineW直接调用函数F。GetGlyphOutlineA在调用函数F前,要依次调用GdiGetCodePage、IsDBCSLeadByteEx和MultiByteToWideChar,将当前代码页的字符编码转换成Unicode编码。
如果我们调用GetGlyphOutlineA时传入“baba”,这是“汉”字的GBK编码,用调试器可以看到传给函数F的字符编码是“6c49”,这是“汉”字的Unicode编码。

从以上例子可见,A版本总会在某处将输入的字符串转换为Unicode字符串,然后和W版本执行相同的代码。在由A/W版本API引出MBCS程序和Unicode程序前,让我们先解释一下Locale和ANSI代码页。

2 Locale和ANSI代码页

2.1 Locale和LCID

Locale是指特定于某个国家或地区的一组设定,包括字符集,数字、货币、时间和日期的格式等。在Windows中,每个Locale可以用一个32位数字表示,记作LCID。在winnt.h中可以看到LCID的组成。它的高16位表示字符的排序方法,一般为0。在它的低16位中,低10位是primary language的ID,高4位指定sublanguage。sublanguage被用来区分同一种语言的不同编码。下面是部分primary language和sublanguage的常数定义:

#define LANG_CHINESE 0×04
#define LANG_ENGLISH 0×09
#define LANG_FRENCH 0×0c
#define LANG_GERMAN 0×07

#define SUBLANG_CHINESE_TRADITIONAL 0×01 // Chinese (Taiwan Region)
#define SUBLANG_CHINESE_SIMPLIFIED 0×02 // Chinese (PR China)
#define SUBLANG_ENGLISH_US 0×01 // English (USA)
#define SUBLANG_ENGLISH_UK 0×02 // English (UK)

好,现在我们可以计算简体中文的LCID了,将sublanguage的常数左移10位,即乘上1024,再加上primary language的常数:2*1024+4=2052,16进制是0804。美国英语是:1*1024+9=1033,16进制是0409。。繁体中文是1*1024+4=1028,16进制是0404。

2.2 代码页

每个Locale都联系着很多信息,可以通过GetLocalInfo函数读取。其中最重要的信息就是字符集了,即Locale对应的语言文字的编码。Windows将字符集称作代码页。

每个Locale可以对应一个ANSI代码页和一个OEM代码页。Win32 API使用ANSI代码页,底层设备使用OEM代码页,两者可以相互映射。

例如English (US)的ANSI和OEM代码页分别为“1252 (ANSI – Latin I)”和“437 (OEM – United States)”。 Chinese (PRC)的ANSI和OEM代码页都是“936 (ANSI/OEM – Simplified Chinese GBK)”。  Chinese (TW)的ANSI和OEM代码页都是“950 (ANSI/OEM – Traditional Chinese Big5)”。

附录1中有一张很长的表。列出了我正在使用的Windows所支持的135个Locale的部分信息,包括 LCID、国家/地区名称、语言名称、语言缩写和对应的ANSI代码页。

2.3 系统Locale、用户Locale,再谈ANSI代码页

在Windows中,通过控制面板可以为系统和用户分别设置Locale。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。这不是一个好的设计,后面会谈到它带来的问题。

使用GetSystemDefaultLCID函数和GetUserDefaultLCID函数分别得到系统和用户的LCID。有很多材料将这两个函数和另外两个函数混淆:GetSystemDefaultUILanguage和GetUserDefaultUILanguage。

GetSystemDefaultUILanguage和GetUserDefaultUILanguage得到的是您当前使用的Windows版本所带的UI资源的语言。

用户程序缺省使用的代码页是当前系统Locale的ANSI代码页,可以称作ANSI编码,也就是A版本的Win32 API默认的字符编码。对于一个未指定编码方式的文本文件,Windows会按照ANSI编码解释。

2.4 AppLocale

如果一个文本文件采用BIG5编码,系统当前的ANSI代码页是GBK。打开这个文件,就会显示乱码。例如“中文”在BIG5中的编码是A4A4、A4E5,这两个编码在GBK中对应的字符是“いゅ”。这是日文的两个平假名。

在Windows XP平台有一个AppLocale程序,可以以指定的语言运行非Unicode程序。用Win32dsm打开看一看,其实它只是在运行程序前设置了两个环境变量。我们可以用个批处理文件模仿一下:

@ECHO OFF
SET __COMPAT_LAYER=#ApplicationLocale
SET ApplocaleID=0404
start notepad.exe

在简体中文平台,用这个批处理文件启动的记事本可以正确显示BIG5编码的文本文件。用它打开GBK编码的文本文件会怎么样?“中文”会被显示为“笢恅”。设置这两个环境变量会作用于当前进程和其子进程。Windows 2000平台不支持这个方法。

3 MBCS程序和Unicode程序

3.1 与字符编码有关的编译参数

让我们回到Win32 API。我们在程序中使用的Win32 API没有A/W后缀,Windows的头文件会根据编译参数UNICODE将没有后缀的函数名替换为A版本或W版本,例如:

#ifdef UNICODE
#define CreateFile CreateFileW
#else
#define CreateFile CreateFileA
#endif

C RunTime库(CRT)使用_UNICODE和_MBCS来区分三套字符串处理函数,分别用于SBCS、MBCS和Unicdoe字符串。SBCS和MBCS分别指单字节字符串和多字节字符串。例如_tcsclen的3个版本分别为strlen、_mbslen和wcslen ,猜猜以下函数返回几?

strlen(”VOIP网关”);
_mbslen((unsigned char *)”VOIP网关”);
wcslen(L”VOIP网关”);

答案是8、6、6。L”ANSI字符串”通知编译器将ANSI字符串转换为Unicode字符串,这是VC++编译器提供的一个小甜点。不过我们应该用宏:_T(”ANSI字符串”)。_T宏只在我们定义了_UNICODE时才转换。这样同一套代码既可以编译MBCS版本,也可以编译Unicode版本。

MFC用_UNICODE参数区分Unicode版本特有的代码,决定使用什么版本的导入库或静态库。

3.2 Unicode程序、MBCS程序和多语言支持

Unicode程序直接使用Unicode版本的CRT和Win32 API。Unicode程序的运行与当前的ANSI代码页没有关系。MBCS程序的运行依赖于ANSI代码页。如果设计者和使用者使用不同的代码页,就可能出现乱码。微软开发的程序大都是Unicode程序,不管我们怎样变换系统Locale,它们总能正常运行。

使用VCL类库的Delphi程序都是MBCS程序。VCL框架在程序启动会调用GetThreadLocale获取当前用户的LCID,然后在当前目录查找对应的资源文件,命名规则是:程序名+’.’+语言缩写,语言缩写可以参见附录1。在找不到时才会使用EXE文件中的资源。不过如果系统LCID是English(United States),用户LCID是Chinese(PRC),由VCL产生的程序就会出现乱码。读者可以自己分析原因。

为VCL程序做多语言版本。只要用Delphi自带的Resource DLL Wizard再做一个特定语言的资源DLL,原来的程序都不用改。不过很多程序员用其它组件做多语言版本,例如TsiLang 。

MBCS程序虽然也可以做成多语言版本,但它无法在同时显示不同代码页特有的字符,这时就必须使用Unicode程序了。

VS.NET文档中有个多语言资源的例子:SatDLL。它只用Win32 API的例子,却用了VC7项目。我在学习时将它改成了VC6项目,并纠正了它的两个问题:
1、用GetUserDefaultUILanguage读到的是Windows资源版本,不是当前用户设置的代码页。
2、启动时没有使用资源DLL里的菜单。

在我的个人主页(http://fmddlmyy.home4u.china.com)上可以下载修改过的SatDLL。这个程序说明了支持多语言资源的基本思路:将不同语言资源放到不同的DLL中,在程序启动时根据当前Locale装载对应的资源DLL。必要时动态切换资源。为了标记不同语言的资源,可以将它们放到不同的目录中,以LCID作为目录名,例如“2052”、“1033”。当然我们也可以用其它方法联系LCID和资源DLL。

MFC程序可以在App类的InitInstance函数中用AfxSetResourceHandle函数设置资源DLL。在Delphi中动态切换资源可以参考Delphi Demo目录RichEdit项目的ReInit.pas。在读取当前设定时,建议用GetSystemDefaultLCID函数,因为系统Locale决定ANSI代码页。

3.4 资源和乱码

通过检查可执行文件,我们可以确定VC和Delphi的资源编译器都以Unicode保存字符资源。在VC环境编辑资源时,我们会指定资源的代码页。编译器根据资源的代码页,将其转换到Unicode。

Unicode程序直接使用以Unicode编码保存的资源。MBCS程序需要将Unicode资源先转换回当前ANSI代码页,然后再使用。如果资源中的Unicode字符串不能映射到当前代码页中的字符,就会出现??。

例如Windows的标准对话框也会出现乱码。假设我们使用简体中文Windows,当前Locale是Chinese (TW),我们的程序是MBCS的,使用标准的打开文件对话框。因为在BIG5中没有“开”这个字,所以“打开”会被显示成“打?”。将程序编译成Unicode版本,就可以避免这个问题。

如果字符不是保存在资源中,而是硬编码在程序中。然后开发者和用户使用不同的代码页,就会导致乱码。假设开发者的Locale是Chinese (PRC),用户的Locale是English (US),程序中硬编码了字符串“文件”。 Chinese (PRC)的ANSI代码页是GBK,“文件”的编码“CE C4 BC FE”。English (US)的ANSI代码页是Latin I,用户按照Latin I编码去解释“CE C4 BC FE”,就会看到“Îļþ”。

回答我前面提过的一个问题:Delphi程序根据用户LCID转换资源中的字符串。如果用户LCID是Chinese (PRC),系统LCID是English (US)。那么资源中的Unicode字符串会被转换为GBK编码,然后按照Latin I显示,这时我们看到的就是类似“Îļþ”的东东,不是??。

既然资源是以Unicode保存的,MBCS程序如果不将其转换到ANSI代码页,而用W版本的函数直接显示,就不会产生乱码。例如MFC程序菜单里的中文,在English (US)的Locale也可以正常显示。不过这取决于各部分代码的具体实现,menu bar控件里的中文在English (US)的Locale会全部显示成??。

进一步的参考资料

本文的第0节和附录0主要参考了《Inside Windows 2000 Third Edition》,国内出过该书的影印版。DDK文档中有大量Windows内核的信息。用Win32dsm和各种调试器查看Windows系统文件可以获得更直接的信息。

关于Window程序的字符编码,最好的参考资料是winnt.h等SDK的包含文件、VCL、MFC、CRT的源文件。我们不需要阅读它们,只要找到自己感兴趣的信息就可以了,用Source Insight可能方便一些。

本文所谈的不是什么万古不迁的道理,只是别的程序员的一些设定,我们因为需要使用他们的程序,所以有必要了解一些细节。研究问题的方法和兴趣永远比问题本身重要,如一句拉丁俗语所说:res, non verba,实质胜于文字。

尾声

“明月虽有圆缺,但毕竟永恒不灭,人生却如过眼烟云,一去不回,真不知计较为何?”

“蛙声虽是短促,但却是万籁中一个活泼的禅机,也可以说万古如斯,永恒不迁,无奈感受到的,能有几人?”

这是一本武侠书中的对话。在时间的长河中,人生和蛙声一样易逝。说到蛙声,我的20个月的小宝宝在喝汤后,略加酝酿,就会紧闭着嘴巴,发出很像蛙鸣的声音。我们会逗他说:“小青蛙又来了”。小家伙益发得意,不管我的抗议,将连汤带油的小下巴亲热地贴在我的身上。

 

附录1 一些关于LCID的信息

使用EnumSystemLocales函数可以枚举系统支持的LCID。用GetLocaleInfo可以得到ANSI代码页的ID,再通过GetCPInfoEx可以获得代码页的全称。以下是我在中文Windows XP上读到的内容。

LCID 国家或地区 语言 语言缩写 ANSI代码页
1025 沙特阿拉伯 阿拉伯语(沙特阿拉伯) ARA 1256  (ANSI – 阿拉伯文)
1026 保加利亚 保加利亚语 BGR 1251  (ANSI – 西里尔文)
1027 西班牙 加泰隆语 CAT 1252  (ANSI – 拉丁文 I)
1028 台湾 中文(台湾) CHT 950   (ANSI/OEM – 繁体中文 Big5)
1029 捷克共和国 捷克语 CSY 1250  (ANSI – 中欧)
1030 丹麦 丹麦语 DAN 1252  (ANSI – 拉丁文 I)
1031 德国 德语(德国) DEU 1252  (ANSI – 拉丁文 I)
1032 希腊 希腊语 ELL 1253  (ANSI – 希腊文)
1033 美国 英语(美国) ENU 1252  (ANSI – 拉丁文 I)
1034 西班牙 西班牙语(传统) ESP 1252  (ANSI – 拉丁文 I)
1035 芬兰 芬兰语 FIN 1252  (ANSI – 拉丁文 I)
1036 法国 法语(法国) FRA 1252  (ANSI – 拉丁文 I)
1037 以色列 希伯来语 HEB 1255  (ANSI – 希伯来文)
1038 匈牙利 匈牙利语 HUN 1250  (ANSI – 中欧)
1039 冰岛 冰岛语 ISL 1252  (ANSI – 拉丁文 I)
1040 意大利 意大利语(意大利) ITA 1252  (ANSI – 拉丁文 I)
1041 日本 日语 JPN 932   (ANSI/OEM – 日文 Shift-JIS)
1042 朝鲜 朝鲜语 KOR 949   (ANSI/OEM – 韩文)
1043 荷兰 荷兰语(荷兰) NLD 1252  (ANSI – 拉丁文 I)
1044 挪威 挪威语(伯克梅尔) NOR 1252  (ANSI – 拉丁文 I)
1045 波兰 波兰语 PLK 1250  (ANSI – 中欧)
1046 巴西 葡萄牙语(巴西) PTB 1252  (ANSI – 拉丁文 I)
1048 罗马尼亚 罗马尼亚语 ROM 1250  (ANSI – 中欧)
1049 俄罗斯 俄语 RUS 1251  (ANSI – 西里尔文)
1050 克罗地亚 克罗地亚语 HRV 1250  (ANSI – 中欧)
1051 斯洛伐克语 斯洛伐克语 SKY 1250  (ANSI – 中欧)
1052 阿尔巴尼亚 阿尔巴尼亚语 SQI 1250  (ANSI – 中欧)
1053 瑞典 瑞典语 SVE 1252  (ANSI – 拉丁文 I)
1054 泰国 泰语 THA 874   (ANSI/OEM – 泰文)
1055 土耳其 土耳其语 TRK 1254  (ANSI – 土耳其文)
1056 巴基斯坦伊斯兰共和国 乌都语 URD 1256  (ANSI – 阿拉伯文)
1057 印度尼西亚 印度尼西亚语 IND 1252  (ANSI – 拉丁文 I)
1058 乌克兰 乌克兰语 UKR 1251  (ANSI – 西里尔文)
1059 比利时 比利时语 BEL 1251  (ANSI – 西里尔文)
1060 斯洛文尼亚 斯洛文尼亚语 SLV 1250  (ANSI – 中欧)
1061 爱沙尼亚 爱沙尼亚语 ETI 1257  (ANSI – 波罗的海文)
1062 拉脱维亚 拉脱维亚语 LVI 1257  (ANSI – 波罗的海文)
1063 立陶宛 立陶宛语 LTH 1257  (ANSI – 波罗的海文)
1065 伊朗 法斯语 FAR 1256  (ANSI – 阿拉伯文)
1066 越南 越南语 VIT 1258  (ANSI/OEM – 越南)
1067 亚美尼亚 亚美尼亚语 HYE 936   (ANSI/OEM – 简体中文 GBK)
1068 阿塞拜疆 阿塞拜疆语(拉丁文) AZE 1254  (ANSI – 土耳其文)
1069 西班牙 巴士克语 EUQ 1252  (ANSI – 拉丁文 I)
1071 前南斯拉夫马其顿共和国 马其顿语(FYROM) MKI 1251  (ANSI – 西里尔文)
1078 南非 南非语 AFK 1252  (ANSI – 拉丁文 I)
1079 格鲁吉亚 格鲁吉亚语 KAT 936   (ANSI/OEM – 简体中文 GBK)
1080 法罗群岛 法罗语 FOS 1252  (ANSI – 拉丁文 I)
1081 印度 印地语 HIN 936   (ANSI/OEM – 简体中文 GBK)
1086 马来西亚 马来语(马来西亚) MSL 1252  (ANSI – 拉丁文 I)
1087 吉尔吉斯坦 哈萨克语 KKZ 1251  (ANSI – 西里尔文)
1088 吉尔吉斯斯坦 吉尔吉斯语 (西里尔文) KYR 1251  (ANSI – 西里尔文)
1089 肯尼亚 斯瓦希里语 SWK 1252  (ANSI – 拉丁文 I)
1091 乌兹别克斯坦 乌兹别克语(拉丁文) UZB 1254  (ANSI – 土耳其文)
1092 鞑靼斯坦 鞑靼语 TTT 1251  (ANSI – 西里尔文)
1094 印度 旁遮普语 PAN 936   (ANSI/OEM – 简体中文 GBK)
1095 印度 古吉拉特语 GUJ 936   (ANSI/OEM – 简体中文 GBK)
1097 印度 泰米尔语 TAM 936   (ANSI/OEM – 简体中文 GBK)
1098 印度 泰卢固语 TEL 936   (ANSI/OEM – 简体中文 GBK)
1099 印度 卡纳拉语 KAN 936   (ANSI/OEM – 简体中文 GBK)
1102 印度 马拉地语 MAR 936   (ANSI/OEM – 简体中文 GBK)
1103 印度 梵文 SAN 936   (ANSI/OEM – 简体中文 GBK)
1104 蒙古 蒙古语(西里尔文) MON 1251  (ANSI – 西里尔文)
1110 西班牙 加里西亚语 GLC 1252  (ANSI – 拉丁文 I)
1111 印度 孔卡尼语 KNK 936   (ANSI/OEM – 简体中文 GBK)
1114 叙利亚 叙利亚语 SYR 936   (ANSI/OEM – 简体中文 GBK)
1125 马尔代夫 第维埃语 DIV 936   (ANSI/OEM – 简体中文 GBK)
2049 伊拉克 阿拉伯语(伊拉克) ARI 1256  (ANSI – 阿拉伯文)
2052 中华人民共和国 中文(中国) CHS 936   (ANSI/OEM – 简体中文 GBK)
2055 瑞士 德语(瑞士) DES 1252  (ANSI – 拉丁文 I)
2057 英国 英语(英国) ENG 1252  (ANSI – 拉丁文 I)
2058 墨西哥 西班牙语(墨西哥) ESM 1252  (ANSI – 拉丁文 I)
2060 比利时 法语(比利时) FRB 1252  (ANSI – 拉丁文 I)
2064 瑞士 意大利语(瑞士) ITS 1252  (ANSI – 拉丁文 I)
2067 比利时 荷兰语(比利时) NLB 1252  (ANSI – 拉丁文 I)
2068 挪威 挪威语(尼诺斯克) NON 1252  (ANSI – 拉丁文 I)
2070 葡萄牙 葡萄牙语(葡萄牙) PTG 1252  (ANSI – 拉丁文 I)
2074 塞尔维亚 塞尔维亚语(拉丁文) SRL 1250  (ANSI – 中欧)
2077 芬兰 瑞典语(芬兰) SVF 1252  (ANSI – 拉丁文 I)
2092 阿塞拜疆 阿塞拜疆语(西里尔文) AZE 1251  (ANSI – 西里尔文)
2110 文莱达鲁萨兰 马来语(文莱达鲁萨兰) MSB 1252  (ANSI – 拉丁文 I)
2115 乌兹别克斯坦 乌兹别克语(西里尔文) UZB 1251  (ANSI – 西里尔文)
3073 埃及 阿拉伯语(埃及) ARE 1256  (ANSI – 阿拉伯文)
3076 香港特别行政区 中文(香港特别行政区) ZHH 950   (ANSI/OEM – 繁体中文 Big5)
3079 奥地利 德语(奥地利) DEA 1252  (ANSI – 拉丁文 I)
3081 澳大利亚 英语(澳大利亚) ENA 1252  (ANSI – 拉丁文 I)
3082 西班牙 西班牙语(国际) ESN 1252  (ANSI – 拉丁文 I)
3084 加拿大 法语(加拿大) FRC 1252  (ANSI – 拉丁文 I)
3098 塞尔维亚 塞尔维亚语(西里尔文) SRB 1251  (ANSI – 西里尔文)
4097 利比亚 阿拉伯语(利比亚) ARL 1256  (ANSI – 阿拉伯文)
4100 新加坡 中文(新加坡) ZHI 936   (ANSI/OEM – 简体中文 GBK)
4103 卢森堡 德语(卢森堡) DEL 1252  (ANSI – 拉丁文 I)
4105 加拿大 英语(加拿大) ENC 1252  (ANSI – 拉丁文 I)
4106 危地马拉 西班牙语(危地马拉) ESG 1252  (ANSI – 拉丁文 I)
4108 瑞士 法语(瑞士) FRS 1252  (ANSI – 拉丁文 I)
5121 阿尔及利亚 阿拉伯语(阿尔及利亚) ARG 1256  (ANSI – 阿拉伯文)
5124 澳门特别行政区 中文(澳门特别行政区) ZHM 950   (ANSI/OEM – 繁体中文 Big5)
5127 列支敦士登 德语(列支敦士登) DEC 1252  (ANSI – 拉丁文 I)
5129 新西兰 英语(新西兰) ENZ 1252  (ANSI – 拉丁文 I)
5130 哥斯达黎加 西班牙语(哥斯达黎加) ESC 1252  (ANSI – 拉丁文 I)
5132 卢森堡 法语(卢森堡) FRL 1252  (ANSI – 拉丁文 I)
6145 摩洛哥 阿拉伯语(摩洛哥) ARM 1256  (ANSI – 阿拉伯文)
6153 爱尔兰 英语(爱尔兰) ENI 1252  (ANSI – 拉丁文 I)
6154 巴拿马 西班牙语(巴拿马) ESA 1252  (ANSI – 拉丁文 I)
6156 摩纳哥公国 法语(摩纳哥) FRM 1252  (ANSI – 拉丁文 I)
7169 突尼斯 阿拉伯语(突尼斯) ART 1256  (ANSI – 阿拉伯文)
7177 南非 英语(南非) ENS 1252  (ANSI – 拉丁文 I)
7178 多米尼加共和国 西班牙语(多米尼加共和国) ESD 1252  (ANSI – 拉丁文 I)
8193 阿曼 阿拉伯语(阿曼) ARO 1256  (ANSI – 阿拉伯文)
8201 牙买加 英语(牙买加) ENJ 1252  (ANSI – 拉丁文 I)
8202 委内瑞拉 西班牙语(委内瑞拉) ESV 1252  (ANSI – 拉丁文 I)
9217 也门 阿拉伯语(也门) ARY 1256  (ANSI – 阿拉伯文)
9225 加勒比海 英语(加勒比海) ENB 1252  (ANSI – 拉丁文 I)
9226 哥伦比亚 西班牙语(哥伦比亚) ESO 1252  (ANSI – 拉丁文 I)
10241 叙利亚 阿拉伯语(叙利亚) ARS 1256  (ANSI – 阿拉伯文)
10249 伯利兹 英语(伯利兹) ENL 1252  (ANSI – 拉丁文 I)
10250 秘鲁 西班牙语(秘鲁) ESR 1252  (ANSI – 拉丁文 I)
11265 约旦 阿拉伯语(约旦) ARJ 1256  (ANSI – 阿拉伯文)
11273 特立尼达和多巴哥 英语(特立尼达) ENT 1252  (ANSI – 拉丁文 I)
11274 阿根廷 西班牙语(阿根廷) ESS 1252  (ANSI – 拉丁文 I)
12289 黎巴嫩 阿拉伯语(黎巴嫩) ARB 1256  (ANSI – 阿拉伯文)
12297 津巴布韦 英语(津巴布韦) ENW 1252  (ANSI – 拉丁文 I)
12298 厄瓜多尔 西班牙语(厄瓜多尔) ESF 1252  (ANSI – 拉丁文 I)
13313 科威特 阿拉伯语(科威特) ARK 1256  (ANSI – 阿拉伯文)
13321 菲律宾共和国 英语(菲律宾) ENP 1252  (ANSI – 拉丁文 I)
13322 智利 西班牙语(智利) ESL 1252  (ANSI – 拉丁文 I)
14337 阿联酋 阿拉伯语(阿联酋) ARU 1256  (ANSI – 阿拉伯文)
14346 乌拉圭 西班牙语(乌拉圭) ESY 1252  (ANSI – 拉丁文 I)
15361 巴林 阿拉伯语(巴林) ARH 1256  (ANSI – 阿拉伯文)
15370 巴拉圭 西班牙语(巴拉圭) ESZ 1252  (ANSI – 拉丁文 I)
16385 卡塔尔 阿拉伯语(卡塔尔) ARQ 1256  (ANSI – 阿拉伯文)
16394 玻利维亚 西班牙语(玻利维亚) ESB 1252  (ANSI – 拉丁文 I)
17418 萨尔瓦多 西班牙语(萨尔瓦多) ESE 1252  (ANSI – 拉丁文 I)
18442 洪都拉斯 西班牙语(洪都拉斯) ESH 1252  (ANSI – 拉丁文 I)
19466 尼加拉瓜 西班牙语(尼加拉瓜) ESI 1252  (ANSI – 拉丁文 I)
20490 波多黎各(美) 西班牙语(波多黎各(美)) ESU 1252  (ANSI – 拉丁文 I)

LCID取决于语言,在表中列出国家名只是为了增加趣味性。例如可以看到以色列还在使用古老的希伯来语。“希伯来语”的法文是hébreu,这个单词还有一个意思,就是“不能理解的东西”。

 

在winnt.h中,对subsystem的定义如下:
#define IMAGE_SUBSYSTEM_UNKNOWN 0 // Unknown subsystem.
#define IMAGE_SUBSYSTEM_NATIVE 1 // Image doesn’t require a subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_GUI 2 // Image runs in the Windows GUI subsystem.
#define IMAGE_SUBSYSTEM_WINDOWS_CUI 3 // Image runs in the Windows character subsystem.
#define IMAGE_SUBSYSTEM_OS2_CUI 5 // image runs in the OS/2 character subsystem.
#define IMAGE_SUBSYSTEM_POSIX_CUI 7 // image runs in the Posix character subsystem.
#define IMAGE_SUBSYSTEM_NATIVE_WINDOWS 8 // image is a native Win9x driver.
#define IMAGE_SUBSYSTEM_WINDOWS_CE_GUI 9 // Image runs in the Windows CE subsystem.
CUI就是Console UI了。我们使用的subsystem主要是3和2。

在用户态看起来很底层的东西,例如Win32 subsystem的核心:kernel32.dll、user32.dll、gdi32.dll,基本上只是ntdll.dll的一个包装,而ntdll.dll包装了从用户态到核心态的system call,也称作“Native System Service”。
用户态不能访问核心态的任何函数和变量,所以system call不同于一般的API调用。system call可以被看作:将要调用的功能ID放到eax,然后执行INT 2e。
ntdll.dll通过system call使用核心态的ntoskrnl.exe和win32k.sys提供的功能。ntoskrnl.exe被尊称为“Executive”,可以看作是NT的大脑级模块。win32k.sys提供NT图形库接口的API。

2006年01月15日

 Google于近期“偷偷”地发布了一个新功能:Google Suggest,当用户在搜索栏中输入查找请求时,搜索栏中会出现一个下拉列表,列出许多与用户输入的内容相近的提示,供用户选择。

  Google Suggest的主页面如图1所示。从主页面我们可以看到,该功能目前还处于测试阶段。


 

供用户选择Google提示功能新鲜试用
图1 Google Suggest的主页面

  我们来试一下。比如在搜索栏中输入“刘”,立刻就会出现如图2所示的下拉列表,并在每一个提示右侧列出该项被搜索的次数。当列表出现时,移动鼠标指针到一个提示上面并单击,就会搜索该内容并列出搜索结果。

供用户选择Google提示功能新鲜试用
图2 提示列表

  Google的“提示”功能使用大范围信息来预测用户要搜索的内容,列出多数用户经常搜索的问题清单,包括各种搜索内容的整体普及率数据。Google表示,公司没有使用个体搜索历史记录帮助生成提示列表。Google还在Google Zeitgeist列出了搜索内容的排名,我们可以点击该链接查看这个页面,如图3所示,你会发现它与百度的中文搜索风云榜非常类似。

供用户选择Google提示功能新鲜试用
图3 Google Zeitgeist

  使用Google的“提示”功能在某种程度上可以使用户的搜索更加便利和有效,同时Google提供的这些提示还会让你了解全球的网友都对哪些内容比较感兴趣。

2006年01月03日

在Mozilla的Wiki网站上我们看到了一份Firefox 2.0的产品计划书草稿,从中我们可以了解这个开源浏览器2.0新版有何新功能。

  Firefox 2(代号Bon Echo)功能/组件及其开发优先级:

  优先级1 新的书签和历史系统
  优先级1 分页浏览UI(用户界面)改进和除错
  优先级1 完整集成RSS功能
  优先级1 扩展管理器(Extension Manager)增强

  - 安装/升级/选项
  - 黑名单功能
  - 安全性

  优先级1 搜索引擎UI改进(增加/移除/选项)
  优先级1 Platform patch uplift
  优先级1 反钓鱼
  优先级1 视觉效果更新,支持“原生主题”(native theme)
  优先级1 浏览器状态显示系统及其UI
  优先级1 aviary-2.0 blocker(除虫+UI微调)
  优先级2 Session saver
  优先级3 Extension uplift
  优先级3 增强自动完成功能
  优先级3 离线模式侦测,更好利用缓存

  优先级仍未决定 In-line拼写检查
  优先级仍未决定 Instrumentation / exit surveys
  优先级仍未决定 插件式下载管理器

  Firefox 2发布目标及标准:

  - 完成上述所有优先级1的项目
  - 完成50%以上优先级2/3的项目
  - 解决75%以上aviary-2.0 blocker问题
  - 相比Firefox 1.5不能有功能上的退步

  Firefox 2发布时间表:

  2006/02/10 : Alpha 1
  2006/03/10 : Alpha 2(功能完成)
  2006/04/07 : Beta 1(代码完成,功能定型)
  2006/05/05 : Beta 2(代码定型)
  2006/05/26 : Release Candidate 1
  2006/06/07 : Release Candidate 2
  2006/06/19 : Release Candidate 3
  2006/06/27 : Firefox 2正式推出

  有关Firefox 2的代号“Bon Echo”

  根据惯例,Firefox 2的预发行代号将取至一家公共公园。Bon Echo地方公园(http://www.ontarioparks.com/english/bone.html)位于加拿大安大略地区。该代号可直译为“好的回声”,很好地反映出了Firefox 2的开发目标,即在Firefox 1的基础之上,继续着重改进用户浏览体验,让这款浏览器更简单,更有效,更快速,更好用。

  注:Firefox 1.5的代号为Deer Park。

2005年05月10日

不久前,美通无线正式宣布其与NBA在手机游戏方面合作,共同首次推出了NBA的官方手机游戏。“国内注册的SP(无线增值服务提供商)至少超过9000家。”美通无线CEO王唯嘉展开接受《经济参考报》采访时说,“SP是最典型的‘蚁象并存’的市场,但80%的销售额只把握在有限的十几家SP手中,因此SP的竞争将会更加激烈”。
 
    正是在这种情况下,许多SP将目光投向了手机游戏市场,希望需求新的突破口。

  近年来,我国网络游戏市场发展迅速,根据有关部门统计2004年网络游戏用户达到2300万,市场规模为25.7亿元。“如果能将这一庞大的市场转接到手机上,那么手机游戏市场容量将更加客观。”许多业内的SP都有着这样的盘算。根据信息产业部的统计,目前国内手机用户就已超过3.5亿,按照8%~10%高端手机用户比例计算的话,那么,我国市场将有3500万愿意使用高端产品和手机游戏服务的高级用户。假设这3500万用户每人每月只下载一个游戏,每下载一个游戏收费5元,那么一个月的市场就有1.75亿元,一年就有近21亿元。正是看到了这一市场的潜力,众多SP们包括世嘉、育碧等也已减少了对PC网络游戏的开发力度,相继成立手机游戏开发部门,增大对这一新兴网络游戏的资金和人力投入,以期在该市场获得最大利润。

  Frost&Sullivan研究公司预计,2008年全球移动游戏业创收将超过93亿美元,中国的手机游戏市场也会达到400亿元人民币的规模。毫无疑问,面对巨大的市场诱惑,互联网风潮过后,很多投资热情都倾泻在这个由互联网、手机所催生的SP市场。

  王唯嘉告诉记者,目前国内手机游戏已走过了从最初的文字类游戏、嵌入式游戏、可下载的单机版游戏到大型手机网络游戏的发展历程,手机游戏的业务形态日益成熟。而根据美通无线的调查,使用下载游戏的用户在25至35岁之间居多,这部分人有一定的消费能力(单条游戏下载游戏5至8元,或者包月制每月200至300元),并且很多也是传统PC游戏的爱好者。“毫无疑问,无论是从未来的3G发展还是个人娱乐化趋势看,手机游戏市场都将是各个SP争抢的蛋糕,”

  虽然市场潜力无限,但新兴的中国手机游戏市场却同样面临问题。一个重要方面就是手机终端问题,手机游戏玩得顺心必须有一款得心应手的手机,而目前国内的彩屏手机价格相对较贵,许多年轻的玩家只能够望而却步,有的手机还非常耗电,坚持不了多久的时间,而且手机屏幕大小不一,平台也不统一。

  同时,目前的网络对手机游戏的局限性还比较大,除了网络速度限制,网络延时、无法同步数据也是一个很大的障碍,这也是造成互动式、回合制的实时联网游戏很难发展起来的原因。为了给手机游戏玩家更丰富的游戏体验,3G手机是必需的。在这样一个平台上,手机网络游戏的交互功能、网络稳定性完全可以做到像现在的PC网络游戏这样。

  另一方面,目前,国内的手机游戏还是处于产品推广阶段,高昂的费用显然会抬高门槛,毕竟走在时尚前沿的还是集中在16-30岁的年青人群,相当一部分是学生。对消费能力不高的用户,应当由终端厂商和游戏开发商在中低端手机上体现更多的游戏应用。

  此外,游戏开发制作人才的严重匮乏,也是我国手机游戏发展中一个无法回避的问题。目前的手机游戏大多来自于国外,日、韩、欧美等地开发的游戏几乎占据了中国手机游戏市场80%的游戏内容。虽然,手机游戏服务商们已经有了强烈的自主开发的意愿,可是边学习边开发的速度远远赶不上手机网络游戏的发展速度。

  王维嘉透露,虽然美通从2001就推出了“泡泡小新”,该款游戏更成为中国第一个用户量超过百万的手机短信游戏;在Kjava方面,美通2004年的三界传说是国内第一款正式运营的手机网络游戏。但目前,几款游戏仍处于培育期的中国手机游戏市场中,美通仍然没有赢利。其实,各家SP的盈利情况都不算理想,因为目前大多数产品仍属于免费向用户提供。

  “因为整个移动增值行业都是一个新兴的行业,尤其是手机游戏这个领域,用户接受、产业链的形成,包括和电信运营商的合作,大家的盈利分成模式还需要协商改进。不过这些都是市场发展初期肯定会遇到的。”王维嘉表示,美通无限的目标是在2005年底前获得盈利。而他也判断,届时,中国手机游戏市场将步入正轨,发展速度将大幅提高。(

Trenoma有限公司发表新闻通报称,以综合性的.com,.net,.org或国家根.de,.cn,.tw等为域名根的互联网有限时代即将结束,因为,所谓的“姓域名根”时代从今天开始了。也就是说,域名根进入了自由时代。但是,对此事的可靠性,德国之声中文网记者认为还需谨慎。

    至今为止,网站域名根是有限的,比如.com, .net, .org.,或以国家缩写为根的:.de是德国的根,.cn是中国的根,.tw是台湾的根,等等。这种有限域名根情况导致一种激烈的“争名夺利”。据称,德国姓施密特而想要以www.schmidt.de做域名的人多达70万,而在中国想要以同一个www.姓.cn做域名的甚至最高达1000万人左右。

    据Trenoma Ltd.公司今天发出的新闻通报,这种情况将发生根本变化。人的姓可以做域名根了。也就是说,从现在开始,可以在这家在伦敦和柏林开业的公司注册“www.名.姓”这样的域名,比如www.gerhard.schroeder.但是衷谥Ц陡鳷renoma有限公司35欧元手续费,还只是“预注册”,有待国际互联网管理机构2009年批准。

    德国之声中文网记者电话询问了Trenoma有限公司柏林公司的创建人和总经理迪尔克.克里申诺夫斯基。记者问,如果现在许多人花了钱预注册了姓名域名,到2009年如果批不下来怎么办?克里申诺夫斯基说:按国际互联网权威机构ICANN最近几次会议的精神(他也参加了有关会议),这是一个趋势。现在新增的域名根如travel等,每增加一个域名,ICANN也获得2美元的收入。因此,ICANN自然是欢迎域名大增的。

    他还说,该公司是想出这个主意来的,是全世界第一家做这方面登记的公司。记者问,该公司是否有这个权利来做这件事。他说,不存在权利问题。该公司已经在ICANN挂了号。其它公司将来可以做其它域名根的事,但至少欧洲拉丁文姓做域名根这件事是他的公司在做了。他说,今天这个消息发布后,已经有不少网民登记申请。

    记者问,这是否意味着域名根可以任意取,比如说一家公司叫施耐德有限公司(Schneider GmbH),这个网站名是否可以以“有限公司”(gmbh)为根:www.schneider.gmbh.他说,这是不行的,域名根必须是一个姓,可以是一家公司老板的名字,比如叫www.schneider-gmbh.schneider就可以。

    克里申诺夫斯其对记者说,目前只能以欧美已知的姓为域名根,还不涉及世界其它国家的姓,比如中国的姓、日本的姓等。但明年该公司考虑把姓根域名逐步扩展到全世界。他的公司将有来自世界各地的员工,来核实有关的姓是否真的存在。

    克里申诺夫斯基还说,有些姓在欧洲、德国是罕见的,但确实存在,比如姓“汉堡”的(Hamburg),或姓“市长”的(Buergermeister),碰到这些罕见的姓,公司将予以核实,要求登记人提供身份证的复印件,证明确实是姓这个。

    德国有一些很长的姓,还有双姓的。对此,克里申诺夫斯基的答复是:这些都可以作为域名根登记,如果是双姓,中间允许保留原来的小杠,如Hans Schmidt-Schneider,域名可以叫www.hans.schmidt-schneider.

        引入姓域名根制后,“争名夺利”现象可以得到很大缓解,但是别说天下了,即使在德国,同名又同姓的人也多得是。在这种情况下,就采取“先来后到”的政策。比如德国叫Hans Schmidt的人有多少万,第一个登记的人的网站可以叫www.hans.schmidt,而第二个就得另外想招了,比如叫www.hans2.schmidt.

        Trenoma公司估计,到2015年,全世界将有约1000万个以姓为域名的网站。

    根据电话询问的情况,德国之声记者认为,此事的可靠性还未能得到证实。