九天雁翎的博客
如果你想在软件业获得成功,就使用你知道的最强大的语言,用它解决你知道的最难的问题,并且等待竞争对手的经理做出自甘平庸的选择。 -- Paul Graham

异常处理与MiniDump详解(4) MiniDump

异常处理与MiniDump详解(4) MiniDump

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   综述

总算讲到MiniDump了。

Dump有多有用我都无法尽数,基本上属于定位错误修复BUG的倚天剑。(日志可以算是屠龙刀)这些都是对于那些不是必出的BUG,放在外面运行的时候出现的BUG而言的,那些能够通过简单调试就能发现的BUG,一般都不足为惧。

 

二、   基本应用

MiniDump之所以叫MiniDump,自然是有其Mini之处。。。(废话),呵呵,MS提供了一个API函数,MiniDumpWriteDump,(在Dbghelp.h中声明,需要导入DbgHelp.lib使用)所以我才将其称为MiniDump,其实Dump也能表达同样的意思。。。。

MiniDump最简单的应用在于程序崩溃的时候,将崩溃时那一刻的信息写进一个文件,以方便以后查找错误。使用方法说简单就简单,说难也难。

1.             怎么感知到程序的崩溃?

Window提供了较为方便的方法去感知到程序的几种崩溃情况。

在《Breakpad在进程中完成dump的流程描述》一文中,我描述了一下Breakpad获取到程序崩溃的方法,事实上,这也是典型的Windows下感知程序崩溃的方法,那篇文章是刚开始工作的时候,完成公司自己的ExceptionHandle库的时候写的工作笔记,现在看起来也还是有一定的参考价值。

Windows下感知程序崩溃(其实就是运行时的严重错误)的方法有3个核心的函数,分别如下:

SetUnhandledExceptionFilter(HandleException)确定出现没有控制的异常发生时调用的函数为HandleException.

_set_invalid_parameter_handler(HandleInvalidParameter)确定出现无效参数调用发生时调用的函数为HandleInvalidParameter.

_set_purecall_handler(HandlePureVirtualCall)确定纯虚函数调用发生时调用的函数为HandlePureVirtualCall.

3个函数的使用方法一致,都是在发生自己关心的(见上面的描述)异常时,调用参数传进来回调函数,Windows会将崩溃信息通过参数传入回调函数,这时候就是进行Dump的绝佳时机。详细的信息可以查阅MSDN,我这里就不复制资料了,那样有copy文档之嫌,这里以SetUnhandledExceptionFilter为例,演示实际与MiniDumpWriteDump配合使用的情况。像这些比较复杂的API,MSDN中连个Example都没有,说实话,当时掌握花了一点时间。

ExceptionExample:

#include <windows.h>

#include <Dbghelp.h>

using namespace std;

 

#pragma auto_inline (off)

#pragma comment( lib, "DbgHelp" )

 

// 为了程序的简洁和集中关注关心的东西,按示例程序的惯例忽略错误检查,实际使用时请注意

LONG WINAPI MyUnhandledExceptionFilter(

struct _EXCEPTION_POINTERS* ExceptionInfo

    )

{

    HANDLE lhDumpFile = CreateFile(_T("DumpFile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);

 

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;

    loExceptionInfo.ExceptionPointers = ExceptionInfo;

    loExceptionInfo.ThreadId = GetCurrentThreadId();

    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFile, MiniDumpNormal, &loExceptionInfo, NULL, NULL);

 

    CloseHandle(lhDumpFile);

 

    return EXCEPTION_EXECUTE_HANDLER;

}

 

 

void Fun2()

{

    int *p = NULL;

    *p = 0;

}

 

void Fun()

{

    Fun2();

}

 

int main()

{

    SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);

 

    Fun();

 

    return 1;

}

 

API的调用仅仅作为释放,查看下MSDN就知道使用方法了,

#pragma auto_inline (off)

#pragma comment( lib, "DbgHelp" )

两句讲一下,第一句是取消掉自动内联效果,这样才能达到更好的演示效果,不然,Fun,Fun2这种简单的函数会被自动内联,那么也就没有堆栈,不好看到实际中Dump的作用。效果与VS2005编译选项的,C/C++->优化->内联函数展开->only _inline一样。

第二句是标志导入DbgHelp库,以使用MiniDumpWriteMiniDump API。与VS2005编译选项的链接器->输入->附加依赖项中添加dbgHelp.lib效果一样。

 

实际运行程序,(不能在VS中调试运行,不然异常控制权总是会被VS掌握,那么,总是没有办法让MyUnhandledExceptionFilter获得控制权,详细的描述见参考2),可以获得一个名叫DumpFile.dmp的文件,此文件就是我们折腾了半天所谓的dump文件了。其他两个函数与MiniDumpWriteMiniDump的配合使用方式也类似,就不多说了。

 

2.      Dump文件的使用

Dump文件的在Windows下的使用非常简单,但是就是因为太过于简单,所以网上的描述也是非常简单,想起来,那时候折腾出Dump文件时非常兴奋,解决发现拿dump文件没有办法,网上简单的描述用VS打开调试的方法总是没有头绪。。。。呵呵

正确的使用方法是,将崩溃程序的dmp, pdb,exe文件都放在同一个目录下,然后双击运行dmp,(或者用VS打开),然后就会出现一个名为dumpfile的解决方案并且包含一个dumpfile的工程,此时右键点击此工程,选择调试->启动新实例(或者启动并进入单步调试新实例)都行,此时程序会自动的调到源码中崩溃的那一行,并且在call stack中有完整的堆栈信息,并且临时变量的值也可以通过VS显示出来,还有模块信息,线程信息,

在上例中,堆栈信息是:

>     Exception.exe!Fun2()  36       C++

      Exception.exe!main()  50 C++

      Exception.exe!__tmainCRTStartup()  597 + 0x17 字节       C

      kernel32.dll!7c817077()       

      [下面的框架可能不正确和/或缺失,没有为 kernel32.dll 加载符号]   

      ntdll.dll!7c93005d()  

 

然后,寄存器的值为:

EAX = 00000000 EBX = 00000000 ECX = 0000B623

EDX = 7C92E514 ESI = 00000001 EDI = 00403384

EIP = 00401072 ESP = 0013FF7C EBP = 0013FFC0

EFL = 00010246

 

normal模式下,dump文件速度较快,但是没有内存信息,你甚至可以通过调整MiniDumpWriteMiniDump的参数来将运行时的整个内存都dump下来,这些都非常简单,查看一下MSDN MiniDumpWriteMiniDump的信息即可。

有了这些信息,程序的错误定位(C++下一般是空指针的访问比较多)已经是非常明朗的了,再配合日志,一般的错误不难发现。这里顺带说明一下,当运行的程序被改名或者糅合进其他地方后运行,用这样的方式,一开始堆栈信息中是没有完整的信息的,这时候可以在堆栈信息中,用右键菜单中的加载符号,选择合适的文件pdb,这样信息就出来了。。。。。(以前这个问题困扰了我们一天)

 

三、   高级应用

程序崩溃的问题解决了,问题是,有很多时候,很多程序是不允许随便崩溃的,这样,在程序崩溃后再去发现问题就有些晚了,那么,有没有程序不崩溃时也能发现问题的方法呢?前面描述的SEH就是一种让程序不崩溃的方法,不过在那种方式下,按以前描述的方法,崩溃是不崩溃了,但是实际上,掩盖了很多问题,对于问题的发现有些不利的地方。本文前面描述过了,MiniDump是一种快速发现问题的好方法,但是却没有办法避免程序崩溃,那么终极办法是啥呢?我们的目的既然是程序不崩溃+快速发现问题,那么终极办法自然就是SEH+MiniDump了:)SEHMiniDump都是Windows的特性,MS也的确提供了结合的方式。见下面的例子,呵呵,别太激动了。。。。这也是我们公司的服务器从内测时一天多次无任何通知,预告,警告的崩溃(总监甚至还曾因为我的问题,半夜3点爬起来解决服务器崩溃问题)到现在服务器基本做到永不崩溃,即便出现问题了也有充足的时间从容的解决,然后在服务器中发通告,告诉文件服务器需要临时维护。。。。呵呵,都依赖于此终极解决方案。。。。。

SEH的用法和特性讲解这里不重复了,见前面的文章。《异常处理与MiniDump详解(3) SEHStructured Exception Handling

要想利用MiniDumpWriteMiniDump,需要获取的是MINIDUMP_EXCEPTION_INFORMATION结构的信息,这个结构中最重要的信息来源于PEXCEPTION_POINTERS的信息,这个信息在上述的例子中是在程序崩溃的时候,由Windows作为参数传入我们设定好的异常处理函数的,现在最主要的问题就是从哪里获取到这个异常信息了,通过MSDN,我们查到了GetExceptionInformation的函数,返回的就是这个信息,见MSDN

LPEXCEPTION_POINTERS GetExceptionInformation(void);

不过,这里MS给出了一个notice:

The Microsoft C/C++ Optimizing Compiler interprets this function as a keyword, and its use outside the appropriate exception-handling syntax generates a compiler error.

事实上,刚开始我使用的时候,哪个地方都试遍了,果然都是报编译错误。因为此函数使用方式如此奇怪,并且没有example。。。。。最后在绝望中。。。看到了Platform Builder for Microsoft Windows CE 5.0的词函数的说明,里面有个说明,然后我吐血了。。。。

try

{

    // try block

}

