[转载]WebKit编译小结

mikel阅读(1004)

[转载]WebKit编译小结 – Nosnowwolf – 博客园.

在Windows平台上利用微软的VS编译WebKit,然后在VS IDE里设置断点跟踪WebKit的运行,是一件很好玩的事情。到目前为止,我们至少可以通过两个开源项目来做这件事情:Apple的WebKit开源项目(http://webkit.org)和Google的Chromium开源项目(http://code.google.com/chromium)。前者我一共编译过3次,每次都要磕磕绊绊,相比而言后者的编译过程要顺利得多。

所以针对Safari的WebKit自己写个小结,希望下次再编译时能快点。

网 上已经有很多先烈——先人?也不合适,还是前人吧——指明了一条最容易犯的错误:千万别自作聪明,要严格按照官方指导,从安装编译工具开始 (http://webkit.org/building/tools.html),亦步亦趋的来,不然到了出错时再反过来找错误的原因就比较头疼了。

所以还是按照官方指导的顺序来说吧。

Installing the Developer Tools 安装编译工具

– http://webkit.org/building/tools.html

1. 微软有免费(不是试用)的Visual Studio 2005 Express Edition可以下载使用,注意指导上说了新版本尚不支持。我有个朋友一开始就跳过了这一步(他用了显然是盗版的VS 2008并且还自鸣得意可以少花点时间),最后不但人崩溃了,机器也崩溃了。另外网上也有不少攻略会告诉你,VS 2008也能编译,但对于具体怎么配置似乎语焉不详,况且你不希望好不容易编译过了调试又崩溃了吧?

2. 官指(官方指导)上提到的Windows补丁,以及Service Pack,Security Update,都需要老老实实安装。安装Platform SDK时还得跳到微软的官网去看How-to,包括VS IDE的环境配置,烦人得很…这里需要注意一下,最好全都使用缺省的安装目录,否则编译时找不到这个头文件那个库文件很头疼,当然如果对VS开发环境 比较熟悉倒也不是什么大问题。

3. 关于Cygwin组件。苹果利用了Cygwin中的一些脚本和工具去调用VS来编译WebKit,理论上你完全可以直接在VS IED里打开WebKit的工程文件来编译之,前提是你曾经用VS开发过一些大型工程。在这一步常出的错误在于Cygwin中各个组件的版本。几年前第一 次编译时由于自己机器上已经安装了Cygwin,就自作聪明的跳过了这一步,导致一些莫名的编译错误。官指中的cygwin- downloader.zip其实已经指定了编译所需要的组件的最小集合,老老实实的安照要求先下载最新版本到本地然后选择本地安装即可。

4. QuickTime SDK也是必须的,谁让它是Apple的呢。我也曾自作聪明跳过了这一步,然后在安装时又自定义了目录…DirectX SDK也是必须的,Windows版本的WebKit画图时需要用到它的一些头文件和库。

5. 终于有一个不是必须的了:WinDbg和NTSD

这一部分最花时间的是Visual Studio和Windows SDK和DirectX SDK的下载,好像一共有3-4G——其实也就相当于一部半高清的电影。

Getting WebKit 下载源码

– http://webkit.org/building/checkout.html

1. 这一步问题不大,选择的余地不小。你可以

a. 在Cygwin环境下通过SVN来检出(check out)完整的代码

b. 下载这个完整包:http://nightly.webkit.org/files/WebKit-SVN-source.tar.bz2。早先的时候只 有200多M,现在急剧增长到了800M,其实代码文件基本没变化,多出来的都是些测试和bug fix相关的文件。

c. 还有一种最快捷的办法:仅下载源文件包,大概只有10几M,缺点是源文件里不带SVN信息,所以是没法进行渐进式SVN代码更新和编译的。在这 里:http://nightly.webkit.org/builds/trunk/src/1 或通过 WebKit Nightly Builds (http://nightly.webkit.org/) -> Source #View all available builds.

2. 安装WebKit Support Libraries,这是Apple预编译好的一套Windows上的动态库,包括基础/网络/图形库等,只需按照官方指导上所说的,下载后存放到 WebKit源码的根目录下,然后在Cygwin环境下运行update-webkit来下载更新最新的头文件和库文件即可。如果下一步编译时提示某个头 文件或库文件有错,可以先到WebKit Support Libraries这个目录下看看是否属于这里,如果是的话或许可以通过升级到最新版本来解决这类编译问题。

注意update-webkit这个脚本还可能会替你更新代码,如果你使用的代码里带有SVN信息的话。

Building WebKit 编译

– http://webkit.org/building/build.html

完成了以上步骤,至少半天就过去了,但很可能你才走出了第一步,因为运气不好的话,后边还会有一堆的编译错误在等着你。

如果运气足够好,只要在Cygwin环境下通过build-webkit脚本就可以得到编译结果。常见的错误包括但不限于:

1. 找不到一些头文件或库文件

– 首先通过文件名猜测或是网上搜索一下,看看这个文件大概完成什么功能,属于哪部分(例如是Windows SDK还是Apple自己提供的基础库),只要知道它应属的位置甚至目录名,就不难解决问题了:要么重新下载正确的版本,要么在VS IDE里设置正确的包含路径,或者Windows环境变量

2. 提示注册表里找不到某个key

– 可能是对应的SDK或库文件没有正确安装

3. 看不太明白的错误…  其实编译错误信息在这里: …/WebKit/WebKitBuild/buildfailed 这个文件里说明了最近一次编译错误的模块名project,然后到以下对应的文件可以查看更具体的错误: …/WebKit/WebKitBuild/obj/<project>/Debug/BuildLog.htm

5. 最后记住一点,不管出什么错,都不要怀疑代码本身;错误都是由编译环境和你自己导致的。

如果你看到以下提示,恭喜你,任务完成了。

===========================================================

WebKit is now built (56m:16s).

To run Safari with this newly-built code, use the

“WebKit/WebKitTools/Scripts/run-safari” script.

===========================================================

Running WebKit 运行

早先的时候直接执行 WebKit/WebKitTools/Scripts/Debug-safari 就能启动VS IDE,后者会自动找到并启动Safari Shell,同时加载你先前编译好的各种库文件。

但在最新版本中这个命令好像仅仅启动了VS IDE,那就自己通过 WebKit\WebKitBuild\bin 目录下自己编译出来的 Safari.exe 来启动Safari吧,然后在VS IDE里attach process就可以设置断点跟踪调试了。

============================

题 外话:第一次编译WebKit的时候,我受了某位WebKit开源贡献者(source contributor)的影响,认为Apple这个公司不太地道,既然都开源了为什么还很小气的设了很多人为的障碍来捣乱,要知道有多少同学冲着 WebKit的名声满腔热血的冲上来,最后被这一个又一个挫折击退了。后来自己也经历过一次WebKit开源,也写了step by step的编译步骤,才明白这个一个复杂的系统,其依赖的库和编译环境,甚至编译工具,也都是在动态变化的;最重要的是,有些内容你(包括Apple)并 不希望开源——或许这是导致编译步骤加倍复杂的根本原因——这也是我提到的那位开源贡献者对Apple WebKit开发组“不爽”之处。

2010/12/30 补记:有人问为什么要编译WebKit?我想可能因为:

1. WebKit现在颇为流行

2. 好玩

3. 学习

4. 最重要的,如果你有基于WebKit的开发或移植工作,那么在VS IDE里跟踪调试最新的WebKit代码绝对是一件事半功倍的事情

为 什么基于Safari调试?因为Safari的debug模式自带了很多开发功能,包括查看每个页面元素的具体信息(例如布局和事件监听 Event Listener等),JavaScript调试等。当然,Google的Chrome也有相应的功能。严格来讲,如果你专注于WebCore引擎本身, 那么使用哪个Browser Shell其实关系不大;如果你更关注狭义上的WebKit,也就是平台相关的部分,那么Chrome可能是更好的选择,因为它完全开源。至于Qt,如果 你的工作与它完全无关,最好还是绕开它吧…

[转载]WebKit 内核编译

mikel阅读(1206)

[转载]WebKit 内核编译 – elfylin的专栏 – 博客频道 – CSDN.NET.

WebKit内核编译实在是一项折磨人的事情,虽然webkit.org官方有一个文档,但是有时候机器本身配置环境还是和教程有差别。

经过三天三夜平均每天2点钟的奋斗,今天终于搞定了。

 

其实,官方的描述已经非常到位,只要保证每一步都执行了,差别应该不大,需要多加处理的就是本机实际环境导致的具体问题。

 

首先按照官方文档进行操作

http://www.webkit.org/building/tools.html

一、安装Visual 2005

  1. If you own Visual Studio 2005 (newer versions of Visual Studio are currently unsupported):

    Install Microsoft Visual Studio 2005 Team Suite Service Pack 1.

    If you are building from Vista, install Service Pack 1 Update for Windows Vista.

    Install Visual Studio 2005 Service Pack 1 ATL Security Update.

    Install the following hotfixes to improve Visual Studio’s performance and responsiveness:

    1. KB918559
    2. KB935225
    3. KB943969
    4. KB947315

    Use the default options for these installations.

(我在装下面四个补丁的时候提示找不到升级的程序,我装的是2005 中文版,估计补丁有问题,后来没有理会了)

二、安装cygwin

  1. Install Cygwin

    Cygwin is a collection of utilities for Windows that includes not only a Subversionclient, but also additional tools that are required to build the WebKit source. Wehave made a downloader available that automatically collects all of the requiredpackages.

    Download cygwin-downloader.zip.

    Extract the content of the archive to some folder and start cygwin-downloader.exe from that folder.This will download all the Cygwin packages you need.

    When all the packages have finished downloading, the Cygwin installer will launch. ChooseInstall from Local Directory, then clickNext until theinstall is complete. If you are running Vista, the installer won’t be able to launch automatically, so you will have to manually launch Cygwin’s Setup.exe.

    Note: If you are behind a proxy you need to set the http_proxy environment variable to the URL of the proxy. This wouldlikely match this pattern:http[s]://[username[:password]@]somehost.com[:port]. Where the square brackets ([]) are not part of the url but enclose url elements that are optional indicating that those elements might be missing in your case together with their enclosed delimiter such as @ and :.

    Note: Vista may warn you that Cygwin did not install correctly. Ignore this warning and tell Vista that the install was successful.

    Warning: If you are running Vista or Windows 7, Cygwin may have trouble with implementingfork(the POSIX function frequently used to start child processes, needed by many of the WebKit build tools).In order to enablecygwin1.dll to implement fork successfully it is better if no DLL loaded in the parent process is relocated at runtime.One way to achive this is to rebase all cygwin dlls exceptcyglsa64.dll andcygwin1.dll to base addresses that cause them to not overlap in memory.

    In order to rebase all cygwin dlls a rebaseall script is provided in the cygwin installation’sbin directory(usuallyC:\cygwin\bin).This script must be run fromash.exe (found in the same directory) while no other cygwin application is running.
    ash.exe has very limited command line editing features so cutting and pasting to the commandline is recommended.
    The PATH environment variable may not contain the /bin directory by defaultthus the script should be launched as/bin/rebaseall.
    If rebaseall is complaining about failure to write to a Windows Temp directory (i.e./cygdrive/c/Users/you/AppData/Local/Temp),fix the permissions on that folder (i.e./bin/chown you /cygdrive/c/Users/you/AppData/Local/Temp) orchange the TMP environment variable to point to a directory with the appropriate permission flags (i.e.:export TMP=/tmp).

    Note: The rebase process may need to be repeated to include any new libraries added later to cygwin by listing these libraries by full cygwin path in a file and passing the file torebaseall via the-T fileName commandline argument.The dlls underLayoutTests/http/conf are a relevant use case.

    Remove the following lines from /home/[username]/.bashrc (if present):

    unset TMP
    unset TEMP

    Those lines would prevent picking up the Windows TMP and TEMP environment variables.If you are behind a proxy you need to do the following to allow cygwin svn to get through the proxy (similar steps are available for your Windows svn):

    • In bash (Cygwin) run: svn help. This will create in your cygwin home directory the file .subversion/servers.
    • Edit the file mentioned above and follow the instructions in the file to set up your proxy settings.

(上面的每一步都要执行,包括用他cygwin配置,执行ash.exe /binrebaseall)

 

三 安装 QuickTime SDK和 DirectX SDK

  1. Install QuickTime SDK

    Download QuickTime SDK for Windows from http://developer.apple.com/quicktime/download/ and install it to the default location (\Program Files\QuickTime SDK). This is needed for media support.

  2. Install DirectX SDK

    Download the February 2010 DirectX SDK (newer versions do not support Visual Studio 2005). This is needed for accelerated compositing.

(都要装)

 

四 其他

  1. Optional: Install the 32-bit version of Debugging Tools for Windows

    Debugging Tools for Windows includes many useful debugging tools such as WinDbg and NTSD. Some of WebKit’s tests and scripts use these tools. Follow theinstructions for installing the 32-bit version of Debugging Tools for Windows.

  2. Optional: Hotfix for 64-bit Windows to disable the user-mode callback filter for exceptions

    Without the 976038 hotfix, exceptions may be thrown in callback routines that do not cause a crash but leave the application in an inconsistent state that might cause a crash later. You will need to click on “View and request hotfix downloads” and fill out the form. A link to download the hotfix and a password will be emailed to you.

(编译通过的时候这些我没装),最后还是通过了。


五 总结一下

需要我们自己做的事情

更改VC为DEBUG、设置“警告作为错误”为false、

 

 

编译错误和解决

首先,这一篇文档算是比较全的。

http://hi.baidu.com/ksoftware/blog/item/75dace1f94750179f724e457.html

转载一下

1) ERROR # PRJ0018 : 未找到下列环境变量: >$(WebKitOutputDir)
SOLUTION # 添加环境变量WebKitOutputDir
2) ERROR # fatal error C1083: 无法打开包括文件:“unicode/uchar.h”: No such file or directory
# fatal error C1083: 无法打开包括文件:“pthread.h”: No such file or directory
# ‘grep’ 不是内部或外部命令,也不是可运行的程序或批处理文件
# fatal error C1083: 无法打开包括文件:“WebKit2/WebKit2.h”: No such file or directory
# error LNK2019: 无法解析的外部符号 _u_charType_3_2,该符号在函数 “bool __cdecl WTF::Unicode::isSeparatorSpace(int)” (?isSeparatorSpace@Unicode@WTF@@YA_NH@Z) 中被引用
SOLUTION # run update-webkit and build-webkit scripts again
3) ERROR # error PRJ0002 : 错误的结果 1 (从“C:\WINDOWS\system32\cmd.exe”返回)
SOLUTION # 关联错误 无需修正
4) ERROR # error C2220: 警告被视为错误 – 没有生成“object”文件
# error C2001: 常量中有换行符
# fatal error C1057: 宏展开中遇到意外的文件结束
SOLUTION # 发现DefaultLocalizationStrategy.cpp中的双引号格式为全角格式,改成半角并加转义。
#if USE(CF)
RetainPtr<CFStringRef> selectedCFString(AdoptCF, truncatedStringForLookupMenuItem(selectedString).createCFString());
return formatLocalizedString(WEB_UI_STRING(“Look Up \”%@\“”, “Look Up context menu item with selected word”), selectedCFString.get());
#else
return WEB_UI_STRING(“Look Up \”<selection>\””, “Look Up context menu item with selected word”).replace(“<selection>”, truncatedStringForLookupMenuItem(selectedString));
#endif
5) ERROR # error MIDL2152 : [version] format is incorrect
# error MIDL2214 : semantic check incomplete due to previous errors
SOLUTION # 查看WebKid.idl 定位到150行     version(__TYPELIB_VERSION__) 发现一个version的函数
#define __TYPELIB_VERSION__ __BUILD_NUMBER_MAJOR__##.##__BUILD_NUMBER_MINOR__
试了下修改为
#define __TYPELIB_VERSION__ 0.1
重新生成,成功。
6) ERROR # error C2220: 警告被视为错误 – 没有生成“object”文件
SOLUTION # 在vs2005中重新保存即可

