posts - 319, comments - 22, trackbacks - 0, articles - 11
  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理

15款很棒的 JavaScript 开发工具

ugmbbc发布于 2011-05-17 14:18:36|3743 次阅读 字体: 打印预览 分享至新浪微博 转贴到开心网 分享到校内人人网 添加到Google书签

网络软件

感谢山边小溪的投递
在开发中,借助得力的工具可以事半功倍。今天,这篇文章向大家分享最新收集的15款非常有用的 JavaScript 开发工具。
TestSwarm: Continious & Distributed JS Testing

TestSwarm是Mozilla实验室推出的一个开源项目,它旨在为开发者提供在多个浏览器版本上快速轻松测试自己JavaScript代码的方法。
Javascript-212 in Useful JavaScript and jQuery Tools, Libraries, Plugins

Minimee
在网络上,速度是很重要的,Minimee能帮助你将CSS和JavaScript文件进行自动压缩和打包。
Javascript-169 in Useful JavaScript and jQuery Tools, Libraries, Plugins

Doctor JS
Doctor JS 是一款帮你分析 JavaScript 代码的工具,测试你的代码在多态、原型、异常和回调方面写得怎么样。
Javascript-174 in Useful JavaScript and jQuery Tools, Libraries, Plugins

Remy Sharp’s JSConsole
一个在线的 JavaScript 控制台工具,对于测试、调试和演示非常有用。
Javascript-269 in Useful JavaScript and jQuery Tools, Libraries, Plugins

JavaScript Library Boilerplate
JavaScript Library Boilerplate 帮助你随时随地创建自己的 JavaScript 库。
Javascript-260 in Useful JavaScript and jQuery Tools, Libraries, Plugins

jsdoc-toolkit
JsDoc Toolkit 是一款辅助工具,你只需要根据约定在 JavaScript 代码中添加相应的注释,它就可以根据这些注释来自动生成API文档。
Js-010 in Useful JavaScript and jQuery Tools, Libraries, Plugins

Jasmine: BDD for your JavaScript
Jasmine 是一个有名的javascript单元测试框架,它是独立的“行为驱动开发”框架。
Js-011 in Useful JavaScript and jQuery Tools, Libraries, Plugins

ObfuscateJS: JavaScript compressor
一款 JavaScript 混淆工具,去除空白和注释,重命名变量等。
Javascript-282 in Useful JavaScript and jQuery Tools, Libraries, Plugins

PEG.js
PEG.js 是一个JavaScript的表达式语法解析器,它使您能够轻松地建立复杂的数据或计算机程序语言的快速分析器。
Javascript-228 in Useful JavaScript and jQuery Tools, Libraries, Plugins

JSONView
JSONView 是一款帮助你在浏览器中查看JSON文档的Firefox插件。
Js-014 in Useful JavaScript and jQuery Tools, Libraries, Plugins

JSonduit
JSonduit 是一个将网页内容转换为 JSON 格式订阅器的工具。
Javascript-182 in Useful JavaScript and jQuery Tools, Libraries, Plugins

jsplumb
jsPlumb 为开发者提供了可视化链接元素到页面的方法,可以结合jQuery、MooTools 和 YUI3使用。
Javascript-193 in Useful JavaScript and jQuery Tools, Libraries, Plugins

Helma
Helma 是一个用来开发快速、稳定的Web应用程序的开源框架,它使用JavaScript 来作为服务端脚本环境,从而可以省略编译周期。
Js-015 in Useful JavaScript and jQuery Tools, Libraries, Plugins

HTML + JSON Report
一款将 JSON 数据转换为可读性更高的HTML格式内容的在线工具。
Javascript-300 in Useful JavaScript and jQuery Tools, Libraries, Plugins

