PE文件解析库 – LibPE v0.1 Preview

最近突然想学习一下PE文件的结构,所以就写了一个PE文件的解析库,纯属好玩。于是经过了1个月的业余时间的折腾,LibPE v0.1 Preview终于诞生了。

项目名称:LibPE
项目地址:https://github.com/r1f/libpe
License:The BSD 2-Clause License

欢迎大家去围观,如果有人参与那就更加的Happy鸟~Licese是BSD的,所以基本上大家可以拿来随便用了。

功能

现在这个库到底能做什么呢?说出来真的是太简单了,所以才把这个版本定位v0.1 Preview,供围观之用。

  • 解析磁盘上的PE文件,暂时不支持解析映射好的PE。
  • 支持解析32位(PE32)和64位(PE32+)的PE文件。
  • RVA,VA,FOA转换
  • 解析PE基本头部信息,段表,导出表,导入表,资源表,重定向表和导入函数地址表
  • 支持获取PE文件的原始数据结构,并且对结构中的字段提供想对应的getter,如IPEOptionalHeader::GetFieldDllCharacteristics
    (注意:这里拿到的是原始数据,在文件中和在映射好后的映像中可能会不一样)

好了,围观完了之后,我们现在再大概描述一下LibPE想做什么吧~

目标和设计原则

LibPE想成为一个容易使用,32位64位兼容,性能不错并且可以跨越平台的PE解析库。虽然里面有一部分内容还没有完成,比如跨平台的迁移,IMAGE相关的结构还真的是多到不行啊,迁移起来还真是需要一些时间。

1. 容易使用

  • 尽量避免让使用者通过RVA和FOA去操作PE结构,而是直接提供获取PE元素的函数。
  • 获取到的每个PE结构都具备有获取RVA,FOA和大小的函数。
  • 能编译成lib和dll,方便使用
  • 使用引用技术来管理对象的生命周期,接口中只使用基本的数据类型,避免跨dll的问题。
  • 能够解析不同状态的PE文件结构,比如在磁盘上的,已经被映射进来之后的。当然这个现在还没有完成。。。。嗯。。。

2. 兼容32位和64位的PE格式

  • 这个功能简直就是必备的
  • 统一32位和64位的接口!!
    这个特性个人觉得很有用,网上大部分的PE库,为了支持64位都是使用模版来完成的,这样我们想实现一个批量的PE文件处理的话就会出现很多问题,而LibPE就是想解决这些问题:

    • 冗余的代码:除非使用模版,要不然处理的逻辑得32位和64位都写一遍,但是使用模版,又会让代码看起来更加晦涩,这些都是LibPE想避免的问题
    • 冗余的IO:由于处理的时候需要先判断文件是32位还是64位,所以就会出现需要一次IO去判断文件类型,再一次IO去解析文件,这样会导致处理大量文件时性能下降,因为这个时候磁盘IO是整个系统的瓶颈。

3. 不错但不是极致的性能

LibPE并不追求极致的性能,相对极致的性能,LibPE更加注重是否对程序员友好,整个库是否好用。但是性能也是很重要的!

为了达到不错的性能,我们主要做了两个方便的工作:

  • 即用即解析:如果不需要看导入表,那我解析个毛啊!
  • 最小化磁盘IO:在现在手机CPU都可以处理登月数据的年代,CPU其实已经非常的强。而大量的文件处理时,磁盘IO才是真正的瓶颈,减少磁盘IO才是真正对整个系统最有帮助的优化。

4. 跨平台

跨平台啊,跨平台,相当蛋疼的体力活也相当重要。客户端的代码一般都不会出现大量处理PE文件的情况,而后台一般都是linux,所以支持跨平台很重要。

5. 最小外部依赖

为什么不用boost或者其他跨平台的基础库?作出这个蛋疼决定的出发点只有一条:让大家能少checkout一点代码,改libpe时能少学一点东西。

使用

好了,扯了这么多七里八里的东西,看看到底要怎么用这玩意吧~

1. 编译LibPE,把生成的LibPE.lib(lib方式编译),LibPEDll.lib,LibPE.dll(Dll方式编译)拷贝到你自己的工程目录中去。

2. 开始开心的写代码吧,先来个导入表玩玩:

#include <stdio.h>
#include <tchar.h>

#define LIBPE_DLL
#include "LibPE.h"
using namespace LibPE;
#pragma comment(lib, "LibPEDll.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    LibPEPtr<IPEFile> pFile;
    ParsePEFromDiskFile(L"C:\\Windows\\system32\\kernel32.dll", &pFile);

    // Import Table
    LibPEPtr<IPEImportTable> pImportTable;
    pFile->GetImportTable(&pImportTable);

    printf("Import Table:\n");
    UINT32 nImportModuleCount = pImportTable->GetModuleCount();
    for(UINT32 nImportModuleIndex = 0; nImportModuleIndex < nImportModuleCount; ++nImportModuleIndex) {
        LibPEPtr<IPEImportModule> pImportModule;
        pImportTable->GetModuleByIndex(nImportModuleIndex, &pImportModule);
        printf("Import Module: %s (Bound: %s)\n", pImportModule->GetName(), pImportModule->IsBound() ? "true" : "false");

        for(UINT32 nImportFunctionIndex = 0; nImportFunctionIndex < pImportModule->GetFunctionCount(); ++nImportFunctionIndex) {
            LibPEPtr<IPEImportFunction> pImportFunction;
            pImportModule->GetFunctionByIndex(nImportFunctionIndex, &pImportFunction);
            printf("Import Function: %s\n", pImportFunction->GetName());
        }

        printf("\n");
    }

    return 0;
}

更多的使用方法,大家可以看看工程中的LibPETest,其实这也不是一个Test工程,就是用来大概试一下新的写的东西。

当然,现在LibPE还有很多问题,还有很多需要做的内容没有做。日后再慢慢完善吧~

Posted in 03 Binary Life. Tags: , , . 6 条评论 »

duilib学习笔记

原创文章,转载请注明:转载自Soul Apogee
本文链接地址:duilib学习笔记

前段时间对皮肤引擎比较感兴趣,于是在VS第一人称快的无法直视的dot大神推荐下,看了一个小巧又好用的皮肤引擎:duilib。

1. duilib简介

duilib是一个开源的DirectUI界面库,简洁但是功能强大。而且还是BSD的license,所以即便是在商业上,大家也可以安心使用。
现在大家可以从这个网站获取到他们所有的源码:http://code.google.com/p/duilib/

为了让我们能更简单的了解其机制,我们按照如下顺序一步一步的来对他进行观察:

  1. 工具库:用于支撑整个项目的基础
  2. 控件库:这是dui最关键的部分之一,相信也是大家最关注的部分之一,另外这里也来看看它是如何管理这些控件的
  3. 消息流转:有了控件库,我们需要将Windows窗口的原生消息流转给这些控件,另外在这里也来看看Focus,Capture等等的实现
  4. 资源组织和皮肤加载:有了上面所有的这些,我们再来看看它是如何自动创建皮肤的
  5. 简单使用:最后,来看看到底要如何使用它

以下是duilib工程带的一副总体设计图,在看代码之前看看这幅图,对看代码会很有帮助。
duilib:
duilib

2. 工具库

由于duilib没有对外部的任何库进行依赖,所以在其内部实现了很多用于支撑项目的基础类,这些类分布在Util文件夹中:

  • UI相关:CPoint / CSize / CDuiRect
  • 简单容器:CStdPtrArray / CStdValArray / CStdString / CStdStringPtrMap

上面这些类看名字就基本能够理解其具体的含义了,当然除了基本的基础库,还有一些和窗口使用相关的工具的封装:

  • 窗口工具:WindowImplBase,这个工具我们在这里不详述,后面会再次提到。

3. 控件库

控件库在duilib的实现中被分为了两块:Core和Control:

  • Core中包含的是所有控件公用的部分,里面主要是一些基类和绘制的封装。
  • Control中包含的就是各个不同的控件的行为了。

Core部分和控件相关的类图非常简单:
duilib-core:
duilib-core

3.1. 控件基类:CControlUI

CControlUI在整个控件体系中非常重要,它是所有控件的基类,也是组成控件树的基本元素,控件树中所有的节点都是一个CControlUI。
他基本包括了所有控件公共的属性,如:位置,大小,颜色,是否有焦点,是否被启用,等等等等。当然这个类中还提供了非常多的基础函数,用于重载来实现子控件,如获取控件名称和ClassName,是否显示,等等等等。
另外为了方便从XML中直接解析出控件的各个属性,这个类中还在提供了一个SetAttribute的方法,传入字符串的属性名称和值对特定的属性进行设置,内部其实就是挨个比较字符串去完成的,所以平时使用的时候就还是不要使用的比较好了,因为每个属性实际上都有特定的方法来获取和设置。
另外每个控件中还有几个事件管理的对象——CEventSource,这些对象会在特定的时机被触发,如OnInit,调用其中保存的各个回调函数。

3.1.1. 控件类型转换

这里我们就碰到一个问题,控件树中的每一个节点都是CControlUI,但是其实这些节点可能是文字,可能是图像,也有可能是列表,那么他怎么在这些控件指针之间进行转换呢?
强制转型不是一个好的选择,duilib中使用的是CControlUI::GetInterface,传入一个字符串,传出指向控件的指针。类似于COM的QueryInterface。