(我下载的是WebKit-r94831.tar.bz2 ,遇到了ERROR # error C2220: 警告被视为错误 – 没有生成“object”文件,要安装上面修改,于此同时要关闭警告被视为错误的VC编译选项)

 

其次,连接错误

参见这篇博客http://blog.csdn.net/dummas/article/details/3871134

 

1>Linking…
11>LINK : warning LNK4044: unrecognized option ‘/dynamicbase’; ignored
11>   Creating library D:/liangjh/WebKit-r40102/WebKitBuild/lib/WebKit.lib and o
bject D:/liangjh/WebKit-r40102/WebKitBuild/lib/WebKit.exp
11>WebCore_debug.lib(CSSGradientValue.obj) : error LNK2001: unresolved external
symbol “__declspec(dllimport) public: void __thiscall std::exception::_Raise(voi
d)const ” (__imp_?_Raise@exception@std@@QBEXXZ)
11>WebCore_debug.lib(RenderLayer.obj) : error LNK2019: unresolved external symbo
l “__declspec(dllimport) public: void __thiscall std::exception::_Raise(void)con
st ” (__imp_?_Raise@exception@std@@QBEXXZ) referenced in function “struct std::p
air<class WebCore::RenderLayer * *,int> __cdecl std::get_temporary_buffer<class
WebCore::RenderLayer *>(int)” (??$get_temporary_buffer@PAVRenderLayer@WebCore@@@
std@@YA?AU?$pair@PAPAVRenderLayer@WebCore@@H@0@H@Z)
11>WebCore_debug.lib(Gradient.obj) : error LNK2001: unresolved external symbol ”
__declspec(dllimport) public: void __thiscall std::exception::_Raise(void)const
” (__imp_?_Raise@exception@std@@QBEXXZ)
11>WebCore_debug.lib(CSSFontSelector.obj) : error LNK2001: unresolved external s
ymbol “__declspec(dllimport) public: void __thiscall std::exception::_Raise(void
)const ” (__imp_?_Raise@exception@std@@QBEXXZ)
11>WebCore_debug.lib(CompositeAnimation.obj) : error LNK2001: unresolved externa
l symbol “__declspec(dllimport) public: void __thiscall std::exception::_Raise(v
oid)const ” (__imp_?_Raise@exception@std@@QBEXXZ)
11>WebCore_debug.lib(CSSGradientValue.obj) : error LNK2001: unresolved external
symbol “__declspec(dllimport) public: __thiscall std::exception::exception(char
const *,int)” (__imp_??0exception@std@@QAE@PBDH@Z)
11>WebCore_debug.lib(RenderLayer.obj) : error LNK2019: unresolved external symbo
l “__declspec(dllimport) public: __thiscall std::exception::exception(char const
*,int)” (__imp_??0exception@std@@QAE@PBDH@Z) referenced in function “public: __
thiscall std::bad_alloc::bad_alloc(char const *)” (??0bad_alloc@std@@QAE@PBD@Z)
11>WebCore_debug.lib(Gradient.obj) : error LNK2001: unresolved external symbol ”
__declspec(dllimport) public: __thiscall std::exception::exception(char const *,
int)” (__imp_??0exception@std@@QAE@PBDH@Z)
11>WebCore_debug.lib(CSSFontSelector.obj) : error LNK2001: unresolved external s
ymbol “__declspec(dllimport) public: __thiscall std::exception::exception(char c
onst *,int)” (__imp_??0exception@std@@QAE@PBDH@Z)
11>WebCore_debug.lib(CompositeAnimation.obj) : error LNK2001: unresolved externa
l symbol “__declspec(dllimport) public: __thiscall std::exception::exception(cha
r const *,int)” (__imp_??0exception@std@@QAE@PBDH@Z)
11>WebCore_debug.lib(CSSGradientValue.obj) : error LNK2001: unresolved external
symbol “__declspec(dllimport) void __cdecl std::_Throw(class std::exception cons
t &)” (__imp_?_Throw@std@@YAXABVexception@1@@Z)
11>WebCore_debug.lib(RenderLayer.obj) : error LNK2019: unresolved external symbo
l “__declspec(dllimport) void __cdecl std::_Throw(class std::exception const &)”
(__imp_?_Throw@std@@YAXABVexception@1@@Z) referenced in function “protected: vi
rtual void __thiscall std::bad_alloc::_Doraise(void)const ” (?_Doraise@bad_alloc
@std@@MBEXXZ)
11>WebCore_debug.lib(Gradient.obj) : error LNK2001: unresolved external symbol ”
__declspec(dllimport) void __cdecl std::_Throw(class std::exception const &)” (_
_imp_?_Throw@std@@YAXABVexception@1@@Z)
11>WebCore_debug.lib(CSSFontSelector.obj) : error LNK2001: unresolved external s
ymbol “__declspec(dllimport) void __cdecl std::_Throw(class std::exception const
&)” (__imp_?_Throw@std@@YAXABVexception@1@@Z)
11>WebCore_debug.lib(CompositeAnimation.obj) : error LNK2001: unresolved externa
l symbol “__declspec(dllimport) void __cdecl std::_Throw(class std::exception co
nst &)” (__imp_?_Throw@std@@YAXABVexception@1@@Z)
11>D:/liangjh/WebKit-r40102/WebKitBuild/bin/WebKit.dll : fatal error LNK1120: 3
unresolved externals
11>Project : warning PRJ0018 : The following environment variables were not foun
d:
11>$(PRODUCTION)
11>Build log was saved at “file://D:/liangjh/WebKit-r40102/WebKitBuild/obj/WebKi
t/Debug/BuildLog.htm”
11>WebKit – 16 error(s), 14 warning(s)

 

这篇博客说,是google STL有关,要修改 WebKit/WebCore/config.h里面,在<wtf/platform.h>上面加上

#define _HAS_EXCEPTIONS 0
#define _STATIC_CPPLIB

但是,我这么做事失败了的。搞了很久发现自己有一个东西忘安装了Install Microsoft Visual C++ Express 2005 Service Pack 1,官方下载的是

VS80sp1-KB926601-X86-ENU.exe,其实这个我这里装不起,后来装了VS80sp1-KB926604-X86-CHS.exe,才解决了这个问题。

 

最后,其他连接错误

正在创建库 D:\WEBKIT\webkitH\WebKitBuild\lib\JavaScriptCore.lib 和对象 D:\WEBKIT\webkitH\WebKitBuild\lib\JavaScriptCore.exp
3>FunctionPrototype.obj : error LNK2019: 无法解析的外部符号 _u_charType_46,该符号在函数 “bool __cdecl WTF::Unicode::isSeparatorSpace(int)” (?isSeparatorSpace@Unicode@WTF@@YA_NH@Z) 中被引用
3>JSGlobalObjectFunctions.obj : error LNK2001: 无法解析的外部符号 _u_charType_46
3>Lexer.obj : error LNK2001: 无法解析的外部符号 _u_charType_46
3>StringPrototype.obj : error LNK2019: 无法解析的外部符号 _u_strToLower_46,该符号在函数 “int __cdecl WTF::Unicode::toLower(wchar_t *,int,wchar_t const *,int,bool *)” (?toLower@Unicode@WTF@@YAHPA_WHPB_WHPA_N@Z) 中被引用
3>WTF.lib(StringImpl.obj) : error LNK2001: 无法解析的外部符号 _u_strToLower_46
3>StringPrototype.obj : error LNK2019: 无法解析的外部符号 _u_strToUpper_46,该符号在函数 “int __cdecl WTF::Unicode::toUpper(wchar_t *,int,wchar_t const *,int,bool *)” (?toUpper@Unicode@WTF@@YAHPA_WHPB_WHPA_N@Z) 中被引用
3>WTF.lib(StringImpl.obj) : error LNK2001: 无法解析的外部符号 _u_strToUpper_46
3>RegexCompiler.obj : error LNK2019: 无法解析的外部符号 _u_tolower_46,该符号在函数 “int __cdecl WTF::Unicode::toLower(int)” (?toLower@Unicode@WTF@@YAHH@Z) 中被引用
3>RegexInterpreter.obj : error LNK2001: 无法解析的外部符号 _u_tolower_46
3>RegexJIT.obj : error LNK2001: 无法解析的外部符号 _u_tolower_46
3>RegexCompiler.obj : error LNK2019: 无法解析的外部符号 _u_toupper_46,该符号在函数 “int __cdecl WTF::Unicode::toUpper(int)” (?toUpper@Unicode@WTF@@YAHH@Z) 中被引用
3>RegexInterpreter.obj : error LNK2001: 无法解析的外部符号 _u_toupper_46
3>RegexJIT.obj : error LNK2001: 无法解析的外部符号 _u_toupper_46
3>WTF.lib(StringImpl.obj) : error LNK2019: 无法解析的外部符号 _u_strFoldCase_46,该符号在函数 “int __cdecl WTF::Unicode::foldCase(wchar_t *,int,wchar_t const *,int,bool *)” (?foldCase@Unicode@WTF@@YAHPA_WHPB_WHPA_N@Z) 中被引用
3>WTF.lib(StringImpl.obj) : error LNK2019: 无法解析的外部符号 _u_charDirection_46,该符号在函数 “enum WTF::Unicode::Direction __cdecl WTF::Unicode::direction(int)” (?direction@Unicode@WTF@@YA?AW4Direction@12@H@Z) 中被引用
3>WTF.lib(WTFString.obj) : error LNK2001: 无法解析的外部符号 _u_charDirection_46
3>WTF.lib(StringImpl.obj) : error LNK2019: 无法解析的外部符号 _u_foldCase_46,该符号在函数 “int __cdecl WTF::Unicode::foldCase(int)” (?foldCase@Unicode@WTF@@YAHH@Z) 中被引用
3>WTF.lib(StringImpl.obj) : error LNK2019: 无法解析的外部符号 _u_memcasecmp_46,该符号在函数 “int __cdecl WTF::Unicode::umemcasecmp(wchar_t const *,wchar_t const *,int)” (?umemcasecmp@Unicode@WTF@@YAHPB_W0H@Z) 中被引用
3>WTF.lib(CollatorICU.obj) : error LNK2019: 无法解析的外部符号 _ucol_strcoll_46,该符号在函数 “public: enum WTF::Collator::Result __thiscall WTF::Collator::collate(wchar_t const *,unsigned int,wchar_t const *,unsigned int)const ” (?collate@Collator@WTF@@QBE?AW4Result@12@PB_WI0I@Z) 中被引用
3>WTF.lib(CollatorICU.obj) : error LNK2019: 无法解析的外部符号 _ucol_setAttribute_46,该符号在函数 “private: void __thiscall WTF::Collator::createCollator(void)const ” (?createCollator@Collator@WTF@@ABEXXZ) 中被引用
3>WTF.lib(CollatorICU.obj) : error LNK2019: 无法解析的外部符号 _ucol_open_46,该符号在函数 “private: void __thiscall WTF::Collator::createCollator(void)const ” (?createCollator@Collator@WTF@@ABEXXZ) 中被引用
3>WTF.lib(CollatorICU.obj) : error LNK2019: 无法解析的外部符号 _ucol_getAttribute_46,该符号在函数 “private: void __thiscall WTF::Collator::createCollator(void)const ” (?createCollator@Collator@WTF@@ABEXXZ) 中被引用
3>WTF.lib(CollatorICU.obj) : error LNK2019: 无法解析的外部符号 _ucol_getLocaleByType_46,该符号在函数 “private: void __thiscall WTF::Collator::createCollator(void)const ” (?createCollator@Collator@WTF@@ABEXXZ) 中被引用
3>WTF.lib(CollatorICU.obj) : error LNK2019: 无法解析的外部符号 _ucol_close_46,该符号在函数 “private: void __thiscall WTF::Collator::releaseCollator(void)” (?releaseCollator@Collator@WTF@@AAEXXZ) 中被引用
3>D:\WEBKIT\webkitH\WebKitBuild\bin\JavaScriptCore.dll : fatal error LNK1120: 15 个无法解析的外部命令

google搜索是什么icu3.2 icu4.0的东西,后面涉及到什么VS2005 2008冲突。

这个问题,我遇到没搞定,后来重装的系统,这个问题就没出现了。

[转载]Visual Studio 2008编译webkit

mikel阅读(1163)

[转载]Visual Studio 2008编译webkit – OpenSolaris – 博客园.

自己编译webkit,遇到了一些问题进行了总结,放到这里和大家分享一下:

1.编译前的准备工作一

DirectX SDK(http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=10084) (这个文件少了 webcore会编译不通过),

VS需要做一下升级  安装 VS90SP1  以及补丁 KB945140,KB960075,KB957912,KB967631

下载google提供的 cygwin (webkit.org/repository/webkit/trunk/Tools/CygwinDownloader/cygwin- downloader.zip) cygwin中有100多个软件要让他全部下载完,不然后面的工作会出现问题,如果你的机器已经安装了perl等,可能会和它自带的冲突

Quicktime SDK(http://developer.apple.com/quicktime/download/)需要申请账号才能下载

下载并安装以上文件到c盘

下载源码—编译前的准备工作二

http://nightly.webkit.org/  放到C:\cygwin\home\ 下 在Cygwin中使用tar -xjvf WebKit-r112532.tar.bz2 ,然后重命名 WebKit

 

下载一些库—编译前准备工作三

下载

WebKitSupportLibrary和WebKitAuxiliary:http://developer.apple.com/opensource/

将上面文件(下载后不用解压)放到 C:\cygwin\home\WebKit 下,因为我的cygwin是建立在c盘下的

 

2.设置环境变量 —编译

将WEBKITLIBRARIESDIR设定为  WebKit 源代码目录\WebKitLibraries\win路径; 将WEBKITOUTPUTDIR 设为你打算要存放obj和dll 的输出文件夹,当然这个文件夹所在的分区至少要有4G的剩余空间。

说明: WEBKITOUTPUTDIR可以随便设,如果要保持和其他Build方式的一致性,那么就设置成为%WEBKIT_DIR%\output,这 里%WEBKIT_DIR%是Webkit文件夹的绝对路径。WEBKITLIBRARIESDIR必须设置 为%WEBKIT_DIR%\WebKitLibraries\win文件夹。

例如:

PATH c:\cygwin\bin
WEBKIT_DIR C:\cygwin\home\WebKit (我的WebKit 放在了home目录下)
WEBKITLIBRARIESDIR %WEBKIT_DIR%\WebKitLibraries\win
WEBKITOUTPUTDIR %WEBKIT_DIR%\WebKitBuild

 

运行脚本—编译

打开cygwin  在 WebKit/webkit/Tools/Scripts/下执行脚本

perl  update-webkit

perl build-webkit    运行时出现 如下问题

*************************************************************
Cannot find ‘/cygdrive/c/Program Files/Microsoft Visual Studio 9.0/Common7/IDE/VCExpress.exe’
Please execute the file ‘vcvars32.bat’ from
‘C:\Program Files\Microsoft Visual Studio 8\VC\bin\’
to setup the necessary environment variables.
*************************************************************
Died at /home/WebKit/Tools/Scripts/webkitdirs.pm line 1551.

不用关心 ,不影响正常编译

 

编译过程-编译

a)         用VS打开WebKit/WebKit/win/WebKit.vcproj/WebKit.sln,选择正确的Configuration,也就是Debug和Release,选择Debug即可。(切记不要选择Debug_all)

b)        选择DumpRenderTree, QTMovieWin, WebCore, WebKit, WinLauncher等所有项目(把c++项目的属性都设置一遍)的项目属性,左侧选择“C/C++”,将右侧的“Treat Warnings As Errors”(“将警告视为错误”)设置为“No”。这个必须设置。否则编译时会报错。