except (FilterFunction(GetExceptionInformation())

{

    // exception handler block

}

 

原来是这样使用的啊。。。。。。。。。。晕

HandleWithoutCrash例子:

#include <windows.h>

#include <Dbghelp.h>

using namespace std;

 

#pragma auto_inline (off)

#pragma comment( lib, "DbgHelp" )

 

// 为了程序的简洁和集中关注关心的东西,按示例程序的惯例忽略错误检查,实际使用时请注意

LONG WINAPI MyUnhandledExceptionFilter(

struct _EXCEPTION_POINTERS* ExceptionInfo

    )

{

    HANDLE lhDumpFile = CreateFile(_T("DumpFile.dmp"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL ,NULL);

 

    MINIDUMP_EXCEPTION_INFORMATION loExceptionInfo;

    loExceptionInfo.ExceptionPointers = ExceptionInfo;

    loExceptionInfo.ThreadId = GetCurrentThreadId();

    loExceptionInfo.ClientPointers = TRUE;

    MiniDumpWriteDump(GetCurrentProcess(), GetCurrentProcessId(),lhDumpFile, MiniDumpNormal, &loExceptionInfo, NULL, NULL);

 

    CloseHandle(lhDumpFile);

 

    return EXCEPTION_EXECUTE_HANDLER;

}

 

 

void Fun2()

{

    __try

    {

       static bool b = false;

      

       if(!b)

       {

           b = true;

           int *p = NULL;

           *p = 0;

        }

       else

       {

           MessageBox(NULL, _T("Here"), _T(""), MB_OK);

       }

 

    }

    __except(MyUnhandledExceptionFilter(GetExceptionInformation()))

    {

    }

}

 

void Fun()

{

    Fun2();

}

 

int main()

{

    Fun();

    Fun();

 

    return 1;

}

 

这里例子中,你可以调试程序了,因为程序不会崩溃,这样VS不会和你抢异常的控制。同时,看到dump文件的同时,也可以看到,程序实际上是继续运行了下去,因为MessageBox还是弹出来了。这。。。就是我们想要的。。。。。

我突然想到一首歌。。。。“I want to nobody but you...I want nobody but you.......”呵呵,目的达到了,惊艳吗?

这里有几个要点,GetExceptionInformation()仅仅只能在__exceptMS所谓的Filter中调用,其他地方会报编译错误,其次,返回的值和一般的__except的意义是一样的,要想程序运行,需要返回EXCEPTION_EXECUTE_HANDLER表示异常得到了控制。其他几个值的含义见前篇的SEH

 

四、   参考资料

1.     MSDN—Visual Studio 2005 附带版,Microsoft

2.     Windows用户态程序高效排错,熊力著,电子工业出版社

前面的系列文章:

异常处理与MiniDump详解(3) SEHStructured Exception Handling

异常处理与MiniDump详解(2) 智能指针与C++异常

异常处理与MiniDump详解(1) C++异常

 

  

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

异常处理与MiniDump详解(3) SEH(Structured Exception Handling)


异常处理与MiniDump详解(3) SEHStructured Exception Handling

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   综述

SEH--Structured Exception Handling,是Windows操作系统使用的异常处理方式。

对于SEH,有点需要说明的是,SEH是属于操作系统的特性,不为特定语言设计,但是实际上,作为操作系统的特性,几乎就等同与面向C语言设计,这点很好理解,就像Win32 API,Linux下的系统调用,都是操作系统的特性吧,实际还是为C做的。但是,作为为C语言设计的东西,实际上可调用的方式又多了,汇编,C++对于调用C语言的接口都是比较方便的。

 

二、   基础篇

还是简单介绍一下SEH的使用,但是不准备太详细的介绍了,具体的详细介绍见参考中提及的书目。关于SEH的基本应用,Windows核心编程》绝对是最佳读物(其实个人一直认为《Windows核心编程》是Windows编程领域必看的第二本书,第一本是《Programming Windows》。关于SEH更深入的一点的知识可能就要参考一些能用汇编讲解的书籍了,《Windows用户态程序高效排错》算是其中讲的不错的一本。

首先,SEH也有像C++异常一样的语法,及类try-catch语法,在SEH中为__try-except语法,抛出异常从throw改为RaiseException,MSDN中的语法描述为:

__try 

{

   // guarded code

}

__except ( expression )

{

   // exception handler code

}

 

见一个实际使用的例子:

1

#include <iostream>

#include <windows.h>

using namespace std;

 

int main()

{

    __try

    {

       RaiseException(0, 0, 0, NULL);

    }

    __except(EXCEPTION_EXECUTE_HANDLER)

    {

       cout <<"Exception Raised." <<endl;

 

    }

 

    cout <<"Continue running" <<endl;

}

 

这可能是最简单的SEH的例子了,输出如下:

Exception Raised.

Continue running

 

这个例子和普通C++异常的try-catch类似,也很好理解。只不过catch换成了except

因为C语言没有智能指针,那么就不能缺少finally的异常语法,与JAVA,Python等语言中的也类似,(这是C++中没有的)finally语法的含义就是无论如何(不管是正常还是异常),此句总是会执行,常用于资源释放。

 

2

 

#include <iostream>

#include <windows.h>

using namespace std;

 

int main()

{

    __try

    {

 

       __try

       {

           RaiseException(0, 0, 0, NULL);

       }

       __finally

       {

           cout <<"finally here." <<endl;

 

       }

    }

    __except(1)

    {

 

    }

 

    __try

    {

 

       __try

       {

           int i;

       }

       __finally

       {

           cout <<"finally here." <<endl;

 

       }

    }

    __except(1)

    {

 

    }

    cout <<"Continue running" <<endl;

    getchar();

}

 

这个实例看起来过于奇怪,因为没有将各个try-finally放入独立的模块之中,但是说明了问题:

1.     finally的语句总是会执行,无论是否异常finally here总是会输出。

2.     finally仅仅是一条保证finally语句执行的块,并不是异常处理的handle语句(与except不同),所以,假如光是有finally语句块的话,实际效果就是异常会继续向上抛出。(异常处理过程也还是继续)

3.     finally执行后还可以用except继续处理异常,但是SEH奇怪的语法在于finallyexcept无法同时使用,不然会报编译错误。

如下例:

    __try

    {

       RaiseException(0, 0, 0, NULL);

    }

    __except(1)

    {

 

    }

    __finally

    {

       cout <<"finally here." <<endl;

 

    }

 

VS2005会报告

error C3274: __finally 没有匹配的try

这点其实很奇怪,难道因为SEH设计过于老了?-_-!因为在现在的语言中finally都是允许与except(或类似的块,比如catch)同时使用的。C#JAVA,Python都是如此,甚至在MSC++做的托管扩展中都是允许的。如下例:(来自MSDN中对finally keyword [C++]的描述)

using namespace System;

 

ref class MyException: public System::Exception{};

 

void ThrowMyException() {

    throw gcnew MyException;

}

 

int main() {

    try {

       ThrowMyException();

    }

    catch ( MyException^ e ) {

       Console::WriteLine(  "in catch" );

       Console::WriteLine( e->GetType() );

    }

    finally {

       Console::WriteLine(  "in finally" );

    }

}

 

当你不习惯使用智能指针的时候常常会觉得这样会很好用。关于finally异常语法和智能指针的使用可以说是各有长短,这里提供刘未鹏的一种解释,(见参考5RAII部分,文中比较的虽然是JAVAC#,但是实际SEH也是类似JAVA的)大家参考参考。

 

SEH中还提供了一个比较特别的关键字,__leave,MSDN中解释如下

Allows for immediate termination of the __try block without causing abnormal termination and its performance penalty.

简而言之就是类似goto语句的抛出异常方式,所谓的没有性能损失是什么意思呢?看看下面的例子:

#include <iostream>

#include <windows.h>

using namespace std;

 

int main()

{

    int i = 0;

    __try

    {

       __leave;

       i = 1;

    }

    __finally

    {

       cout <<"i: " <<i <<" finally here." <<endl;

    }

 

 

    cout <<"Continue running" <<endl;

    getchar();

}

 

 

输出:

i: 0 finally here.

Continue running

实际就是类似Goto语句,没有性能损失指什么?一般的异常抛出也是没有性能损失的。

MSDN解释如下:

The __leave keyword

The __leave keyword is valid within a try-finally statement block. The effect of __leave is to jump to the end of the try-finally block. The termination handler is immediately executed. Although a goto statement can be used to accomplish the same result, a goto statement causes stack unwinding. The __leave statement is more efficient because it does not involve stack unwinding.

 

意思就是没有stack unwinding,问题是。。。。。。如下例,实际会导致编译错误,所以实在不清楚到__leave到底干啥的,我实际中也从来没有用过此关键字。

 

#include <iostream>

#include <windows.h>

using namespace std;

 

 

void fun()

{

    __leave;

}

 

int main()

{

    __try

    {

       fun();

    }

    __finally

    {

       cout <<" finally here." <<endl;

    }

 

 

    cout <<"Continue running" <<endl;

    getchar();

}

 

三、   提高篇

1.      SEH的优点

1)    一个很大的优点就是其对异常进程的完全控制,这一点是C++异常所没有的,因为其遵循的是所谓的终止设定。

这一点是通过except中的表达式来控制的(在前面的例子中我都是用1表示,实际也就是使用了EXCEPTION_EXECUTE_HANDLER方式。

EXCEPTION_CONTINUE_EXECUTION (–1)   表示在异常发生的地方继续执行,表示处理过后,程序可以继续执行下去。 C++中没有此语义。

EXCEPTION_CONTINUE_SEARCH (0)   异常没有处理,继续向上抛出。类似C++throw;

EXCEPTION_EXECUTE_HANDLER (1)  异常被处理,从异常处理这一层开始继续执行。 类似C++处理异常后不再抛出。

 

 

2)    操作系统特性,不仅仅意味着你可以在更多场合使用SEH(甚至在汇编语言中使用),实际对异常处理的功能也更加强大,甚至是程序的严重错误也能恢复(不仅仅是一般的异常),比如,除0错误,访问非法地址(包括空指针的使用)等。这里可以用一个例子来说明:

#include <iostream>

#include <windows.h>

using namespace std;

 

 

 

int main()

{

    __try

    {

       int *p = NULL;

       *p = 0;

    }

    __except(1)

    {

       cout <<"catch that" <<endl;

    }

 

 

    cout <<"Continue running" <<endl;

    getchar();

}

 

输出:

catch that

Continue running

C++中这样的情况会导致程序直接崩溃的,这一点好好利用,可以使得你的程序稳定性大增,以弥补C++中很多的不足。但是,问题又来了,假如异常都被这样处理了,甚至没有声息,非常不符合发生错误时死的壮烈的错误处理原则。。。。。。。很可能导致程序一堆错误,你甚至不知道为什么,这样不利于发现错误。

但是,SEHMS提供的另外的特性MiniDump可以完美的配合在一起,使得错误得到控制,但是错误情况也能捕获到,稍微的缓解了这种难处(其实也说不上完美解决)。

这一点需要使用者自己权衡,看看到底开发进入了哪个阶段,哪个更加重要,假如是服务器程序,那么在正式跑着的时候,每崩溃一次就是实际的损失。。。所以在后期可以考虑用这种方式。

关于这方面的信息,在下一次在详细讲解。

 

2.      SEH的缺点

其实还是有的,因为是为操作系统设计的,实际类似为C设计,那么,根本就不知道C++中类/对象的概念,所以,实际上不能识别并且正确的与C++/对象共存,这一点使用C++的需要特别注意,比如下例的程序根本不能通过编译。

例一:

int main()

{

    CMyClass o;

    __try

    {

    }

    __except(1)

    {

       cout <<"catch that" <<endl;

    }

 

 

    cout <<"Continue running" <<endl;

    getchar();

}

 

例二:

 

int main()

{

    __try

    {

       CMyClass o;

    }

    __except(1)

    {

       cout <<"catch that" <<endl;

    }

 

 

    cout <<"Continue running" <<endl;

    getchar();

}

 

 

错误信息都为:

warning C4509: 使用了非标准扩展:“main”使用SEH,并且“o”有析构函数

error C2712: 无法在要求对象展开的函数中使用__try

这点比较遗憾,但是我们还是有折衷的办法的,那就是利用函数的特性,这样可以避开SEH的不足。

比如,希望使用类的使用可以这样:

这个类利用了上节的CResourceObserver类,

class CMyClass : public CResourceObserver<CMyClass>

{

 

};

 

void fun()

{

    CMyClass o;

}

 

 

#include <iostream>

#include <windows.h>

using namespace std;

 

 

int main()

{

    __try

    {

       fun();

    }

    __except(1)

    {

       cout <<"catch that" <<endl;

    }

 

 

    cout <<"Continue running" <<endl;

    getchar();

}

 

 

输出:

class CMyClass Construct.

class CMyClass Deconstruct.

Continue running

可以看到正常的析构,简而言之就是将实际类/对象的使用全部放进函数中,利用函数对对象生命周期的控制,来避开SEH的不足。

 

 

四、   参考资料

1.     Windows核心编程(Programming Applications for Microsoft Windows,4版,Jeffrey Richter著,黄陇,李虎译,机械工业出版社

2.     MSDN—Visual Studio 2005 附带版,Microsoft

3.     加密与解密,段钢编著,电子工业出版社

4.     Windows用户态程序高效排错,熊力著,电子工业出版社

5. 错误处理(Error-Handling):为何、何时、如何(rev#2),刘未鹏(pongba)

 

 

  

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

初学编程该怎么学?——对初学者程序设计语言学习的思考(2)

初学编程该怎么学?——对初学者程序设计语言学习的思考(2)

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

作为学习编程几年的并且现在还靠着号称世界上特别复杂的一种语言C++活着的人来说,我也能在这些简单的图形中绘制中找到编程的乐趣,这就是turtle的乐趣了,我想初学者能找到的乐趣会比我更加多吧。

其实,对于初级编程来说,最主要的就是流程的控制,而流程的控制也就那么几种形式,这里有个我以前为Python写的流程控制表,其实实际上各类语言都差不多:

电脑,程序基础模型:

 

基本程序执行流程

 

  1. 顺序执行:

    Python的表现形式,一条接一条的语句

  2. 分支:

    Python表现方式:If-else

     

     

    python表现语法形式:if-elif-

 

3.循环:

python语法表现形式:for-in

 

各类语言在这个层面上除了语法的略微不同外,基本上是一致的,这些确实培养计算机思维逻辑的基础,也可以算是编程的初步知识,起码Small Basic用于熟练这些知识还有有用的,虽然它的设计原本是面对小孩子的。不要因为Small Basic是面向小孩子设计的就觉得它功能弱到学习功能都不够,事实上,因为SB对图形的重视,使得对图形控制如此容易,反而可以轻松的做一些很有意思的事情。

比如下面的例子,(超炫的文字显示,来自于http://social.msdn.microsoft.com/Forums/en-US/smallbasic/thread/b1b383c1-6b13-49c0-bf29-00de86103ac6

很炫的效果,才区区几行代码:

GraphicsWindow.BackgroundColor = "midnight"

gw = GraphicsWindow.Width

gh = GraphicsWindow.Height

GraphicsWindow.FontSize = 100

Turtle.Move (100)

Turtle.Turn (1*1)

While ("True")

For i = 1 To 50

GraphicsWindow.SetPixel(Math.GetRandomNumber(gw),Math.GetRandomNumber(gh),GraphicsWindow.GetRandomColor())

EndFor

Turtle.Move(1)

GraphicsWindow.BrushColor = "Black"

GraphicsWindow.DrawBoundText(30,110,gw-20,"Small Basic")

 

EndWhile

 

这是几乎每个人执行完后都会说"Impressive"的程序。

再来一些更多的SB程序图:

 

大家可以到(http://social.msdn.microsoft.com/Forums/en-US/smallbasic/threads)

中过去看看,其中有很多很有意思的例子。

其实我说了这么多,并不是鼓励大家都去学习Small Basic,仅仅是为初学者指出一条路,我觉得编程本质的复杂度来源于数学,思维和逻辑,不在于程序语言,即便是如SB这样的kids语言,在合理的思维逻辑下,一样有强大的效果,程序语言的本质并没有改变。

 

"Small Basic is a project that's aimed at bringing "fun" back to programming."MS如是说,的确是,什么时候我们失去了编程的乐趣了?为什么我们用的都是这样庞大的难以理解的语言?因为商业的需求,一个一个现代化的软件都是如此的庞大,犹如新特性的集合体,大量库的堆积。。。。让我们失去了太多本来该有的乐趣。

当然,假如仅仅局限于SB语言,那么用途毕竟还是比较有限的,教育意义也就没有那么大了。。。。虽然我感觉到能从SB中获得乐趣,但是。。。人还是要吃饭的,所以,对于正的想以编程作为职业的人来说,从Python开始,也还算是不错的主意(仅仅从学习角度,目前国内对Python的需求还是比较有限)。当时我处于这个考虑,准备用PyQt实现一个与SB一模一样的turtle库,结果经过查阅,发现Python的标准库中已经附带上一个了,呵呵,一如既往,Python的标准库之丰富与兼容并包简直不可想象。。。。。。。。你能想象到哪天C++的标准库里面加上一个这样的画乌龟的库吗?-_-!

因为Python标准库中就带有turtle库,所以我们完全可以用Python的语法来实现上述功能,同时还能使用一些Python比SB更强大一些的功能(Python的turtle库功能更多一些),并且,还有同时熟悉Python语法的功效。假如说Python是作为初学者迈入程序设计大门的合适的第一步的话,我认为Python的turtle库的使用,可以作为学习Python的合适的第一步。首先熟悉的是程序的思维和逻辑,其他的仅仅是这些的延生。(哪怕上升到DP,OO层次,这些还是不可或缺的基础)

这里给出原来用SB实现的那个最复杂的圆环结构的示例,其他的就省略了。要说的是,虽然用Python可以用更熟悉的语法,但是回到Python的编辑器来后,发现SB那个IDE也真是'Impressive'啊。。。

源代码:

import turtle

tr = turtle.getturtle()

tr.shape("turtle")

 

tr.speed('fast')

IN_TIMES = 40

TIMES = 20

for i in range(TIMES):

tr.right(360/TIMES)

tr.forward(200/TIMES)

for j in range(IN_TIMES):

tr.right(360/IN_TIMES)

tr.forward (400/IN_TIMES)

 

 

#tr.write(" Click me to exit", font = ("Courier", 12, "bold") )

screen = turtle.Screen()

screen.exitonclick()

 

可以看看Pythonturtle库中的example代码,其操作复杂到让你会误以为这是Flash完成的效果,其实,这都是乌龟的爬行而已。呵呵,要想以一个乌龟完成很多事情,除了需要足够好的数学基础外,还需要一点想象力。。。。。。。当然,还有美感。Gregor Lingl明显都不缺这些,所以他完成了一个很有意思的turtle库及很有意思的示例。

    turtle熟悉Python的基本语法估计是没有问题并且充满乐趣的,乐趣对初学者来说可是最重要的东西。并且,当你想用其实现更复杂逻辑的时候,也不得不用到更多的Python特性,这个自然过程会促进/巩固对Python的学习。这也许是除了单独学习一门语言外,另一个初学者可以尝试的入门之路,充满乐趣的路途。

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

初学编程该怎么学?——对初学者程序设计语言学习的思考(1)


初学编程该怎么学?——对初学者程序设计语言学习的思考

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

作为一个靠C++吃饭的服务器端程序员,同时也可以算是个计算机程序语言的爱好者,与干一行厌一行的人不同,我是先因为自己喜欢编程,然后才放弃自己的专业通过自学走上了靠编程吃饭的道路,并且虽然现实和理想有点偏差-_-!(估计大家都知道我说的是啥)但是,无论工作多么忙,加班多么晚,我没有放弃自己的爱好,还是愉快的学习着。同时,也尝试着传播自己的想法。

对于普通人,逻辑方面不是非常强的普通人,对于Python这个号称接近自然语言,号称是可以执行的伪码的语言,一般也不会感觉到多么有趣,总是觉得一堆字符太过枯燥。的确是,一般而言,图形的编程即使在Python中也是比较复杂的(更不用说C++,JAVA了),没有语言的教学一开始就教图形编程的,(拖拖控件的那种我认为不算语言的教学,最多算工具的教学)这也是程序语言教程枯燥的地方。

即便很多人说过Python适合作为程序语言学习的第一种语言,但是Python语言本身对于初学者来说并没有多大实际的趣味性,虽然很多高人号称交互式的命令行方式很适于学习,但是对于真正的初学者还是一样的枯燥。而更多更加高等级抽象的内容,比如列表,元组,字典,字符串的格式化等概念对于初学者来说就不是太好理解,更不用说列表解析语法,对象,异常等东西了,甚至,函数的概念,对于有些人来说都不好理解。那么,既然,Python这样号称非常简单的语言对于初学者来说都不是那么好理解并且枯燥的,那么,还有更简单的语言吗?

以前我一直以为很难找到了。后来偶然看到了MSSB语言,名字很奇怪吧。。。Small Basic语言,设计给小孩子学习的语言,因为设计给小孩子用,所以足够的简单,并且,MS的一贯作风,设计的足够花哨,足够好看,足够有意思。其中的turtle,即便是我初次使用都感觉很有意思,看到一个乌龟走来走去,有意思。。。。的确,假如仅仅是学习程序的逻辑和语法的话,一个乌龟就够了。。。。这是我当时的想法。。。当然,这仅仅是针对于不是准备将程序设计作为职业的人来说(其实即便你想以程序设计为职业也不是不可以从一个有趣的地方开始)。

    通过简单的程序语法,就可以实现画出较为复杂的图形,这在普通的Python中要实现,好歹也得学会TKWxPython,PyGTK,PyQt等东西中其中的一种吧,这些可没有那么简单。图形对于初学者来说和字符就是完全两种感受,他们不会感觉到对于数字的计算,字符串的拼接是在编程,但是对于实际能看到的一个乌龟的一段爬行,那就是编程了。

    看看turtle的例子。(初级的例子Introducing Small Basic中有介绍,此文是安装Small Basic后附带的文档)。

Turtle.Show()

表示显示,

Turtle.Move(100)

表示移动,

TurnTurnLeft,TurnRight

表示转向

如此简单,但是却可以完成一些较为复杂的东西。

文中典型的例子是画一个矩形:

Turtle.Move(100)

Turtle.TurnRight()

Turtle.Move(100)

Turtle.TurnRight()

Turtle.Move(100)

Turtle.TurnRight()

Turtle.Move(100)

Turtle.TurnRight()

画出的效果如图:


例一:

首先我提出的是画一个三角形,呵呵,有点意思。

Turtle.TurnLeft()

Turtle.Move(100)

Turtle.Turn(120)

Turtle.Move(100)

Turtle.Turn(120)

Turtle.Move(100)

 

 


第二例,接着我希望有个圆形,这个题目一下子复杂到能难住很多人了。对于从《怎么解题》中获得的教益,我可以提出几个更容易解决的问题,等边3角形,正方形我们已经会了,等边6边形呢?等边12边形呢?当边越来越多,会发现越来越接近圆。。。

 


当我画出一个50边形的时候。。。你还能认出来吗?

 


你看着就像是一个圆了。。。。。思路似乎来自于原来学校中讲圆时数学老师讲的圆某个公式的推导。本来画一个圆的常规想法应该是,以一点与圆心保持一个半径的距离,并且环绕一周。。。。

当画的边越来越多,自然会发现,原来一步一步的代码输入方式不行了,会很强烈的感觉到“循环”引入的需要,于是,循环的语法出来了。上述作图的源码如下:

TIMES = 50

For j = 1 To TIMES

  Turtle.Turn(360/TIMES)

  Turtle.Move(600/TIMES)

EndFor

(写此文时才发现Introducing Small Basic中已经有类似的例子了,并且源代码如下:)

sides = 12

length = 400 / sides

angle = 360 / sides

For i = 1 To sides

Turtle.Move(length)

Turtle.Turn(angle)

EndFor

我接着看到了Introducing Small Basic中一个很漂亮的图形,

于是提出解决此问题,竟然很容易从图形中看出思路,无非就是乌龟在原地没转一个方向就画一个圆嘛。

OUT_TIMES = 20

TIMES = 50

Turtle.Speed = 10

For i = 1 To OUT_TIMES

  For j = 1 To TIMES

   Turtle.Turn(360/TIMES)

   Turtle.Move(600/TIMES)

  EndFor

  Turtle.Turn(360/OUT_TIMES)

EndFor

 

(文中也有类似实现)

 

 

 


接着文中用直接画椭圆的方式画出了如下图形:

 


我决定用乌龟走出来,思路也来的很简单,首先乌龟在中间那个圆上走,然后每走一步,就向外再走一个圆,就成了这样的管道形状了。。。。。。。效果如下:

 

 


源代码:

IN_TIMES = 40

TIMES = 20

Turtle.Speed = 10

For i = 1 To TIMES

   Turtle.Turn(360/TIMES)

   Turtle.Move(200/TIMES)

   For j = 1 To IN_TIMES

     Turtle.Turn(360/IN_TIMES)

     Turtle.Move(400/IN_TIMES)

   EndFor

EndFor

 

作为学习编程几年的并且现在还靠着号称世界上特别复杂的一种语言C++活着的人来说,我也能在这些简单的图形中绘制中找到编程的乐趣,这就是turtle的乐趣了,我想初学者能找到的乐趣会比我更加多吧。

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

异常处理与MiniDump详解(2) 智能指针与C++异常


异常处理与MiniDump详解(2)  智能指针与C++异常

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   综述

异常处理与MiniDump详解(1) C++异常》稍微回顾了下C++异常的语法及其类似于函数参数传递的抛出异常对象的copy,引用语义,但是有个问题没有详细讲,那就是C++异常的绝佳搭档,智能指针。在没有智能指针的时候会感觉C++的异常少了一个用于释放资源的finally语法,但是C++没有这样的语法是有理由的,因为C++的智能指针。假如不用智能指针仅仅使用异常,那就像是吃一道没有放肉的辣椒炒肉一样。。。。。。。。。。。

智能指针对于C++的重要性很多人可能并没有认识到,看看C++相关的书籍吧,几乎每本都有其痕迹,从《C++ Primer,TCPL到《C++沉思录》《C++编程艺术》《C++Templates》,Meyes的《Effective C++》中讲过,《More Effective C++》再讲,无论是概念,用法,实现都是一本一本反复提起,这样反复提起,反复讲解的知识,自然是有其作用的,其作用也很简单,为了弥补C++对于内存管理的不足。众所周知,C++对于内存管理的方式称为手动管理,说白了就是C++本身不管理,都由程序员管理,事实上导致的问题一片一片,不记得多少次反复的在公司的服务器程序中去查内存泄露了,几乎随着每次大规模功能的更新,都会有新的内存泄露出现。。。。。C++相信程序员的原则告诉我们,程序员总是对的,那么即使是内存泄露了,也是有他的理由。。。。。。他的理由就是下一次可以进行内存泄露检查的工作,以浪费一天又一天的时间。随着BoundsChecker这样的工具出现,虽然简化了一部分明显的内存泄露检查,但是实际上复杂情况下的内存泄露还是只能靠自己完成,BoundsChecker误报的本事太强了,并且根本无法正常运行公司的地图服务器并退出,可能因为随后的内存泄露正常报告+误报超出了BoundsChecker的整数上限,总是会将BoundsChecker拉入无响应的状态。

事实上,我工作中也用的相对较少,毕竟C++标准库中的auto_ptr是个比较扭曲的东西,不仅使用语义奇怪,碰到稍微复杂的应用就根本胜任不了了,这是很无奈的事情。这一点可以参考我很久以前的文章《C++可怜的内存管理机制漫谈及奇怪补救auto_ptr介绍》,文中可以看到,在加入了类,异常机制,并且延续着C语言中手动管理内存方式的C++中,auto_ptr其实最多也就算种奇异的补丁机制。

但是我们可以求助于boost的智能指针库,那里丰富的资源改变了很多事情,但是我工作中是不允许使用boost库的。。。。又一次的无奈。

 

二、   智能指针

1.      什么是智能指针

要知道什么是智能指针,首先了解什么称为 “资源分配即初始化”这个翻译的异常扭曲的名词。RAII—Resource Acquisition Is Initialization,外国人也真有意思,用一个完整的句子来表示一个应该用名词表示的概念,我们有更有意思了,直接翻译过来,相当扭曲。。。。。

在《C++ Primer》这样解释的,“通过定义一个类来封装资源的分配和释放,可以保证正确释放资源”

而智能指针就是这种技术的实现,《More Effective C++》中这样描述的:“Smart pointers are objects that are designed to look,act,and feel like build-in pointers,but to offer greater functionality.They have a variety of applications, including resource management.

Effective C++》给出的关键特点是:

1.     资源分配后立即由资源管理对象接管。

2.     资源管理对象用析构函数来确保资源被释放。

基本上,这就是智能指针的核心概念了,至于智能指针实现上的特点,比如所有权转移,所有权独占,引用计数等,都是次要的东西了。

 

目前我见过关于各种智能指针分类,介绍,使用方法说明最详细的应该是《Beyond the C++ Standard Library: An Introduction to Boost》一书,此书第一章第一个专题库就是关于智能指针的,除了对标准库中已有的auto_ptr没有介绍(因为本书是讲Boost的嘛),对Boost库中的智能指针进行了较为详细的描述,推荐想了解的都去看看。

       文中论及的智能指针包括

scoped_ptrscoped_array:所有权限制实现

shared_ptrshared_array:引用计数实现

intrusive_ptr:引用计数插入式实现

weak_ptr:无所有权实现

关于智能指针的实现及原理,本人看过最详细的介绍是在More Effective C++ Items 28,29

这里仅仅介绍最广泛使用的智能指针shared_ptr,加上以前写过的auto_ptr(C++可怜的内存管理机制漫谈及奇怪补救auto_ptr介绍)给出智能指针的一些用法示例,其他的智能指针因为实现上的区别导致使用上也有一些区别,但是核心概念是一样的,都是上面提及的两条关键特点。

 

2.      shared_ptr介绍

shared_ptr是通过引用计数计数实现的智能指针,应用也最为广泛,也是早在TR1就已经确认会进入下一版C++标准的东西,现在我还会因为标准库中没有,boost库不准用而遗憾,过几年,总有一天,我们就能自由使用类似shared_ptr的指针了。

原型如下:

namespace boost {

 

    template<typename T> class shared_ptr {

    public:

       template <class Y> explicit shared_ptr(Y* p);

       template <class Y,class D> shared_ptr(Y* p,D d);

 

       ~shared_ptr();

 

       shared_ptr(const shared_ptr & r);

       template <class Y> explicit

           shared_ptr(const weak_ptr<Y>& r);

       template <class Y> explicit shared_ptr(std::auto_ptr<Y>& r);

 

       shared_ptr& operator=(const shared_ptr& r);

 

       void reset();

 

       T& operator*() const;

       T* operator->() const;

       T* get() const;

 

       bool unique() const;

       long use_count() const;

 

       operator unspecified_bool_type() const;

 

       void swap(shared_ptr<T>& b);

    };

 

    template <class T,class U>

    shared_ptr<T> static_pointer_cast(const shared_ptr<U>& r);

}

 

 

首先,为了查看资源分配方便,引入一个方便查看资源转移,拷贝情况的类:

#include <string>

#include <iostream>

 

template<class T>

class CResourceObserver

{

public:

    CResourceObserver()

    {

       cout <<typeid(T).name() <<" Construct." <<endl;

    }

 

    CResourceObserver(const CResourceObserver& orig)

    {

       cout <<typeid(T).name() <<" Copy Construct." <<endl;

    }

 

    operator=(const CResourceObserver& orig)

    {

       cout <<typeid(T).name() <<" operator = " <<endl;

    }

 

    virtual ~CResourceObserver(void)

    {

       cout <<typeid(T).name() <<" Deconstruct." <<endl;

    }

 

};

 

这个类,利用了运行时类型识别及模板,这样发生与资源有关的操作时,都能通过输出恰当的反映出来。

 

shared_ptr的最简单应用

这里看个最简单的shared_ptr使用的例子,顺便看看CResourceObserver的使用。在最简单的一次性使用上,shared_ptr几乎没有区别。

例一:

#include <boost/smart_ptr.hpp>

#include "ResourceObserver.h"

using namespace std;

using namespace boost;

 

class MyClass : public CResourceObserver<MyClass>

{

};

 

 

void Fun()

{

    shared_ptr<MyClass> sp(new MyClass);

}

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    cout <<"Fun called." <<endl;

    Fun();

    cout <<"Fun ended." <<endl;

 

    return 0;

}

 

输出如下:

Fun called.

class MyClass Construct.

class MyClass Deconstruct.

Fun ended.

我们只new,并没有显式的delete,但是MyClass很显然也是析构了的。

这里将shared_ptr替换成auto_ptr也是完全可以的,效果也一样。

 

shared_ptr的与auto_ptr的区别

shared_ptrauto_ptr的区别在于所有权的控制上。如下例:

例二:

#include <boost/smart_ptr.hpp>

#include <memory>

#include "ResourceObserver.h"

using namespace std;

using namespace boost;

 

class MyClass : public CResourceObserver<MyClass>

{

public:

    MyClass() : CResourceObserver<MyClass>()

    {

       mstr = typeid(MyClass).name();

 

    }

 

    void print()

    {

       cout <<mstr <<" print" <<endl;

    }

 

    std::string mstr;

};

 

typedef  shared_ptr<MyClass> spclass_t;

//typedef  auto_ptr<MyClass> spclass_t;

 

void Fun2(spclass_t& asp)

{

    spclass_t sp3(asp);

    cout <<asp.use_count() <<endl;

 

    asp->print();

    return;

}

 

 

void Fun()

{

    spclass_t sp(new MyClass);

    cout <<sp.use_count() <<endl;

 

    spclass_t sp2(sp);

    cout <<sp.use_count() <<endl;

 

    Fun2(sp);

    cout <<sp.use_count() <<endl;

}

 

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    cout <<"Fun called." <<endl;

    Fun();

    cout <<"Fun ended." <<endl;

 

    return 0;

}

 

输出:

Fun called.

class MyClass Construct.

1

2

3

class MyClass print

2

class MyClass Deconstruct.

Fun ended.

此例中将shared_ptr分配的资源复制了3份(实际是管理权的复制,资源明显没有复制),每一个shared_ptr结束其生命周期时释放一份管理权。每一个都有同等的使用权限。输出的引用计数数量显式了这一切。在这里,可以尝试替换shared_ptrauto_ptr,这个程序没有办法正确运行。

 

shared_ptr的引用计数共享所有权

因为没有拷贝构造及operator=的操作,我们可以知道,对象没有被复制,为了证实其使用的都是同一个资源,这里再用一个例子证明一下:

3

#include <boost/smart_ptr.hpp>

#include <memory>

#include "ResourceObserver.h"

using namespace std;

using namespace boost;

 

class MyClass : public CResourceObserver<MyClass>

{

public:

    MyClass() : CResourceObserver<MyClass>()

    {

       mstr = typeid(MyClass).name();

 

    }

 

    void set(const char* asz)

    {

       mstr = asz;

    }

 

    void print()

    {

       cout <<mstr <<" print" <<endl;

    }

 

    std::string mstr;

};

 

typedef  shared_ptr<MyClass> spclass_t;

 

void Fun2(spclass_t& asp)

{

    spclass_t sp3(asp);

 

    sp3->set("New Name");

    return;

}

 

 

void Fun()

{

    spclass_t sp(new MyClass);

    spclass_t sp2(sp);

 

    Fun2(sp);

 

    sp->print();

    sp2->print();

}

 

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    cout <<"Fun called." <<endl;

    Fun();

    cout <<"Fun ended." <<endl;

 

    return 0;

}

 

 

输出:

Fun called.

class MyClass Construct.

New Name print

New Name print

class MyClass Deconstruct.

Fun ended.

 

shared_ptr与标准库容器

在标准库容器中存入普通指针来实现某个动态绑定的实现是很普遍的事情,但是实际上每次都得记住资源的释放,这也是BoundsChecker误报的最多的地方。

4

#include <boost/smart_ptr.hpp>

#include <vector>

#include "ResourceObserver.h"

using namespace std;

using namespace boost;

 

class MyClass : public CResourceObserver<MyClass>

{

public:

    MyClass() : CResourceObserver<MyClass>()

    {

       mstr = typeid(MyClass).name();

 

    }

 

    void set(const char* asz)

    {

       mstr = asz;

    }

 

    void print()

    {

       cout <<mstr <<" print" <<endl;

    }

 

    std::string mstr;

};

 

typedef  shared_ptr<MyClass> spclass_t;

typedef  vector< shared_ptr<MyClass> > spclassVec_t;

 

 

void Fun()

{

    spclassVec_t spVec;

    spclass_t sp(new MyClass);

    spclass_t sp2(sp);

 

    cout <<sp.use_count() <<endl;

    cout <<sp2.use_count() <<endl;

 

    spVec.push_back(sp);

    spVec.push_back(sp2);

 

    cout <<sp.use_count() <<endl;

    cout <<sp2.use_count() <<endl;

 

    sp2->set("New Name");

    sp->print();

    sp2->print();

 

    spVec.pop_back();

    cout <<sp.use_count() <<endl;

    cout <<sp2.use_count() <<endl;

 

    sp->print();

    sp2->print();

 

}

 

 

 

int _tmain(int argc, _TCHAR* argv[])

{

    cout <<"Fun called." <<endl;

    Fun();

    cout <<"Fun ended." <<endl;

 

    return 0;

}

 

输出:

Fun called.

class MyClass Construct.

2

2

4

4

New Name print

New Name print

3

3

New Name print

New Name print

class MyClass Deconstruct.

Fun ended.

当在标准库容器中保存的是shared_ptr时,几乎就可以不考虑资源释放的问题了,该释放的时候自然就释放了,当一个资源从一个容器辗转传递几个地方的时候,常常会搞不清楚在哪个地方统一释放合适,用了shared_ptr后,这个问题就可以不管了,每次的容器Item的添加增加计数,容器Item的减少就减少计数,恰当的时候,就释放了。。。。方便不可言喻。

 

 

3.      智能指针的高级应用:

已经说的够多了,再说下去几乎就要脱离讲解智能指针与异常的本意了,一些很有用的应用就留待大家自己去查看资料吧。

1.     定制删除器,shared_ptr允许通过定制删除器的方式将其用于其它资源的管理,几乎只要是通过分配,释放形式分配的资源都可以纳入shared_ptr的管理范围,比如文件的打开关闭,目录的打开关闭等自然不在话下,甚至连临界区,互斥对象这样的复杂对象,一样可以纳入shared_ptr的管理。

2.     this创建shared_ptr  

以上两点内容在《Beyond the C++ Standard Library: An Introduction to Boost》智能指针的专题中讲解了一些,但是稍感不够详细,但是我也没有看到更为详细的资料,聊胜于无吧。

3.     Pimpl

Beyond the C++ Standard Library: An Introduction to Boost》中将智能指针的时候有提及,在《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices 43 Pimpl judiciously中对其使用的好处,坏处,方式等都有较为详细的讲解,大家可以去参考一下,我就不在此继续献丑了。

但是,事实上,很多时候并不是只能使用智能指针(比如pimpl),只是,假如真的你合理的使用了智能指针,那么将会更加安全,更加简洁。

以下是《Beyond the C++ Standard Library: An Introduction to Boost》对shared_ptr使用的建议:

1.当有多个使用者使用同一个对象,而没有一个明显的拥有者时

2.当要把指针存入标准库容器时

3.当要传送对象到库或从库获取对象,而没有明确的所有权时

4.当管理一些需要特殊清除方式的资源时

 

三、   智能指针与C++异常

因为考虑到大家可能对智能指针不够熟悉,所以讲到这里的时候对智能指针进行了较多的讲解,几乎就脱离主题了,在这里开始进入正题。

一开始我就讲了智能指针对于C++的异常机制非常重要,到底重要在哪里呢?这里看两个C++异常的例子,一个使用了没有使用智能指针,一个使用了智能指针。

 

void Fun()

{

    MyClass* lp1 = NULL;

    MyClass* lp2 = NULL;

    MyClass* lp3 = NULL;

 

    lp1 = new MyClass;

    try

    {

       lp2 = new MyClass;

    }

    catch(bad_alloc)

    {

       delete lp1;

    }

 

    try

    {

       lp3 = new MyClass;

    }

    catch(bad_alloc)

    {

       delete lp1;

       delete lp2;

    }

 

    // Do Something.....

 

    delete lp1;

    delete lp2;

    delete lp3;

}

 

void Fun2()

{

    try

    {

       spclass_t sp1(new MyClass);

       spclass_t sp2(new MyClass);

       spclass_t sp3(new MyClass);

    }

    catch(bad_alloc)

    {

       // No need to delete anything

    }

 

    // Do Something

 

 

    // No need to delete anything

}

 

区别,好处,明眼人不用看第二眼。这里为了简单,用内存的作为示例,虽然现在内存分配的情况很少见了,但是其他资源原理上是一样的,个人经验最深的地方实在PythonC API使用上,哪怕一个简单的C++Python函数相互调用,都会有NPyObject*出来,一个一个又一个,直到头昏脑胀,使用了智能指针后,简化的不止一半代码。

其实从本质上来讲,异常属于增加了程序从函数退出的路径,而C++原来的内存管理机制要求每个分支都需要手动的释放每个分配了的资源,这是本质的复杂度,在用于普通return返回的时候,还有一些hack技巧,见《do...while(0)的妙用》,但是异常发生的时候,能够依赖的就只有手动和智能指针两种选择了。

       在没有智能指针的光使用异常的时候,甚至会抱怨因为异常增加了函数的出口,导致代码的膨胀,说智能指针是C++异常处理的绝佳搭档就在于其弥补的此缺点。

另外,其实很多语言还有个finally的异常语法,JAVA,Python都有,SEH也有,其与使用了智能指针的C++异常比较在刘未鹏关于异常处理的文章《错误处理(Error-Handling):为何、何时、如何(rev#2)》中也有详细描述,我就不在此多费口舌了,将来讲SEH的时候自然还会碰到。个人感觉是,有也不错。。。。毕竟,不是人人都有机会在每个地方都用上智能指针。

 

四、   参考资料

1.C++ Primer,中文版第4版,Stanley B.Lippman, Josee lajoie, Barbara E.Moo 人民邮电出版社

2.Effective C++Third Edition 英文版,Chapter 3 Resource Management Scott Meyes著,电子工业出版社

3.More Effective C++(英文版),Scott Meyes著,Items 28,29,机械工业出版社

4.Beyond the C++ Standard Library: An Introduction to BoostBy Björn Karlsson著,Part 1,Library 1Addison Wesley Professional

5.C++ Coding Standards: 101 Rules, Guidelines, and Best Practices

Herb Sutter, Andrei Alexandrescu著, Addison Wesley Professional

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

异常处理与MiniDump详解(1) C++异常

异常处理与MiniDump详解(1) C++异常

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

一、   综述

我很少敢为自己写的东西弄个详解的标题,之所以这次敢于这样,自然还算是有点底气的。并且也以此为动力,督促自己好好的将这两个东西研究透。

当年刚开始工作的时候,第一个工作就是学习breakpad的源代码,然后了解其原理,为公司写一个ExceptionHandle的库,以处理服务器及客户端的未处理异常(unhandle exception),并打下dump,以便事后分析,当年这个功能在有breakpad的示例在前时,实现难度并不大,无非就是调用了SetUnhandledExceptionFilter等函数,让windows在出现未处理异常时让自己的回调函数接管操作,然后利用其struct _EXCEPTION_POINTERS* ExceptionInfo的指针,通过MiniDumpWriteDump APIDump写下来。但是仍记得,那时看到《Windows 核心编程》第五部分关于结构化异常处理的描述时那种因为得到新鲜知识时的兴奋感,那是我第一次这样接近Windows系统的底层机制,如同以前很多次说过的,那以后我很投入的捧着读完了《Windows 核心编程》,至今受益匪浅。当时也有一系列一边看源代码一边写下心得的时候,想想,都已经一年以前的事情了。

windows核心编程,结构化异常部分,理解摘要

Breakpad在进程中完成dump的流程描述

Breakpad 使用方法理解文档

直到最近,为了控制服务器在出现异常时不崩溃,(以前是崩溃的时候打Dump),对SEHwindows结构化异常)又进行了进一步的学习,做到了在服务器出现了异常情况(例如空指针的访问)时,服务器打下Dump,并继续运行,并不崩溃,结合以前也是我写的监控系统,通知监控客户端报警,然后就可以去服务器上取回dump,并分析错误,这对服务器的稳定性有很大的帮助,不管我们对服务器的稳定性进行了多少工作,作为C++程序,偶尔的空指针访问,几乎没有办法避免。。。。。。但是,这个工作,对这样的情况起到了很好的缓冲作用。在这上面工作许久,有点心得,写下来,供大家分享,同时也是给很久以后的自己分享。

 

二、   为什么需要异常

Windows核心编程》第4版第13章开头部分描述了一个美好世界,即所编写的代码永远不会执行失败,总是有足够的内存,不存在无效的指针。。。。但是,那是不存在的世界,于是,我们需要有一种异常的处理措施,在C语言中最常用的(其实C++中目前最常用的还是)是利用错误代码(Error Code)的形式。

这里也为了更好的说明,也展示一下Error Code的示例代码:

Error Code常用方式:

1.最常用的就是通过返回值判断了:

比如C Runtime Library中的fopen接口,一旦返回NULL,Win32 API中的CreateFiley一旦返回INVALID_HANDLE_VALUE,就表示执行失败了。

 

2.当返回值不够用(或者携带具体错误信息不够的)时候,C语言中也常常通过一个全局的错误变量来表示错误。

比如C Runtime Library中的errno 全局变量,Win32 API中的GetLastErrorWinSock中的WSAGetLastError函数就是这种实现。

 

既然Error Code在这么久的时间中都是可用的,好用的,为什么我们还需要其他东西呢?

这里可以参考一篇比较浅的文章。《错误处理和异常处理,你用哪一个》,然后本人比较钦佩的pongba还有一篇比较深的文章:《错误处理(Error-Handling):为何、何时、如何(rev#2)》,看了后你一定会大有收获。当pongba列出了16条使用异常的好处后,我都感觉不到我还有必要再去告诉你为什么我们要使用异常了。

但是,这里在其无法使用异常的意外情况下,(实际是《C++ Coding Standards: 101 Rules, Guidelines, and Best Practices》一书中所写)

一,     用异常没有带来明显的好处的时候:比如所有的错 误都会在立即调用端解决掉或者在非常接近立即调用端的地方解决掉。

二,     在实际作了测定之后发现异常的抛出和捕获导致了显著的时间开销:这通常只有两种情 况,要么是在内层循环里面,要么是因为被抛出的异常根本不对应于一个错误。

很明显,文中列举的都是完全理论上理想的情况,受制于国内的开发环境,无论多么好的东西也不一定实用,你能说国内多少地方真的用上了敏捷开发的实践经验?这里作为现实考虑,补充几个没有办法使用异常的情况:

一.     所在的项目组中没有合理的使用RAII的习惯及其机制,比如无法使用足够多的smart_ptr时,最好不要使用异常,因为异常和RAII的用异常不用RAII就像吃菜不放盐一样。这一点在后面论述一下。

二.     当项目组中没有使用并捕获异常的习惯时,当项目组中认为使用异常是奇技淫巧时不要使用异常。不然,你自认为很好的代码,会在别人眼里不可理解并且作为异类,接受现实。

三、   基础篇

先回顾一下标准C++的异常用法

1.      C++标准异常

只有一种语法,格式类似:

try

{

}

catch()

{
}

经常简写为try-catch,当然,也许还要算上throw。格式足够的简单。

以下是一个完整的例子:

MyException:

#include <string>

#include <iostream>

using namespace std;

 

class MyException : public exception

{

public:

    MyException(const char* astrDesc)

    {

       mstrDesc = astrDesc;

    }

 

 

    string mstrDesc;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

    try

    {

       throw MyException("A My Exception");

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<endl;

    }

 

    return 0;

}

 

 

这里可以体现几个异常的优势,比如自己的异常继承体系,携带足够多的信息等等。另外,虽然在基础篇,这里也讲讲C++中异常的语义,

如下例子中,

throwException:

#include <string>

#include <iostream>

using namespace std;

 

class MyException : public exception

{

public:

    MyException(const char* astrDesc)

    {

       mstrDesc = astrDesc;

    }

 

    MyException(const MyException& aoOrig)

    {

       cout <<"Copy Constructor MyException" <<endl;

       mstrDesc = aoOrig.mstrDesc;

    }

 

    MyException& operator=(const MyException& aoOrig)

    {

       cout <<"Copy Operator MyException" <<endl;

       if(&aoOrig == this)

       {

           return *this;

       }

 

       mstrDesc = aoOrig.mstrDesc;

       return *this;

    }

 

    ~MyException()

    {

       cout <<"~MyException" <<endl;

    }

 

 

    string mstrDesc;

};

 

void exceptionFun()

{

    try

    {

       throw MyException("A My Exception");

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" In exceptionFun." <<endl;

       e.mstrDesc = "Changed exception.";

       throw;

    }

}

 

int _tmain(int argc, _TCHAR* argv[])

{

    try

    {

       exceptionFun();

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" Out exceptionFun." <<endl;

       throw;

    }

 

 

    return 0;

}

 

输出:

Copy Constructor MyException

A My Exception In exceptionFun.

~MyException

Copy Constructor MyException

A My Exception Out exceptionFun.

~MyException

可以看出当抛出C++异常的copy语义,抛出异常后调用了Copy Constructor,用新建的异常对象传入catch中处理,所以在函数中改变了此异常对象后,再次抛出原异常,并不改变原有异常。

这里我们经过一点小小的更改,看看会发生什么:

throwAnotherException

#include <string>

#include <iostream>

using namespace std;

 

class MyException : public exception

{

public:

    MyException(const char* astrDesc)

    {

       mstrDesc = astrDesc;

    }

 

    MyException(const MyException& aoOrig)

    {

       cout <<"Copy Constructor MyException" <<endl;

       mstrDesc = aoOrig.mstrDesc;

    }

 

    MyException& operator=(const MyException& aoOrig)

    {

       cout <<"Copy Operator MyException" <<endl;

       if(&aoOrig == this)

       {

           return *this;

       }

 

       mstrDesc = aoOrig.mstrDesc;

       return *this;

    }

 

    ~MyException()

    {

       cout <<"~MyException" <<endl;

    }

 

 

    string mstrDesc;

};

 

void exceptionFun()

{

    try

    {

       throw MyException("A My Exception");

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" In exceptionFun." <<endl;

        e.mstrDesc = "Changed exception.";

       throw e;

    }

}

 

int _tmain(int argc, _TCHAR* argv[])

{

    try

    {

       exceptionFun();

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" Out exceptionFun." <<endl;

       throw;

    }

 

 

    return 0;

}

 

这里和throwException程序的唯一区别就在于不是抛出原有异常,而是抛出改变后的异常,输出如下:

Copy Constructor MyException

A My Exception In exceptionFun.

Copy Constructor MyException

Copy Constructor MyException

~MyException

~MyException

Changed exception. Out exceptionFun.

~MyException

你会发现连续的两次Copy Constructor都是改变后的异常对象,这点很不可理解。。。。。。。因为事实上一次就够了。但是理解C++Copy异常处理语义就好理解了,一次是用于传入下一次的catch语句中的,还有一次是留下来,当在外层catch再次throw时,已经抛出的是改变过的异常对象了,我用以下例子来验证这点:

throwTwiceException

#include <string>

#include <iostream>

using namespace std;

 

class MyException : public exception

{

public:

    MyException(const char* astrDesc)

    {

       mstrDesc = astrDesc;

    }

 

    MyException(const MyException& aoOrig)

    {

       cout <<"Copy Constructor MyException" <<endl;

       mstrDesc = aoOrig.mstrDesc;

    }

 

    MyException& operator=(const MyException& aoOrig)

    {

       cout <<"Copy Operator MyException" <<endl;

       if(&aoOrig == this)

       {

           return *this;

       }

 

       mstrDesc = aoOrig.mstrDesc;

       return *this;

    }

 

    ~MyException()

    {

       cout <<"~MyException" <<endl;

    }

 

 

    string mstrDesc;

};

 

void exceptionFun()

{

    try

    {

       throw MyException("A My Exception");

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" In exceptionFun." <<endl;

       e.mstrDesc = "Changed exception.";

       throw e;

    }

}

 

void exceptionFun2()

{

    try

    {

       exceptionFun();

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" In exceptionFun2." <<endl;

       throw;

    }

 

}

 

int _tmain(int argc, _TCHAR* argv[])

{

    try

    {

       exceptionFun2();

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" Out exceptionFuns." <<endl;

       throw;

    }

 

 

    return 0;

}

 

输出如下,印证了我上面的说明。

Copy Constructor MyException

A My Exception In exceptionFun.

Copy Constructor MyException

Copy Constructor MyException

~MyException

~MyException

Changed exception. In exceptionFun2.

~MyException

Copy Constructor MyException

Changed exception. Out exceptionFuns.

上面像语言律师一样的讨论着C++本来已经足够简单的异常语法,其实简而言之,C++总是保持着一个上次抛出的异常用于用户再次抛出,并copy一份在catch中给用户使用。

但是,实际上,会发现,其实原有的异常对象是一直向上传递的,只要你不再次抛出其他异常,真正发生复制的地方在于你catch异常的时候,这样,当catch时使用引用方式,那么就可以避免这样的复制。

referenceCatch

#include <string>

#include <iostream>

using namespace std;

 

class MyException : public exception

{

public:

    MyException(const char* astrDesc)

    {

       mstrDesc = astrDesc;

    }

 

    MyException(const MyException& aoOrig)

    {

       cout <<"Copy Constructor MyException: " <<aoOrig.mstrDesc <<endl;

       mstrDesc = aoOrig.mstrDesc;

    }

 

    MyException& operator=(const MyException& aoOrig)

    {

       cout <<"Copy Operator MyException:" <<aoOrig.mstrDesc <<endl;

       if(&aoOrig == this)

       {

           return *this;

       }

 

       mstrDesc = aoOrig.mstrDesc;

       return *this;

    }

 

    ~MyException()

    {

       cout <<"~MyException" <<endl;

    }

 

 

    string mstrDesc;

};

 

void exceptionFun()

{

    try

    {

       throw MyException("A My Exception");

    }

    catch(MyException& e)

    {

       cout <<e.mstrDesc <<" In exceptionFun." <<endl;

       e.mstrDesc = "Changed exception.";

       throw;

    }

}

 

void exceptionFun2()

{

    try

    {

       exceptionFun();

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" In exceptionFun2." <<endl;

       throw;

    }

 

}

 

int _tmain(int argc, _TCHAR* argv[])

{

    try

    {

       exceptionFun2();

    }

    catch(MyException e)

    {

       cout <<e.mstrDesc <<" Out exceptionFuns." <<endl;

       throw;

    }

 

 

    return 0;

}

 

上例中,使用引用方式来捕获异常,输出如下:

A My Exception In exceptionFun.

Copy Constructor MyException: Changed exception.

Changed exception. In exceptionFun2.

~MyException

Copy Constructor MyException: Changed exception.

Changed exception. Out exceptionFuns.

~MyException

完全符合C++的引用语义。

基本可以发现,做了很多无用功,因为try-catch无非是一层迷雾,其实这里复制和引用都还是遵循着原来的C++简单的复制,引用语义,仅仅这一层迷雾,让我们看不清楚原来的东西。所以,很容易理解一个地方throw一个对象,另外一个地方catch一个对象一定是同一个对象,其实不然,是否是原来那个对象在于你传递的方式,这就像这是个参数,通过catch函数传递进来一样,你用的是传值方式,自然是通过了复制,通过传址方式,自然是原有对象,仅此而已。

另外,最终总结一下,《C++ Coding  Standards》73条建议Throw by value,catch by reference就是因为本文描述的C++的异常特性如此,所以才有此建议,并且,其补上了一句,重复提交异常的时候用throw;

四、   参考资料

1.     Windows核心编程(Programming Applications for Microsoft Windows,4版,Jeffrey Richter著,黄陇,李虎译,机械工业出版社

2.     MSDN—Visual Studio 2005 附带版,Microsoft

3.     错误处理和异常处理,你用哪一个apollolegend

4.     错误处理(Error-Handling):为何、何时、如何(rev#2)刘未鹏

 

  

write by 九天雁翎(JTianLing) -- www.jtianling.com

 

阅读全文....

igoogle 子曰:undefined

平时用igoogle,有个子曰的小widget,很有意思,会随机的抽出论语的某句话,一般没有什么问题,正常效果示意如图:

正常子曰

今天出现的问题就很恶搞了。。。。。我看着吓了一跳,孔子果然语出惊人,如图:

 

子曰:undefined

 

原来,孔子外语学的还不错。。。。。。我汗颜

阅读全文....

PyQt(2) 对话框


PyQt(2) 对话框

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

综述

对话框是比较简单的一种GUI形式,说其简单,不仅仅是处于开发的角度,从用户使用的角度来说也是如此,对话框可以说是一般图形界面程序的基本元素,即便是复杂的应用程序,往往也需要对话框来完成一些与用户交互的工程,并且,往往来说,创建基于对话框的程序也是比较简单的。也因为这样,在公司开发的众多工具当中,除了数据校验工具使用了MFC的文档视图(属于密集信息输出型),其他工具清一色的都是对话框,只不过,加了比较多的tab页,同样的强大。

最最简单的Win32汇编程序是一个MessageBox,这是对话框的一种简单形式,Windows中很多好用的公用对话框,也是极大的简化了开发。RAD工具的出现也几乎是为了对话框而设置的,当拖放控件的潮流出现后,很多人就开始设想下一步不用敲代码完成程序的编写了:)

基本上,开发对话框程序的难易,在一定程度上决定着一个GUI库的使用难易程度。下面介绍手动创建一个简单的对话框PyQt程序。

最简单形式的对话框:

这里就像是PyQt(1)中的例子quit.pyw一样,那个例子是一个单纯的按钮。。。。。。。。实际上又没有办法突然显示个按钮,所以Qt自动为其加上了一个边框,真正的程序应该是这样的:

simplestdialog.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4
 5 app = QtGui.QApplication(sys.argv)
 6
 7 dialog = QtGui.QDialog()
 8 quit = QtGui.QPushButton("Quit",dialog)
 9 QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
10                        app, QtCore.SLOT("quit()"))
11
12 dialog.show()
13 sys.exit(app.exec_())

 

这样两个程序其实差不多,但是仔细看还是有一定区别的。如图:

默认的Qt对话框没有最大化和最小化按钮,而自动为button生成的有,然后button那里button占据了整个用户区域,而对话框这里由其自动生成的大小并没有。

 

从面向过程到面向对象

到目前为止,PyQt(1)中所有的程序都还是用面向过程的方式。大概如PyQt(1)layout.pyw例子中的形式。

在面向过程到面向对象的比较上原来奇趣的一个教程有很有示范性的对比。(reference 3)

比如,面向过程是这样的形式:

 1 #!/usr/bin/env python
 2
 3 # PyQt tutorial 3
 4
 5
 6 import sys
 7 from PyQt4 import QtCore, QtGui
 8
 9
10 app = QtGui.QApplication(sys.argv)
11
12 window = QtGui.QWidget()
13 window.resize(200, 120)
14
15 quit = QtGui.QPushButton("Quit", window)
16 quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
17 quit.setGeometry(10, 40, 180, 40)
18 QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
19                        app, QtCore.SLOT("quit()"))
20
21 window.show()
22 sys.exit(app.exec_())

 

这是同样程序的面向对象版本:

 

 1 #!/usr/bin/env python
 2
 3 # PyQt tutorial 4
 4
 5
 6 import sys
 7 from PyQt4 import QtCore, QtGui
 8
 9
10 class MyWidget(QtGui.QWidget):
11     def __init__(self, parent=None):
12         QtGui.QWidget.__init__(self, parent)
13
14         self.setFixedSize(200, 120)
15
16         self.quit = QtGui.QPushButton("Quit", self)
17         self.quit.setGeometry(62, 40, 75, 30)
18         self.quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
19
20         self.connect(self.quit, QtCore.SIGNAL("clicked()"),
21                      QtGui.qApp, QtCore.SLOT("quit()"))
22
23
24 app = QtGui.QApplication(sys.argv)
25 widget = MyWidget()
26 widget.show()
27 sys.exit(app.exec_())

 

以上是两个几乎完全一样的程序,除了实现方式上的不同。

在实现上:

面向过程的方式大概的思路就像是生成一个默认的widget,然后通过函数调用,通过子widget按钮的创建去修饰这个widget,然后显示。

面向对象的方式大概思路就像是先描述我们想要的widget是有一个子widget按钮的,大小是多少。。。。。然后直接创建一个我们描述过的想要的widget,将其显示。

有人说,编程的方式取决于你看世界的方式-_-!其实这点用于对于面向过程和面向对象是比较合适的。

至于面向过程和面向对象的好处就不在这里费太多口舌了,也超出了本文的讨论范围。但是,即使是在这样简单的一个GUI程序中,不去讨论什么封装,数据隐藏等东西,也可以得出面向对象版本会更加容易复用一点。虽然N多书籍强调了使用面向对象技术最主要的原因不在于提高代码复用率,但是这一点确实是很有用并且最容易通过对比讲解的。比如,假如我们需要两个一样的这样的对话框显示不同标题,同时显示在不同位置的情况。

面向对象版本可以这样实现:

 1 #!/usr/bin/env python
 2 # PyQt tutorial 4 ext objWindow
 3
 4 import sys
 5 from PyQt4 import QtCore, QtGui
 6
 7
 8 class MyWidget(QtGui.QWidget):
 9     def __init__(self, parent=None):
10         QtGui.QWidget.__init__(self, parent)
11
12         self.setFixedSize(200, 120)
13
14         self.quit = QtGui.QPushButton("Quit", self)
15         self.quit.setGeometry(62, 40, 75, 30)
16         self.quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
17
18         self.connect(self.quit, QtCore.SIGNAL("clicked()"),
19                      QtGui.qApp, QtCore.SLOT("quit()"))
20
21
22 app = QtGui.QApplication(sys.argv)
23 widget = MyWidget()
24 widget.move(350,50)
25 widget.setWindowTitle("A Widget")
26 widget.show()
27 anotherWidget = MyWidget()
28 anotherWidget.move(50,50)
29 anotherWidget.setWindowTitle("Another Widget")
30 anotherWidget.show()
31 sys.exit(app.exec_())

 

尝试想象,面向过程的版本会怎么样?只能将所有的代码全部再复制一次,这面机械的操作我甚至都不想给出例子了。程序员的天性应该讨厌复制!

其实,这个面向对象版本的程序还有点问题,因为原来的程序中将对话框与应用程序混在一起了,所以连接按钮到qApp的退出槽上了,这样当你点击其中一个对话框的时候,两个对话框都关闭了。更加面向对象的做法是将其与对话框连接在一起。将那行代码改为:

self.connect(self.quit, QtCore.SIGNAL("clicked()"),

self, QtCore.SLOT("close()"))

这样就让两个对话框可以分开控制了,呵呵,关于类的职责问题一直是面向对象书籍讨论的重点,在这个简单的演示例子里面奇趣的教程也就没有考虑这么多了,所以才会出现这样的情况。

 

面向对象的对话框

前面的例子中,我们的widget是由QWidget继承而来,那么就是一个普通的widget,假如从dialog继承而来,那么就是一个dialog了,这样两者还是有一定区别的。

比如将上述的例子改成对话框。

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4
 5 class MyDialog(QtGui.QDialog):
 6     def __init__(self, parent=None):
 7         QtGui.QDialog.__init__(self, parent)
 8
 9         self.setFixedSize(200, 120)
10
11         self.quit = QtGui.QPushButton("Quit", self)
12         self.quit.setGeometry(62, 40, 75, 30)
13         self.quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
14
15         self.connect(self.quit, QtCore.SIGNAL("clicked()"),
16                      self, QtCore.SLOT("close()"))
17
18
19 app = QtGui.QApplication(sys.argv)
20 dialog = MyDialog()
21 dialog.show()
22 sys.exit(app.exec_())

 

显示的效果对比与前面的对比很相似:

 

这里我没有说明哪个是对话框,那个是widget,不过看过前面的对比也很明显了吧。这里有点要说明的是,在Qt中对话框与普通widget的区别与MFC中对话框与文档视图的区别要小多了,在上面的例子中我们也基本可以确定,当我们仅仅是想要显示一个button的时候,实际上Qt是为我们默认生成的是一个widget。

 

更复杂的对话框

《C++ GUI Qt4编程(第二版)》中第2章实现了一个比较复杂的对话框程序,我用Qt Creater尝试了其C++版本,好多代码要敲啊,所以反而丧失了再次敲代码去实现PyQt版本的意思。。。。。。。呵呵,不过,其实知道了大概流程和含义,看到C++版本的Qt程序,再实现一个PyQt版本的程序是非常省事的事情,都差不多。

不过,作为学习,我还是实现一个简化版本的PyQt对话框程序吧,不然private slots,Q_OBJECT,tr在PyQt中对应的东西我还真不知道。

complicateDialog.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4 class MyDialog(QtGui.QDialog):
 5     def __init__(self, parent=None):
 6         QtGui.QDialog.__init__(self, parent)
 7     
 8         self.quit = QtGui.QPushButton("Quit")
 9         
10         self.change = QtGui.QPushButton("Change")
11         self.change.setEnabled(False)
12
13         # funny widget
14         self.lcd = QtGui.QLCDNumber(2)
15
16         self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
17         self.slider.setRange(0, 99)
18         self.slider.setValue(0)
19         
20         self.lineEdit = QtGui.QLineEdit()
21         
22         self.connect(self.quit, QtCore.SIGNAL("clicked()"),
23                      QtGui.qApp, QtCore.SLOT("quit()"))
24         self.connect(self.lineEdit, QtCore.SIGNAL("textChanged(const QString&)"),
25                      self.enableChangeButton)
26         self.connect(self.slider, QtCore.SIGNAL("valueChanged(int)"),
27                      self.SliderChange)
28         self.connect(self.change, QtCore.SIGNAL("clicked()"),
29                      self.Change)
30         
31         self.rightLayout = QtGui.QVBoxLayout()
32         self.rightLayout.addWidget(self.lineEdit)
33         self.rightLayout.addWidget(self.change)
34         
35         self.leftLayout = QtGui.QVBoxLayout()
36         self.leftLayout.addWidget(self.lcd)
37         self.leftLayout.addWidget(self.slider)
38         
39         self.layout = QtGui.QHBoxLayout()
40         self.layout.addWidget(self.quit)
41         self.layout.addLayout(self.leftLayout)
42         self.layout.addLayout(self.rightLayout)
43         
44         self.setLayout(self.layout);
45         
46     def enableChangeButton(self, text):
47         self.change.setEnabled(text.isEmpty() == False)
48
49     def Change(self):
50         value = int(self.lineEdit.text())
51         self.lcd.display(value)
52         self.slider.setValue(value)
53
54     def SliderChange(self):
55         value = self.slider.value()
56         self.lcd.display(value)
57         self.lineEdit.setText(str(value))
58
59 app = QtGui.QApplication(sys.argv)
60 dialog = MyDialog()
61 dialog.show()
62 sys.exit(app.exec_())

 

 

我曾经怎么说来着?来个简化版?-_-!最后写着写着,为了试验新特性,结果说是复杂版也不为过了。

效果如图:

这个程序演示了几个Qt的特性:

  1. 很有意思的lcd效果的数字,这种效果竟然原生在Qt的Widget中,很有意思。
  2. 其中演示了4个widget的交互,分别是lcd,slider,change,lineEdit其中三个是可以控制的,lcd被动显示,每改动一个,都能在其他三个(两个)中反映出来。控制的方式是通过connect某个singal到对话框类的某个方法上。其中我发现PyQt的connect消息方式很搞笑,为什么这样说呢?因为在Python中,竟然出现了"textChanged(const QString&)"这样的字符串-_-!再强大的东西,毕竟是通过C++来实现的。。。呵呵,估计非C++背景的PyQt使用者会感到这样的语法比较困惑吧。。。。。。我也比较奇怪,PyQt为啥不干脆一次性将这样的Singal的字符串也改成Python的语法呢?呵呵,也许工作量会更大吧。当然,slot响应的方式倒是简化了一些,当时我还在想,以什么形式将slot的参数用字符串表示出来呢。。。。结果发现,完全不用表示。。。。如源代码所示。这方面有个文章讲的比较详细:Connecting with signals and slots

 

  1. layout的嵌套,实现了比较复杂的dialog,并且,在代码中我没有实际的通过任何坐标来控制它们,它们是自动放在了我希望它们在的地方,在这一点上,Qt对手工编写代码的友好性胜过MFC又何止百倍啊。

 

但是,其实上述代码的问题还是有的,比如,lineEdit widget可以输入任何形式的字符,而不仅仅是数字,这在我们这个例子中应该是不允许的,在Qt中也提供了一种远胜MFC的限制方式,不用继承并实现一个自己的lineEdit widget就能实现非常 复杂的限制功能,这和STL中泛型的算法思维有点类似。这就是Qt 中的Validator,功能强大到你甚至可以很简单的就使用正则表达式去限制lineEdit。。。。呵呵,强大。。。这里我根据需要,使用QIntValidator就足够了。

在上例中加入如下代码:

intValidator = QtGui.QIntValidator(0,99, self)

 

self.lineEdit = QtGui.QLineEdit()

self.lineEdit.setValidator(intValidator)

 

加在那个位置就不用我教了吧,就是定义self.lineEdit的上下位置。再试试效果,除了0,99的整数,什么都输不进入了。

 

题外话:

在进入使用designer之前,我决定先看看一些关于PyQt的资料,(以前就是看 reference1那本书。单纯这样从C++转换到Python,光靠自己的理解还是难免会有些问题,虽然摸索着上面那样的程序也能写出来:)。但是,就我的经验来说,在使用图形化的设计工具前不将手工代码的原理都弄清楚,以后可能会不再想回过头来彻底弄清楚了,(因为图形化设计会简单的多),但是,碰到稍微有点例外的情况,却会手足无措(使用MFC的经验)。

 

reference

  1. C++ GUI Qt4编程(第二版) C++ GUI Programming with Qt4, Second Edition。Jasmin Blanchette【加拿大】,Mark Summerfield【英】著,闫锋欣,曾泉人,张志强译,电子工业出版社。
  2. GUI Programming with Python: Qt Edition
  3. Qt tutorial
  4. PyQt GPL v4.4.4 for Python v2.6 examples from riverbankcomputing

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

PyQt的学习(1) 入门


PyQt的学习(1) 入门

write by 九天雁翎(JTianLing) -- www.jtianling.com

讨论新闻组及文件

 

动机:

刚学习完Python,借鉴以前学习C++时的经验,当时用MFC巩固了C++的学习,并且可以做出实际的程序,给了我继续学习的动力,学习Qt也是出于类似的目的。

为什么我要写,为了学习。不仅仅为了将来有个可以用的,好用的跨平台GUI库,也为了用Python快速开发时也能有个GUI库,但是又不喜欢TK,那么,综合考虑,Qt也就是不错的考虑了。

 

综述

这些文章基本遵循《C++ GUI Qt 4编程》第2版(C++ GUI Programming with Qt4, Second Edition)的流程,文字可能会很少,除非自己想出的例子,不然例子就是书中C++例子的Python版本,基本上,可以以此书为蓝本对照这些文章中的例子来看。

虽然用途不算太大,但是考虑到PyQt的好教材是如此的稀少,这样也不算完全没有价值吧,毕竟还有只明白Python不懂C++的人。

这里有一本PyQt的书《GUI Programming with Python: Qt Edition》,但是老到基本上是属于Qt2时代的东西了。

至于PyQt 嘛,你不会不知道到google上搜索一下,取得其下载地址吧?安装方式如此简单,点击下一步以后,就可以在Python中通过import PyQt4来使用这个库了。

 

HelloWorld

自从K&R;后,好像所有的程序语言讲解都是以HelloWorld开始,我们也不免俗吧,我们从HelloQT开始。并且,这个例子也可以作为一个测试程序,测试一下看看安装PyQt是否成功。

HelloQt.pyw

1 import sys
2 from PyQt4 import QtGui
3
4
5 app = QtGui.QApplication(sys.argv)
6 label = QtGui.QLabel("Hello Qt!")
7 label.show()
8
9 sys.exit(app.exec_())

 

这样,就是一个现实"Hello Qt"的窗口,利用了QLabel部件。--说明一下,Qt中将MFC中常常称作控件(control)的东西称作部件(Widget),其实一回事。

这里也可以看出Qt的足够简洁,我很欣赏,而其对象使用的风格和方式也是比较好的,不然,尝试用VS生成一个类似的MFC程序试试?^^

其实这里还不足以见证Qt的强大,看下面的例子,QLabel竟然至此HTML样式。。。。。-_-!

MoreHelloQt.pyw

1 import sys
2 from PyQt4 import QtGui
3
4
5 app = QtGui.QApplication(sys.argv)
6 label = QtGui.QLabel("

Hello QT!

") #这里我没有办法让CSDN不将其解释为HTML。。。所以,参考界面的源代码看看是什么吧
7 label.show()
8
9 sys.exit(app.exec_())

 

强大吧:)

 

Qt(C++) VS PyQt

这里,顺便比较一下PyQt与普通Qt(C++)生成程序的区别。一般而言,两者速度没有可比性,但是,速度在这里不是主要问题,原因在于PyQt的核心也就是Qt库,那是用C++写的,这样,一般而言不会占用太多时间的逻辑代码速度慢点,不会成为瓶颈,使得PyQt的程序可以跑得足够的快。但是,使用方式上,却没有失去Python的优雅语法,快速开发的能力,也结合了Qt的强大,呵呵,广告用语。。。。。。。。。。。来点实在的。


左边的是用C++开发出来的Qt程序,右边是PyQt开发出来的程序,由于都是使用了同一个库,看不出两者的区别。基本上,多懂了一种语言,就多了一种选择。。。比如说这个时候的Python。

 

建立连接

quit.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4 app = QtGui.QApplication(sys.argv)
 5
 6 quit = QtGui.QPushButton("Quit")
 7
 8 QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
 9                        app, QtCore.SLOT("quit()"))
10
11 quit.show()
12 sys.exit(app.exec_())

 

这里展示了Qt的信槽模式,有点怪异的是,在Python中,信号,槽,都是用字符串来表示-_-!这点似乎有点奇怪。

我还不懂Qt的原理,也没有看过Qt的源代码,但是总是感觉这里奇怪,于是我翻看了一下Qt的SIGNAL,SLOT宏,于是一切也就没有那么奇怪了。

# define SLOT(a) qFlagLocation("1"#a QLOCATION)

# define SIGNAL(a) qFlagLocation("2"#a QLOCATION)

本来,他们就是以字符串来表示的。。。。。。。。我原本还以为在Qt中这些都是通过回调函数的形式出现的呢。。。。。。。汗-_-!真那样,与一般的WxWidget原理有啥不同啊。。。呵呵,看来,Qt的原理还得好好理解理解。其实就现在的信息看,无非也就是Observer模式的一种扩展,再难也难不到哪儿去。

 

在PyQt的安装包中,有个tutorial,展示了更复杂一点的button使用方法,可以参考参考

morequit.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4
 5 app = QtGui.QApplication(sys.argv)
 6
 7 quit = QtGui.QPushButton("Quit")
 8 quit.resize(75, 30)
 9 quit.setFont(QtGui.QFont("Times", 18, QtGui.QFont.Bold))
10
11 QtCore.QObject.connect(quit, QtCore.SIGNAL("clicked()"),
12                        app, QtCore.SLOT("quit()"))
13
14 quit.show()
15 sys.exit(app.exec_())

 

分别是设置Button的大小和按钮文字的字体和样式,这个阶段就不深抠细节了,了解概念和Qt大概的风格就好。

 

窗口部件的布局

用过MFC的人都知道在MFC中创建一个随窗口动态改变控件大小的程序的困难。。。。。。需要每次OnMove,OnSize的时候去重新计算控件的大小,为了保持布局合理,最好还得将所有控件的位置用百分比来计算,痛苦不言而喻,可以参考我写的正则表达式测试程序0.3版(要么就是更老的版本)。但是在Qt中好像就要简单的比较多。看看示例:

layout.pyw

 1 import sys
 2 from PyQt4 import QtCore, QtGui
 3
 4 app = QtGui.QApplication(sys.argv)
 5
 6 window = QtGui.QWidget()
 7
 8 spinBox = QtGui.QSpinBox()
 9 slider = QtGui.QSlider(QtCore.Qt.Horizontal)
10 spinBox.setRange(0, 130)
11 slider.setRange(0, 130)
12
13 QtCore.QObject.connect(spinBox, QtCore.SIGNAL("valueChanged(int)"),
14                        slider, QtCore.SLOT("setValue(int)"))
15 QtCore.QObject.connect(slider, QtCore.SIGNAL("valueChanged(int)"),
16                        spinBox, QtCore.SLOT("setValue(int)"))
17 spinBox.setValue(35)
18
19 layout = QtGui.QHBoxLayout()
20 layout.addWidget(spinBox)
21 layout.addWidget(slider)
22 window.setLayout(layout)
23
24 window.show()
25 sys.exit(app.exec_())

 

行了,基本上PyQt的程序是怎么样子的,大概有个了解了:),下面就开始慢慢来了。我也需要睡觉去了。

 

 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....

通过纯静态分析来还原算法,获取《加密与解密》第2章的TraceMe的注册机


通过纯静态分析来还原算法,获取《加密与解密》第2章的TraceMe的注册机 

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

 
 

其实原书有源代码,并且也介绍了一种通过内存断点来获取注册码的方法。但是这里我是为了检验今天看静态分析这一章的成果,纯粹靠IDA Pro静态分析不带调试信息Release版本,也不动态调试的方法来分析算法。并还原算法,写出注册机。这里并没有鼓励或提倡以此方式来分析程序的意思。呵呵,书中好像也鼓励大家试试写出一个注册机来,作为购买了正版书籍的读者,自然要响应作者号召了。

另外,我虽然最近老是发一些奇怪的文章,但是我只是个C++程序员,是以开发游戏为生的,最近研究这些东西并不是准备将来以此牟利,仅仅是为了学习,最近的工作是反外挂,所以才学习这些东西。这方面我是纯粹的新手,发点文章属于记录学习过程和个人爱好,有不对或者稚嫩的地方请高手指教。

这里仅仅只看我注释的片段,全部源代码即完整程序请去下载《加密与解密》(第3版)的配套光盘第2章部分,或者购买原书。

关键函数如下:调用环境其实可知lpString1为用户输入的key,lpString2为用户输入的用户名,aiLen为长度

.text:00401340 ; int __cdecl KeyGenAndCompare(LPCSTR lpString1, LPSTR lpString2, int aiLen)

.text:00401340 KeyGenAndCompare proc near ; CODE XREF: DialogFunc+115p

.text:00401340

.text:00401340 lpString1 = dword ptr 4

.text:00401340 lpString2 = dword ptr 8

.text:00401340 aiLen = dword ptr 0Ch

.text:00401340

.text:00401340 000 push ebp

.text:00401341 004 mov ebp, [esp+4+lpString2]

.text:00401345 004 push esi

.text:00401346 008 push edi

.text:00401347 00C mov edi, [esp+0Ch+aiLen] ; 总是通过esp来操作,有点混乱,其实就是将长度保存到edi

.text:0040134B 00C mov ecx, 3

.text:00401350 00C xor esi, esi ; 清空esi

.text:00401352 00C xor eax, eax ; 清空eax

.text:00401354 00C cmp edi, ecx ; Compare Two Operands

.text:00401356 00C jle short loc_401379 ; 判断当长度小于等于3时,不进行计算了,因为肯定是错的

.text:00401358 00C push ebx

.text:00401359

.text:00401359 KeyGenLoop: ; CODE XREF: KeyGenAndCompare+36j

.text:00401359 010 cmp eax, 7 ; three below: if ( eax > 7 ) eax = 0;

.text:0040135C 010 jle short loc_401360 ; Jump if Less or Equal (ZF=1 | SF!=OF)

.text:0040135E 010 xor eax, eax ; Logical Exclusive OR

.text:00401360

.text:00401360 loc_401360: ; CODE XREF: KeyGenAndCompare+1Cj

.text:00401360 010 xor edx, edx ; Logical Exclusive OR

.text:00401362 010 xor ebx, ebx ; Logical Exclusive OR

.text:00401364 010 mov dl, [ecx+ebp] ; 这里可以知道是从第3个元素才开始比较的

.text:00401367 010 mov bl, gacCode[eax] ; 这里用eax从0增长,等于7时清零来循环遍历一个数组

.text:0040136D 010 imul edx, ebx ; Signed Multiply

.text:00401370 010 add esi, edx ; Add

.text:00401372 010 inc ecx ; Increment by 1

.text:00401373 010 inc eax ; Increment by 1

.text:00401374 010 cmp ecx, edi ; Compare Two Operands

.text:00401376 010 jl short KeyGenLoop ; Jump if Less (SF!=OF)

.text:00401378 010 pop ebx

.text:00401379

.text:00401379 loc_401379: ; CODE XREF: KeyGenAndCompare+16j

.text:00401379 00C push esi

.text:0040137A 010 push offset aLd ; "%ld"

.text:0040137F 014 push ebp ; LPSTR

.text:00401380 018 call ds:wsprintfA ; Indirect Call Near Procedure

.text:00401386 018 mov eax, [esp+18h+lpString1]

.text:0040138A 018 add esp, 0Ch ; Add

.text:0040138D 00C push ebp ; lpString2

.text:0040138E 010 push eax ; lpString1

.text:0040138F 014 call ds:lstrcmpA ; Indirect Call Near Procedure

.text:00401395 00C neg eax ; Two's Complement Negation

.text:00401397 00C sbb eax, eax ; Integer Subtraction with Borrow

.text:00401399 00C pop edi

.text:0040139A 008 pop esi

.text:0040139B 004 inc eax ; Increment by 1

.text:0040139C 004 pop ebp

.text:0040139D 000 retn ; Return Near from Procedure

.text:0040139D KeyGenAndCompare endp

 

 

还原的算法源代码如下:

 1 #include
 2 #include
 3 int gacCode[] = {0x0c, 0x0A, 0x13, 0x9, 0x0c, 0x0b, 0x0a, 0x08};
 4
 5 int __cdecl KeyGen(char* lpName, int aiLen)
 6 {
 7     int liTemp = 0; // means esi
 8     int i = 3; //just because using VC6,know 3 from ecx == 3
 9     int j = 0;
10     for(; i
11     {
12         if(j > 7)
13         {
14             j = 0;
15         }
16         liTemp += lpName[i] * gacCode[j];
17     }
18
19     return liTemp;
20 }
21
22
23 int main()
24 {
25     char lszName[] = "abcdefg";
26     int liKey = KeyGen(lszName, strlen(lszName));
27
28     printf("%i/n",liKey);
29     return 0;
30 }
31

 

write by 九天雁翎(JTianLing) -- www.jtianling.com

阅读全文....