LPVOID CControlUI::GetInterface(LPCTSTR pstrName)
{
    if( _tcscmp(pstrName, _T("Control")) == 0 ) return this;
    return NULL;
}

3.2. 容器基类:CContainerUI

有了基本的控件基类之后,我们就需要容器来将他管理起来,这个容器就是CContainerUI,其内部用一个数组来保存所有的CControlUI的对象,后续的所有工作,就都是基于这个对象来进行的了。
这样在CContainerUI里面,主要实现了一下几个功能:

  • 子控件的查找:CContainerUI::FindControl
  • 子控件的生命周期管理:是否销毁(在Remove的时候自动销毁) / 是否延迟销毁(交给CPaintMangerUI去一起销毁)。
  • 滚动条:所有的容器都支持滚动条,在其内部会对键盘和鼠标滚轮事件进行处理(CContainerUI::DoEvent),对其内部所有的元素调整位置,最后在绘制的时候实现滚动的效果
  • 绘制:由于容器中有很多元素,所以为了加快容器的绘制,绘制的时候会获取其真正需要绘制的区域,如果子控件不在此区域中,那么就不予绘制了

3.3. 控件实现

有了普通的基类和容器的基类之后,我们就可以在其之上搭建控件了。其类图大致如下:
duilib-control:
duilib-control

3.3.1. 基本控件

duilib实现了非常多的基本控件,他们分布在Control文件夹下,每一个头文件就是一个控件,主要有:

  • CLabelUI / CTextUI / CEditUI / CRichEditUI
  • CButtonUI / CCheckBoxUI / COptionUI (RadioButton)
  • CScrollBarUI / CProgressUI / CSliderUI
  • CListUI
  • CDateTimeUI / CActiveXUI / CWebBrowserUI

3.3.2. Layout

除了基本控件之外,duilib为了辅助大家对界面元素进行布局,还在中间实现了专门用于Layout的元素:

  • CChildLayoutUI
  • CHorizontalLayoutUI / CVerticalLayoutUI / CTileLayoutUI:纵向排列,横向排列格子排列
  • CTabLayoutUI:Tab

3.3.3. 控件绘制

绘制控件实际上有很多代码都是可以抽取出来的,比如:九宫格拉伸图片,平铺图片等等工作,我们实际上都不需要每次都去重写。所以这部分代码被抽取出来,形成了CRenderEngine,这个类在Core/UIRender下。在这个里面,我们可以看到很多的用于绘制方法。

class UILIB_API CRenderEngine
{
public:
    // ......
    static void DrawLine(HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor);
    static void DrawRect(HDC hDC, const RECT& rc, int nSize, DWORD dwPenColor);
    static void DrawRoundRect(HDC hDC, const RECT& rc, int width, int height, int nSize, DWORD dwPenColor);
    static void DrawText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, \
        DWORD dwTextColor, int iFont, UINT uStyle);
    static void DrawHtmlText(HDC hDC, CPaintManagerUI* pManager, RECT& rc, LPCTSTR pstrText, 
        DWORD dwTextColor, RECT* pLinks, CDuiString* sLinks, int& nLinkRects, UINT uStyle);
    // ......
};

3.4. 控件管理:CPaintManagerUI

当所有这些基本的控件都准备好了之后,我们就只要将这些控件管理起来,这样一个基本的控件库就完成了,而这个管理就是CPaintManagerUI来负责的。
在duilib中,一个Windows的原生窗口和一个CPaintManagerUI一一对应。其主要负责如下几个内容,后面会分开来细说,现在先了解一个概念就行:

  • 控件管理
  • 资源管理
  • 转化并分发Windows原生的窗口消息

为了实现上面这些功能,其中有几个用于管理控件和资源的关键的数据结构:

  • m_pRoot:保存根控件的节点
  • m_mNameHash:保存控件名称Hash和控件对象指针的关系
  • m_mOptionGroup:保存控件相关的Group,这个Group并不是TabOrder,他用于实现Option控件
  • m_aCustomFonts:用来管理字体资源
  • m_mImageHash:用来管理图片资源

这些结构基本都可以看作是一堆列表和Map,这样可以用其来实现控件和资源的管理了。

4. 消息流转

有了控件,现在我们的问题是,如何将原生的窗口消息分发给界面中所有的控件,使其行为和原生的一样呢?

4.1. 窗口基础类:CWindowWnd

在duilib中,用来表示窗口的最基础的类是CWindowWnd,在这个类中实现了如下基本的内容:

  • 原生窗口的创建(CWindowWnd::Create)
  • Subclass(CWindowWnd::Subclass)
  • 最基本的消息处理函数(CWindowWnd::__WndProc)和消息分发(CWindowWnd::HandleMessage)
  • 模态窗口(CWindowWnd::ShowModal)

duilib通过这个类,将原生窗口的消息分发给其派生类,最后传给整个控件体系。另外在duilib中,需要进行消息处理的基本控件,都是从这个类继承出来的。

4.2. 消息分发

一旦我们使用CWindowWnd类创建了窗口之后,消息就会通过CWindowWnd::HandleMessage进行分发,我们可以和WTL等其他的库一样,在此对原始的窗口消息进行处理。

LRESULT CWindowWnd::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    return ::CallWindowProc(m_OldWndProc, m_hWnd, uMsg, wParam, lParam);
}

当然如果我们觉得这样麻烦,我们也可以使用CPaintManagerUI来对其进行默认处理。我们上面提到CPaintManagerUI还会对所有的控件进行管理,这样,消息就传递给了窗口内部特定的控件了。
这些默认处理集中在CPaintManagerUI::MessageHandler()中,其内部会对很多窗口消息进行处理,并将其分发到对应的控件上去,比如对WM_LBUTTONDOWN的处理。

case WM_LBUTTONDOWN:
    {
        // ......
        POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
        m_ptLastMousePos = pt;
        CControlUI* pControl = FindControl(pt);
        // ......
        TEventUI event = { 0 };
        event.Type = UIEVENT_BUTTONDOWN;
        // ......
        pControl->Event(event);
    }
    break;

4.2.1. Focus & Capture

通过上面这个最简单的例子,我们基本可以猜到duilib对Focus和Capture的处理方法了:用一个成员变量保存对应的控件,在消息到达时直接转发消息。
在CPaintMainagerUI中,大家可以找到一个成员变量:m_pFocus,这个就是用来保存焦点控件的。在WM_KEYDOWN等键盘消息发生时,duilib就会模拟Windows行为,将消息直接转给当前Focus的控件。

case WM_KEYDOWN:
    {
        if( m_pFocus == NULL ) break;
        TEventUI event = { 0 };
        event.Type = UIEVENT_KEYDOWN;
        // ...
        m_pFocus->Event(event);
        // ...
    }
    break;

但是很奇怪的是,duilib里面并没有对Capture做处理,分发鼠标消息到对应的子控件上,可能是还没有完善的原因。

4.2.2. 其他消息分发方式

除了Event以外,CPaintManagerUI还提供了其他几种用于处理消息的方法:

  • Notifier:在窗口上处理一些控件的逻辑,可以将其看成和WM_NOTIFY差不多的功能
  • PreMessageFilter:消息预处理,这个大家肯定不陌生了。
  • PostPaint:绘制后的回调
  • TranslateAccelerator:快捷键的处理

这里需要注意的是:PreMessageFilter和TranslateAccelerator是通过全局数组来实现的,这并不符合多线程的窗口编程要求,所以duilib对多线程的支持并不是很好!

4.3. WindowImplBase

为了简化duilib的使用,库中提供了一个非常方便的工具:WindowImplBase。
这个类将常用的功能封装在其内部,比如Notifier和PreMessageFilter,并在其中提供了各种默认的虚回调函数,供派生类重载。通过这个类,我们可以非常方便的来实现一个简单的界面。