c)  设置警告级别,不输出报警

d)      右击WinLauncher,选择“设为启动项目”,然后F7,开始编译。

 

编译后会出现如下错误

error C2001: 常量中有换行符
error C2001: 常量中有换行符
fatal error C1057: 宏展开中遇到意外的文件结束

 

到相应的行 删除全角符号 “” 即可

之前

RetainPtr<CFStringRef> selectedCFString(AdoptCF, truncatedStringForLookupMenuItem(selectedString).createCFString());
return formatLocalizedString(WEB_UI_STRING(“Look Up “%@” “, “Look Up context menu item with selected word”), selectedCFString.get());
#else
return WEB_UI_STRING(“Look Up ”<selection>” “, “Look Up context menu item with selected word”).replace(“<selection>”, truncatedStringForLookupMenuItem(selectedString));

之后改为

RetainPtr<CFStringRef> selectedCFString(AdoptCF, truncatedStringForLookupMenuItem(selectedString).createCFString());
return formatLocalizedString(WEB_UI_STRING(“Look Up %@ “, “Look Up context menu item with selected word”), selectedCFString.get());
#else
return WEB_UI_STRING(“Look Up <selection> “, “Look Up context menu item with selected word”).replace(“<selection>”, truncatedStringForLookupMenuItem(selectedString));