JSON Editor
这个编辑器可以帮助你方便的编辑 JSON 字符串。
Javascript-222 in Useful JavaScript and jQuery Tools, Libraries, Plugins
你可能还喜欢

  • 推荐40个优秀的免费CSS工具
  • 分享18个常用的网站性能测试工具
  • 推荐25个提高网站可用性和转化率的工具
  • JavaScript初学者应注意的七个细节
  • 你可能不知道的10个JavaScript小技巧

  • (编译来源:梦想天空  原文来自:Useful JavaScript and jQuery Tools, Libraries, Plugins

     

    posted @ 2011-05-17 18:59 RTY 阅读(300) | 评论 (0)编辑 收藏

    VirtualBox 宣布了 4.0.8 版本,该版本改进了对 Gnome 3 的 3D 支持。同时还修复了不少bug,包括改变 guest 窗体大小时可能导致程序崩溃的问题以及 Ubuntu 11.04 以及 Fedora 15 下 Gnome 3 的渲染问题。

    VirtualBox 是一款功能强大的 x86 虚拟机软件,它不仅具有丰富的特色,而且性能也很优异。

    更多关于VirtualBox的详细信息,或者下载地址请点这里

    posted @ 2011-05-17 18:55 RTY 阅读(353) | 评论 (0)编辑 收藏

    20+ Useful jQuery Content Slider Scripts and Tutorials

    Posted by: Adrian, In: Coding, Tutorials, On: May 16, 2011 | 2 Comments

    Maybe you’ve seen some businesses with beautifully designed websites that also have a slider which displays their available products and services; and since then you’ve been asking yourself how it’s done? If you still haven’t found out, then lend me an ear, because here’s the answer. It’s done with jQuery.

    So what is jQuery? As you probably know, there’s JavaScript – a scripting language, but Query is just a branch. jQuery is used to make easier the effects and interaction with the development code and works just like JavaScript. Released in January 2006, it gains popularity pretty quickly as it’s a nice technology that helps making the websites over the internet more interactive and fun. Also it takes less code than with JavaScript to create effects like drop down menus, animations, drag and drop elements and form validation.

    So, as it is easy and simple to use, needs less coding and creates interesting and beautiful effects, why not try making a slider with your recent products to put on your website? Here are 20+ of the most useful jQuery content slider scripts and tutorials. Hope to have inspired at least some of you and soon we will see more sites that are interactive and client oriented, saving space and showcasing their offer into nicely designed sliders.

    Rotating Image Slider with jQuery

    Parallax Slider with jQuery

    Compact News Previewer with jQuery

    Awkward Showcase – A jQuery Plugin

    WOW Slider

    Automatic Image Slider w/ CSS & jQuery

    Making a Mosaic Slideshow With jQuery & CSS

    How to Make Auto-Advancing Slideshows

    Coding a Rotating Image Slideshow w/ CSS3 and jQuery

    Create Beautiful jQuery slider tutorial

    CU3ER Image Slider

    jQueryGlobe – jQuery Plugin

    Nivo Slider

    Piecemaker XML Gallery

    Slide Deck

    Slides

    Slider Gallery with jQuery

    Estro – jQuery Ken Burns & swipe effect slider

    FSS – Full Screen Sliding Website Plugin

    li JQuery Slider/Image Rotator

    Point of ViUU

    posted @ 2011-05-17 18:52 RTY 阅读(567) | 评论 (0)编辑 收藏

    IntelliJ IDEA 10.5 正式版发布了,建议所有人升级。IDEA 10.5 是一个重要的升级版本,该版本主要的改进包括:

    • 完全支持 Java 7

      Java 7 migration helpers

    • 重构功能、搜索替换功能界面的重构,简化代码自动完成

      In-place search-replace


      In-place introduce refactorings

    • 支持 Chrome 的 JavaScript 调试器
    • 支持 Groovy 1.8 和 Spring 3.1

      Shift-less code completion

      Spring 3.1 profiles

       

    • JavaScript, Android and Flex 开发改进
    • Jetty 集成
    • XSLT2 支持

    完整内容请看官方发行说明:http://www.jetbrains.com/idea/whatsnew/

    posted @ 2011-05-17 18:50 RTY 阅读(626) | 评论 (0)编辑 收藏

    安装Msysgit

    下一步

    同意GNU协议

    选择安装位置,下一步

    选择TrueType  Front,下一步

    不创建启动文件夹

    默认Git Bash,就可以了

    选择使用OpenSSH

    选择默认的Checkout Style

    安装完成了

    配置本地的Git

    就这样安装好Msysgit后,就可以开始配置开发环境了,在你的工作目录建立一个新的文件夹Git,比如我的

    选择Git文件夹,右键,选择Git Bash Here,会弹出shell命令行界面

    生成你的Public Key

    输入命令

    ssh-keygen –C “你的email地址 “ –t rsa

    就会为你生成一个 SSH Key,然后会询问一些保存文件的位置,设置密码神马的,直接回车,回车,回车,默认的就可以了

    因为主要是在本地使用,可不使用口令,直接回车就可以了!

    好了,现在为你生成了sshkey了。

    设置Github帐号的Public Key

    如果你有一个github的账号的话,

    登陆github.Com 网站,在SSH Public Keys 设置你的SSH Public Keys

    创建一个数据仓库

    设置相关的信息

    设置你的Email和Nickname

    现在回到你的工作目录,可以选择设置你默认的email和nickname
    输入命令

    git config –global user.email 你的email地址
    git config –global user.name "Arthur"

    Clone你的项目

    右键你的工作目录Git,选择Git GUI,选择克隆已有版本库

    输入你在github的项目地址,例如

    git@github.com:caijiamx/Magento-Theme.git

    会弹出一个框出来让你是否选择信任github,输入yes

    然后就会从github拉取数据了,

    前面简单的介绍如何克隆版本库,现在就要正式的开始工作了。

    命令行下的Git开发

    新建一个工作文件夹,右键打开 Git Bash,输入

    ssh git@github.com

    选择yes,加入到hosts中

    信任git@github.com站点

    配置你的Github上Username和Token

    设置你的github 用户名

    git config –global github.user caijiamx

    设置你的git账户的token

    Git config –global github.token your token

    这个token在github,com的Account Settings->Account Admin右侧找到。

    我们重新开始上面介绍的从GUI拉取数据,现在我们从命令开始你的项目

    Git下的开发

    为你的项目添加说明文件

    touch readme.txt
    //输入hello world

    将新建的文件添加到暂存区,输入命令

    git add readme.txt

    提交你的更改 输入命令

    git commit –m "first commit"

    添加远程服务器仓库,输入命令

    git remote add origin git@github.com:directoo/Magento-Theme.git

    提交你的更改到服务器,输入命令

    git push origin master

    参考资源

    github set up git

    如何设置你的SSH Key

    github官方帮助教程

    初次运行 Git 前的配置

    posted @ 2011-05-15 00:30 RTY 阅读(828) | 评论 (0)编辑 收藏

    1、Run-Time Library

    Run-Time Library是编译器提供的标准库,提供一些基本的库函数和系统调用。
    我们一般使用的Run-Time Library是C Run-Time Libraries。当然也有Standard C++ libraries。 
    C Run-Time Libraries实现ANSI C的标准库。VC安装目录的CRT目录有C Run-Time库的大部分源代码。

    C Run-Time Libraries有静态库版本,也有动态链接库版本;有单线程版本,也有多线程版本;还有调试和非调试版本。
    可以在"project"-"settings"-"C/C++"-"Code Generation"中选择Run-Time Library的版本。

    动态链接库版本:
    /MD Multithreaded DLL 使用导入库MSVCRT.LIB
    /MDd Debug Multithreaded DLL 使用导入库MSVCRTD.LIB

    静态库版本:
    /ML Single-Threaded 使用静态库LIBC.LIB 
    /MLd Debug Single-Threaded 使用静态库LIBCD.LIB
    /MT Multithreaded 使用静态库LIBCMT.LIB
    /MTd Debug Multithreaded 使用静态库LIBCMTD.LIB

    C Run-Time Library的标准io部分与操作系统的关系很密切,在Windows上,CRT的io部分代码只是一个包装,底层要用到操作系统内核kernel32.dll中的函数,在编译时使用导入库kernel32.lib。这也就是为什么在嵌入式环境中,我们一般不能直接使用C标准库。
    在Linux环境当然也有C标准库,例如:
    ld -o output /lib/crt0.o hello.o -lc
    参数"-lc"就是在引用C标准库libc.a。猜一猜"-lm"引用哪个库文件?

    2、常见的编译参数

    VC建立项目时总会定义"Win32"。控制台程序会定义"_CONSOLE",否则会定义"_WINDOWS"。Debug版定义"_DEBUG",Release版定义"NDEBUG"

     

    与MFC DLL有关的编译常数包括:
    _WINDLL 表示要做一个用到MFC的DLL
    _USRDLL 表示做一个用户DLL(相对MFC扩展DLL而言) 
    _AFXDLL 表示使用MFC动态链接库
    _AFXEXT 表示要做一个MFC扩展DLL
    所以:
    Regular, statically linked to MFC _WINDLL,_USRDLL 
    Regular, using the shared MFC DLL _WINDLL,_USRDLL,_AFXDLL
    Extension DLL _WINDLL,_AFXDLL,_AFXEXT

    CL.EXE编译所有源文件,LINK.EXE链接EXE和DLL,LIB.EXE产生静态库。

    3、subsystem和可执行文件的启动

    LINK的时候需要指定/subsystem,这个链接选项告诉Windows如何运行可执行文件。
    控制台程序是/subsystem:"console"
    其它程序一般都是/subsystem:"windows "

     

    将 subsystem 选成"console"后,Windows在进入可执行文件的代码前(如mainCRTStartup),就会产生一个控制台窗口。
    如果选择"windows",操作系统就不产生console窗口,该类型应用程序的窗口由用户自己创建。

    可执行文件都有一个Entry Point,LINK时可以用/entry指定。缺省情况下,如果subsystem是“console”,Entry Point是 mainCRTStartup(ANSI)或wmainCRTStartuup(UNICODE),即:
    /subsystem:"console" /entry:"mainCRTStartup" (ANSI)
    /subsystem:"console" /entry:"wmainCRTStartuup" (UNICODE)
    mainCRTStartup 或 wmainCRTStartuup 会调用main或wmain。
    值得一提的是,在进入应用程序的Entry Point前,Windows的装载器已经做过C变量的初始化,有初值的全局变量拥有了它们的初值,没有初值的变量被设为0。

    如果subsystem是“windows”,Entry Point是WinMain(ANSI)或wWinMain(UINCODE),即:
    /subsystem:"windows" /entry:"WinMainCRTStartup" (ANSI)
    /sbusystem:"windows" /entry:"wWinMainCRTStartup" (UINCODE)
    WinMainCRTStartup 或 wWinMainCRTStartup 会调用 WinMain 或 wWinMain。

    这些入口点函数,在CRT目录都可以看到源代码,例如(为了简洁,我删除了原代码的一些条件编译):

    void mainCRTStartup(void)
    {
            int mainret;
    
            /* Get the full Win32 version */
            _osver = GetVersion();
            _winminor = (_osver >> 8) & 0x00FF ;
            _winmajor = _osver & 0x00FF ;
            _winver = (_winmajor << 8) + _winminor;
            _osver = (_osver >> 16) & 0x00FFFF ;
    
    #ifdef _MT
            if ( !_heap_init(1) )               /* initialize heap */
    #else  /* _MT */
            if ( !_heap_init(0) )               /* initialize heap */
    #endif  /* _MT */
                fast_error_exit(_RT_HEAPINIT);  /* write message and die */
    
    #ifdef _MT
            if( !_mtinit() )                    /* initialize multi-thread */
                fast_error_exit(_RT_THREAD);    /* write message and die */
    #endif  /* _MT */
    
            __try {
                _ioinit();                      /* initialize lowio */
                _acmdln = (char *)GetCommandLineA();        /* get cmd line info */
                _aenvptr = (char *)__crtGetEnvironmentStringsA();        /* get environ info */
                _setargv();
                _setenvp();
                __initenv = _environ;
                mainret = main(__argc, __argv, _environ);
                exit(mainret);
            }
            __except ( _XcptFilter(GetExceptionCode(), GetExceptionInformation()) )
            {
                _exit( GetExceptionCode() );        /* Should never reach here */
            } /* end of try - except */
    }  

    如果使用MFC框架,WinMain也会被埋藏在MFC库中(APPMODUL.CPP):
    extern "C" int WINAPI
    _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
    LPTSTR lpCmdLine, int nCmdShow)
    {
    // call shared/exported WinMain
    return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
    }
    对于ANSI版本,"_tWinMain"就是"WinMain";对于UINCODE版本,"_tWinMain"就是"wWinMain"。可参见afx.h:

    #ifdef _UNICODE
    #define _tmain wmain
    #define _tWinMain wWinMain
    #else
    #define _tmain main
    #define _tWinMain WinMain
    #endif

    全局C++对象的构造函数是在什么地方调用的?答案是在进入应用程序的Entry Point后,在调用main函数前的初始化操作中。所以MFC的theApp的构造函数是在_tWinMain之前调用的。

    4、不显示Console窗口的Console程序

    在默认情况下/subsystem 和/entry开关是匹配的,也就是:
    "console"对应"mainCRTStartup"或者"wmainCRTStartup"
    "windows"对应"WinMain"或者"wWinMain"
    我们可以通过手动修改的方法使他们不匹配。例如:

     

    #include "windows.h"
    #pragma comment( linker, "/subsystem:\"windows\" /entry:\"mainCRTStartup\"" ) // 设置入口地址 
    void main(void)
    {
    MessageBox(NULL, "hello", "Notice", MB_OK);
    }

    这个Console程序就不会显示Console窗口。如果选/MLd的话,这个程序只需要链接LIBCD.LIB user32.lib kernel32.lib。

    其实如果不想看到Console窗口,还有一个更直接的方法:那就是直接在EXE文件中将PE文件头的Subsystem从3改成2。在EXE文件中,PE文件头的偏移地址是0x3c,Subsystem是一个WORD,它在PE文件头中的偏移是0x5c。

    5、MFC的库文件

    MFC的库可以静态链接,也可以动态链接。静态库和动态库又有Debug和Release,ANSI和Unicode版本之分。

     

    静态MFC库主要有:
    ANSI Debug NAFXCWD.LIB
    ANSI Release NAFXCW.LIB
    Unicode Debug UAFXCWD.LIB
    Unicode Release UAFXCW.LIB 

    动态链接库主要有;
    ANSI Debug MFCxxD.LIB (core,MFCxxD.DLL), 
    MFCOxxD.LIB (OLE,MFCOxxD.DLL), 
    MFCDxxD.LIB (database,MFCDxxD.DLL), 
    MFCNxxD.LIB (network,MFCNxxD.DLL), 
    MFCSxxD.LIB (static)

    ANSI Release MFCxx.LIB (combined,MFCxx.DLL)
    MFCSxx.LIB (static)

    Unicode Debug MFCxxUD.LIB (core,MFCxxUD.DLL), 
    MFCOxxUD.LIB (OLE,MFCOxxUD.DLL), 
    MFCDxxUD.LIB (database,MFCDxxUD.DLL), 
    MFCNxxUD.LIB (network,MFCNxxUD.DLL), 
    MFCSxxUD.LIB (static)

    Unicode Release MFCxxU.DLL (combined,MFCxxU.DLL), 
    MFCSxxU.LIB (static)

    上面的LIB文件除了MFCSxx(D、U、UD).LIB以外都是导入库。
    MFC动态链接库版本也需要静态链接一些文件,这些文件就放在MFCSxx(D、U、UD).LIB中。例如包含_tWinMain的appmodul.cpp。

    6、结束语

    研究这些问题的动机是想弄清楚我们的程序是如何装载、运行的。但是,由于Windows不是开源平台,我也只能研究到PE文件(Windows上可执行文件的格式)。entry point、subsystem都是PE文件头的一部分。

    Windows在进入PE文件的entry point之前做了些什么,就看不到了,只能大概推测:应该是创建一个进程,装载PE文件和所有需要的DLL,初始化C变量,然后从某个起点函数开始运行。不同的subsystem,应该有不同的起点。调用这个起点函数时应该传入PE文件的entry point地址。

     

    posted @ 2011-05-12 22:06 RTY 阅读(445) | 评论 (0)编辑 收藏

    1 什么是堆栈

    编译器一般使用堆栈实现函数调用。堆栈是存储器的一个区域,嵌入式环境有时需要程序员自己定义一个数组作为堆栈。Windows为每个线程自动维护一个堆栈,堆栈的大小可以设置。编译器使用堆栈来堆放每个函数的参数、局部变量等信息。

    函数调用经常是嵌套的,在同一时刻,堆栈中会有多个函数的信息,每个函数占用一个连续的区域。一个函数占用的区域被称作帧(frame)。

    编译器从高地址开始使用堆栈。 假设我们定义一个数组a[1024]作为堆栈空间,一开始栈顶指针指向a[1023]。如果栈里有两个函数a和b,且a调用了b,栈顶指针会指向函数b的帧。如果函数b返回。栈顶指针就指向函数a的帧。如果在栈里放了太多东西造成溢出,破坏的是a[0]上面的东西。

    在多线程(任务)环境,CPU的堆栈指针指向的存储器区域就是当前使用的堆栈。切换线程的一个重要工作,就是将堆栈指针设为当前线程的堆栈栈顶地址。

    不同CPU,不同编译器的堆栈布局、函数调用方法都可能不同,但堆栈的基本概念是一样的。

    2 函数调用约定

    函数调用约定包括传递参数的顺序,谁负责清理参数占用的堆栈等,例如 :

     参数传递顺序谁负责清理参数占用的堆栈
    __pascal从左到右调用者
    __stdcall从右到左被调函数
    __cdecl从右到左调用者

    调用函数的代码和被调函数必须采用相同的函数的调用约定,程序才能正常运行。在Windows上,__cdecl是C/C++程序的缺省函数调用约定。

    在有的cpu上,编译器会用寄存器传递参数,函数使用的堆栈由被调函数分配和释放。这种调用约定在行为上和__cdecl有一个共同点:实参和形参数目不符不会导致堆栈错误。

    不过,即使用寄存器传递参数,编译器在进入函数时,还是会将寄存器里的参数存入堆栈指定位置。参数和局部变量一样应该在堆栈中有一席之地。参数可以被理解为由调用函数指定初值的局部变量。

    3 例子:__cdecl和__stdcall

    不同的CPU,不同的编译器,堆栈的布局可能是不同的。本文以x86,VC++的编译器为例。

    VC++编译器的已经不再支持__pascal, __fortran, __syscall等函数调用约定。目前只支持__cdecl和__stdcall。

    采用__cdecl或__stdcall调用方式的程序,在刚进入子函数时,堆栈内容是一样的。esp指向的栈顶是返回地址。这是被call指令压入堆栈的。下面是参数,左边参数在上,右边参数在下(先入栈)。

    如前表所示,__cdecl和__stdcall的区别是:__cdecl是调用者清理参数占用的堆栈,__stdcall是被调函数清理参数占用的堆栈。

    由于__stdcall的被调函数在编译时就必须知道传入参数的准确数目(被调函数要清理堆栈),所以不能支持变参数函数,例如printf。而且如果调用者使用了不正确的参数数目,会导致堆栈错误。

    通过查看汇编代码,__cdecl函数调用在call语句后会有一个堆栈调整语句,例如:

      a = 0x1234;
      b = 0x5678;
      c = add(a, b);

    对应x86汇编:

      mov dword ptr [ebp-4],1234h
      mov dword ptr [ebp-8],5678h
      mov eax,dword ptr [ebp-8]
      push eax
      mov ecx,dword ptr [ebp-4]
      push ecx
      call 0040100a
      add esp,8
      mov dword ptr [ebp-0Ch],eax


    __stdcall的函数调用则不需要调整堆栈:

      call 00401005
      mov dword ptr [ebp-0Ch],eax

    函数

      int __cdecl add(int a, int b)
      {
      return a+b;
      }

    产生以下汇编代码(Debug版本):

      push ebp
      mov ebp,esp
      sub esp,40h
      push ebx
      push esi
      push edi
      lea edi,[ebp-40h]
      mov ecx,10h
      mov eax,0CCCCCCCCh
      rep stos dword ptr [edi]
      mov eax,dword ptr [ebp+8]
      add eax,dword ptr [ebp+0Ch]
      pop edi
      pop esi
      pop ebx
      mov esp,ebp
      pop ebp
      ret // 跳转到esp所指地址,并将esp+4,使esp指向进入函数时的第一个参数

    再查看__stdcall函数的实现,会发现与__cdecl函数只有最后一行不同:

      ret 8 // 执行ret并清理参数占用的堆栈

    对于调试版本,VC++编译器在“直接调用地址”时会增加检查esp的代码,例如:

      ta = (TAdd)add; // TAdd定义:typedef int (__cdecl *TAdd)(int a, int b);
      c = ta(a, b);

    产生以下汇编代码:

      mov [ebp-10h],0040100a
      mov esi,esp
      mov ecx,dword ptr [ebp-8]
      push ecx
      mov edx,dword ptr [ebp-4]
      push edx
      call dword ptr [ebp-10h]
      add esp,8
      cmp esi,esp
      call __chkesp (004011e0)
      mov dword ptr [ebp-0Ch],eax

    __chkesp 代码如下。如果esp不等于函数调用前保存的值,就会转到错误处理代码。

      004011E0 jne __chkesp+3 (004011e3)
      004011E2 ret
      004011E3 ;错误处理代码

    __chkesp的错误处理会弹出对话框,报告函数调用造成esp值不正确。 Release版本的汇编代码要简洁得多。也不会增加 __chkesp。如果发生esp错误,程序会继续运行,直到“遇到问题需要关闭”。

    3 补充说明

    函数调用约定只是“调用函数的代码”和被调用函数之间的关系。

    假设函数A是__stdcall,函数B调用函数A。你必须通过函数声明告诉编译器,函数A是__stdcall。编译器自然会产生正确的调用代码。

    如果函数A是__stdcall。但在引用函数A的地方,你却告诉编译器,函数A是__cdecl方式,编译器产生__cdecl方式的代码,与函数A的调用约定不一致,就会发生错误。

    以delphi调用VC函数为例,delphi的函数缺省采用__pascal约定,VC的函数缺省采用__cdecl约定。我们一般将VC的函数设为__stdcall,例如:

      int __stdcall add(int a, int b);

    在delphi中将这个函数也声明为__stdcall,就可以调用了:

      function add(a: Integer; b: Integer): Integer;
      stdcall; external 'a.dll';

    因为考虑到可能被其它语言的程序调用,不少API采用__stdcall的调用约定。

    posted @ 2011-05-12 22:00 RTY 阅读(397) | 评论 (0)编辑 收藏

    3 字符编码模型

    程序员经常会面对复杂的问题,而降低复杂性的最简单的方法就是分而治之。Peter Constable在他的文章"Character set encoding basics Understanding character set encodings and legacy encodings"中描述了字符编码的四层模型。我觉得这种说法确实可以更清晰地展现字符编码中发生的事情,所以在这里也介绍一下。

    3.1 字符的范围(Abstract character repertoire)

    设计字符编码的第一层就是确定字符的范围,即要支持哪些字符。有些编码方案的字符范围是固定的,例如ASCII、ISO 8859 系列。有些编码方案的字符范围是开放的,例如Unicode的字符范围就是世界上所有的字符。

    3.2 用数字表示字符(Coded character set)

    设计字符编码的第二层是将字符和数字对应起来。可以将这个层次理解成数学家(即从数学角度)看到的字符编码。数学家看到的字符编码是一个正整数。例如在Unicode中:汉字“字”对应的数字是23383。汉字“”对应的数字是134192。

    在写html文件时,可以通过输入"字"来插入字符“字”。不过在设计字符编码时,我们还是习惯用16进制表示数字。即将23383写成0x5BD7,将134192写成0x20C30。

    3.3 用基本数据类型表示字符(Character encoding form)

    设计字符编码的第三层是用编程语言中的基本数据类型来表示字符。可以将这个层次理解成程序员看到的字符编码。在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32。UTF是“UCS Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。例如,“汉字”对应的数字是0x6c49和0x5b57,而编码的程序数据是:

     

    	BYTE data_utf8[] = {0xE6, 0xB1, 0x89, 0xE5, 0xAD, 0x97};	// UTF-8编码
    	WORD data_utf16[] = {0x6c49, 0x5b57};				// UTF-16编码
    	DWORD data_utf32[] = {0x6c49, 0x5b57};				// UTF-32编码

     

    这里用BYTE、WORD、DWORD分别表示无符号8位整数,无符号16位整数和无符号32位整数。UTF-8、UTF-16、UTF-32分别以BYTE、WORD、DWORD作为编码单位。

    “汉字”的UTF-8编码需要6个字节。“汉字”的UTF-16编码需要两个WORD,大小是4个字节。“汉字”的UTF-32编码需要两个DWORD,大小是8个字节。4.2节会介绍将数字映射到UTF编码的规则。

    3.4 作为字节流的字符(Character encoding scheme)

    字符编码的第四层是计算机看到的字符,即在文件或内存中的字节流。例如,“字”的UTF-32编码是0x5b57,如果用little endian表示,字节流是“57 5b 00 00”。如果用big endian表示,字节流是“00 00 5b 57”。

    字符编码的第三层规定了一个字符由哪些编码单位按什么顺序表示。字符编码的第四层在第三层的基础上又考虑了编码单位内部的字节序。UTF-8的编码单位是字节,不受字节序的影响。UTF-16、UTF-32根据字节序的不同,又衍生出UTF-16LE、UTF-16BE、UTF-32LE、UTF-32BE四种编码方案。LE和BE分别是Little Endian和Big Endian的缩写。

    3.5 小结

    通过四层模型,我们又把字符编码中发生的这些事情梳理了一遍。其实大多数代码页都不需要完整的四层模型,例如GB18030以字节为编码单位,直接规定了字节序列和字符的映射关系,跳过了第二层,也不需要第四层。

    4 再谈Unicode

    Unicode是国际组织制定的可以容纳世界上所有文字和符号的字符编码方案。Unicode用数字0-0x10FFFF来映射这些字符,最多可以容纳1114112个字符,或者说有1114112个码位。码位就是可以分配给字符的数字。UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。

    Unicode字符集可以简写为UCS(Unicode Character Set)。早期的Unicode标准有UCS-2、UCS-4的说法。UCS-2用两个字节编码,UCS-4用4个字节编码。UCS-4根据最高位为0的最高字节分成2^7=128个group。每个group再根据次高字节分为256个平面(plane)。每个平面根据第3个字节分为256行 (row),每行有256个码位(cell)。group 0的平面0被称作BMP(Basic Multilingual Plane)。将UCS-4的BMP去掉前面的两个零字节就得到了UCS-2。

    Unicode标准计划使用group 0 的17个平面: 从BMP(平面0)到平面16,即数字0-0x10FFFF。《谈谈Unicode编码》主要介绍了BMP的编码,本文将介绍完整的Unicode编码,并从多个角度浏览Unicode。本文的介绍基于Unicode 5.0.0版本。

    4.1 浏览Unicode

    先看一些数字:每个平面有2^16=65536个码位。Unicode计划使用了17个平面,一共有17*65536=1114112个码位。其实,现在已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16。其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000-0xFFFFD和0x100000-0x10FFFD。所谓专用区,就是保留给大家放自定义字符的区域,可以简写为PUA。

    平面0也有一个专用区:0xE000-0xF8FF,有6400个码位。平面0的0xD800-0xDFFF,共2048个码位,是一个被称作代理区(Surrogate)的特殊区域。它的用途将在4.2节介绍。

    238605-65534*2-6400-2408=99089。余下的99089个已定义码位分布在平面0、平面1、平面2和平面14上,它们对应着Unicode目前定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、3419、43253和337个字符。平面2的43253个字符都是汉字。平面0上定义了27973个汉字。

    在更深入地了解Unicode字符前,我们先了解一下UCD。

    4.1.1 什么是UCD

    UCD是Unicode字符数据库(Unicode Character Database)的缩写。UCD由一些描述Unicode字符属性和内部关系的纯文本或html文件组成。大家可以在Unicode组织的网站看到UCD的最新版本

    UCD中的文本文件大都是适合于程序分析的Unicode相关数据。其中的html文件解释了数据库的组织,数据的格式和含义。UCD中最庞大的文件无疑就是描述汉字属性的文件Unihan.txt。在UCD 5.0,0中,Unihan.txt文件大小有28,221K字节。Unihan.txt中包含了很多有参考价值的索引,例如汉字部首、笔划、拼音、使用频度、四角号码排序等。这些索引都是基于一些比较权威的辞典,但大多数索引只能检索部分汉字。

    我介绍UCD的目的主要是为了使用其中的两个概念:Block和Script。

    4.1.2 Block

    UCD中的Blocks.txt将Unicode的码位分割成一些连续的Block,并描述了每个Block的用途:

    开始码位结束码位Block名称(英文)Block名称(中文)
    0000007FBasic Latin基本拉丁字母
    008000FFLatin-1 Supplement拉丁字母补充-1
    0100017FLatin Extended-A拉丁字母扩充-A
    0180024FLatin Extended-B拉丁字母扩充-B
    025002AFIPA Extensions国际音标扩充
    02B002FFSpacing Modifier Letters进格修饰字符
    0300036FCombining Diacritical Marks组合附加符号
    037003FFGreek and Coptic希腊文和哥普特文
    040004FFCyrillic西里尔文
    0500052FCyrillic Supplement西里尔文补充
    0530058FArmenian亚美尼亚文
    059005FFHebrew希伯来文
    060006FFArabic基本阿拉伯文
    0700074FSyriac叙利亚文
    0750077FArabic Supplement阿拉伯文补充
    078007BFThaana塔纳文
    07C007FFNKoN'Ko字母表
    0900097FDevanagari天成文书(梵文)
    098009FFBengali孟加拉文
    0A000A7FGurmukhi锡克教文
    0A800AFFGujarati古吉拉特文
    0B000B7FOriya奥里亚文
    0B800BFFTamil泰米尔文
    0C000C7FTelugu泰卢固文
    0C800CFFKannada卡纳达文
    0D000D7FMalayalam德拉维族文
    0D800DFFSinhala僧伽罗文
    0E000E7FThai泰文
    0E800EFFLao老挝文
    0F000FFFTibetan藏文
    1000109FMyanmar缅甸文
    10A010FFGeorgian格鲁吉亚文
    110011FFHangul Jamo朝鲜文
    1200137FEthiopic埃塞俄比亚文
    1380139FEthiopic Supplement埃塞俄比亚文补充
    13A013FFCherokee切罗基文
    1400167FUnified Canadian Aboriginal Syllabics加拿大印第安方言
    1680169FOgham欧甘文
    16A016FFRunic北欧古字
    1700171FTagalog塔加路文
    1720173FHanunoo哈努诺文
    1740175FBuhid布迪文
    1760177FTagbanwaTagbanwa文
    178017FFKhmer高棉文
    180018AFMongolian蒙古文
    1900194FLimbu林布文
    1950197FTai Le德宏傣文
    198019DFNew Tai Lue新傣文
    19E019FFKhmer Symbols高棉文
    1A001A1FBuginese布吉文
    1B001B7FBalinese巴厘文
    1D001D7FPhonetic Extensions拉丁字母音标扩充
    1D801DBFPhonetic Extensions Supplement拉丁字母音标扩充增补
    1DC01DFFCombining Diacritical Marks Supplement组合附加符号补充
    1E001EFFLatin Extended Additional拉丁字母扩充附加
    1F001FFFGreek Extended希腊文扩充
    2000206FGeneral Punctuation一般标点符号
    2070209FSuperscripts and Subscripts上标和下标
    20A020CFCurrency Symbols货币符号
    20D020FFCombining Diacritical Marks for Symbols符号用组合附加符号
    2100214FLetterlike Symbols似字母符号
    2150218FNumber Forms数字形式
    219021FFArrows箭头符号
    220022FFMathematical Operators数学运算符号
    230023FFMiscellaneous Technical零杂技术用符号
    2400243FControl Pictures控制图符
    2440245FOptical Character Recognition光学字符识别
    246024FFEnclosed Alphanumerics带括号的字母数字
    2500257FBox Drawing制表符
    2580259FBlock Elements方块元素
    25A025FFGeometric Shapes几何形状
    260026FFMiscellaneous Symbols零杂符号
    270027BFDingbats杂锦字型
    27C027EFMiscellaneous Mathematical Symbols-A零杂数学符号-A
    27F027FFSupplemental Arrows-A箭头符号补充-A
    280028FFBraille Patterns盲文
    2900297FSupplemental Arrows-B箭头符号补充-B
    298029FFMiscellaneous Mathematical Symbols-B零杂数学符号-B
    2A002AFFSupplemental Mathematical Operators数学运算符号
    2B002BFFMiscellaneous Symbols and Arrows零杂符号和箭头
    2C002C5FGlagolitic格拉哥里字母表
    2C602C7FLatin Extended-C拉丁字母扩充-C
    2C802CFFCoptic科普特文
    2D002D2FGeorgian Supplement格鲁吉亚文补充
    2D302D7FTifinagh提非纳字母
    2D802DDFEthiopic Extended埃塞俄比亚文扩充
    2E002E7FSupplemental Punctuation标点符号补充
    2E802EFFCJK Radicals Supplement中日韩部首补充
    2F002FDFKangxi Radicals康熙字典部首
    2FF02FFFIdeographic Description Characters汉字结构描述字符
    3000303FCJK Symbols and Punctuation中日韩符号和标点
    3040309FHiragana平假名
    30A030FFKatakana片假名
    3100312FBopomofo注音符号
    3130318FHangul Compatibility Jamo朝鲜文兼容字母
    3190319FKanbun日文的汉字批注
    31A031BFBopomofo Extended注音符号扩充
    31C031EFCJK Strokes中日韩笔划
    31F031FFKatakana Phonetic Extensions片假名音标扩充
    320032FFEnclosed CJK Letters and Months带括号的中日韩字母及月份
    330033FFCJK Compatibility中日韩兼容字符
    34004DBFCJK Unified Ideographs Extension A中日韩统一表意文字扩充A
    4DC04DFFYijing Hexagram Symbols易经六十四卦象
    4E009FFFCJK Unified Ideographs中日韩统一表意文字
    A000A48FYi Syllables彝文音节
    A490A4CFYi Radicals彝文字根
    A700A71FModifier Tone Letters声调修饰字母
    A720A7FFLatin Extended-D拉丁字母扩充-D
    A800A82FSyloti NagriSyloti Nagri字母表
    A840A87FPhags-paPhags-pa字母表
    AC00D7AFHangul Syllables朝鲜文音节
    D800DB7FHigh Surrogates高位替代
    DB80DBFFHigh Private Use Surrogates高位专用替代
    DC00DFFFLow Surrogates低位替代
    E000F8FFPrivate Use Area专用区
    F900FAFFCJK Compatibility Ideographs中日韩兼容表意文字
    FB00FB4FAlphabetic Presentation Forms字母变体显现形式
    FB50FDFFArabic Presentation Forms-A阿拉伯文变体显现形式-A
    FE00FE0FVariation Selectors字型变换选取器
    FE10FE1FVertical Forms竖排标点符号
    FE20FE2FCombining Half Marks组合半角标示
    FE30FE4FCJK Compatibility Forms中日韩兼容形式
    FE50FE6FSmall Form Variants小型变体形式
    FE70FEFFArabic Presentation Forms-B阿拉伯文变体显现形式-B
    FF00FFEFHalfwidth and Fullwidth Forms半角及全角字符
    FFF0FFFFSpecials特殊区域
    100001007FLinear B Syllabary线形文字B音节文字
    10080100FFLinear B Ideograms线形文字B表意文字
    101001013FAegean Numbers爱琴海数字
    101401018FAncient Greek Numbers古希腊数字
    103001032FOld Italic古意大利文
    103301034FGothic哥特文
    103801039FUgaritic乌加里特楔形文字
    103A0103DFOld Persian古波斯文
    104001044FDeseret德塞雷特大学音标
    104501047FShavian肃伯纳速记符号
    10480104AFOsmanyaOsmanya字母表
    108001083FCypriot Syllabary塞浦路斯音节文字
    109001091FPhoenician腓尼基文
    10A0010A5FKharoshthi迦娄士悌文
    12000123FFCuneiform楔形文字
    124001247FCuneiform Numbers and Punctuation楔形文字数字和标点
    1D0001D0FFByzantine Musical Symbols东正教音乐符号
    1D1001D1FFMusical Symbols音乐符号
    1D2001D24FAncient Greek Musical Notation古希腊音乐符号
    1D3001D35FTai Xuan Jing Symbols太玄经符号
    1D3601D37FCounting Rod Numerals算筹
    1D4001D7FFMathematical Alphanumeric Symbols数学用字母数字符号
    200002A6DFCJK Unified Ideographs Extension B中日韩统一表意文字扩充 B
    2F8002FA1FCJK Compatibility Ideographs Supplement中日韩兼容表意文字补充
    E0000E007FTags标签
    E0100E01EFVariation Selectors Supplement字型变换选取器补充
    F0000FFFFFSupplementary Private Use Area-A补充专用区-A
    10000010FFFFSupplementary Private Use Area-B补充专用区-B

    Block是Unicode字符的一个属性。属于同一个Block的字符有着相近的用途。Block表中的开始码位、结束码位只是用来划分出一块区域,在开始码位和结束码位之间可能还有很多未定义的码位。使用UniToy,大家可以按照Block浏览Unicode字符,既可以按列表显示:

     

    也可以显示每个字符的详细信息:

    4.1.3 Script

    Unicode中每个字符都有一个Script属性,这个属性表明字符所属的文字系统。Unicode目前支持以下Script:

    Script名称(英文)Script名称(中文)Script包含的字符数
    Arabic阿拉伯文966
    Armenian亚美尼亚文90
    Balinese巴厘文121
    Bengali孟加拉文91
    Bopomofo汉语注音符号64
    Braille盲文256
    Buginese布吉文30
    Buhid布迪文20
    Canadian Aboriginal加拿大印第安方言630
    Cherokee切罗基文85
    CommonCommon5020
    Coptic科普特文128
    Cuneiform楔形文字982
    Cypriot塞浦路斯音节文字55
    Cyrillic西里尔文277
    Deseret德塞雷特大学音标80
    Devanagari天成文书(梵文)107
    Ethiopic埃塞俄比亚文461
    Georgian格鲁吉亚文120
    Gothic哥特文94
    Glagolitic格拉哥里字母表27
    Greek希腊文506
    Gujarati古吉拉特文83
    Gurmukhi锡克教文77
    Han汉文71570
    Hangul韩文书写系统11619
    Hanunoo哈努诺文21
    Hebrew希伯来文133
    Hiragana平假名89
    InheritedInherited461
    Kannada卡纳达文86
    Katakana片假名164
    Kharoshthi迦娄士悌文65
    Khmer高棉文146
    Lao老挝文65
    Latin拉丁文系1070
    Limbu林布文(尼泊尔东部)66
    Linear B线形文字B211
    Malayalam德拉维族文(印度)78
    Mongolian蒙古文152
    Myanmar缅甸文78
    New Tai Lue新傣文80
    NkoN'Ko字母表59
    Ogham欧甘文字29
    Old Italic古意大利文35
    Old Persian古波斯文50
    Oriya奥里亚文81
    OsmanyaOsmanya字母表40
    Phags PaPhags Pa字母表(蒙古)56
    Phoenician腓尼基文27
    Runic古代北欧文78
    Shavian肃伯纳速记符号48
    Sinhala僧伽罗文80
    Syloti NagriSyloti Nagri字母表(印度)44
    Syriac叙利亚文77
    Tagalog塔加路文(菲律宾)20
    TagbanwaTagbanwa文(菲律宾)18
    Tai Le德宏傣文35
    Tamil泰米尔文71
    Telugu泰卢固文(印度)80
    Thaana马尔代夫书写体50
    Thai泰国文86
    Tibetan藏文195
    Tifinagh提非纳字母表55
    Ugaritic乌加里特楔形文字31
    Yi彝文1220

    其中,有两个Script值有着特殊的含义:

    • Common:Script属性为Common的字符可能在多个文字系统中使用,不是某个文字系统特有的。例如:空格、数字等。
    • Inherited:Script属性为Inherited的字符会继承前一个字符的Script属性。主要是一些组合用符号,例如:在“组合附加符号”区(0x300-0x36f),字符的Script属性都是Inherited。

    UCD中的Script.txt列出了每个字符的Script属性。使用UniToy可以按照Script属性查看字符。例如:

    左侧Script窗口中,第一层节点是按英文字母顺序排列的Script属性。第二层节点是包含该Script文字的行(row),点击后显示该行内属于这个Script的字符。这样,就可以集中查看属于同一文字系统的字符。

    4.1.4 Unicode中的汉字

    前面提过,在Unicode已定义的99089个字符中,有71226个字符是汉字。它们的分布如下:

    Block名称开始码位结束码位数量
    中日韩统一表意文字扩充A34004db56582
    中日韩统一表意文字4e009fbb20924
    中日韩兼容表意文字f900fa2d302
    中日韩兼容表意文字fa30fa6a59
    中日韩兼容表意文字fa70fad9106
    中日韩统一表意文字扩充B200002a6d642711
    中日韩兼容表意文字补充2f8002fa1d542

    UCD的Unihan.txt中的部首偏旁索引(kRSUnicode)可以检索全部71226个汉字。kRSUnicode的部首是按照康熙字典定义的,共214个部首。简体字按照简体部首对应的繁体部首检索。UniToy整理了康熙字典部首对应的简体部首,提供了按照部首检索汉字的功能:

    4.2 UTF编码

    在字符编码的四个层次中,第一层的范围和第二层的编码在4.1节已经详细讨论过了。本节讨论第三层的UTF编码和第四层的字节序,主要谈谈第三层的UTF编码,即怎样将Unicode定义的编码转换成程序数据。

    4.2.1 UTF-8

    UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下:

    Unicode编码(16进制)UTF-8 字节流(二进制)
    000000 - 00007F0xxxxxxx
    000080 - 0007FF110xxxxx 10xxxxxx
    000800 - 00FFFF1110xxxx 10xxxxxx 10xxxxxx
    010000 - 10FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

    UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。

    例1:“汉”字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用用3字节模板了:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到:11100110 10110001 10001001,即E6 B1 89。

    例2:“”字的Unicode编码是0x20C30。0x20C30在0x010000-0x10FFFF之间,使用用4字节模板了:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):0 0010 0000 1100 0011 0000,用这个比特流依次代替模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。

    4.2.2 UTF-16

    UniToy有个“输出编码”功能,可以输出当前选择的文本编码。因为UniToy内部采用UTF-16编码,所以输出的编码就是文本的UTF-16编码。例如:如果我们输出“汉”字的UTF-16编码,可以看到0x6C49,这与“汉”字的Unicode编码是一致的。如果我们输出“”字的UTF-16编码,可以看到0xD843, 0xDC30。“”字的Unicode编码是0x20C30,它的UTF-16编码是怎样得到的呢?

    4.2.2.1 编码规则

    UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下:

    • 如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。
    • 如果U≥0x10000,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy110111xxxxxxxxxx。

    为什么U'可以被写成20个二进制位?Unicode的最大码位是0x10ffff,减去0x10000后,U'的最大值是0xfffff,所以肯定可以用20个二进制位表示。例如:“”字的Unicode编码是0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。

    4.2.2.2 代理区(Surrogate)

    按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。

    为了将一个WORD的UTF-16编码与两个WORD的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区(Surrogate):

    D800DB7FHigh Surrogates高位替代
    DB80DBFFHigh Private Use Surrogates高位专用替代
    DC00DFFFLow Surrogates低位替代

    高位替代就是指这个范围的码位是两个WORD的UTF-16编码的第一个WORD。低位替代就是指这个范围的码位是两个WORD的UTF-16编码的第二个WORD。那么,高位专用替代是什么意思?我们来解答这个问题,顺便看看怎么由UTF-16编码推导Unicode编码。

    解:如果一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?我们知道第二个WORD的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:

    1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111

    按照编码的相反步骤,取出高低WORD的后10位,并拼在一起,得到

    1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111

    即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,得到0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。

    4.2.3 UTF-32

    UTF-32编码以32位无符号整数为单位。Unicode的UTF-32编码就是其对应的32位无符号整数。

    4.2.4 字节序

    根据字节序的不同,UTF-16可以被实现为UTF-16LE或UTF-16BE,UTF-32可以被实现为UTF-32LE或UTF-32BE。例如:

    字符Unicode编码UTF-16LEUTF-16BEUTF32-LEUTF32-BE
    0x6C4949 6C6C 4949 6C 00 0000 00 6C 49
    0x20C3043 D8 30 DCD8 43 DC 3030 0C 02 0000 02 0C 30

     

    那么,怎么判断字节流的字节序呢?

     

    Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字符"零宽无中断空格"。这个字符的编码是FEFF,而反过来的FFFE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定义的码位,不应该出现在实际传输中。下表是各种UTF编码的BOM:

    UTF编码Byte Order Mark
    UTF-8EF BB BF
    UTF-16LEFF FE
    UTF-16BEFE FF
    UTF-32LEFF FE 00 00
    UTF-32BE00 00 FE FF

    5 结束语

    程序员的工作就是将复杂的世界简单地表达出来,希望这篇文章也能做到这一点。本文的初稿完成于2007年2月14日。我会在我的个人主页http://www.fmddlmyy.cn维护这篇文章的最新版本。

    posted @ 2011-05-12 21:58 RTY 阅读(476) | 评论 (0)编辑 收藏

    我曾经写过一篇《谈谈Unicode编码,简要解释UCS、UTF、BMP、BOM等名词》(以下简称《谈谈Unicode编码》),在网上流传较广,我也收到不少朋友的反馈。本文探讨《谈谈Unicode编码》中未介绍或介绍较少的代码页、Surrogates等问题,补充一些Unicode资料,顺带介绍一下我最近编写的一个Unicode工具:UniToy。本文虽然是前文的补充,但在写作上尽量做到独立成篇。

    标题中的“浅谈”是对自己的要求,我希望文字能尽量浅显易懂。但本文还是假设读者知道字节、16进制,了解《谈谈Unicode编码》中介绍过的字节序和Unicode的基本概念。

    0 UniToy

    UniToy是我编写的一个小工具。通过UniToy,我们可以全方位、多角度地查看Unicode,了解Unicode和语言、代码页的关系,完成一些文字编码的相关工作。本文的一些内容是通过UniToy演示的。大家可以从我的网站(www.fmddlmyy.cn)下载UniToy的演示版本

    1 文字的显示

    1.1 发生了什么?

    我们首先以Windows为例来看看文字显示过程中发生了什么。用记事本打开一个文本文件,可以看到文件包含的文字:

    字符和编码

    如果我们用UltraEdit或Hex Workshop查看这个文件的16进制数据,可以看到:

    D7D6 B7FB BACD B1E0 C2EB

    我们看到:文件“例子GBK.txt”有10个字节,依次是“D7 D6 B7 FB BA CD B1 E0 C2 EB”,这就是记事本从文件中读到的内容。记事本是用来打开文本文件的,所以它会调用Windows的文本显示函数将读到的数据作为文本显示。Windows首先将文本数据转换到它内部使用的编码格式:Unicode,然后按照文本的Unicode去字体文件中查找字体图像,最后将图像显示到窗口上。 总结一下前面的分析,文字的显示应该是这样的:

    • 步骤1:文字首先以某种编码保存在文件中。
    • 步骤2:Windows将文件中的文字编码映射到Unicode。
    • 步骤3:Windows按照Unicode在字体文件中查找字体图像,画到窗口上。
    所谓编码就是用数字表示字符,例如用D7D6表示“字”。当然,编码还意味着约定,即大家都认可。从《谈谈Unicode编码》中,我们知道Unicode也是一种文字编码,它的特殊性在于它是由国际组织设计,可以容纳全世界所有语言文字。而我们平常使用的文字编码通常是针对一个区域的语言、文字设计,只支持特定的语言文字。例如:在上面的例子中,文件“例子GBK.txt”采用的就是GBK编码。

     

    如果上述3个步骤中任何一步发生了错误,文字就不能被正确显示,例如:

    • 错误1:如果弄错了编码,例如将Big5编码的文字当作GBK编码,就会出现乱码。

    • 错误2:如果从特定编码到Unicode的映射发生错误,例如文本数据中出现该编码方案未定义的字符,Windows就会使用缺省字符,通常是?。

    • 如果当前字体不支持要显示的字符,Windows就会显示字体文件中的缺省图像:空白或方格。

    在Unicode被广泛使用前,有多少种语言、文字,就可能有多少种文字编码方案。一种文字也可能有多种编码方案。那么我们怎么确定文本数据采用了什么编码?

    1.2 采用了哪种编码?

    按照惯例,文本文件中的数据都是文本编码,那么它怎么表明自己的编码格式?在记事本的“打开”对话框上:

    我们可以看到记事本支持4种编码格式:ANSI、Unicode、Unicode big endian、UTF-8。如果读者看过《谈谈Unicode编码》,对Unicode、Unicode big endian、UTF-8应该不会陌生,其实它们更准确的名称应该是UTF-16LE(Little Endian)、UTF-16BE(Big Endian)和UTF-8,它们是基于Unicode的不同编码方案。

    在《谈谈Unicode编码》中介绍过,Windows通过在文本文件开头增加一些特殊字节(BOM)来区分上述3种编码,并将没有BOM的文本数据按照ANSI代码页处理。那么什么是代码页,什么是ANSI代码页?

    2 代码页和字符集

    2.1 Windows的代码页

    2.1.1 代码页

    代码页(Code Page)是个古老的专业术语,据说是IBM公司首先使用的。代码页和字符集的含义基本相同,代码页规定了适用于特定地区的字符集合,和这些字符的编码。可以将代码页理解为字符和字节数据的映射表。

    Windows为自己支持的代码页都编了一个号码。例如代码页936就是简体中文 GBK,代码页950就是繁体中文 Big5。代码页的概念比较简单,就是一个字符编码方案。但要说清楚Windows的ANSI代码页,就要从Windows的区域(Locale)说起了。

    2.1.2 区域和ANSI代码页

    微软为了适应世界上不同地区用户的文化背景和生活习惯,在Windows中设计了区域(Locale)设置的功能。Local是指特定于某个国家或地区的一组设定,包括代码页,数字、货币、时间和日期的格式等。在Windows内部,其实有两个Locale设置:系统Locale和用户Locale。系统Locale决定代码页,用户Locale决定数字、货币、时间和日期的格式。我们可以在控制面板的“区域和语言选项”中设置系统Locale和用户Locale:

    每个Locale都有一个对应的代码页。Locale和代码页的对应关系,大家可以参阅我的另一篇文章《谈谈Windows程序中的字符编码》的附录1。系统Locale对应的代码页被作为Windows的默认代码页。在没有文本编码信息时,Windows按照默认代码页的编码方案解释文本数据。这个默认代码页通常被称作ANSI代码页(ACP)。

    ANSI代码页还有一层意思,就是微软自己定义的代码页。在历史上,IBM的个人计算机和微软公司的操作系统曾经是PC的标准配置。微软公司将IBM公司定义的代码页称作OEM代码页,在IBM公司的代码页基础上作了些增补后,作为自己的代码页,并冠以ANSI的字样。我们在“区域和语言选项”高级页面的代码页转换表中看到的包含ANSI字样的代码页都是微软自己定义的代码页。例如:

    • 874 (ANSI/OEM - 泰文)
    • 932 (ANSI/OEM - 日文 Shift-JIS)
    • 936 (ANSI/OEM - 简体中文 GBK)
    • 949 (ANSI/OEM - 韩文)
    • 950 (ANSI/OEM - 繁体中文 Big5)
    • 1250 (ANSI - 中欧)
    • 1251 (ANSI - 西里尔文)
    • 1252 (ANSI - 拉丁文 I)
    • 1253 (ANSI - 希腊文)
    • 1254 (ANSI - 土耳其文)
    • 1255 (ANSI - 希伯来文)
    • 1256 (ANSI - 阿拉伯文)
    • 1257 (ANSI - 波罗的海文)
    • 1258 (ANSI/OEM - 越南)

    在UniToy中,我们可以按照代码页编码顺序查看这些代码页的字符和编码:

    我们不能直接设置ANSI代码页,只能通过选择系统Locale,间接改变当前的ANSI代码页。微软定义的Locale只使用自己定义的代码页。所以,我们虽然可以通过“区域和语言选项”中的代码页转换表安装很多代码页,但只能将微软的代码页作为系统默认代码页。

    2.1.3 代码页转换表

    在Windows 2000以后,Windows统一采用UTF-16作为内部字符编码。现在,安装一个代码页就是安装一张代码页转换表。通过代码页转换表,Windows既可以将代码页的编码转换到UTF-16,也可以将UTF-16转换到代码页的编码。代码页转换表的具体实现可以是一个以nls为后缀的数据文件,也可以是一个提供转换函数的动态链接库。有的代码页是不需要安装的。例如:Windows将UTF-7和UTF-8分别作为代码页65000和代码页65001。UTF-7、UTF-8和UTF-16都是基于Unicode的编码方案。它们之间可以通过简单的算法直接转换,不需要安装代码页转换表。

    在安装过一个代码页后,Windows就知道怎样将该代码页的文本转换到Unicode文本,也知道怎样将Unicode文本转换成该代码页的文本。例如:UniToy有导入和导出功能。所谓导入功能就是将任一代码页的文本文件转换到Unicode文本;导出功能就是将Unicode文本转换到任一指定的代码页。这里所说的代码页就是指系统已安装的代码页:

    其实,如果全世界人民在计算机刚发明时就统一采用Unicode作为字符编码,那么代码页就没有存在的必要了。可惜在Unicode被发明前,世界各国人民都发明并使用了各种字符编码方案。所以,Windows必须通过代码页支持已经被广泛使用的字符编码。从这种意义看,代码页主要是为了兼容现有的数据、程序和习惯而存在的。

    2.1.4 SBCS、DBCS和MBCS

    SBCS、DBCS和MBCS分别是单字节字符集、双字节字符集和多字节字符集的缩写。SBCS、DBCS和MBCS的最大编码长度分别是1字节、两字节和大于两字节(例如4或5字节)。例如:代码页1252 (ANSI-拉丁文 I)是单字节字符集;代码页936 (ANSI/OEM-简体中文 GBK)是双字节字符集;代码页54936 (GB18030 简体中文)是多字节字符集。

    单字节字符集中的字符都用一个字节表示。显然,SBCS最多只能容纳256个字符。

    双字节字符集的字符用一个或两个字节表示。那么我们从文本数据中读到一个字节时,怎么判断它是单字节字符,还是双字节字符的首字符?答案是通过字节所处范围来判断。例如:在GBK编码中,单字节字符的范围是0x00-0x80,双字节字符首字节的范围是0x81到0xFE。我们顺序读取字节数据,如果读到的字节在0x81到0xFE内,那么这个字节就是双字节字符的首字节。GBK定义双字节字符的尾字节范围是0x40到0x7E和0x80到0xFE。

    GB18030是多字节字符集,它的字符可以用一个、两个或四个字节表示。这时我们又如何判断一个字节是属于单字节字符,双字节字符,还是四字节字符?GB18030与GBK是兼容的,它利用了GBK双字节字符尾字节的未使用码位。GB18030的四字节字符的第一字节的范围也是0x81到0xFE,第二字节的范围是0x30-0x39。通过第二字节所处范围就可以区分双字节字符和四字节字符。GB18030定义四字节字符的第三字节范围是0x81到0xFE,第四字节范围是0x30-0x39。

    2.2 代码页实例

    2.2.1 实例一:GB18030代码页

    1.1节的“错误2”中演示了一个全被显示成'?'的文件。这个文件的数据是:

    其实,这是一个包含了6个四字节字符的GB18030编码的文件。记事本按照GBK显示这些数据,而GB18030的四字节字符编码在GBK中是未定义的。Windows根据首字节范围判断出12个双字节字符,然后因为找不到匹配的转换而将其映射到默认字符'?'。使用UniToy按照GB18030代码页导入这个文件,就可以看到:

    这个GB18030编码的文件是用UniToy创建的,编辑Unicode文本,然后导出到GB18030编码格式。

    2.2.2 实例二:GBK和Big5的转换

    综合使用UniToy的导入、导出功能就可以在任意两个代码页之间转换文本。其实,由于各代码页支持的字符范围不同,我们一般不会直接在代码页间转换文本。例如将以下GBK编码的文本:

    直接转换到Big5编码,就会看到:

    变成'?'的字符都是Big5编码不支持的简化字。在从Unicode转换到Big5编码时,由于Big5编码不支持这些字符,Windows就用默认字符'?'代替。在UniToy中,我们可以先将简体字转换到繁体字,然后再导出到Big5编码,就可以正常显示:

    同理,将Big5编码的文本转换到GBK编码的步骤应该是:

    • 将Big5编码的文本导入到Unicode文本;
    • 将繁体的Unicode文本转换简体的Unicode文本;
    • 将简体的Unicode文本导出到GBK文本。

    2.3 互联网的字符集

    2.3.1 字符集

    互联网上的信息缤纷多彩,但文本依然是最重要的信息载体。html文件通过标记表明自己使用的字符集。例如:

    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">

    或者:

    <meta http-equiv="charset" content="iso-8859-1">

    那么我们可以使用哪些字符集(charset)呢?在IETF(互联网工程任务组)的网页上维护着一份可以在互联网上使用的字符集的清单:CHARACTER SETS。如果有新的字符集被登记,IETF会更新这份文档。

    简单浏览一下,2006年12月7日的版本列出了253个字符集。其中也包括微软的CP1250 ~ CP1258,在这里它们不会被称作什么ANSI代码页,而是被简单地称作windows-1250、windows-1251等。其实在Unicode被广泛使用前,除了中日韩等大字符集,世界上,特别是西方使用最广泛的字符集应该是ISO 8859系列字符集。

    2.3.2 ISO 8859系列字符集

    ISO 8859系列字符集是欧洲计算机制造商协会(ECMA)在上世纪80年代中期设计,并被国际标准化(ISO)组织采纳为国际标准。ISO 8859系列字符集目前有15个字符集,包括:

    • ISO 8859-1 大部分的西欧语系,例如英文、法文、西班牙文和德文等(Latin-1)
    • ISO 8859-2 大部分的中欧和东欧语系,例如捷克文、波兰文和匈牙利文等(Latin-2)
    • ISO 8859-3 欧洲东南部和其它各种文字(Latin-3)
    • ISO 8859-4 斯堪的那维亚和波罗的海语系(Latin-4)
    • ISO 8859-5 拉丁文与斯拉夫文(俄文、保加利亚文等)
    • ISO 8859-6 拉丁文与阿拉伯文
    • ISO 8859-7 拉丁文与希腊文
    • ISO 8859-8 拉丁文与希伯来文
    • ISO 8859-9 为土耳其文修正的Latin-1(Latin-5)
    • ISO 8859-10 拉普人、北欧与爱斯基摩人的文字(Latin-6)
    • ISO 8859-11 拉丁文与泰文
    • ISO 8859-13 波罗的海周边语系,例如拉脱维亚文等(Latin-7)
    • ISO 8859-14 凯尔特文,例如盖尔文、威尔士文等(Latin-8)
    • ISO 8859-15 改进的Latin-1,增加遗漏的法文、芬兰文字符和欧元符号(Latin-9)
    • ISO 8859-16 罗马尼亚文(Latin-10)

    其中缺少的编号12据说是为了预留给天城体梵文字母(Deva-nagari)的。印地文和尼泊尔文都使用了这种在七世纪形成的字母表。由于印度定义了自己的编码ISCII(Indian Script Code for Information Interchange),所以这个编号就未被使用。ISO 8859系列字符集都是单字节字符集,即只使用0x00-0xFF对字符编码。

    大家都知道ASCII吧,那么大家知道ANSI X3.4和ISO 646吗?在1968年发布的ANSI X3.4和1972年发布的ISO 646就是ASCII编码,只不过是不同组织发布的。绝大多数字符集都与ASCII编码保持兼容,ISO 8859系列字符集也不例外,它们的0x00-0x7f都与ASCII码保持一致,各字符集的不同之处在于如何利用0x80-0xff的码位。使用UniToy可以查看ISO 8859系列所有字符集的编码,例如:

    通过这些演示,大家是不是觉得代码页和字符集都是很简单、朴实的东西呢?好,在进入Unicode的话题前,让我们先看一个很深奥的概念。

    posted @ 2011-05-12 21:56 RTY 阅读(738) | 评论 (0)编辑 收藏

         摘要: 字符集编码与 C/C++ 源文件字符编译乱弹 2010年2月24日Breaker原创发表评论阅读评论 最近在看国际化编程 (i18n: internationalization) 的东西,也弄清楚了点字符集有关的一些问题,其实网上的一些牛人已经将字符集、Unicode 等相关的问题说的很清楚了,我在这里引用他们的总结并自己小结一下心得,并且实验一下在编译时,源代码自身的字...  阅读全文

    posted @ 2011-05-12 21:40 RTY 阅读(2214) | 评论 (4)编辑 收藏

    仅列出标题
    共31页: First 20 21 22 23 24 25 26 27 28 Last