class UILIB_API WindowImplBase
	: public CWindowWnd
	, public CNotifyPump
	, public INotifyUI
	, public IMessageFilterUI
	, public IDialogBuilderCallback
{
	// ......
	virtual UINT GetClassStyle() const;
	// ......
	virtual LRESULT OnClose(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
	virtual LRESULT OnDestroy(UINT /*uMsg*/, WPARAM /*wParam*/, LPARAM /*lParam*/, BOOL& bHandled);
	// ......

5. 资源组织和皮肤加载

好了,现在我们已经有了控件管理和控件库,现在我们需要让UI框架来帮忙组织这些资源,并且自动的来帮我们创建皮肤,减少我们的开发量。
duilib中的皮肤文件主要有几个部分组成:

  • xml描述文件:描述窗口中控件的布局和样式
  • 各种资源如图片

我们把这些资源放在一个文件夹中,这样就形成了基础的皮肤包。当然我们还可以将其组合成一个zip包,从而加快IO访问,但是修改起来就会相对麻烦。所以我们可以在debug中使用前者,而在release中使用后者。
我们可以在bin\skin下面找到duilib中自带demo的所有的皮肤包。

皮肤中,最关键的部分就是这个xml描述文件了,一个xml描述文件对应着一个窗口的信息,如:控件的类型和样式等等。为了有一个直观的印象,我截取了duilib中ListDemo的xml描述文件的一部分放在这里:

<?xml version="1.0" encoding="utf-8"?>
<Window caption="0,0,0,30" roundcorner="5,5,5,5" sizebox="4,4,4,4" mininfo="600,320" showdirty="true">
...
<VerticalLayout bkimage="file='bg.png' corner='10,100,10,10' hole='true'" bkcolor="#FF313C00">
...
	<HorizontalLayout height="35" inset="0,4,0,8">
		<VerticalLayout inset="8,4,2,2" width="80">
			<Text text="Domain/ip:" textcolor="#000000" font="1"></Text>
		</VerticalLayout>
		<VerticalLayout>
			<Edit height="23" text="List控件添加使用案例,每行可以响应事件" bordercolor="#C6CFD8" name="input" bkimage="file='search_bg.png' source='0,0,258,23' corner='1,1,1,1'"/>
		</VerticalLayout>
		<VerticalLayout width="80">
			<Button name="btn" text="Search" font="0" float="true" pos="5,0,63,23" maxwidth="63" maxheight="23" normalimage="file='button.png' source='0,0,63,23'" hotimage="file='button.png' source='0,23,63,46'" pushedimage="file='button.png' source='0,23,63,46'"/>
		</VerticalLayout>
	</HorizontalLayout>
...
</VerticalLayout>
</Window>

为了通过配置文件自动创建皮肤,duilib提供了一个类:CDialogBuilder(DuiLib\Core\UIDlgBuilder.h)。
这个类提供了从皮肤包(文件夹和zip格式)中的xml中创建皮肤的方法:CDialogBuilder::Create。内部实际上就是一个xml的解析,依次创建各式控件。
除了创建控件,这个类还将一些可以复用的资源提取出来放入CPaintManagerUI中统一管理,如字体和图片等等。

6. 简单使用

由于项目里面实在是带了太多太多的demo,而且在duilib的工程中,还有一个doc的目录,里面也非常详细的描述了要如何使用duilib来创建一个简单的工程
所以关于duilib的简单使用,这里就不再详述了,这里就只列出GameDemo的main函数,这个函数非常的简单,但是已经基本可以表达了。

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE /*hPrevInstance*/, LPSTR /*lpCmdLine*/, int nCmdShow)
{
    CPaintManagerUI::SetInstance(hInstance);
    CPaintManagerUI::SetResourcePath(CPaintManagerUI::GetInstancePath() + _T("skin"));
    CPaintManagerUI::SetResourceZip(_T("GameRes.zip"));

    HRESULT Hr = ::CoInitialize(NULL);
    if( FAILED(Hr) ) return 0;

    CGameFrameWnd* pFrame = new CGameFrameWnd();
    if( pFrame == NULL ) return 0;
    pFrame->Create(NULL, _T(""), UI_WNDSTYLE_FRAME, 0L, 0, 0, 1024, 738);
    pFrame->CenterWindow();
    ::ShowWindow(*pFrame, SW_SHOWMAXIMIZED);

    CPaintManagerUI::MessageLoop();

    ::CoUninitialize();
    return 0;
}

7. 总结

总的来说,duilib还是一个很小巧好用的皮肤引擎的,但是他仍然有其不好的地方:对多线程的支持不好,不支持动画。但是无论如何,它还是不错的,所以如果你已经看到了这里,那么接下来跑到vs里面建一个工程,玩一把才是正经事~

Posted in 03 Binary Life. Tags: , , . 没有评论 »

一步走下去,后面想回头就难了

最近工作上发生了一些事情,可能不久之后就要换工作了,前段时间由于项目压力和忙于各种面试啥的,也没有时间来写博客,就这样好几篇博文都被压着没有时间写,好几个开源项目也被压着没有时间看,等反应过来的时候,博客都已经长草了,上一篇博客距离现在已经三个月了。

话说回来,找工作真的是一件非常头疼的事情,感觉一步走错,之后N年都毁了。前些时间也很迷茫,是不是应该继续在Windows这个诡异的平台上继续学习下去呢?

  • 首先,目前来看,Windows在移动端上目前看来发展并不好。
  • 其次,天天面对一堆黑盒,感觉做任何事情都有点束手束脚。
  • 最后,很多时候(经常是大部分时间)开发的任务并不是为了解决一个问题,而是在修正各种神奇的细节,里面不少是Windows本身的Bug。

但是最后,我还是选择了留在这个平台上,刚到一个平台三年,如果就选择放弃,东搞搞西搞搞也不是个法子,了解不到底层的知识。这真是一步走下去,后面再想回头就难了。Windows啊Windows,趁着iphone开始变萎,安卓各种缓慢的时候,你的平板要做好点啊,各种资源都这么匮乏,你要怎么保持开发者们对你的激情呢?

Posted in 01 My Soul Apogee. Tags: . 10 条评论 »

近期随想:科技发展的终结

最近看了一些视频,世界进步的真是好快。

希拉・尼伦伯格:用假体眼睛治疗失明 [Link]

Google Glass [Link]

Anthony Atala:三维细胞打印机打印肾脏 [Link]

看到这里,我不由得开始乱想,现在的科技已经发展到了现在这种程度,学科的分支已经如此的多,想学通多个分支已经相当的难。那到底什么时候才是科技发展的尽头呢?是不是很幼稚?:D,不要当真,乱想嘛。

1. 科技发展的基石:学习

人类通过学习传承自己的科技,而学习的方法受制于人类本身感知世界的方法——触觉,听觉和视觉。

1.1 触觉

对于学习大量知识来说,触觉应该是最不直观的了,现在使用触觉来学习最常用的方式就是布莱叶盲文体系了。首先抛开写成盲文的资料本就不多,我们先直接看看盲文本身吧。盲文的构成很容易理解,一个盲文字母使用6个点来表示,每个点有两个状态,平坦和凸点,也就是:0和1。也就是说,一个盲文字母可以表示2^6 = 64种不同的状态。这些状态用来表示基本的英文字母和数字已经基本足够。而如果再加上大小写和符号,则开始显得有些吃力了。于是还加入了专门用于切换大小写的盲文,专门用于切换英文和数字的盲文,用来增加更多的状态。在这套体系下,学习的速度就和触摸和理解的速度有关系了,但是即便一秒钟摸五个字母,我们学习的速度,也只有4Bytes/s。啊!还真是小啊。难道我们就不能改进他么?当然可以,那就是二级布莱叶盲文。任何一个盲文字母都可以表示一个缩写,如w表示will。我们现在主要的盲文出版物基本都是基于这个体系上的,这样,我们的学习速度提升到了12-20Bytes/s,但是貌似还是没有质的飞跃。那我们还能不能创造三级布莱叶盲文呢?这里有一个问题就不得不考虑了:越复杂的编码,会导致信息本身越难解码。而且二级盲文已经开始存在歧义,需要读完一个单词之后才能真正理解其含义,三级盲文,肯定歧义更大,更难理解。

1.2 听觉

接下来是听觉,从听觉开始,信息已经不是那么的简简单单,声音不仅仅承载了基本的信息,他本身还具有四要素:音质、音长、音强和音高。所以我们只好分开来思考声音这个媒介了:信息和声音本身。

声音传递的信息量是有限的,基本就是有多少个音,就承载了多少信息。如果不考虑人对声音的接受能力,人类的语速极限是在250-300bpm左右(点此看看245bpm说话的感觉吧,1分多钟的时候开始),而语言的基础——音素,基本都是在100种以内,比如英语就只有48个音素,其中元音20个,辅音28个。如果一个音素表示一个字母数字或符号,那么,声音能传递的信息的速度大概是4-5Bytes/s,居然比盲文慢这么多,真是难以至信!难道我们就不能和盲文一样,将我们要传递的信息进行压缩么?当然可以,方法就是音节。多个音素很快的连在一起读,就成了一个音节。音节能将我们的信息压缩1-2倍,这样,信息的传输速度就被提升至了8-15Bytes/s。终于和盲文差不多了,但是这看着还是很少啊。没有关系,我们还能继续压缩:一个音素表示用多个字符。这在英语中很常见,如[i:]可以表示ee。这样又能将信息压缩1-2倍,这样,传输速度终于超过了盲文:16-45Bytes/s。

我们再来看看声音本身,如果我们需要完完整整的记录下声音本身,而不是里面的语言,比如:音乐。这样我们吸收信息的速度会是多少呢?现在我们先一起来看看要如何来表示一个声音。表示声音一般有两种方式:通过记录声音的四个要素来表示声音,或者通过记录声音完整的震动波形来记录声音。由于我们的记忆肯定记不住完整的波形,一般记下来的都是什么物体发出什么声音,所以我们这里只看前者这种表示方式。

首先看音质,音质分两种,噪音和乐音,其实指的是振动的频率是否规则。比如锣,就没有规律,他属于噪音。但是不管是哪一种类型,我们可以使用一个类型来表示不同类型的物体发出来的声音,计算机中一般大家都会使用一个4字节来表示,他能表示4294967296种不同的声音来源,这看上去应该足够了。
接下来是音长,音长比较好办,记录一个时间即可,一般计算机中使用4-8字节表示时间,我们算8个。
然后是音强,音强其实就是声音的大小,单位是分贝,由于150分贝就造成人体鼓膜破损导致耳聋,另外再考虑浮点数,那么我们使用4个字节即可表示所有的音强了。
最后是音高,音高是声音的频率,我们只考虑人能够听见的声音频率,20至20000Hz,因为只有这些对我们来说才是有意义的。那么我们可以使用2个字节来表示。最后我们发现一个声音需要由18个字节来表示。其实midi格式的音乐文件就是使用的和这个类似的表示方法。
好,现在声音的传输速度,就和我们听到的音的速度有关系了,大家可以听一听999bpm的野蜂飞舞,看看能听清楚么?我们就算1000bpm吧,这样信息的传递速度是:300Bytes/s!速度有了质的飞跃!真不错!
声音还有一个别的感官没有的特性:重叠。多种不同的旋律可以重叠在一起,每种各自作为一个单独的音轨独立存在。在钢琴中,巴赫的平均律已经算是很难理解的复调作品了,最多也就是5个声部(试一试BWV846的赋格部分,这一段是四声部的)。多数室内乐一般也就是四重奏和五重奏,极限也就是九重奏,而且这类的作品非常非常的少。我们对这种可以被认知的叠加个数取极值:9个。这样,我们传递信息的速度又有了新的突破:2.7KBytes/s。居然达到了K级别!虽然很少有人能达到这个级别的学习速度,因为这种人基本可以听一边九重奏,就可以完整的将其复述出来,这不是天才是什么!不过还是突然觉得学东西原来可以这么快!

1.3 视觉

最后是视觉,视觉是人类最直观的接收知识的方法,我们从小的学习基本都离不开视觉,书本,黑板等等,直到现在周围有很多同事在说明一个问题的时候,都会不自主的想要画个图来表示他想表达的问题,或者是事物之间的联系。但是想要计算视觉承载的信息量,似乎就很麻烦了,因为通过视觉认知到的东西似乎有很多,比如阅读到的书本的内容或者对一些事情的记忆片段,需要表示这些不同的内容所需要的信息量也是不同的。我们这里简单的分为两种吧:文字和图像。

文字,也就是阅读,其信息的传输速度就是每个人的阅读速度。现在,人类已知的阅读速度的极限大概在150000字/分钟,也就是2500字/秒,听上去是那么的不可思议,不知道是真是假。反正既然我们大多数人都无法超越这个标准,那么我们就用这个标准做极值的计算吧。在这个阅读速度下,那么信息传递速度是12.5KBytes/s(一个字约为5个字母),轻而易举的超越了听觉!写到这,连笔者自己也很诧异!要是有这速度,《Windows核心编程》这本看的我要死的书,岂不是分分钟就看完了?所以我们还是来看看普通人吧。人类的平均阅读速度是在200-300字/分钟左右,这速度就慢很多了,在这个速度下,信息传递速度只有16-25Bytes/s。看起来似乎比听觉要差,但是实际上已经好很多了,因为这是一个平均值,每个人都可以达到。

再来看看图像,人类对于图像有着相当强悍的识别能力,人眼可以分辨出1000万种以上不同的色彩,一般我们使用RGB空间来表示色彩:他使用3个字节,每个字节表示一个颜色:红,绿,蓝,每种颜色有256种状态,一共可以表示1600多万种不同的色彩。而人眼能接收的有效的像素值也可以达到3.24亿个像素。那么要表示一副我们看到的画面,实际上我们需要926.97M的数据!现在市面上一般的视频帧率基本是在每秒25-30帧左右,也就是说,每秒钟流经人眼的数据大概有22-27G!写到这里,我不知道要说什么好了,这数据太恐怖了。我只想说人脑的处理能力真是太惊人了,另外还好我们不会真正记住那么多东西

总的来说,视觉应该算是我们接收知识最快的途径了。

1.4 科技发展的终点

终于,我们貌似有一些方法能很快的学习了。可是我们的大脑到底能承受多少呢?虽然我不是很清楚,但是如果人类的学习能力已经无法超越任何单独一门学科分支的内容,科技的发展就到了尽头了。

2. 终有一天,我们要如何继续

学习,最后的问题还是如何学习。如果我们不能改变自己学习的方式,我们就无法改变我们科技发展的极限。

这里有一个比较有意思的视频,通过改造大脑,通过光来对生物进行控制,不知道以后这项技术会如何发展,说不定能直接将知识灌入,那这样学习的成本就小了。

Gero Miesenboeck:改造大脑 [Link]

另外,我们能不能也让知识能遗传呢?所有的动物都有着很多本能,这些本能实际上应该就属于可遗传的知识,如果能让自己的知识遗传给下一代,那下一代学习的成本就真的小了很多:啊!为师已经将毕生80年的功力都传授与你,你可以下山了!

3. 结尾

看到这里,你应该会觉得我已经疯了。嗯,你是对的,我也觉得突然想这些东西不会正常到哪里去。

《程序员修炼之道》读书笔记

原创文章,转载请注明:转载自Soul Apogee
本文链接地址:《程序员修炼之道》读书笔记

前段时间读了《程序员修炼之道》,读之前以为这本书估计基本是在重复《Unix编程艺术》,《编程珠玑》,《重构》等等里面的各种思想,结果我发现我错了,这本书完全不是那么一回事。其他的书说的只是代码或工程的哲学,而这本书除了讲述工程方面的哲学之外,还在讲述程序员应该如何规划自己的人生。看完之后确实是受益匪浅,越看到后面,越觉得这世间的万事万物其实是相通的。事物的表现或许不同,但是背后的规律却是如此的相似。

1. 修行啊,属于程序员的修行

不知道大家有没有这样的感觉,时间总是那么的不够用,想要学的东西太多,学习的速度太慢,有时候还不一定有激情。我们没有龙珠里面的时间与精神的小屋,也没有岛国的时间停止器,修行是一辈子的事情,而且有时候没有想象中的那么简单。

1.1 重中之重:注重实效

这四个字是书中最关键的几个字,在书中频繁出现,仿佛作者写这本书就只是想将这四个字潜移默化给大家一样。方法和过程都因人而异,但是只有注重实效是永远不变的。

1.2 投资并管理好你的知识

曾经有一个同事F对我说:“一个技术人员最不可或缺的是什么?技术。如果没有技术的,那其他的东西,如人际,都没有意义,因为那些都只是加分而已。”。这句话我觉得说的很对。所以,一个技术人员在修炼自己之前,首先需要有一个意识:你的知识就是你的资产,学会如何经营他。在这本书里面,作者给出了如下几个建议:

  • 定期评估已有资产,并进行投资
    定期的回顾你已经学习到的东西,并找出其中你遗忘的或者你想学的,以后会有用的内容,进行学习。说不定,以后这些知识就真的能够帮到你。
    书中推荐的几个目标是:

    • 每年学习一个新的语言,从新语言中学习新的解决问题的思想。
    • 每季度看完一本技术书籍,专业领域当然不能拉下。
    • 当然也要看看非技术书籍,和你打交道的他们都是人,不要什么时候都纯技术。
  • 多元化
    多元化投资是长期成功的关键,谁也不知道以后的市场会有怎样的突变,偶尔去看看别的方面,对自己是有好处的。也许你现在很熟悉C++了,那么尝试着去看看动态语言吧。如果你已经做了两三年Windows上的开发了,那么去看看Unix吧。分散的投资到最后总会有一处会收到回报。
    注意,这并不是让大家乱学知识,什么都不深,看着这段时间语言排行榜一变,就一股脑钻进去了。多元化的投资的前提是,你已经有一个擅长的专长或领域了。
  • 平衡风险和回报
    学习和投资一样,有风险有回报,管理好学习的风险,不要学了很久,也许带来不了任何回报,开阔眼界是好的,但是也要注意,你的时间是有限的。不要瞎忙活。
  • 主动联系大神们,向他们学习
    互联网带来的最大的好处就是将所有人和事物的关系都拉近了,主动的寻找你这个方向的大神,并向他们请教,也许他们不会马上回复你,组织好自己的问题,耐心一点,礼貌一点,总会有结果的。

1.3 交流和沟通

虽然我们每天处理的都是代码,但是无可避免的和我们打交道的还是人,所以人和人之间的沟通其实是很重要的。在书中给了几点关于建议:

  • 知道你想要说什么,了解你的听众并让文档美观
    在和其他人交流之前,记得提前整理好自己的想要说明的问题,并且了解你的听众,不然就容易失去重点,有一茬没一茬,最后你和对方都不知道想聊的内容是什么。在整理谈话内容的时候,有一些简单的可供参考的标准,书中称为WISDOM离合诗:

    • 你想让他们学到什么?
    • 他们对你讲的什么感兴趣?
    • 他们有多富有经验?
    • 他们想要多少细节?
    • 你想要让谁拥有这些信息?
    • 你如何组是他们听你说话?
  • 一些沟通的技巧
    沟通是需要技巧的,书中说了如下几种建议,他们实在是太实在了,所以基本都不用展开赘述了,大家肯定也能明白其中的意思。

    • 选择一个好的时机开启话题
    • 让听众参与
    • 做倾听者
    • 回复他人

1.4 警惕无处不在的马太效应

在一个项目中,由于版本压力,时间不足等等的问题,有些较为复杂的问题又需要尽快的解决,于是山寨的方法就出现了,打破框架的限制,打破软件的分层,简单粗暴的解决掉这个问题。一扇破窗就形成了,慢慢的破窗越积越多,只到某天出现了这样一段对话:

—— 你觉得我们现在项目存在什么问题吗?
—— 我觉得我们现在项目有点XX问题,blah,blah……另外,还有一些这样的问题,blah,blah……
—— 那你觉得这个会对我们影响很大么?/ 那你觉得我们有必要现在停下来调整一下么?

这个时候迫于KPI等等的压力,不是每个团队都会停下来进行调整,要是完不成任务怎么办呢?于是答案就出来了:

—— 我觉得其实还好,后期重构就好,现在还是版本比较重要。 /  我觉得其实还好,敏捷开发嘛,先把功能出来,后面再慢慢的完善。

扪心自问一下,当你写出你自己都觉得恶心的,难以理解的代码,后期还会有谁想来接手这一摊子事情,来重构这块代码的基本只有你自己。而你自己肯定还会被KPI继续压着,做更多别的事情,直到整个架构崩坏。

“凡有的,还要加给他叫他多余;没有的,连他所有的也要夺过来。”
坏的代码必然越来越坏,不要抱有对其会突然变好的妄想, 出现问题,一定要想一想是不是要停下来去重构一下。在《重构》一书中有一句话我觉得很好:一而再,再而三,三而重构。
相对的,好的代码将更容易保持其好的状态,就如同你去上流的地方吃饭一样,悠扬的音乐一响起来,你吃饭的速度自然就慢下来了。而如果是在食堂吃饭……

也许你会奇怪,这难道不是应该放在之后的如何完成项目中么?问题就在于这还仅仅是马太效应的冰山一角,可怕的事情即将来临。

  • 出了问题,之后重构吧。再出了问题,再之后重构吧。最后永不重构。
  • 今天版本忙,不去运动了吧。第二周又重复第一周。最后永不运动。
  • 写博客,今天好累啊,明天写吧。明天好累啊,后天写吧。最后永不写博。
  • 今年的推广指标/投入资金用不完,明年就会变少。今年的用完了,还吵着说不够,明年就会更多。面对这种情况,有一种粗暴的解决方案
  • 还有一个好玩的:今天任务做不完,加班吧。明天任务做不完,加班吧。最后天天加班。(总是有新的任务来,美名:能者多劳)
  • ……

到现在为止,你中了几枪?所以不要像温水煮青蛙一样,习惯一个环境,就不去改变,直到最后被你熟悉的环境所毁灭。

2. 如何完成项目

当然,除了对人生规划的建议以外,这本书里面也有不少对项目和编码的建议。有一些在其他的书中也有提及,比如真理的唯一性,测试的重要性,等等等等,但是有几个部分,作者可谓是不厌其烦,一再提及。

2.1 正交性,DRY原则和解耦合

正交性是一个相当重要的概念,它在《Unix编程艺术》这本书中被特意提了出来,当作是重构的本质。但是其实这个概念本身非常简单,就是修改一个模块,不会对其他模块产生影响。说的直白一点就是一个模块只做一件事情,不要让模块承担过多的工作。不然修改了一个功能,就会很容易对另外一个功能产生影响。随着项目越来越大,到后期如果碰到问题,则很难找到合适的解决方法,而且大家会越来越不敢修改代码,因为谁也不知道修改了这块代码会导致哪些地方出问题。相反的,遵守正交性可以为项目带来的很多的好处:

  • 促进模块的复用
    一个模块越简单,那么他就越容易被理解和复用,正交的代码就是这样。这个思想被广泛的用于Unix的环境中,导致了现在Unix的工具链+管道的开发习惯。项目中也是一样,小巧锐利的工具,大家都喜欢。想想,平时在工作之中,是不是有同事常问你:“哥们,我们的代码里面有用来干XX事情的模块么?”。
  • 使模块变得更好修改
    这个结论似乎是那么的好理解,你是愿意在一滩逻辑泥沼中摸索它对各个模块的影响呢,还是愿意修改影响显而易见的代码呢?
  • 使模块易于测试
    相比无所不能的模块,一个只干一件事情的模块肯定更容易理解,并且具有更少的依赖,所以不会担心当需要为这个模块编写白盒测试时,要写一大堆的测试用例才能达到一定的效果,或者是要调整半天工程依赖才能进行正常的编译。

正交性有一个好朋友,那就是常被提及的DRY原则——Don’t Repeat Yourself,在《Unix编程艺术》中又被称为SPOT原则——真理的单点性。说的直白一点就是:只有一个模块做这一件事情。遵守DRY原则带来的好处就是可以避免散弹式修改(Shotgun Surgery),而最常见的场景就是魔数,在《重构》一书中还专门探讨了如何来解决这种代码的坏味道,其实本质上就是DRY原则。

但是在想要实现正交性,却不是那么的容易。如何来判断一段代码是不是正交的呢?如果发现其不是正交的,如何将其修改为正交的实现呢?
这个问题在书中给了一个简单易用的答案——解耦合。这个方法说起来似乎还是有点飘渺,因为什么样的代码才算是耦合严重呢?书中给出了一个方便快捷的判断标准:得莫忒尔法则。

函数的得莫忒耳法则规定,某个对象的任何方法都应该只调用属于以下情形的方法。

  • 它自身
  • 传入该方法的任何参数
  • 他创建的任何对象
  • 任何直接持有的组件对象

通过这个法则编写的代码,一般都会更好的实践上面提到的两个原则,从而实现错误更少的代码。因为如果n个对象都互相了解,那么一个对象的改动就可能导致其他n – 1个对象都需要改动。当然,如果一直遵循这个法则,可能会出现大量的包装方法,从而导致效率的损耗。所以,如果出现了效率问题,那么就应该要逆着这个法则来做优化。法则并不是死的,还是要根据实际情况来做决定,优化也不要太早,盲目的优化必然带来更加复杂的实现和难以维护的代码。

2.2 自动化

懒惰是人类的天性,谁也不想自己一年工作时间,一年半的工作经验,面对经常重复的工作,自动化就是对付它们的一件大杀器。
automation:
automation

面对自动化,你肯定不会陌生。自动化的好处是不言而喻的,在项目中,我们利用它来帮助我们跟进开发进度并尽早发现问题,如:每天都进行的dailybuild,每天都跑的白盒测试和黑盒测试,每天根据新的代码生成新的接口文档。这些事情大家肯定接触的不少,所以这里我想提的是自动化的反面。

书中使用了无所不在来描述自动化这件事情,甚至提出了一切都要自动化的概念,当然我相信这个的针对可重复的事情而言的,如流程等等。但是不要忘记了,上面这条曲线还是有手动来的更快捷的部分。也不要想着将一件事情自动化了就是上流的表现,因为有可能这件事情你将永远都不会重复它,这样你付出的将比你得到的要多的多,所以要谨记第一原则还是注重实效。还是那句话,一而再,再而三,三而重构。同样一件重复的事情,不要让自己做三遍,如果做了三遍,那么就把他自动化吧,因为它极有可能是你要经常重复的工作。

2.3 弄清楚自己在做什么,原型还是初号机

很多项目开始的时候,或者很多特性在规划的时候,第一步就是需求分析。但是有的时候,就连开发团队自己都弄不清楚这个特性到底是要干什么。他们当然弄不清楚,因为他们不是用户,真正的需求只有用户才知道。不过可惜的是,你不一定有机会能和用户大量的沟通,用户也不一定能把他的想法表述的那么真真切切,没有歧义。这样在开发的时候就会碰到一个问题:我现在写的代码,后面到底还有没有用呢?
programmers-06-need:
programmers-06-need

这个问题相信不少人都遇见过,而我遇到的最通常的解决方法是,写一个非常复杂的模块,可以适应各种环境,产生各种变化,用户要是不是要的现在这种,稍稍配置一下,它就变成另外一种。这种程序开发和维护成本都很高,而且更加杯具的事情是:计划赶不上变化。也许收到的用户反馈或者产品建议是:完全用另外一种方法来实现吧。笔者就遇到过这样的问题,那感觉,悲催的。而笔者的一位同事则更加的惨,整整半年都泡在来回折腾的需求里面,需求不是你想改,想改就能改,现在又要拍脑袋让我改回来。碰到这种情况,是个人都想早日的解脱出来,可惜程序员们都有着不同程度的代码洁癖,这点着实要命。说了这么多废话,其实就是想说,一定要想清楚现在你做的东西到底是什么,只是一个原型给大家尝尝鲜,过过瘾呢?还是一个真正的产品,最终要发布出去。这完全是两码事情。

书中针对这种需求不明的开发,提出了两种解决方案:原型和曳光弹。

  • 原型
    原型开发有一个非常重要的前提:你写的这段代码,将一定会被遗弃!如果不满足这个条件,请不要使用这种方法,否则会出现很悲惨的事情。既然是一个原型,那么我们可以考虑使用更加简单的方法来实现他。比如C#或者java?或者使用一些项目不建议使用的库来简化开发?甚至直接用黑板,将你的想法画出来?这样你不但能很快让大家看到一个基本的效果,还能减少不少的开发量。在原型中也不要担心效率问题,实现的优不优雅等等因素,因为请谨记:你的这段代码将被丢弃。所以请放心大胆的在上面实验你的各种想法吧。
  • 曳光弹
    一旦使用原型找到合适的方向了之后,我们就可以将原型抛弃而转入曳光弹的开发了,这也是这两种开发模式本质的区别。在曳光弹开发时,你可以为产品或特性想一个较为合适的实现了,搭建一个足已支撑项目的框架,然后开始慢慢进入正常的产品开发迭代:完成一定的功能,交付使用,根据反馈再继续修改这部分功能……直到最终成型并交付。

2.4 让别人有兴趣加入你

程序员的工作很多时候是属于劳动力密集型的,我这么说也许你会不开心,但是姑且先听我解释。每个开发人员的开发能力是基本恒定的,比如:平均每天提交代码的数量。一般来说这个数值会维持在400左右,当然这个数据每个人会不同,而且还和项目阶段相关,我们这里不详细讨论。而一个项目,有时候会多达几十万行代码,也就是说,一个项目需要几个人年。现在世界变化这么快,几年过去,这个世界已然换了一个模样了。所以你的工作一定会需要同伴来帮忙,而一定程度上同伴越多,开发速度就会越快。所以这也就是为什么一个开发团队总是想招人的原因了,想快,那就得招人,没办法。

(这里需要注明一下:平均每日提交代码数,这只是反应一个人开发能力的很小的一方面,有可能有个人平均每天都提交2k行,但是却各种重复,所以请不要把这个看成衡量程序员能力的标准。)

所以,让别人有兴趣加入你的工作很重要。

这里其实是书中介绍的一个小的Trick了,听上去很腹黑,但是它却十分管用。那就是:在项目中,需要不断的给大家新鲜的东西,去刺激大家来支持你。也许这个功能能加一个这样的特性,效果会好很多,但是我需要更多的人/我需要时间/我需要更多来自后台的支持,等等。但是说实话这招能有多大的用,就只能看个人的忽悠能力了。你可以将它吹的天花乱坠,但是事情的关键,也是难点是:你要不停的给其他人新鲜感,不要一下把大招全放完了。

2.5 文本化的魅力

文本化已经有太多太多的书提到过了,看过《Unix编程艺术》的朋友肯定影响非常深刻。文本化,DDD(Data Driven Development),blah…blah…blah…,耳朵都起茧了。但是和《Unix编程艺术》一样,这本书花费了整整一个大章——基本工具,来阐述纯文本带来的好处,因为他实在太有好处了。

纯文本带来的好处有三个:易懂,易修改和版本控制。前两个特性导致了DDD的产生。而最后一个特性就霸气了,所有需要长时间来维护的,会多次改动的工作,都可以使用到版本控制。版本控制可以使得整个文档的修改易于追踪,而相比起来二进制文件就是无限的悲催了,根据这个你想想你的身上有没有可以利用版本控制来管理的东西呢?

 

……………… 让我们为思考留出一点时间 ………………

 

现在让我们来简单的列举几样吧:

3. 结语

看完这本书之后,我感觉,现在的我,感受的东西实在肤浅,如果再过个两年回头看这本书,肯定又会感悟到不同的东西。总的来说这本书确实相当不错,如果各位看到这,能被我激起一些兴趣去读这本书,就太好了。

现在回头再看这本书才发现,这还真是一本现实的书啊。

日本之行

这几天旅游去了一次日本,跟着旅行团,几天之内晃悠了大半个日本,所以路上非常的紧张,而且还很不幸的遇上了台风,导致风景自然就没有那么好看了,不过这一趟还是不虚此行的。

第一天,我们在成田下了飞机到了酒店,刚到酒店,台风就登陆了,雨开始哗啦啦的下了起来,但是即便这样也挡不住我们一群人想要撒欢的心。下午自由活动的时候,我们自己坐了1个半小时的地铁到了东京塔,登上塔上的时候,天已经黑了,正好在上面俯瞰东京的夜景。东京塔上是一个很浪漫的地方,特别是在晚上,要是带上MM一起去,真是一件非常舒服的事情,可惜这次我没有,感觉真是格外的寂寞。
japan-tokyo-tower:
japan-tokyo-tower

当然,在东京塔上看台风也是有一番别样的感觉,那种冲击的感觉,实在是相当刺激。听说最顶层的眺望台还会被风吹得晃动,幅度可以达到1m!

疯狂了一天之后,第二天,天居然放晴了!日本的台风果然是来的快去的更快!日本的小街道真的是很有感觉,虽然不知道是什么感觉,但是就是觉得很舒服。
japan-naritasan:
japan-naritasan

第二天就去了成田的新胜寺。这里面供奉的是不动明王,虽然也不知道是谁,但是感觉很厉害的样子。
japan-Shinsho-ji:
japan-Shinsho-ji

接下来的一天相当紧张,我们去了皇居,秋叶原,浅草,新宿,最后,我们到了银座。每个地方呆的时间都不长,实在觉得相当遗憾,意犹未尽。

我也不知道这个是哪里,路上
japan-tokyo-street:
japan-tokyo-street

皇居前面的楠木正成,可以看看wiki上和百度百科上对楠木正成的解释,寿限无寿限无扔屎机……&^&*@^*@&#^*@….军神丸,很有意思。皇居旁边还有很多松树,这些都是当时将军们为了表示对天皇的忠心,送给天皇的,一人一株。
japan-kusunoki-masashige:
japan-kusunoki-masashige

这个雕像前面就是二重桥了,具体为什么会来这里呢,有一种说法是说在1945年8月15日,日本天皇就是在二重桥宣布日本战败并无条件投降的,所以中国来的旅游团喜欢将这里定为必游项目。
japan-tokyo-imperial-palace:
japan-tokyo-imperial-palace

秋叶原,各种CD DVD,女仆咖啡店
japan-akihabara:
japan-akihabara

浅草观音寺,大门外面有相当多的小玩意买
japan-senso-ji:
japan-senso-ji

银座7丁目,街上全是奢侈品,我等屌丝看不懂
japan-ginza:
japan-ginza

当然很多东西和当时我们想的不太一样,比如大家并不能自由的出入皇居,不像白宫和五角大楼;比如秋叶原并不是遍地的女仆,电器还是占大多数;比如日本买电器并没有比国内要低很多,没有办法,因为他们本来就挣的更多,等等等等。

第三天开始,就是各种辗转了,之后的几天基本都是在车上,二三个小时的车程到景点,之后能下车看个几十分钟。

富士山,但是由于台风的影响,山基本都被云挡住了,实在是杯具。在富士山五合目的地方有一个邮局,寄一个明信片回去还是比较有纪念意义的。
japan-mount-fuji:
japan-mount-fuji

平和公园,舍利白塔,这个地方供奉着释迦摩尼的舍利子,相当的平静,仙风道骨,旁边的狮子是各个地方为了纪念二战,祈求世界和平而在战后送到日本来的,其中有台湾,香港,但是没有看到大陆。在白塔旁边还有一条三十三观音平安赐福古道,不过时间不足,无法去感受一下了,应该也是一个非常平静的地方。
japan-heiwa-kouen:
japan-heiwa-kouen

浜名湖弁天岛,小街道什么的很有意思,还有温泉可以泡,但是和想象中的那种在地面上的温泉池不一样,是在宾馆,室内的。
japan-bentenjima:
japan-bentenjima
japan-bentenjima-2:
japan-bentenjima-2

第四天,去了奈良,大阪。

奈良有一个吃货很爱的特产——神户牛,这牛肉连烤起来都和普通牛肉不一样,因为肥瘦相间的原因,肉在烤的时候会自己烧起来,当然烤的时候看得心惊肉跳的,也就很难烤焦了。
奈良公园,梅花鹿啊梅花鹿,满街跑,各种萌啊,听说这些鹿是当时和释迦摩尼一起来日本的,所以大家都很尊重它们,不会杀他们什么的,另外还可以摸摸他们的鹿茸,软软的哦~~
japan-nara-park-1:
japan-nara-park-1
japan-nara-park-2:
japan-nara-park-2

之后是大阪城天守阁,丰臣秀吉的地盘,中国匠人的杰作,外观五层,内部八层的结构。
japan-osaka-castle:
japan-osaka-castle
japan-osaka-castle-2:
japan-osaka-castle-2

路过一群好基友跑步,后面一个MM骑着自行车为他们送水,青春啊,好有爱的感觉。
japan-osaka-castle-3:
japan-osaka-castle-3

在大阪,有一个叫做心斋桥道顿崛的地方,类似于步行街,各种购物商店神马,各种小吃神马的,章鱼小丸子。
在这里的一家卖寿司的店子里面,居然还碰到了老乡,各种high。可惜没有什么照片留下来。

来到日本,怎么能不看看和服呢?最后一天,看看神马和服秀啥的,大家猜猜这个MM多少岁~
japan-nishi-jin:
japan-nishi-jin

最后看了看一个历史上某一位天皇的故居,平安神宫。
japan-heian-shrine:
japan-heian-shrine

写到这里发现,这还真是一篇纯流水账啊。好无聊。真是纯粹做一个记录好了。
另外,感谢隔壁老王的各种照片,让我等没有相机的穷屌丝也能记录下自己的生活。

通过DIA SDK查看pdb中的符号信息

Mirror 0.2的x86版本已经膨胀到了793KB了,这让我觉得很好奇,到底是什么东西在疯狂的膨胀呢?实在想一探究竟。
于是杯具的事情发生了:Mirror的方向终于还是跑歪了,偏离了窗口,朝着编译器的方向走了弱弱的一小步。

加载pdb,就可以看到代码段大小的分布。
mirror-pdb:
mirror-pdb

发现还是stl膨胀的比较猛,特别是map和vector。但是没有想想中的那么多,代码只有400多k,还有300多k估计是资源什么的,这个可能要等写完pe分析了之后才能知道了。

当时在写这个功能的时候,我想到的方法有两种:
1. 使用dbghelp.dll提供的Symbol Api
使用这种方法来写的话,需要将dll加载起来才能进行分析,但是实际上,我们需要的内容在pdb文件里面就已经都有了,所以这么做就有点多此一举了。

2. 自己手写pdb的分析类
虽然较为简单的分析代码,应该可以从breakpad中找到,但是我还是觉得这种方法太蛋疼,难道就没有现成的库来使用么?这不像是Windows的风格啊。

正当我纠结的蛋疼的时候,幸运的事情发生了,无意中在vs中发现了一个的pdb分析库:Debug Interface Access SDK
这个库随着vs的发布而发布,反正Mirror也只有开发人员才会用,所以基本上机器上应该都有vs,所以就直接使用这个SDK来实现了。

这个库里面有两个最主要的类:

  • IDiaDataSource:提供通过Exe,dll和Pdb加载调试信息的接口。功能类似于IDiaSession的类厂。
  • IDiaSession:通过IDiaDataSource创建,通过这个Session可以获取文件内部各种详细的调试信息。

关于这个SDK,微软已经给出了非常好的使用方法,所以这里就不罗列代码了,大家可以照着这个step by step来学习使用。
另外微软还提供了一个完整的实例程序Dia2dump展示Dia的方方面面:http://msdn.microsoft.com/en-us/library/b5ke49f5(v=vs.80)

Posted in 03 Binary Life. Tags: , , . 没有评论 »

最近貌似很喜欢旅游

今天来看博客的草稿箱,发现这篇文章已经放置有将近一年了,本来是2011年8月份的时候,写天津之行的,结果一直放到现在。
一晃一年过去了,走了一些地方,满足了一些自己的好奇心,原来在上海,没有去杭州。在深圳,去了香港,没去澳门。现在来到了北京,周边城市什么的还是去看看比较好。就先去了天津。

当时还是由于T哥家在天津,想着还能有个伴,就带着MM和T哥一起去玩了两天。去的时候,我对天津的印象只有两个:竹板这么一打呀,别的咱不夸,夸一夸咱们那个狗不理包子!

到了天津算是去了不少地方:瓷房子,五大道,意大利风情街,劝业场。不过最搞的还是古玩街。
tianjin:
tianjin

嗯,原来只听过泥人张,结果。。。这。。。。。简直就是张家村呀~

后来就到了12年的4月份,再次脚痒了,这次去了趟远的,去了成都,还是跟着T哥的脚步,顺便看了看T哥的新房子,很不错。

成都真是一个悠闲的城市,听说很多人每天都4点就下班,在青羊宫里面,也体会了一把,一杯盖碗茶喝一下午的那种悠闲的心情,难怪古话说:少不入川,老不出蜀。话说成都的服务意识相当的强,在喝盖碗茶的地方,一个估计快70的老大妈对我说:“你付了钱,就是来享受的,自己倒什么水啊?叫他们上茶。”,而这个盖碗茶只要3块钱一碗。
chengdu-1:
chengdu-1

成都的历史我觉得是保存的很好的,不像大帝都,一顿拆,成都由于城市本身又不是那么的大,老景观基本是一个接着一个,感觉非常好。当然到了成都有两个地方就是肯定要去的了:去草堂拜访去年一直都很忙的杜甫,去武侯祠看看从小玩到大的三国。
chengdu-2:
chengdu-2

好基友,一杯子,估计你猜出来这两个是谁了。
chengdu-3:
chengdu-3

武侯祠
chengdu-4:
chengdu-4

岳飞写的后出师表
chengdu-5:
chengdu-5

成都的小吃也是相当的出名,各种苍蝇馆子,虽然不一定会很干净,但是味道都相当不错。在成都,由于氛围太悠闲,你还可以看到一个很特别的场景。由于大家下班都下的很早,下了班之后也没有什么事情做,所以大家都在好吃的馆子前面排队,一排就是一两个小时,大家也没有怨言,互相聊天,打发时间。
chengdu-6:
chengdu-6

当然还少不了小吃街,虽然消费比较高,但是小吃相当多,可以吃个爽。
chengdu-7:
chengdu-7

到了5月,脚再次痒了,于是又进军大连。大连是一个神奇的地方,在市里面感觉环境真的很好,很舒服,但是又听说开发区那边爆炸啥的很丰富哟。
大连最Happy的地方就在于临海了,海景啊,美丽的海景,海鲜啊,美味的海鲜。这就是大连的主题。但是有一个地方很囧,大连的海没有沙滩,都是石滩,小石子啥的,打赤脚走起来,感觉可是很凶残的哟。

棒槌岛,个人觉得是大连最美的地方了
dalian:
dalian

不知道为什么,感觉大连人都特别富裕,遍地都是跑车。 嗯,也许是我的幻觉吧。

还是不知道为什么,我就没有P图的习惯呢,弄个自己的小标记啥的。也许是因为不怎么会摄影,也没有很霸气的相机吧。

玩了一天就实在忍不住要吐槽暗黑3了

原创文章,转载请注明:转载自Soul Apogee
本文链接地址:玩了一天就实在忍不住要吐槽暗黑3了

难得有时间,玩了一天整的暗黑3,感觉不知道要如何表达,有些内容实在忍不住要吐槽一下了。
p.s. 我的游戏时间才一天,对游戏的理解肯定不深,下面这些都只是我的个人YY而已,暴雪出品,必是精品,这句话我还是坚信的。如果说的有什么地方不对,欢迎各种指正。

写在前面

现在的电脑游戏已经越来越简单,这是真的!以前,你见过中间休息一下就能补满血的FPS游戏么?你见过杀第一个小怪就弹出什么类似于“初出茅庐”的成就么?你见过走几步就弹一个还带视频的教学的RPG么?你见过按一个键就能发大招的KOF么?而现在这种简单的趋势在暗黑3中更是表现的淋漓尽致。
另外,我实在觉得3里面有太多2的影子,以至于让我产生了一种在玩2的新资料片的感觉。。。这。。。应该不太正常吧。。。
好吧,现在我们来细细的聊一下我觉得的各种槽点吧~

故事背景

暴雪的CG依然是那么的给力,每到CG开始我都特别激动。但是故事背景还是那么的单纯,具体是什么,你懂的。在暗黑2的时代,毕竟大家见的少,弄一个出来很新鲜。但是现在这种RPG已经遍地都是,所以感觉总是缺乏新意。当然,我们可以说暗黑3的剧情必须要继承暗黑的传统去发展,所以才不会有特别大的变化。不过即便是这样,我还是觉得少了点什么。
可能是对暴雪期望太高,毕竟暗黑2是一个里程碑。

人物设定和游戏操作

现在我总共也才玩一天,我接触到的也还是一些最简单的部分,比如最浅显的人物设定和基本的游戏操作。

1. 游戏大厅
在暗黑2中,一旦你登录到战网中,首先看到的是一个战网大厅,大家可以在里面聊天,组团,约着一起打怪,要么刷巴尔,要么刷莫非斯托,大家都很high。
但是在暗黑3中,这一切都没有了,取而代之的是一个自动加入房间的入口。这算什么?!欢乐斗地主么?而且就连斗地主也没有取消大厅啊!这样多不利于大家交流啊。

2. 买血瓶,补血球还是信春哥?
由于游戏被简化,血瓶的地位被强力的弱化了,甚至还加上了一个很长的CD,以至于在设计地图的时候出现了这种事情:在第二幕中,如果你用传送门传送回来,想去买一个药,居然要到另外一个城镇地图中才有。但是貌似,一旦你切换了场景,传送门就消失了,这样你想回去原先的场景,就杯具了。(这个没有特意确认过,但是游戏中有遇到过这种场景,杯具。)
既然血瓶已经被暴雪抛弃了,那么取代他的是什么呢?补血球。这种设定已经很常见了,比如《战神》系列,杀死怪物时,会掉落专门用来加血的东西,因为怪物时时有,所以只要你还能杀死怪,你就不用喝血瓶了,这些补血球,基本够你无敌的了,这样游戏难度就降低了不少。
我们还记得暗黑2的雇佣兵么?如果雇佣兵要喝血怎么办呢?这个好办,雇佣兵不需要喝血,他们都信春哥,如果倒下了,只要稍事休息,即可满血满魔原地复活。所以,在暗黑3里面,你就基本不要太担心自己的小弟了,尽管让其来当炮灰就好了。想想有多少RPG游戏通过这种设计来降低游戏的难度啊。。。春哥无处不在。

3. 铁匠
你看过不会修东西的铁匠么?城市里面除了铁匠,貌似基本上所有的商人都可以修理武器。。。这。。。吐槽无力了。。。

4. 迪卡凯恩与辨视物品
躺枪的还有迪卡凯恩,这可怜的老头现在不提供辨视服务了,而且在出场不久之后,就杯具的便当了。当然整个游戏里面是没有辨视卷轴的,这并没有关系,这一次的主人公都是无所不知的大拿,自己辨视就好了,只是要花一点点时间。

5. 回城卷轴与重生
与辨视卷轴一起便当掉的,就是他的亲兄弟回城卷轴了。回城变成了一个技能,有一个较长的施法时间,但是技能本身没有消耗和CD,可以随便放,不过一旦释放成功,你就直接回城了。就和山口山里面的炉石一样,搓啊搓的,就回去了。问题就来了:你在一个人玩的时候,再也没有办法留传送门了。也许你会想暗黑3中改变还是挺多的,是不是已经不需要留传送门了?我们来考虑死亡后重生这种情况,这也是留传送门最常见的情况之一。

在暗黑3中,每到一个新的场景,或者在一个新的事件前,会有一个自动的储存点,一旦你挂了,你会在这里复活。另外,暗黑3中你再也不用裸奔着去捡自己的1号尸体,2号尸体,n号尸体了,这个事情是挺好的,特别在联机游戏中,再也不用担心爆尸了。问题是,暗黑中一个场景的地图,有时候巨大无比,如果你死亡的地方离上一个储存点很远的话。。。。。。

九九那个艳阳天来哟,十八岁的哥哥呀告诉小英莲,这一去呀翻山那个又过海呀,这一去三年两载呀不回还。

当然你也不要太妄想在有追兵的时候,可以使用回城卷轴回城了,不要忘记那巨长的施法时间。。。(在紧急关头时,你会觉得这个时间巨长的。)

6. 诡异的地图

地图,貌似相当随机。。。。。而且相当的大。。。。。这是暗黑的传统,我们都知道。变态的事情是:暗黑3默认加上了部分maphack的功能,他能帮你指示路径。这个事情是个好事,本身不变态,但是和地图结合起来就变态了。

我们知道暗黑3为了降低游戏难度,或者游戏上手的门槛,花了不少心思,地图上他也做了不少手脚。我估计暴雪里面有人提出过暗黑2复杂的地图让游戏的时间大为增加,让大家找图找的很厌烦,从而在一定程度上降低了游戏的可玩性,才出了各种maphack,现在暗黑3必须要改!那地图大小不能减小,不然游戏时间就短了,所以就只能改地图路线了。把路线延长,把分支减少。这样,大家找路再怎么错,也不会错大方向了,纠错起来就会快,就不会一天到晚想着maphack了(不是所有的图都这样,但是我确实遇到过不少)。
好,图改好了,那就一条路到底。但是,默认的maphack爱加不加,让大家在里面转吧。我们再把图弄随机一点,让他们每次退出一次游戏,就要重新转一次,嗯。

这。。。。

怪物

1. 新的怪物,我要新的怪物

新的怪物,你们在哪里,新的场景,你们在哪里?我才玩了三幕,就已经蛋疼了。第一幕和暗黑2基本一样,连大教堂一进一出再去地下打boss的流程都没有变啊!第二幕,也是沙漠。蜘蛛,苍蝇,小蝙蝠,兄弟们,好久不见。。你们还在沙漠里面那。。。至于第三幕,算是有新鲜感了,但是总让我想到亚瑞特高原,挥之不去。。

2. 好像有什么奇怪的东西混进来了

还记得暗黑2里面的罗格营地刚出门时遇见的怪物么?沉沦魔啊沉沦魔,现在在暗黑3中,已经被放在第三幕出现了(似乎第二幕也有,不太记得了),而原来在第三幕才出现的怪物,比如树精什么的,在第二幕就出现了。沉沦魔就这样完成了一次华丽的逆袭,而且大沉沦魔比什么地狱xx兽之类的怪物强多了。。。。这。。。这不科学。。。

技能啊技能,装备啊装备

接下来是技能和装备,这两个现在实在分不太开,原因我来慢慢说。

1. 装备词缀系统

为了根据武器的属性动态的生成装备的名称,暗黑2里面引入了一套词缀的系统,比如,视野范围+1,那就叫光明的xxx,这种。这套东西似乎暗黑3沿用了,原来的词缀含义基本不变,然后加一些新的进来,就成了现在的。这个我也没有真的去验证,只是看到有些武器,确实差不多。
貌似这个也不算是什么槽点。。。。。 =.=|||

2. 镶嵌装备

在暗黑2中,装备的镶嵌有如下几种:宝石,符文和珠宝。宝石的属性是固定的,符文可以组合成符文之语产生特殊效果,珠宝属性随机,有时能出现很有用的属性。暗黑3中呢?

只有宝石!这不是坑爹啊!选什么不好,选宝石啊!最没有可玩性的就是宝石啊!

3. 人物属性

现在我玩的是一个DH,有时候会打到一些力量加一堆的装备,我就很好奇,这力量对于DH来说有没有用呢?看了半天,一个状态都没有变。。难道是后期的装备有力量要求?我看很多聊天里面发的极品装备都没有力量的要求啊?很不解。

4. 技能树

技能树消失了,变成了技能槽。。一个槽对应四个左右的技能,每个槽选一个技能放在状态中使用。如果你想用的技能恰巧有两个在一个槽里面,你就杯具了。比如DH的烟雾和钉爪刺。。当然你可以换技能,但是更换技能之后,不管任何技能都有一个很长的CD,而且在这个时候是不可继续更换的。所以如果你想在杀怪过程中换技能,甚至还来一个小连套,那你就只能去墙角画圈圈了。

当然每个技能也没有等级了,而是靠符文来解锁,这里的解锁实际上就是升级的时候系统帮忙自动升级了,但是这样,每个人的技能就是一样的了。。。一样的了。。。样的了。。。的了。。。了。。。

好吧,起码这样再也不需要同一个人物练多个了。但是想想,在人物属性一样,技能一样的暗黑3的世界中,武器会变得多么的重要。

5. 技能和武器,DPS不是一切

你看过任何一个游戏,魔法师技能的伤害主要和他手持武器伤害相关,而不是和技能等级相关的游戏么?暗黑3就是这样。

想想,一个法师,如果每天拿着双手大剑,双手大斧,发着各种冰弹火弹的样子吧。喜欢法师的朋友们是不是有一种蛋碎的感觉。

最后的最后

好吧,不知道暗黑3会不会出资料片。

Posted in 05 G.A.M.E. Tags: , . 没有评论 »

Mirror v0.2 Preview 2 发布 —— Windows开发辅助工具

在发布了0.1 Preview版本之后,又折腾了一个月,Mirror v0.2 Preview 2终于诞生了。
这一次的发布主要是让界面可以自由缩放了,这样能适当的缓解一些界面太大导致的调试困难。另外还增加了一些小功能,并且修复了一些bug。
由于加入的部分功能在64位的机器上只有64位的API才能正常工作,所以之后的Mirro可能都将发布32位和64位两个版本,后面我会尽量想办法将两个版本合并。
欢迎您点此乱入反馈各种意见和问题,或者点此捐助作者鸡蛋灌饼,支持作者继续开发~

下载地址

Mirror v0.2 Preview 2 (0.2.69.102)
项目地址:https://bitbucket.org/bigasp/mirror
打包下载 (推荐):Mirror-0.2.69.102 (234)
下载地址:Mirror-0.2.69.102-x86 (203) (32位) | Mirror-0.2.69.102-x64 (184) (64位)

主要修改

2012-05-12
紧急修正了两个较为严重的问题:
- 拖拽窗口时,如果控件很多,则非常的卡 (#33)
- 窗口最小化时退出程序,之后将再也无法正常显示主界面,除非删除配置文件 (#32)

2012-05-11
新增功能:

- 由于一些API在64位系统上需要64位程序才能正常使用,所以将程序分为32位和64位两个版本
- 界面终于能缩放大小了,并在关闭的时候自动保存其位置
- 状态栏添加反馈问题的快捷入口
- 刷新窗口树时,自动定位到当前观察的窗口
- 保存当前窗口列表中显示的窗口树
- 增加一些新的窗口高亮:
- 高亮窗口中选中的窗口
- 高亮当前正在观察的窗口
- 获取窗口Unicode属性
- 发送窗口消息支持Send和Post两种,并且支持发送自定义的消息
- 在Vista之上的系统,可以检测窗口所在线程的等待链
- 增加了一些新的模拟鼠标输入
- 添加程序日志Tab

修正Bug:
- 修正无法获取进程外窗口的窗口过程的问题 (#19) (通过注入代码解决,在窗口失去响应的时候,可能会有一些不稳定)
- 修正模拟消息输入WPARAM和LPARAM永远为0的Bug (#31)
- 修正没有选中窗口,就不可以刷新部分全局的数据 (#16)
- 其他一些小Bug和崩溃

软件截图

支持自动布局的界面,想摆多大弄多大
mirror-auto-layout:
mirror-auto-layout

以后会陆续完善各种Log,让你不丢失调试中的各种信息
mirror-log:
mirror-log

窗口列表随时存,Dump回去查问题
mirror-save-window-list:
mirror-save-window-list

窗口消息任意发,窗口阻塞不着急(等待链检测需要Vista以上的系统支持)
mirror-window-op:
mirror-window-op

软件历史

Mirror v1.0 Preview (0.1.21.101)

新增功能:
- 查看/修改窗口基本信息
- 查看窗口所在的线程信息
- 查看/高亮显示/设置前景窗口,焦点窗口,激活窗口
- 查看窗口类信息
- 查看/修改窗口Prop
- 发送窗口消息,并提供一些常用的窗口相关的Api调用
- 模拟鼠标,键盘输入
- 查看/修改DWM信息,可以自己设置几乎所有的窗口DWM的属性
- 基本上面提到的所有的功能,都可以对其设置热键,甚至包括切换软件中的Tab
- 应用设置之后,新的热键会被自动保存在mirror.ini配置文件中
- 每隔一天自动检查更新
- 等等等等

更多信息可参阅项目首页:https://bitbucket.org/bigasp/mirror