然后全部清除,重新编译 即可顺利通过

下载安装 safari

编译完成后,需要将C:\Program Files\Common Files\Apple\Apple Application Support下的一些库文件拷贝到C:\cygwin\home\WebKit\WebKitBuild\Debug\bin目录.  

运行MiniBrowser.exe

 

 

运行WinLauncher.exe

 

 

试试吧

[转载]IE浏览器整页截屏程序

mikel阅读(1353)

[转载]IE浏览器整页截屏程序 – 卡卡西村长 – 博客园.

最近项目中涉及到浏览器整页截屏的功能,有点复杂,研究了一天,终于在IE浏览器下实现,至于其他浏览器,以后再研究。

所谓整页截屏,就是说把整个页面全部截进去,包括通过滚动才能看到的部分。

 

在网上搜了一下,大家用的都是同一种办法:通过滚动页面,截取每一屏的图片,然后再合并成一张整的图片。

方法是好的,悲催的是,没有一个代码是能正常运行的,相信很多人都有同感!没办法,自己动手,丰衣足食。

 

我需要用.NET来实现。分析一下,主要有以下几个技术点:

1、如何取得浏览器对象。首先要确定IE版本,我用的是IE8浏览器,对象结构和IE6、IE7有点区别。这个可以通过Win32API中的FindWindow函数来实现。

2、对指定控件截屏。这个可以通过Win32API中的PrintWindow函数来实现,这个函数有一个优点:即使浏览器被其它窗口挡住,也可以正常截屏。但是,如果浏览器窗口最小化了,那就漆黑一片了。。。

3、合并图片。这个用GDI+可以很方便地实现。在内存中创建一个大的画布,然后将图片从上至下依次画上去即可。

 

开始动手。

首先,创建一个Console应用程序(用Form程序也没关系)。

(1)添加对System.Drawing和System.Windows.Forms的引用。

(2)添加对两个COM组件的引用:SHDocVw、MSHTML,并设置属性“嵌入互操作类型”为False(这个很重要,否则无法接下来的程序无法编译通过)。

(3)将程序入口Main方法标记为[STAThread](这个也很重要,否则接下来的程序会运行失败)。

 

然后,加入Win32API类,该类对几个重要的API进行了封装,接下来我们会用到这些API。代码如下:

using System;
using System.Runtime.InteropServices;

namespace IECapture
{
    /// <summary>
    /// Win32API封装类。
    /// </summary>
    internal static class Win32API
    {
        //User32
        [DllImport("User32.dll")]
        public static extern int FindWindow(string lpClassName, string lpWindowName);
        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr FindWindowEx(IntPtr parentHandle, IntPtr childAfter, string className, IntPtr windowTitle);
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowRect(IntPtr hWnd, ref RECT rect);
        [DllImport("user32.dll")]
        public static extern IntPtr GetWindowDC(IntPtr hWnd);
        [DllImport("User32.dll")]
        public static extern bool PrintWindow(IntPtr hwnd, IntPtr hdcBlt, int nFlags);

        //gdi32
        [DllImport("gdi32.dll")]
        public static extern bool BitBlt(IntPtr hObject, int nXDest, int nYDest, int nWidth, int nHeight, IntPtr hObjectSource, int nXSrc, int nYSrc, int dwRop);
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleBitmap(IntPtr hDC, int nWidth, int nHeight);
        [DllImport("gdi32.dll")]
        public static extern IntPtr CreateCompatibleDC(IntPtr hDC);
        [DllImport("gdi32.dll")]
        public static extern bool DeleteDC(IntPtr hDC);
        [DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);
        [DllImport("gdi32.dll")]
        public static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

        public struct RECT
        {
            public int left;
            public int top;
            public int right;
            public int bottom;
        }
    }
}

最后,加入主程序。代码逻辑如下:

(1)获取当前IE浏览器对象。

(2)获取浏览器核心控件的矩形区域,计算整个页面一共有多少屏。

(3)通过滚动窗口的方式,对每一屏的页面进行截屏。

(4)将所有图片合并为一张整的图片。

主程序的源代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Drawing;
using System.Windows.Forms;

namespace IECapture
{
    class Program
    {
        //必须指定COM线程模型为单线程
        [STAThread]
        static void Main(string[] args)
        {
            //获取浏览器对象
            SHDocVw.ShellWindows shellWindows = new SHDocVw.ShellWindowsClass();
            var webBrowser = shellWindows.Cast<SHDocVw.WebBrowser>().FirstOrDefault(c => c.Name == "Windows Internet Explorer");
            if (webBrowser == null)
            {
                Console.WriteLine("当前未打开任何IE浏览器");
                Console.ReadLine();
                return;
            }

            //查找浏览器核心控件
            IntPtr childHandle1 = Win32API.FindWindowEx(new IntPtr(webBrowser.HWND), IntPtr.Zero, "Frame Tab", IntPtr.Zero);
            IntPtr childHandle2 = Win32API.FindWindowEx(childHandle1, IntPtr.Zero, "TabWindowClass", IntPtr.Zero);
            IntPtr childHandle3 = Win32API.FindWindowEx(childHandle2, IntPtr.Zero, "Shell DocObject View", IntPtr.Zero);
            IntPtr childHandle4 = Win32API.FindWindowEx(childHandle3, IntPtr.Zero, "Internet Explorer_Server", IntPtr.Zero);
            if (childHandle4 == IntPtr.Zero)
            {
                Console.WriteLine("当前未打开任何IE浏览器");
                Console.ReadLine();
                return;
            }

            //获取浏览器核心控件的矩形区域
            Win32API.RECT rc = new Win32API.RECT();
            Win32API.GetWindowRect(childHandle4, ref rc);
            int pageHeight = rc.bottom - rc.top;

            //获取HTML文档对象
            mshtml.IHTMLDocument2 htmlDoc = (mshtml.IHTMLDocument2)webBrowser.Document;

            //计算总共有多少页,以及最后一页的高度
            int pageCount = htmlDoc.body.offsetHeight / pageHeight;
            int lastPageHeight = htmlDoc.body.offsetHeight % pageHeight;
            if (lastPageHeight > 0) pageCount++;
            int scrollBarWidth = pageCount > 1 ? SystemInformation.VerticalScrollBarWidth : 0;

            //图片列表,用于放置每一屏的截图
            List<Image> pageImages = new List<Image>();

            //一页一页地滚动截图
            htmlDoc.parentWindow.scrollTo(0, 0);
            for (int pageIndex = 0; pageIndex < pageCount; pageIndex++)
            {
                using (Image image = CaptureWindow(childHandle4))
                {
                    //去掉边框,以及垂直滚动条的宽度
                    Rectangle innerRect = new Rectangle(2, 2, image.Width - scrollBarWidth - 4, image.Height - 4);
                    if (pageCount > 1 && pageIndex == pageCount - 1 && lastPageHeight > 0)
                        innerRect = new Rectangle(2, pageHeight - lastPageHeight + 2, image.Width - scrollBarWidth - 4, lastPageHeight - 4);

                    pageImages.Add(GetImageByRect(image, innerRect));
                }
                htmlDoc.parentWindow.scrollBy(0, pageHeight);
            }

            //拼接所有图片
            Image fullImage = MergeImages(pageImages);
            if (fullImage == null)
            {
                Console.WriteLine("截屏失败,未获得任何图片");
                Console.ReadLine();
                return;
            }

            //将截屏图片保存到指定目录
            try
            {
                string fileName = @"c:\IE整屏截图.png";
                fullImage.Save(fileName);
                Console.WriteLine("截屏成功,图片位置:" + fileName);
            }
            finally
            {
                fullImage.Dispose();
            }

            Console.ReadLine();
        }

        /// <summary>
        /// 合并图片。
        /// </summary>
        /// <param name="imageList">图片列表。</param>
        /// <returns>合并后的图片。</returns>
        static Image MergeImages(List<Image> imageList)
        {
            if (imageList == null || imageList.Count == 0) return null;
            if (imageList.Count == 1) return imageList[0];

            int pageWidth = imageList[0].Width;
            int totalPageHeight = imageList.Sum(c => c.Height);

            Bitmap fullImage = new Bitmap(pageWidth, totalPageHeight);
            using (Graphics g = Graphics.FromImage(fullImage))
            {
                int y = 0;
                for (int i = 0; i < imageList.Count; i++)
                {
                    g.DrawImageUnscaled(imageList[i], 0, y, imageList[i].Width, imageList[i].Height);
                    y += imageList[i].Height;
                    imageList[i].Dispose();//释放图片资源
                }
            }
            return fullImage;
        }

        /// <summary>
        /// 获取图片的指定区域。
        /// </summary>
        /// <param name="image">原始图片。</param>
        /// <param name="rect">目标区域。</param>
        /// <returns></returns>
        static Image GetImageByRect(Image image, Rectangle rect)
        {
            if (image == null || rect.IsEmpty) return image;

            Bitmap bitmap = new Bitmap(rect.Width, rect.Height);
            using (Graphics g = Graphics.FromImage(bitmap))
            {
                g.DrawImage(image, 0, 0, rect, GraphicsUnit.Pixel);
            }
            return bitmap;
        }

        /// <summary>
        /// 为指定窗口或控件截屏。
        /// </summary>
        /// <param name="hWnd">句柄。</param>
        /// <returns>截屏图片。</returns>
        static Image CaptureWindow(IntPtr hWnd)
        {
            IntPtr hscrdc = Win32API.GetWindowDC(hWnd);
            if (hscrdc == IntPtr.Zero) return null;

            Win32API.RECT windowRect = new Win32API.RECT();
            Win32API.GetWindowRect(hWnd, ref windowRect);
            int width = windowRect.right - windowRect.left;
            int height = windowRect.bottom - windowRect.top;

            IntPtr hbitmap = Win32API.CreateCompatibleBitmap(hscrdc, width, height);
            IntPtr hmemdc = Win32API.CreateCompatibleDC(hscrdc);
            Win32API.SelectObject(hmemdc, hbitmap);
            Win32API.PrintWindow(hWnd, hmemdc, 0);

            Image bmp = Image.FromHbitmap(hbitmap);
            Win32API.DeleteDC(hscrdc);
            Win32API.DeleteDC(hmemdc);

            return bmp;
        }
    }
}

【总结】

要想写一个好的整页截屏程序,还是很困难的。就拿本文的程序来说,就存在以下几点不足之处:

(1)仅在IE8浏览器上测试通过,无法在FireFox、Chrome上运行。即使同是IE家族的IE6、IE7、IE9,我也不敢保证能正常运行,各位同学可以测试一下,并尝试修改,欢迎交流。

(2)如果有浮动DIV随着页面一起滚动,在每一屏上都会被截屏。

(3)未考虑水平滚动条的影响。

(4)未考虑在多线程环境下的应用。

[转载]Asp.Net MVC 4 Web API 中的安全认证(一)

mikel阅读(1134)

[转载]Asp.Net MVC 4 Web API 中的安全认证(一) – Nic Pei – 博客园.

ASP.NET MVC4中的Web API提供了很好的API接口开发方式。可以更好地适应现在的跨平台移动开发。相信大家很多的项目现在都在使用Web Service作为提供数据的接口。好吧,那么Web API将是用来革Web Service的命。哈哈。 当然了,WCF这玩意我相信很可能在不久的将来会融入到ASP.NET MVC中。

 

开发提供数据 的API,最重要的是数据的安全性。那么对于我们来说,如何确保数据的安全将会是需要思考的问题。没有SOAP头,只能用别的方式。比如 OAUTH,HTTP AUTH等等。这篇文章将会和大家探讨下普通的http认证使用。然后会在后续提供oauth认证方案。。。。 Smile 

 

声明下:对于老鸟们,下面的文章可能对你没用,因为都是基础的http认证知识。

 

先看看ASP.NET MVC 官网提供的例子:http://code.msdn.microsoft.com/ASPNET-Web-API-JavaScript-d0d64dd7

Web API:

image

 

默认的是使用Form认证。对于我们来说这个是比较熟悉的。如果想了解MVC是如何实现监测form认证的,可以下载下MVC源代码看看(反正我是看着那些代码头疼Smile)。但是吧,这个简单的检测认证部分我还是能看明白的:

image

 

 

如果你使用的是跨平台的调用Web API,那么这个认证好像就没法用了。需要自定义认证方式,简单的办法就是继承Authorize类,override方法OnAuthorization。

这里假设我们使用基本的http认证,那么OnAuthorization方法实现如下:

image

 

在http请求的header中查看是否有认证信息,然后使用Base64解密:

image

 

来吧,浏览器测试下:访问添加了CustomBasicAuthorize的Controller,会弹出windows认证框,输入用户名和密码,使用Fiddler跟踪下:

image

 

 

 

 

 

 

http 请求信息:

image

 

看着认证信息加密了,其实没啥用,Base64加密的,太容易破解了。 所以如果这种认证方式也就适合在公司内部系统使用。 如果你真想在项目中使用,也是可以的,请使用SSL。

 

以上没有提供object-c的调用代码或者Android的调用代码。

[转载]C# 调用并执行SQL脚本文件

mikel阅读(1275)

[转载]C# 调用并执行SQL脚本文件 – Eric.Hu – 博客园.

突然接到leader的任务,为运维写个小工具,给一个参数然后调用一个SQL脚本文件并替换器内部某个参数,然后执行.

于是工作开始了…

 

首先想到的是能让C#程序直接执行SQL脚本文件,类似SSMS直接打开sql文件并执行一样,搜索了一下结果还真有,不过需要借用mssql的几个类库及命名空间:

Microsoft.SQLServer.ConnectionInfo.dll

Microsoft.SQLServer.Smo.dll

——————————————–

using Microsoft.SQLServer.Management.Common;

using Microsoft.SqlServer.Management.Smo;

 

这里有一个小插叙,对于vs2008里可以直接引用这个这两个类库,但在vs2010里的引用就无法找到这两个类库了(不知道微软是怎么考虑的)

还好我电脑vs2008和vs2010都安装了

不过我还是避易就难的讲一下,vs2010在引用里无法找到这两个类库,那我们就想办法找到它,首先我电脑安装了mssqlserver2008 ,

我去X:\Program Files\Microsoft SQL Server\100\SDK\Assemblies 找到

Microsoft.SqlServer.ConnectionInfo.dll

Microsoft.SqlServer.Smo.dll

Microsoft.SqlServer.Management.Sdk.Sfc.dll(这个一定要考到你的程序目录,但你可以不引用)

然后手动添加应用,第三个dll一定要引用,不然会报错

 

下面看代码:

//补卡操作
         private void PatchCard()
         {
             string path = System.Environment.CurrentDirectory;
             string CardNo = txtCardNo.Text.Trim();
             string connectonstring=ConfigurationManager.AppSettings["connectionString"].ToString();
             if(CardNo==null||CardNo=="")
             {
                 MessageBox.Show("卡号不能为空!");
                 return;
             }
             if(!path.EndsWith(@"\"))
             {
                 path += @"\";
             }
             path+="补蓝鲸卡.sql";    //获取脚本位置
             if (File.Exists(path))
             {
                 FileInfo file = new FileInfo(path);
                 string script = file.OpenText().ReadToEnd();
                 script=script.Replace("H00001", CardNo);  //替换脚本里的参数
                 try
                 {
                       //执行脚本
                    SqlConnection conn = new SqlConnection(connectonstring);
                    Microsoft.SqlServer.Management.Smo.Server server = new Server(new ServerConnection(conn));
                    int i= server.ConnectionContext.ExecuteNonQuery(script);
                    if (i == 1)
                    {
                        MessageBox.Show("恭喜!\n"+CardNo+" 补卡成功!","成功");
                        txtCardNo.Text = "";
                        CreateLog(CardNo, true);
                    }
                    else
                    {
                        MessageBox.Show("@_@ 再试一次吧!","失败");
                    }
                     
                 }
                 catch (Exception es)
                 {
                     MessageBox.Show(es.Message);
                     CreateLog(CardNo + "  " + es.Message, false);
                 }
             }
             else
             {
                 MessageBox.Show("脚本不存在!");
                 return;
             }
         }

[转载]新浪微博SDK for .Net 4.0+发布了

mikel阅读(850)

[转载]新浪微博SDK for .Net 4.0+发布了 – 林选臣 – 博客园.

新浪微博开放平台API for .Net 4.0+

首先,感谢大家使用此SDK!请提出宝贵意见,并及时反馈BUG以此来让这个SDK更好的服务广大的Coder。

此SDK的一些特性:

  • 使用.net 4.0新的Dynamic特性,所有API调用返回的JSON原封不动的使用了官方文档中的数据结构,查阅方便。
  • 采用V1版的官方微博API,而不是官方推荐的V2版。因为就我所知官方的OAUTH2.0似乎有问题,会导致用户重复授权。

代码示例:

1 var client = new NetDimension.Weibo.Client("你的AppKey", "你的AppSecret","access token","token secret"); 2 client.API.Statues.Upload("欢迎使用新浪微博API for .Net 4.0");

人无完人,SDK中可能还存在着各种BUG,虽然基本的那几个接口我已经做过测试能够正常发送微博和图片,其他接口就没有那么多时间去一一测试了,如果你在使用这些接口的时候发现问题,请及时的联系我,以便修正,谢谢。

SDK中的各个接口命名基本和官方API文档(http://open.weibo.com/wiki/API%E6%96%87%E6%A1%A3/ )中的一致,我相信使用VS2010来开发应用的各位使用起来应该不会太困难。返回结果通过DynamicJson类处理过了,使用方法在客户端和网页DEMO中都有,结果也同官方API文档中的一致。

如果你在使用本人编写的这个SDK时有任何问题,都可以将问题@我,我会尽快做出答复。

 

项目托管在CodePlex,地址是:

http://weibosdk.codeplex.com/

本人微博:http://weibo.com/xuanchenlin

[转载]没有eclipse截图的OSGi入门教程

mikel阅读(991)

[转载]没有eclipse截图的OSGi入门教程 – chenjianjx – 博客园.

 

网上能找到的OSGi教程,都喜欢在开篇时就教你如何在eclipse里做一个OSGi的东西,就算你跟着做了(如果有足够的耐心,还是不知道OSGi是怎么一回事);更可气的是,这些教程还会配上大段大段的eclipse截图,以达到浪费篇幅的目的。

 

鉴于这种情况,我只好找本英文OSGi书看了看。这本书上没有eclipse截图,我看时觉得很满意,所以顺便把入门部分微缩一下,给大家做个简短的教程。

 


 

Why OSGi?

OSGi用于在同一个JVM内部实现“模块化” (“组件化”)

 

首先,它用来实现模块化,提供模块化的一些常见特性:

1.       模块向外只暴露特定的接口,内部实现对外不可见

2.       模块可独立部署

3.       像服务治理一样管理自己模块暴露的接口,包括服务发布、寻找和版本管理

 

其次,和SOA那种分布式模块化方案不同,OSGi的模块并不会分布在多台机器上,而是部署在同一个jvm进程里的:

1.       每个模块打包成一个JAR

2.       整个应用只需要一个进程,开发、部署会比SOA更高效

 

OSGi的具体功能有哪些?

官方文档把OSGi的功能分成三大块:

1.       Module: 将程序组装成一个一个的“模块”,模块里除了业务逻辑,还有各种元数据

2.       Lifecycle:管理模块的生命周期,比如启动和停止模块,并在运行时让模块跟OSGi框架发生关系(比如“热部署”)

3.       Service:把模块暴露的接口当作SOA服务来管理,比如服务的发布和寻找。

 

实际使用时,你并不需要用上所有的功能。如果你只对模块组织感兴趣,“热部署”、“服务治理”等等你都可以不用。

 

顺便提一下OSGi的规范与实现。”OSGi Alliance”组织只提供了规范(specification),具体的java实现则由第三方提供。目前主流的实现有:

 

1.       Apache Felix

2.       Eclipse Euinox  — Eclipse组织提供的实现。Eclipse还为它提供了相应的IDE Plugin,以快速搭建OSGi应用。估计这也是为什么很多OSGi教程喜欢从Eclipse截图开始的原因。

3.       Knopflerfish

 


 

OSGi应用举例

闲话少叙,接下来我们直接看代码,看看基于OSGi的模块化是怎么实现的。在下面的例子里,我们会涉及到OSGi模块的服务化、打包和生命周期管理。

 

 

Service/Client

在模块化的语境中,一个模块总是在充当服务提供者(Service)或者服务消费者(Client)。所以我们可以先把Service/Client建好:

 

Service:

public interface HelloService {
public void sayHelloTo(String whom);
}

public class HelloServiceImpl implements HelloService {

@Override
public void sayHelloTo(String whom) {
System.out.println("Saying hello to " + whom);
}
}

Client:

public class HelloClient {

private static HelloService helloService;
public void callHelloService() {
helloService.sayHelloTo("beckham"); //向Beckham问好
}

…
}

 

 


 

 

Service/Client都打包成模块

 

接下来我们把ServiceClient分别打成jar包,形成OSGi模块。当然,OSGi模块不仅仅是一个jar包,它还要包含一些必要的元数据和辅助类,以被OSGi框架识别和管理。

 

 

 

我们先从Service模块开始。

 

 

 

Service:

 

 

1.       先写一个“服务激活者”,用于指定本模块的服务的实现者并发布服务。这个类会在模块启动时被执行

public class ServiceActivator implements org.osgi.framework.BundleActivator{

@Override
public void start(BundleContext context) throws Exception {
context.registerService(HelloService.class.getName(), new HelloServiceImpl(), null);
System.out.println(HelloService.class.getName() + " has been registred as a service");
}
…….
}

 

你可能注意到这里出现了”Bundle”字样。这是因为OSGi术语中,模块就是Bundle.

 

2.       在即将打出的jar包的MANIFET.MF中,标识一下本模块(Bundle),指定模块中的哪些类允许被外界使用,并指定本模块的“激活者”:

Bundle-SymbolicName: player.kent.chen.osgi.service
Bundle-Version: 1.0.0
Export-Package: player.kent.chen.osgi.service.interfaces
Bundle-Activator: player.kent.chen.osgi.service.impl.ServiceActivator

还有一个条目:

Import-Package: org.osgi.framework

这指明了本模块可以依赖哪些外部java package;由于“激活者”在运行时依赖了org.osgi.framework,如果没有上面这一句,激活者的运行就会失败。

 

3.     打包成jar

 

Service的接口、实现、激活者及MANIFEST.MF打成jar包,并命名为play-osgi-service.jar.bundle

 

 

Client:

 

1.       先写一个“激活者”,从OSGi Context中寻找Service

public class ClientActivator implements BundleActivator{

public static HelloService helloService;
@Override
public void start(BundleContext context) throws Exception {
ServiceReference ref = context.getServiceReference(HelloService.class.getName());
helloService = (HelloService) context.getService(ref);
HelloClient.setHelloService(helloService);
}
……
}

2. 在MANIFET.MF中标识一下本模块,指定欲依赖的类,并指定激活者

Bundle-SymbolicName: player.kent.chen.osgi.client
Bundle-Version: 1.0.0
Import-Package: player.kent.chen.osgi.service.interfaces, org.osgi.framework
Bundle-Activator: player.kent.chen.osgi.client.ClientActivator

 

3.       打包成jar

 

Client类、激活者及MANIFEST.MF打成jar包,并命名为play-osgi-client.jar.bundle


创建launch程序

ServiceClient模块都构建好了,最后写一个Main程序把它们集成在一起跑起来(launch it)

public class Main {
 
         public static void main(String[] args) throws Exception {
 
                   // 创建launch framework,它是OSGi launch的入口. (getFramework()方法的实现请参见附件里的代码文件)
                   org.osgi.framework.launch.Framework framework = getFramework();
 
                   // 安装service bundle
                   Bundle serviceBundle = framework.getBundleContext().installBundle(fileToUri("../play-osgi-service-and-client/service/dist/play-osgi-service.jar.bundle"));
 
                   // 安装client bundle
                   Bundle clientBundle = framework.getBundleContext().installBundle(fileToUri("../play-osgi-service-and-client/client/dist/play-osgi-client.jar.bundle"));
 
                   // 启动framework和bundles
                   framework.start();
                   serviceBundle.start();
                   clientBundle.start();
 
                   // 启动client
                   Class<?> clientClass = clientBundle.loadClass("player.kent.chen.osgi.client.HelloClient");
                   Method method = clientClass.getMethod("callHelloService", new Class<?>[]{});
                   method.invoke(clientClass.newInstance());
 
                   // 最后停止framework
                  framework.stop();
         }
    …
}

运行一下这个Main程序,控制台将打印” Saying hello to Beckham”,表明HelloService调用成功。

你的第一个OSGi应用写好了!它组建了模块(Bundle),指定了依赖关系;但这个例子本身还不能充分地展示“模块化”,我们要再看一个例子:

体验一个反例:越过Service的接口直接调用内部实现

OSGi的模块中未显式暴露的类对其他模块来说是不可见的,我们来测试一下。

把client的激活者的代码改一下,让它直接依赖HelloServiceImpl对象,看看会怎么样。

public class ClientActivator implements BundleActivator{
        
         public static HelloServiceImpl helloServiceImpl;
         @Override
         public void start(BundleContext context) throws Exception {                  
                   ServiceReference ref = context.getServiceReference(HelloService.class.getName());
                   helloServiceImpl = (HelloServiceImpl) context.getService(ref);
             HelloClient.setHelloService(helloServiceImpl);
         }
   …
}

运行程序,结果是: java.lang.NoClassDefFoundError: player/kent/chen/osgi/service/impl/HelloServiceImpl . 也就是说, Client在执行时,相关的类装载器装载不到HelloServiceImpl,因为HelloServiceImpl不属于client模块的Import-Package (player.kent.chen.osgi.service.interfaces)

那如果client模块引入了HelloServiceImpl呢? 在MANIFEST.MF中加入一个Import-Package试试:

Import-Package: player.kent.chen.osgi.service.interfaces, org.osgi.framework, player.kent.chen.osgi.service.impl

运行结果是:

Unresolved constraint in bundle player.kent.chen.osgi.client: missing requirement package; (package=player.kent.chen.osgi.service.impl)

也就是说,虽然client模块中引入了对player.kent.chen.osgi.service.impl的依赖,但并没有任何模块提供了这个package,client的调用依然以异常告终。

试想,如果没有OSGi,仅把client和service两个jar文件放在一起,除非自写Class Loader,否则你无法阻止client里的代码直接调用service的内部实现,继而导致弱封装、强耦合了。这下你可以看出OSGi的模块化的功效了吧? 

总结

通过上面的介绍,估计你对OSGi的基本作用已经有了一定的了解,对它如何使用也有了感性的认识。你可以下载附件所含的代码示例,自己体验一下。(这些代码使用Apache Felix作为OSGi的实现,由于Felix对bundle的装载使用了缓存,在每次运行之前,你最好都清除一下前一次运行时产生的felix-cache文件夹)

另外,本文内容比较少,只是“入门的入门”,它只能帮你消除对OSGi的神秘感。要想“完整入门”、或者想全面学习OSGi的强大功能,建议阅读专门的书籍。个人推荐的书是: ‘OSGi in Action’

[转载]SQL Server中的执行引擎入门

mikel阅读(1080)

[转载]SQL Server中的执行引擎入门 – CareySon – 博客园.

简介

当查询优化器(Query Optimizer)将T-SQL语句解析后并从执行计划中选择最低消耗的执行计划后,具体的执行就会交由执行引擎(Execution Engine)来进行执行。本文旨在分类讲述执行计划中每一种操作的相关信息。

 

数据访问操作

首先最基本的操作就是访问数据。这既可以通过直接访问表,也可以通过访问索引来进行。表内数据的组织方式分为堆(Heap)和B树,其中表中没有建立聚集 索引时数据是通过堆进行组织的,这个是无序的,表中建立聚集索引后和非聚集索引的数据都是以B树方式进行组织,这种方式数据是有序存储的。通常来说,非聚 集索引仅仅包含整个表的部分列,对于过滤索引,还仅仅包含部分行。

除去数据的组织方式不同外,访问数据也分为两种方式,扫描(Scan)和查找(Seek),扫描是扫描整个结构的所有数据,而查找只是查找整个结构中的部 分数据。因此可以看出,由于堆是无序的,所以不可能在堆上面进行查找(Seek)操作,而相对于B树的有序,使得在B树中进行查找成为可能。当针对一个以 堆组织的表进行数据访问时,就会进行堆扫描,如图1所示。

1

图1.表扫描

 

可以看出,表扫描的图标很清晰的表明表扫描的性质,在一个无序组织表中从头到尾扫描一遍。

而对于B树结构的聚集索引和非聚集索引,同样可以进行扫描,通常来讲,为了获取索引表中的所有数据或是获得索引行树占了数据大多数使得扫描的成本小于查找时,会进行聚集索引扫描。如图2所示。

3

图2.聚集索引扫描

 

聚集索引扫描的图标也同样能够清晰的表明聚集索引扫描的性质,找到最左边的叶子节点后,依次扫描所有叶子节点,达到扫描整个结构的作用。当然对于非聚集索引也是同样的概念,如图3所示。

2

图3.非聚集索引的扫描

 

而对于仅仅选择B树结构中的部分数据,索引查找(Seek)使得B树变得有意义。根据所查找的关键值,可以使得从仅仅从B树根部向下走单一路径,因此免去了扫描不必要页的消耗,图4是查询计划中的一个索引查找。

4

图4.聚集索引查找

 

索引查找的图标也是很传神的,可以看到图标那根线从根节点一路向下到叶子节点。也就是找到所求数据所在的页,不难看出,如果我们需要查找多条数据且分散在 不同的页中,这个查找操作需要重复执行很多回,当这个次数大到一定程度时,SQL Server会选择消耗比较低的索引扫描而不是再去重复索引查找。对于非聚集索引查找,概念是一样的,就不再上图片了。

 

书签查找(Bookmark Lookup)

     你 也许会想,假如非聚集索引可以快速的找到所求的数据,但遗憾的是,非聚集索引却不包含所有所求列时该怎么办?这时SQL Server会面临两个选择,直接访问基本表去获取数据或是在非聚集索引中找到数据后,再去基本表获得非聚集索引没有覆盖到的所求列。这个选择取决于所估 计的行数等统计信息。查询分析器会选择消耗比较少的那个。

一个简单的书签查找如图5所示。

5

图5.一个简单的书签查找

 

从图5可以看出,首先通过非聚集索引找到所求的行,但这个索引并不包含所有的列,因此还要额外去基本表中找到这些列,因此要进行键查找,如果基本表是以堆 进行组织的,那么这个键查找(Key Lookup)就会变成RID查找(RID Lookup),键查找和RID查找统称为书签查找。

不过有时候索引查找所返回的行数过多导致书签查找的性能远不如直接进行扫描操作,因此SQL Server这时会选择扫描而不是书签查找。如图6所示。

6

图6.StateProvinceID列有非聚集索引,但由于返回行数过多,分析器会选择扫描而不是书签查找

 

这个估计是根据统计信息进行的,关于统计信息,可以看我之前的一篇博文:浅谈SQL Server中统计对于查询的影响

 

聚合操作(Aggregation)

聚合函数会导致聚合操作。聚合函数是将一个集合的数据按照某种规则汇总成1个数据,或基于分组按照规则汇总成多个数据的过程。一些聚合函数比 如:avg,sum,min,另外还有distinct关键字都有可能导致两类聚合操作:流聚合(Stream Aggregation)和哈希聚合(Hash Aggregation)。

 

流聚合(Stream Aggregation)

流聚合需要再执行聚合函数之前,被聚合的数据集合是有序的,这个有序数据既可以通过执行计划中的Sort进行,也可以直接从聚集或是非聚集索引中直接获得有序数据,另外,没有Group by的聚合操作被成为标量聚合,这类操作一定是会执行流聚合。

比如,我们直接进行标量聚合,如图7所示。

7

图7.流聚合

 

但对于加了Group by的子句,因为需要数据按照group by 后面的列有序,就需要Sort来保证排序。注意,Sort操作是占用内存的操作,当内存不足时还会去占用tempdb。SQL Server总是会在Sort操作和散列匹配中选择成本最低的。一个需要Sort的操作如图8所示。

8

图8.需要排序的流聚合

 

图8中排序操作按照ProductLine进行排序后,然后就根据各自的分组做聚合操作了。

 

散列聚合(Hash aggregation)

上面的流聚合适合比较少的数据,但是对于相对大一点的表。使用散列集合成本会比排序要低。散列集合通过在内存中建立散列表来实现聚合,因此无需对数据集合进行排序。内存中所建立的散列表以Group by后面的列作为键值,如图9所示。

9
图9.散列聚合

 

在内存中建立好散列表后,会按照group by后面的值作为键,然后依次处理集合中的每条数据,当键在散列表中不存在时,向散列表添加条目,当键已经在散列表中存在时,按照规则(规则是聚合函数,比如Sum,avg什么的)计算散列表中的值(Value)。

 

 

连接(Join)

当多表连接时(书签查找,索引之间的连接都算),SQL Server会采用三类不同的连接方式:循环嵌套连接(Nested Loops Join),合并连接(Merge Join),散列连接(Hash Join)。这几种连接并不是哪种会比另一种更好,而是每种连接方式都会适应特定场景。

 

循环嵌套连接(Nested Loops Join)

由图10可以看到一个简单的循环嵌套连接。

10

图10.一个循环嵌套连接的实例

 

循环嵌套连接的图标同样十分传神,处在上面的外部输入(Outer input),这里也就是聚集索引扫描。和处在下面的内部输入(Inner Input),这里也就是聚集索引查找。外部输入仅仅执行一次,根据外部输入满足Join条件的每一行,对内部输入进行查找。这里由于是290行,对于内 部输入执行290次。

可以通过属性窗口看到.如图11所示:

13

图11.内部输入的执行次数

 

根据嵌套循环的原理不难看出,由于外部输入是扫描,内部输入是查找,当两个Join的表外部输入结果集比较小,而内部输入所查找的表非常大时,查询优化器更倾向于选择循环嵌套方式。

 

合并连接(Merge Join)

不同于循环嵌套的是,合并连接是从每个表仅仅执行一次访问。从这个原理来看,合并连接要比循环嵌套要快了不少。下面来看一个典型的合并连接,如图12所示。

11

图12.合并连接

 

从合并连接的原理不难想象,首先合并连接需要双方有序.并且要求Join的条件为等于号。因为两个输入条件已经有序,所以从每一个输入集合中取一行进行比 较,相等的返回,不相等的舍弃,从这里也不难看出Merge join为什么只允许Join后面是等于号。从图11的图标中我们可以看出这个原理。

如果输入数据的双方无序,则查询分析器不会选择合并连接,我们也可以通过索引提示强制使用合并连接,为了达到这一目的,执行计划必须加上一个排序步骤来实现有序,如图13所示。

12

图13.通过排序来实现Merge Join

 

散列连接(Hash Join)

散列连接同样仅仅只需要只访问1次双方的数据。散列连接通过在内存中建立散列表实现。这比较消耗内存,如果内存不足还会占用tempdb。但并不像合并连接那样需要双方有序。一个典型的散列连接如图14所示。

14

图14.散列连接

 

这里我删除了Costomer的聚集索引,否则两个有序输入SQL Server会选择代价更低的合并连接。SQL Server利用两个上面的输入生成哈希表,下面的输入来探测,可以在属性窗口看到这些信息,如图15所示。

15

图15.散列键生成和散列键探测

 

通常来说,在两个输入数据比较大,且所求数据在其中一方或双方没有排序的条件达成时,会选用散列匹配。

 

并行

    当多个表连接时,SQL Server还允许在多CPU或多核的情况下允许查询并行,这样无疑提高了效率,一个并行的例子如图16所示。

16

图16.并行提高效率

 

总结

本文简单介绍了SQL Server执行计划中常见的操作极其原理,了解这些步骤和原理是优化查询的基本功。

[转载]手把手教你入门EaselJS做HTML5动画

mikel阅读(1441)

[转载]手把手教你入门EaselJS做HTML5动画[转载] – djy_fn – 博客园.

今天借由EaselJS这个脚本库,写一个简单的动画给大家当做为示范。

DEMO演示链接
这个demo的最终效果是游戏割绳子(Cut the Rope)中的小青蛙的动画表情。

首先简单介绍一下EaselJS这个脚本库,它是我最近发现的一个用来绘制Canvas动画的脚本库,比起Canvas的底层API,这个脚本库的接口设计更简单易用,所以做起动画来就易如反掌了。这里有详细的信息文档

素材

我们今天要完成的动画是通过Canvas技术,一帧一帧绘制出来的,那么首先需要每一帧动画的素材。我们从http://cuttherope.ie/找到了一些素材,并把每一帧动画都切割成100*100像素的图像。这里我们选择了小青蛙的两个状态动画:点头的状态和撅嘴的失败的状态,一共19+13=32帧。

在EaselJS中,我们可以把多个连续的图像排列成为一张大图,再用脚本将其切割为多帧动画,所以我们把这些素材按顺序合并成一张100*3200的大图片,这跟CSS Sprites技术有异曲同工之妙。最后的图片保存为了sprites.png

准备工作

首先,导入easeljs脚本库

_script src="easeljs.js">_/script>

然后,创建canvas标签

_canvas id="canvas" width="100" height="100">_/canvas>

准备编写JS

创建舞台(stage)

// create a new stage and point it at our canvas
var stage = new Stage(document.getElementById('canvas'));

创建小青蛙

小青蛙是由一个 BitmapAnimation 类创建出来的全局变量 icon,这类对象可以在舞台中播放动画。而动画是由 sprites.png 中的图片循环播放生成的,这里我们需要通过另一个类创建对象:SpriteSheet,整个小青蛙的创建方法如下:

// create a SpriteSheet using "sprites.png" with a frame size of 100x100
var spriteSheet  = new SpriteSheet({
    images: ["sprites.png"],
    frames: {width: 100, height: 100}
});

// create a BitmapAnimation to display frames from the sprite sheet
icon = new BitmapAnimation(spriteSheet);

播放动画

创建完毕之后,这个小青蛙的对象需要添加到舞台中:

// append into stage and start animations
stage.addChild(icon);
icon.gotoAndPlay(0);
Ticker.addListener(stage);

对上面的代码做一下解释:第一行代码是把小青蛙的对象添加到舞台,用到了舞台的Stage.prototype.addChild 接口;第二行代码是让动画从第1帧开始播放,用到了BitmapAnimation.prototype.gotoAndPlay 接口;第三行代码则是开始播放动画,它的原理有点意思,Ticker 是EaselJS中的又一个类,用来作为控制动画播放的“节拍器”。当我们使用Ticker.addListener 方法时,节拍器会有节奏的触发第一个参数的 tick 函数(默认是20帧/秒),而stage 对象刚好有一个默认函数 Stage.prototype.tick,会把舞台中的所有动画元素向前播放一帧。这样,小青蛙的动作就会连贯的播放出来了。
我们现在看到的效果就是一只小青蛙连续的点头和撅嘴,是不是很可爱 :-)
此时的阶段性DEMO演示链接 1

加入动画的控制,让小青蛙连续点头

接下来加入一些动画的控制,比如让小青蛙处于循环点头的状态——事实上这才是割绳子游戏中的正常状态。

// bind animation events
icon.tick = function () {
    if (icon.currentFrame == 18) {
        icon.currentFrame = 0;
    }
};

这里给icon.tick赋值一个函数,这个函数会在每一帧动画播放之后被触发。我们在这个函数中判断,如果播放到第19帧时,立即从第1帧继续播放。这样,小青蛙点头的动画就可以重复播放了。设置当前帧数的方法是修改 icon.currentFrame 的值。
此时的阶段性DEMO演示链接 2

加入小青蛙撅嘴状态的切换

在这个基础上,我们再加入让青蛙撅嘴的状态,并且可以通过回车键使小青蛙切换到撅嘴的状态。

window.onkeydown = function (event) {
    if (event.keyCode == 13) {
        icon.gotoAndPlay(19);
    }
};

刷新页面,这时,我们看到,小青蛙还是处在连续点头的状态,当我们按下回车键时,小青蛙立刻从第20帧开始继续播放,即撅嘴。但有个小问题,因为节拍器是循环播放的,所以当撅嘴的动画播放完毕之后,动画会自动跳到第1帧。即重新回到连续点头的状态。
此时的阶段性DEMO演示链接 3

这其实不是我们希望看到的,我们希望小青蛙撅嘴之后,停留在最后一帧,当用户再次按下回车键后,再回到连续点头的状态。所以,我们还会对代码做一些修改。

最终完成

首先,加入toggle函数,通过flag记录当前的状态是连续点头还是撅嘴:

function toggle() {
    if (flag) {
        icon.gotoAndPlay(0);
    }
    else {
        icon.gotoAndPlay(19);
    }
    flag = !flag;
}

window.onkeydown = function (event) {
    if (event.keyCode == 13) {
        toggle();
    }
};

然后,绑定 icon 的另一个事件 onAnimationEnd

icon.onAnimationEnd = function () {
    icon.paused = true;
    icon.currentFrame = 31;
}

这个事件会在动画播放到最后一帧时触发,即第32帧时,我们在这个事件中要处理的就是让动画暂停下来,同时让动画停留在第32帧。

再次保存刷新页面,我们的动画就大功告成了!
DEMO演示链接

总结

以 上就是我们对EaselJS脚本库制作动画的一个简单实践。通过这次实践,我们会发现其实动画无非是图片的快速连续播放,而帧的概念、多帧图片合并 (sprites)的方法,让动画制作更加简单,我们结合节拍器对动画的每一帧进行控制。除了EaselJS,类似的Canvas脚本库还有一些,这个是 我觉得相对简单易懂的。在此推荐给各位!

这应该是我今年最后一篇技术博客了,明年我会继续坚持写博客,多多写一些技术类的文章,多多跟大家分享和探讨HTML5。最后预祝大家,尤其是诸位技术宅男们,新年快乐!工作顺利!

转自:http://liuchang.sinaapp.com/2012/01/easeljs/