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

回归游戏的世界,简单图形编程学习的开始

回归游戏的世界,简单图形编程学习的开始

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

讨论新闻组及文件

这个系列是我对游戏开发的重新认识导致的。。。。按《那是飞扬的青春—小评《DOOM启示录》》中所描述过的,以前其实我排斥过游戏开发,所以选择了服务器端程序的开发。但是,慢慢的,从Small Basic画几个图形开始,加上看了《DOOM启示录》。。。。我开始有了新的认识,既然血液中流着游戏的气息,既然到目前为止一辈子就一直和游戏没有办法脱离关系,为啥还要这样强制让自己痛苦呢?必要的时候,还有游戏界开发的宗师的话安慰我,凡是沉迷于游戏的人,那都是家庭原因.......呵呵,说来也是,以前游戏浪费了我很多时间,导致现在感觉时间浪费的不值,但是想想,那时候我不玩游戏我会干什么?真的就会天天耐下心来学习?没有到那一步之前,那是不可能的,起码,那时候,游戏还给了我快乐,那就足够了。人一辈子,快乐的时候又能多少呢?也许那些因为家庭原因的孩子,在没有游戏的时候,会变得比现在更加恶劣。。。起码他们现在还能安静的沉迷在游戏的虚拟世界中-_-!虽然感觉这样的说法实在是有点自欺人。。。。。

既然这样想了,那么就很明确了,真正的游戏开发还是客户端最能体会,服务器这个曾经用来远离游戏的选择就不那么合适了,客户端。。。。从图形编程学起吧。。。。呵呵,又是一个新的领域,我喜欢。

个人学习,总是学习多路并进。。。。对于同一个主题的反复学习,有助于自己的思考,同时也发现了很多不同的思维方式,就像以前学习算法的时候,考虑用C++,python,lua,bash来完成同一个算法一样,现在简单的图形编程学习我还是希望用四种方式来完成。。。。。C++的Windows GDI/GDI+,Python/C++的PyQt/Qt,JAVA的Android,最后,也许在恰当的时候加上small basic,呵呵,之所以加上small basic,纯粹是因为它也算是为我重新发现编程乐趣的语言:)

对了,这里所谓的简单,就是指除了DirectX和OpenGL意外的,系统远程的图形接口绘图的方式完成图形的绘制,DirectX和OpenGL自然是肯定要学的,但是,我希望在学习了这些简单的以后。虽然,我很确定的知道,对于游戏开发来讲,DirectX在Windows下会几乎完全代替GDI的使用,OpenGL的强大功能也足以使Qt,Android的其他绘图方式黯然失色,但是,不知道为啥,我还是想好好的学习一下这些系统/框架的原生绘图方式。其实,从small basic中一些简单,但是让人惊艳的程序中我体会到,其实,最最简单的图形技术,也能产生让人直呼惊艳的程序,重要的是你使用技术的思想。。。。。(参考我以前关于Small basic的文章:《初学编程该怎么学?——对初学者程序设计语言学习的思考(2)

另外,这次不像以前学习数据结构一样了,将3,4种语言的程序同时放在一起,呵呵,那是种自己比较痛快的方式,但是估计不希望了解那么多语言的人看到估计是无用噪音大于有用信息的,就图自己的痛快了,这一次,我虽然还是用几种语言/框架去实现也许一样的功能,但是,我还是为每个单独建立文章 ,然后在文章中建立链接,让有用的人查看吧,这样做,我可能累点,因为也许我的文章中主要的思路都是一样的(我就需要拷贝几份),不一样的仅仅是源代码和截图而已。这样做的好处就是,我不必等几种语言实现的都出来了才可以发文章。

JAVA的Android实现放在这里属于比较大胆的行为,毕竟其实我对JAVA和Android了解的都比较有限,在任何时候我都会实话说,其实本质上我仅仅是一个靠C++吃饭的网络游戏服务器端程序员而已,其实踏足图形编程领域(是不是说的太大了-_-!)都是因为自己的兴趣使然,没有办法。

另外。。。其实,我老是学那么多语言,框架。。。最近甚至因为Android还学了JAVA。。。。。个人感觉,学习C++的人一般不会学JAVA。。。。。就像我学了VIM就不想学Emacs一样。。。呵呵,但是。。。Google的号召力嘛,因为Google app以前只能用Python来开发,我耐心的学习了Python,(其实也不全是,还有当时学习完LUA对脚本语言很感兴趣的原因,但是这也是很大的原因吧),现在Google的Android手机只能用JAVA开发(其实最近用C++开发的NDK也出来了),那么我就学习JAVA。。。。。唉。。。何苦呢。。。。但是,不学点东西也没有啥事干,不是嘛?工作虽然辛苦,但是能学到的新东西毕竟少了,自己不学习,工作后干点啥呢?学习一种新的语言,踏入一个新的领域那是多么让人愉快的事情啊,一路走来,我不就是因为这样才走上了开发程序的道路吗?

学的太多太杂,有很大的贪多嚼不烂的风险,但是作为程序员,多了解几种语言还是有助于开拓思路的,并且,我能够在正确的时候,选择我认为最正确的语言去做一件事情。举例而言,没有学bash之前,在Linux下我用Python作批量运行程序的脚本语言用(其实也没有什么不好),但是学了bash后,可以用更简洁的代码,更快的完成同样的事情。(虽然再复杂点的程序bash就是噩梦),学过bash的时候,回头来回看到makefile文件的语言是何其的亲切,再也不是以前认为的 $乱飞了,即便是看到VS2005中的工程定义,那些宏$()的样子也是如此的熟悉。Python中发现的很多有趣性质,我感觉的C++想做但是不敢做的事情,在学习JAVA的时候再次碰到,也就习以为常了,等等等等,太多太多,即便是Windows API中以前很多看起来非常古怪的使用方式,在开发多了程序后都能感觉正常了(也理解了MS的苦衷,很多时候其实是实现手段的限制),同时也能想到,假如这个接口是Python实现的,那就该怎样简单啊。。。呵呵,其实说了那么多,都仅仅是为自己辩护而已,其实我认为总有一天我会在某个领域深入的学习下去,但是,那一天还没有到来之前,多学点东西,不是能让我更早的发现那样一个领域吗?毕竟我还只工作一年多一点,太早盲目的为自己定下方向,也许不见的会多么正确,就像我直到大学毕业后,才发现我真的是准备走上程序开发的道路,一共用了20多年,那么我又怎么能在仅仅进行程序开发1年多就能发现我准备在程序开发中选择什么样的道路呢?呵呵,在自己还没有老去的时候,随性而去吧,趁自己还对那么多新鲜事物感兴趣并且精力比较旺盛的时候去多学点东西吧,哪怕以后我不会再用到。。。。当你学习你感兴趣的事情时,学习过程,本身也是一件很愉快的事情,不是吗?

 

 

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

阅读全文....

OPhone/Android的学习(3)—再熟悉几个常用的Widget

OPhone/Android的学习(3)—再熟悉几个常用的Widget

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

讨论新闻组及文件

 

一、   综述

其实相对来说,学习一个UI库(其实AndroidAPI最重要的就是这一部分,其他的可以用原有的JAVA标准库)最最主要的是了解其大致框架,并不一定要了解了每一个控件/Widget的用法,因为所有的控件/Widget都是在同样的框架下展开的,了解了一种两种,其他的需要用到的时候看看文档,基本也就知道怎么用了,可以等到实际开发的时候再去学习。整体的框架大概需要了解哪些呢?包括整体程序的执行流程,控件/Widget的创建,销毁流程,消息/事件 的响应及处理方式等,知道了这些,其他的都可以算是枝叶末节。

前面几节Android的学习虽然非常基础,但是都是围绕着整体框架的,其实一般来说一个Hello Wolrd就已经说明了一个程序的一半,这里,再通过几个Widget来熟悉一下上述流程。解释就不多说了,具体的东西参考文档。

 

 

二、   Message:

刚开始我一直在找Android弹出类似Windows MessageBox的对话框的办法,后来找到两种:

example1

package com.JTianLing;

 

import android.app.Activity;

import android.content.Context;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

import android.widget.Toast;

 

public class DevXMLUI extends Activity {

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        LinearLayout layout = new LinearLayout(this);

       

        Button button = new Button(this);

        button.setText("I'm So great Button");

        button.setOnClickListener(new OnClickListener(){

           @Override

           public void onClick(View v) {

            Context context = getApplicationContext();

            CharSequence text = "Hello toast!";

            int duration = Toast.LENGTH_SHORT;

 

            Toast toast = Toast.makeText(context, text, duration);

            toast.show();

           }

        });

        layout.addView(button);

        setContentView(layout);

    }

}

运行效果见插图1,点击按钮的时候弹出一个Toast,为啥叫这个,我也不明白。类似于一个Notice的效果,可以自动消隐。

 

另外一种比较正规,也就是通过对话框,只不过Android里面没有类似MessageBox一样使用简单的对话框,相对来说复杂一点。

Example2:

package com.JTianLing;

 

import android.app.Activity;

import android.app.AlertDialog;

import android.app.Dialog;

import android.content.DialogInterface;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

 

public class DevXMLUI extends Activity {

    static final int DIALOG_PAUSED_ID = 0;

    @Override protected

    Dialog onCreateDialog (int id)

    {

       AlertDialog.Builder builder = new AlertDialog.Builder(this);

       builder.setMessage("Are you sure you want to exit?")

              .setCancelable(false)

              .setPositiveButton("Yes", new DialogInterface.OnClickListener() {

                  public void onClick(DialogInterface dialog, int id) {

                     DevXMLUI.this.finish();

                  }

              })

              .setNegativeButton("No", new DialogInterface.OnClickListener() {

                  public void onClick(DialogInterface dialog, int id) {

                       dialog.cancel();

                  }

              });

       AlertDialog alert = builder.create();

       return alert;

    }

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        LinearLayout layout = new LinearLayout(this);

        

        Button button = new Button(this);

        button.setText("I'm So great Button");

        button.setOnClickListener(new OnClickListener(){

           @Override

           public void onClick(View v) {

              showDialog(DIALOG_PAUSED_ID);

           }

        });

        layout.addView(button);

        setContentView(layout);

    }

}

还是单击那个伟大的按钮,才能弹出对话框,效果如插图2

至于这个复杂程序,我是比较有意见的。。上面那一大堆程序,其实在Windows这种已经是宇宙中最复杂的操作系统中实现都只需要12句话。。。。唉。。。。曾经多么常用的MessageBox啊。。。我甚至看到最极端的使用情况----一次我们游戏在一个玩家的机器上就是跑补起来,甚至连日志都打不出来,总监于是在每隔几行弹出一个MessageBox在玩家的机器上跟踪程序运行情况-_-!

暂告一段落

至此,对于一般的AndroidWidget不再进一步的深入学习,原理上基本上差不多,碰到需要的时候再去翻文档学习就好了。。。。毕竟我是来尝鲜的-_-!用了我45天的下班时间熟悉了一下Android,本质上。。。我还是个靠C++开发网络游戏服务器端程序的程序员,虽然由于工作需要,又碰巧刚开始工作的时候开发过客户端,所以经常到客户端客串编写一些与服务器交互性比较强的模块。

以后,考虑学习自己感兴趣的东西,对于我来说基本也就两部分,作为服务器端程序员,我关心网络,作为开发游戏的程序员我关心图形接口,这些以后就不放入这个系列,另开一个系列。

 

 

 

插图1:

插图2:

 

 

 

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



阅读全文....

OPhone/Android的学习(2)—从分析Eclipse自动生成的代码到以XML控制UI

OPhone/Android的学习(2)—从分析Eclipse自动生成的代码到以XML控制UI

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

讨论新闻组及文件

一、   综述

XML来控制UI我们已经不陌生了,这样的方式比硬代码方式(即将UI的控制完全放在代码中)更加灵活,更加易于管理及改变。特别是在用C++编写UI的时代,这简直就是突飞猛进似的进步。虽然XML也有很多缺点,并因此被人诟病,但是其对UI控制方面带来的好处确实是非常实在的,并且是今日非常流行的网页技术AJAX的核心组成部分之一(X就代表XML)。事实上,不仅是网页技术的应用,用过CEGUI这样著名开源UI的人也能知道,XML对界面的控制简直就是其UI控制的核心。再辅以LUA/Python脚本语言处理界面逻辑及事件响应,对界面层及其逻辑的开发效率可以比用C++这样的语言快上N倍。。。。。

Android提供了一种比较原生的方式来让我们使用XML控制界面,并且配置了很多可以用XML控制的界面Widget属性,我们不需要去了解XML解析和配置的一套系统,(什么SAX,DOM,我们都不用去管了)Google为我们将所有额外复杂的功能都做了,我们需要做的就是编写一个文本文件,然后实现我们想要的功能!虽然XML这样的所谓标记文本文件其实并不是太直观。。。。。(被人诟病的地方)

 

二、    分析ADT生成的HelloWorld

从一个HelloWorld程序来看,ADT生成的程序是比较复杂的了。但是这里不再排斥XML引入带来的复杂性(仅仅是还不了解的时候会这样感觉),重新建立一个程序,并审视我们的程序。

程序由src,gen,res三个主要的目录组成。

1.      res目录下面就是资源:

从我们今天的主题最相关的资源部分入手,因为XML文件在OPhone/Android程序中属于资源。

资源又分成三个组成部分:

drawable

可以绘制的元素,初次生成的程序中带有一个icon.png的图标,这个图标就是最后我们生成的应用程序在OPhone/Android中的图标。

layout

XML文件组成,如其名,用XML文件在此描述了我们的应用程序的框架构成。初次生成的程序中带有一个main.xml的文件。双击此文件打开的话会进入ADT的可视化XML编辑器,这样可以使的编辑XML文件更为容易一点,但是我们直接来看看源代码,毕竟还是比较简单的:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:orientation="vertical"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    >

<TextView 

    android:layout_width="fill_parent"

    android:layout_height="wrap_content"

    android:text="@string/hello"

    />

</LinearLayout>

 

对于完全不懂XML语言的人了解HTML也会有帮助,无非就是以一堆尖括号括起来的一堆属性而已(XML算不上完全的编程语言,虽然可以完成一些这样的任务)。

首先,我们可以看到,此程序中有一个LinearLayout,一个TextView,这些都是很熟悉的东西,不熟悉的看看此系列前面的文章。

OPhone/Android的学习(1)—初步知识,TextView,Button,Layout及事件响应

原来,一个默认生成的程序中已经包含了一个LinearLayout了。。。害得我以前找那么久。它有3个有效的属性:分别是

    android:orientation="vertical"

    android:layout_width="fill_parent"

android:layout_height="fill_parent"

 

意思很明确,方向是“vertical垂直的,大小都填充父窗口。android表示的是类似命名空间的概念。

xmlns:android那一句是说明此程序用的是通常的android的命名空间属性,在Google的文档中明确描述道:

The outermost tag in every Android layout file must have this attribute.即最外层的layout文件必须有此属性。

TextView其他属性与layout类似,text属性表示的是TextView的文字。这里的使用就如同我们用TextView对象的setText方法一样

@string/hello表示的意义见下面关于values的描述。

 

values

       也是由XML文件构成,如其名所述,这里定义了一些我们用到的values,不过这里的values更加像平时我们在编程语言中用到常量,类似C++中的const,JAVA中的final

       目前只有一个strings.xml文件,双击也会进入ADT特有的XML可视化编辑器,使用非常方便,这里不多描述了,我们看看源码:

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string name="hello">Hello World, DevXMLUI!</string>

    <string name="app_name">DevXMLUI</string>

</resources>

 

内容很简单,我们有两个resources,分别是名为helloapp_namestringstring的内容如上面的黑体字描述的内容。我们可以在这里方便的修改,统一的管理,当程序规模扩大的时候才能看到这样的好处,那时候在这里修改一个string比去程序里面漫天的查找一个字符串要容易的多。这里的资源怎么被其他部分利用呢?在其他资源中利用的方式其实我们已经看到过了。TextView的属性android:text="@string/hello" 即是使用了string类型的名为hello的字符串,从strings.xml中我们可以看到,就是Hello World, DevXMLUI!了。

详细的资源的描述可以从Google的在线文档中查看:Resources and Internationalization

这里的资源怎么被程序利用呢?这是个问题。包括layout怎么被程序利用其实我们也遗漏了。那么我们再来看源码。

 

2.      src目录下面就是源代码:

src下面的源码将是我们主要的程序代码编辑区。

目前所有的程序代码如下

package com.JTianLing;

 

import android.app.Activity;

import android.os.Bundle;

 

public class DevXMLUI extends Activity {

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

    }

}

 

程序主要逻辑的意思我们应该了解了,这里大概再描述一下,DevXMLUI类继承自Activity,属于整个程序的代表类(类似MFC中的CApp),ActivityonCreate会在程序创建的时候自动调用,(类似MFC中窗口的OnCreate)。setContentView的意思是指定此程序的内容显示。我们以前也用过了,不过那个时候用的是直接创建的TextView对象,这里是R.layout.mainR.layout.main指的是什么呢?看下节的描述。

 

gen表示是Google为我们自动生成的代码

Google的文档明确的表示不希望我们手工改动此目录下的代码,此代码都有程序自动生成(在Eclipse中及时生成,不是IDE时在调用aapt时生成),目前所有的代码如下;

 

/* AUTO-GENERATED FILE.  DO NOT MODIFY.

 *

 * This class was automatically generated by the

 * aapt tool from the resource data it found.  It

 * should not be modified by hand.

 */

package com.JTianLing;

 

public final class R {

    public static final class attr {

    }

    public static final class drawable {

        public static final int icon=0x7f020000;

    }

    public static final class layout {

        public static final int main=0x7f030000;

    }

    public static final class string {

        public static final int app_name=0x7f040001;

        public static final int hello=0x7f040000;

    }

}

注释中也明确的表示了DO NOT MODIFY.!这里,类R的名字表示了其代表的即是我们通过XML编辑的资源,Rresource的首字母大小。

这里我们可以看到我们编辑过的XML属性,包括icon.png都能在这里一一找到对应的int值,OPhone/Android程序就是通过这些整数找到对应的属性的。了解MFC的话,这里可以做一个对比,这里不就是我们在Resource.h头文件中定义的那一堆宏的升级版嘛?其实总的看来,整个的AndroidResource都可以看做MFC Resource的升级版,之所以说升级,是因为XMLrc那样的格式更加通用,更加易于管理,其实从新版开始,VS自己都开始利用XML来管理配置和属性了,Office2007不都开始用XML格式来保存文件了吗?

讲述完上面的部分,我们应该能猜到了setContentView(R.layout.main); 中的R.layout.main不就是上面R类的layout类的main属性吗?因为都是static的,我们可以这样不通过对象直接使用,这点不用我多说了吧。

 

三、   通过修改程序运行印证分析

总体的分析了一下ADT为我们生成的Hello World程序,大概也知道了OPhone/Android程序的总体结构了。为了印证一下,我们来修改一些内容,看看是否运行后符合我们的设想。

1.      字符串内容的改变

先来将hello改变如下:

<string name="hello">Hello World, XML UI Application!</string>

运行后:

效果不错,如我们所想。

 

2.      框架的改动

TextView改为我们同样熟悉的Button,效果如下:

效果也不错:)

 

一、   了解XMLUI的控制力

现在基本可以肯定我们的分析是正确的了,那么我们具体使用的XML的时候是怎么样的呢?比如,现在设定一个目标,我们给此程序加上三个Button,排成一横排,每点击其中一个Button,三个Button同时向左边移动的效果。(前面一篇文章的升级版:))

首先在可视化的XML编辑器中编辑layoutADT的可视化XML编辑器几乎相当于一个RAD工具。。。相当强大,如下图:

从左边拖放适当的Button放在右边,按钮会按实际适用的layout放在恰当的位置,因为我先将LinearLayoutOrientation属性改成了

horizontal,所以这里自动将按钮排成了水平的了。可以看看生成的XML代码:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:orientation="horizontal">

<Button android:text="Button01" android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

<Button android:text="Button02" android:id="@+id/Button02" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

<Button android:text="Button03" android:id="@+id/Button03" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

</LinearLayout>

 

还算是比较直观的,仅仅是id命名的方式有点奇怪,需要特别注意一下,我们留待以后再描述,不过有了上面的蓝本,依样画葫芦,手动编辑XML文件也不是不可行的。现在运行程序看看:

 

通过手动修改XML文件来生成字符串及换上对应的string值,此时对应的文件分别为:

strings.xml:

<?xml version="1.0" encoding="utf-8"?>

<resources>

    <string name="hello">Hello World, DevXMLUI!</string>

    <string name="button1">I'm Button 1</string>

    <string name="button2">I'm Button 2</string>

    <string name="button3">I'm Button 3</string>

    <string name="app_name">DevXMLUI</string>

</resources>

 

main.xml:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

    android:orientation="horizontal">

<Button android:text="@string/button1" android:id="@+id/Button01" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

<Button android:text="@string/button2" android:id="@+id/Button02" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

<Button android:text="@string/button3" android:id="@+id/Button03" android:layout_width="wrap_content" android:layout_height="wrap_content"></Button>

</LinearLayout>

 

会发现R类也自动做出的相应的更改:

package com.JTianLing;

 

public final class R {

    public static final class attr {

    }

    public static final class drawable {

        public static final int icon=0x7f020000;

    }

    public static final class id {

        public static final int Button01=0x7f050000;

        public static final int Button02=0x7f050001;

        public static final int Button03=0x7f050002;

    }

    public static final class layout {

        public static final int main=0x7f030000;

    }

    public static final class string {

        public static final int app_name=0x7f040004;

        public static final int button1=0x7f040001;

        public static final int button2=0x7f040002;

        public static final int button3=0x7f040003;

        public static final int hello=0x7f040000;

    }

}

 

此时,从

public static final int Button01=0x7f050000;

public static final int Button02=0x7f050001;

public static final int Button03=0x7f050002;

从平时xml文件引用变量的方式是类似@string/的语法和上面的三句代码可以推断@+id/的意思了,意思就是没有对应的变量引用,创建新的变量。。。。。呵呵,+嘛,不就是添加嘛,还算是比较直观

运行程序看看:

 

果然如同我们设想的一样。现在就差时间的响应了,UI都布置好了。。。。(别看我上面描述的非常复杂,其实真正的操作可以1分钟内完成,实际的操作效果不说比RAD工具好,其他也差不到哪去)

剩下的就是实际的点击响应了,这点暂时XML没有办法帮你做了,呵呵,程序源代码如下:

DevXMLUI.java:

package com.JTianLing;

 

import java.util.Random;

 

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

 

public class DevXMLUI extends Activity implements OnClickListener{

    Random random = new Random();

    Button button1;

    Button button2;

    Button button3;

    @Override

    public void onClick(View v)

    {

       // 3个中没有使用过的boolean数组

       boolean[] testUse = { false, false, false };

       int i = random.nextInt(3);

       testUse[i] = true;

       button1.setText("I'm Button " + (i+1));

       while(true) {

           i = random.nextInt(3);

           if (!testUse[i]) {

              testUse[i] = true;

              break;

           }

       }

      

       button2.setText("I'm Button " + (i+1));

       if (!testUse[0]) {

           i = 1;

       }

       else if (!testUse[1]) {

           i = 2;

       }

       else

       {

           i = 3;

       }

       button3.setText("I'm Button " + i);

    }

    /** Called when the activity is first created. */

    @Override

    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);

       

        button1 = (Button) findViewById(R.id.Button01);

        button2 = (Button) findViewById(R.id.Button02);

        button3 = (Button) findViewById(R.id.Button03);

       

        // 三者的消息响应都设为一致的

        button1.setOnClickListener(this);

        button2.setOnClickListener(this);

        button3.setOnClickListener(this);

    }

}

 

onClick的响应程序用了random来随机生成一个0~2的整数,并通过一个boolean的数组来保证不会重复,基本的思想还是比较简单的,这里还可以看出一点,实际上OnClickListener是比较单纯的,也是足够可以复用的。

今天就到这里了。。。。

 

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



阅读全文....

OPhone SDK初体验

OPhone SDK初体验

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

讨论新闻组及文件

背景说明

中国伟大的垄断龙头,世界上也是顶尖的中移动终于出手了,出手不凡,成为了世界上第一个开发自由操作系统的移动运行商。操作系统的名字叫做OPhone,从CSDN上知道此重大消息后,我放下了手头的一切,全身心地开始追随中国移动领域的领头羊。

OPhone:官方的描述如下;

什么是OPhone:

OPhone是基于Linux、面向移动互联网的终端基础软件及系统解决方案。

OPhone SDK是专为OPhone平台设计的软件开发套件,它包括OPhone API,OPhone模拟器,开发工具,示例代码和帮助文档。

 

恩,不是Windows的东西,这点我比较满意,Linux是较为自由的,再加上众所周知的,OPhone是基于Google的Android系统的二次开发(因为如此的总所周知,所以中移动甚至都不需要提醒大家这一点,并且,这种决心不依赖Android这颗相对成熟的大树乘凉,不依靠Google名声去光耀自己招牌的作风更加让我佩服---PS:在中国中移动似乎也没有必要去依赖Google这种国外公司的名声,中国谁不知道中移动?除了搞IT的知道Google的又有多少?)

作为搞IT的,我个人是比较喜欢Google公司的,而且也对Android早有耳闻,所以虽然作为一个网络游戏服务器端程序员,我也为此专门的熟悉了一下Eclipse和JAVA,但是一直没有决心和毅力投身此行业,仅仅作为业余的爱好玩玩而已,毕竟国内甚至连一款Android的手机都没有!但是移动一出手,就知道有没有,随着联想的OPhone手机即将推出,OPhone在中国的开发应用环境已经远胜Android,于是,再加上中移动在中国雄踞南北的霸气,将来OPhone在中国的市场将远大于Android。今日,我决心追随中移动的脚步。。。。再加上OPhone的开发实际是建立在Android之上的,(下面马上就能看到)这样,我学习OPhone的经验永远不会白费,走在世界上任何一个角落,在国外也是有生存余地的,因为上面那么多的原因,我决心投身OPhone领域,谁也拦不住我了。今日是个起点,阿门。

 

    安装OPhone SDK

安装前自然要下载,但是下载前首先要在中移动那里注册,网址是"http://www.ophonesdn.com/",就我的理解,是OPhone-software-develepment-net。首先注册,然后通过验证,然后登录,闲话少说了,直接进入正题,下载,Windows版本下载地址为:

"http://dl.oms-sdn.com/sdk/ophone-sdk_windows-1.0-setup.jar"

Linux下载版本为:

"http://dl.oms-sdn.com/sdk/ophone-sdk_linux-1.0-setup.jar"

jar格式的问题,属于标准的JAVA包的格式,不用说,OPhone下的开发为了方便广大已经熟悉了Android和世界上最流行的语言JAVA的用户,用的是JAVA语言,知道这一点,我窃喜,前段时间为Android看了2,3天的《JAVA编程思想》没有白费。

 

文件不大,就100多M,接着自然就是安装了,OPhone的安装虽然极为简单及人性化,但是中移动还是更为人性化的提供了详细的安装步骤,力求做到傻瓜似教学。

首先,前提条件,需要下载如下东西:

 

先全部下来并安装再说吧。

然后再在安装目录下输入如下命令(以Windows版本为例):

java -jar ophone-sdk_windows-1.0-setup.jar

然后按管理狂按下一步就好

安装后的属性如下:

大小:200 MB (210,116,917 字节)

占用空间:205 MB (215,261,184 字节)

 

比Android最新版1.5 r3的占用大小:

大小:446 MB (468,160,733 字节)

占用空间:461 MB (484,167,680 字节)

 

要小的多,这也体现了中移动对于Android的优化,(从体积上都能看出来),体现了二次开发的成果。最后我通过比较,两者唯一相似的仅仅是Tools的名字及一些库了,相同的库如下:

/tools/lib/jcommon-1.0.12.jar

/tools/lib/jfreechart-1.0.9-swt.jar

/tools/lib/jfreechart-1.0.9.jar

/tools/lib/org.eclipse.core.commands_3.2.0.I20060605-1400.jar

/tools/lib/org.eclipse.equinox.common_3.2.0.v20060603.jar

/tools/lib/org.eclipse.jface_3.2.0.I20060605-1400.jar

/tools/lib/swing-worker-1.1.jar

/tools/lib/swt-awt-win32-3236.dll

/tools/lib/swt-gdip-win32-3236.dll

/tools/lib/swt-wgl-win32-3236.dll

/tools/lib/swt-win32-3236.dll

/tools/lib/swt.jar

可以看到,为了方便大家,也为了更好的体现开源共享的精神,中移动原封不移动的使用了google的jcommon,core等核心库,使用了swing,swt等界面库,实在是广大开发者的福音。更近一步的坚定了我追随中移动的步伐。

接着,按照文档,一步一步的添加用户库,文档啥的,都不在话下,因为以前装过ADT了,所以对我来说还算省事,最后,当我使用了中移动提供的模拟器后,感觉真是惊艳啊。。。。呵呵,这里和原有的Android的模拟器做一个对比。

左边的是OPhone,右边的是Android,从Android稀稀拉拉几个图标与OPhone满布屏幕的图标对比,可以看出OPhone更多内置的功能。OPhone模拟器有着熟悉绚烂的开启铃声,叮叮叮叮叮叮~~~,中文化的界面中可以看到飞信,Monternet,China Mobile的图标,OPhone不愧为移动深度定制的产品。。。以至于我。。。决定从这个月省吃兼用,等联想一出OPhone,一定第一时间购买!

 

Hello World

按照教程,一步一步走来,总算可以真正的尝试开发一下Android程序了,真是兴奋啊。。。呵呵,不是,是开发OPhone程序。。真是兴奋啊。。。啊?为啥开发OPhone程序要新建一个Android工程啊?这点颇为郁闷,整个Android SDK的名字都改成OPhone SDK了,一个小小的Eclipse ADT插件都不能改?在这点上,我稍微的质疑一下中移动同志们垄断3G 及智能手机市场的决心。。。。当然,仅仅是稍微质疑一下,也许ODT正在紧张的开发之中吧。

一开始我的创建新Android工程的画面与官方的有点不一样。。。难道是因为我的ADT太新?。。。。。汗-_-!

对比如下:

官方的:

我的:

在build Target和Min SDK Version两栏我实在是不知道该选什么。。。。真是郁闷啊。回过头来看看原来中移动主页上的说明:

ADT是为在Eclipse IDE下进行OPhone应用开发而提供的Eclipse插件。如果要使用Eclipse作为调试和编译的集成开发环境,则需要首先安装ADT。

下载ADT 0.8.0 zip file (不必解压该文件)。你也可以在SDK目录中找到ADT安装包:sdk_folder/tools/ophone/ADT-0.8.0.zip

晕,果然是版本太新,官方的是Eclipse3.4,ADT 0.8.0

我是EClipse3.5.0,ADT0.9.1了。。。总不能降级吧。。。。我怀着忐忑不安的心,期盼着中移动以高超的技术实现对Android新版插件的兼容。。。

先按上面那样选择再说吧。

通过上面步骤生成出来的程序还比较大,包括一个src,一个packet,一个Res,Res又包括XML格式的layout和values,甚至还有一个drawable的icon.png图标。这样的程序不需要按官方网站上的说明去改造,本身就是一个可以执行的Hello World程序,当尝试运行的时候,我发现。。。。。没有办法Run As OPhone Application.....晕,看了主页上的说明。。。原来官网的做法也是Run As Android Application。。。不去修改ADT的余毒至深矣。。。。。问题是,中移动一般人开发一个OPhone的模拟器容易吗?做来好看的?谁做的官方网页教程啊?竟然这样教人,这不是误人子弟吗?虽然我们知道OPhone的程序实际可以在Android中运行,但是我们尽然是开发OPhone程序,自然要看着它在OPhone中运行啊。。。。。。。

唉。。。。竟然如此,将string资源的hello变量值改为Android吧,运行效果如下:

 

算了,这些姑且不论,仅仅算是OPhone中的一个小瑕疵吧,先来看看中移动的OPhone对Android进行了哪些深度定制,进行了哪些改进,进行了哪些优化或者升级吧。。。。。。

参考一下文档,就能知道个大概了:

OPhone API Reference

Contents

 

恩,不错,看来,OPhone对Android进行了3大块的改造。分别是Home Screen,Local Search,Mail,听起来都是挺重要的模块嘛,一个一个来:

OPhone Home Screen is the home screen application in OPhone platform. The class defines some constants used by Home Screen API. If you want to add/change an item on home screen, you can send some Intent to the home screen application. Then the home screen application will process this action.

不错,OPhone的Home Screen给Android的改进是增加了一种对OPhone手机Home Screen的一种通信机制,看起来还不错。

Local Search这样重要的功能也有了,了不起,Mail这样的功能自然更加是需要啦,不错,果然是OPhone,就是名不虚传。

 

后来,我在OPhone SDK文档中看到了非常振奋人心的消息:

OPhone是基于Linux面向移动互联网的终端基础软件及系统解决方案。OPhone SDK是专为OPhone平台设计的软件开发套件,它包括OPhone API,OPhone模拟器,开发工具,示例代码和SDK帮助文档。OPhone SDK兼容Android SDK,因此开发者在开发OPhone应用的时候可以同时使用OPhone API和Android API。

 

展望

太强大了!!!!OPhone是兼容Android API的,伟大的设计啊。。。大家都知道,Android手机是有一些优点的,OPhone又是与之兼容的,那么我们买了一个OPhone手机,就相当于同时又买了一个Android手机,简直就是买一个顶两个,开发难度却相当于一个,这简直#@%#@%#@%太强大了,无论对于普通用户还是对于程序开发者都是天大的好消息,从此后,开发OPhone应用程序,不回头。



阅读全文....

OPhone/Android的学习(1)—初步知识,TextView,Button,Layout及事件响应

OPhone/Android的学习(1)—初步知识,TextView,Button,Layout及事件响应

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

讨论新闻组及文件

中移动强大的号召力让我暂时停止了前段时间兴趣浓厚的图形编程学习,说起来,无论是图形编程还是Android编程都算是编程领域比较充满趣味的部分,《C语言专家编程》作者就说过,用软件控制硬件总是编程最有乐趣的部分:)图形编程这样容易出成果,让自己看到自己的程序展示的方式,也是同样充满趣味的,何况,这也算是用软件控制硬件(显卡)的一种方式。

今天继续Android的学习,因为OPhone与Android兼容,那么就是说,我也是在学习OPhone。。。。。。呵呵

昨天用ADT自动生成的Hello World程序用了很多东西,资源啊,什么的一大堆,弄得比较复杂,源代码如下:

package oms.hello;

 

import android.app.Activity;

import android.os.Bundle;

 

public class HelloOPhone extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.main);

}

}

 

另外说一句,Eclipse是Windows下我用过的除了VS以外能够直接带程序着色复制的IDE程序,一般的开源的都不好用,Eclipse还算是比较强大。这个代码是简单了。。。。。。但是R.layout.main的出现将问题复杂了,我当时就没有弄清楚Hello Android是怎么来的,后来才发现实在资源里面。这点和一般我们写的程序有点不一样(可能主要是为了大型程序的设计成这样的),作为一个Hello World都这样拐弯抹角就让人受不了了,Google的Andorid教程中就交给我们了另一个较为直观的HelloWorld程序,源代码如下:

package oms.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.widget.TextView;

 

public class HelloOPhone extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

TextView tv = new TextView(this);

tv.setText("Hello, Android");

setContentView(tv);

}

}

 

这样的程序相对来说就直观多了,也符合一般的HelloWorld的简洁,这个程序即使是不了解JAVA的人应该也能一眼看明白。

    Android中的窗口都叫View,TextView自然是Android的文字显示窗口。创建TextView的时候将整个HelloOPhone这个Activity的指针传进了TextView,指定了View的范围,setText指定了View的文字内容,setContentView将此View设为Activity的内容。。。。其实程序简洁,描述反而复杂了。

既然HelloWorld用到了TextView,我们来挖掘一下有用的信息,从Android的Reference中可以看到如下内容:

Known Direct Subclasses

Button, CheckedTextView, Chronometer, DigitalClock, EditText

Known Indirect Subclasses

AutoCompleteTextView, CheckBox, CompoundButton, ExtractEditText, MultiAutoCompleteTextView, RadioButton, ToggleButton

这个一看就牛了,Button,EditText等都是从TextView继承过来的直接子类,既然用到了TextView了,我们也尝试用下这两个看到名字我们就能知道作用的Widget,至于更进一步的CheckBox,RadioButton,ToggleButton也是非常熟悉了,这里就不看了。

先看看Button,到底能不能有效:

package oms.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.widget.Button;

import android.widget.TextView;

 

public class HelloOPhone extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

TextView tv = new Button(this);

tv.setText("Hello, Android");

setContentView(tv);

}

}

 

上面的程序显示出来的效果如下:

 

左边的是普通显示画面,右边的是按下后的样子,很明显按钮的特性显示出来了。。。。呵呵,注意,我仅仅改变了new出来的对象,没有改变原TextView类型的tv值,因为Button从TextView继承而来,所以我们可以这样做。

这个按钮实在太大了,几乎已经看不出是个按钮了。。。。给他瘦瘦身吧。直觉反应的程序应该是下面这样的:

package oms.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.widget.Button;

 

public class HelloOPhone extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

Button btn = new Button(this);

btn.setText("Hello, Android");

btn.setWidth(100);

btn.setHeight(100);

setContentView(btn);

}

}

 

事实上这个程序与上例中一模一样,还是整个按钮充满了整个Activity。思考了一下,可能问题出在setContentView一句上,因为此举设定了整个内容,这样相当于拉伸效果,否定了整个按钮的设定长宽的效果。既然是这样的思路,那么自然就需要将Button放入某个容器中,让Android显示此容器,用此容器撑满整个屏幕,然后让Button属于此容器的一个子Widget,以调整大小,有了此思路,在文档中一搜索,就出现了目标,有两个layout类,layout是多么的熟悉啊,QT中让人解脱的类,MFC中缺乏导致的对话框程序进行缩放编程就是噩梦的类。有了目标,尝试看看,是否如直觉所示。

如下程序,达到了我们的目的:

package oms.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.widget.Button;

import android.widget.LinearLayout;

 

public class HelloOPhone extends Activity {

/** Called when the activity is first created. */

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

LinearLayout layout = new LinearLayout(this);

Button btn = new Button(this);

btn.setText("Hello, Android");

layout.addView(btn, 100, 50);

setContentView(layout);

}

}

 

效果如下:

 

 

所以说,其实学的东西多了,杂了,其实还有一定作用的,毕竟很多东西是相通的,比如这次的layout,呵呵。

有按钮了,点了总要有点反应吧,响应个消息吧。

见下例:

package oms.hello;

 

import android.app.Activity;

import android.os.Bundle;

import android.view.View;

import android.view.View.OnClickListener;

import android.widget.Button;

import android.widget.LinearLayout;

 

public class HelloOPhone extends Activity implements OnClickListener{

/** Called when the activity is first created. */

    boolean isClicked = false;

    @Override public void onClick(View v) {

        if( v instanceof Button) {

            if(!isClicked) {

                ((Button)v).setText("Hello, New Button.");

                isClicked = true;

            }

            else

            {

                ((Button)v).setText("Hello, Old Button.");

                isClicked = false;

            }

        }

    }

 

@Override

public void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

LinearLayout layout = new LinearLayout(this);

Button btn = new Button(this);

btn.setText("Hello, Android");

 

// 添加一个OnClick事件响应的监听对象

btn.setOnClickListener(this);

layout.addView(btn, 100, 50);

setContentView(layout);

}

}

 

这个程序一下子复杂了很多,几点说明:要响应事件(Event),需要有监听事件的对象,这个对象在Android中属于Listener一族的接口类,实现这个类的接口,然后通过setOnxxxxListener指定Listener后,一旦有监听(Listening)的事件,则相应的函数(即实现接口的那个函数)就会被调用,并且,发生事件的对象也会通过参数传入此函数。上例中有点特殊的是HelloOPhone本身实现了OnClickListener,这样的话,直接在类中用this表示Listener比较方便(这也是Android文档中提到过的方法),上例实现了一个动态改变的按钮。效果如下:

首先显示的是第一个按钮,点击一下后,显示的是第二个按钮,再点击则显示第三个按钮,以后的点击,按钮在第二个按钮与第三个按钮间来回切换。

 

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



阅读全文....

那是飞扬的青春—小评《DOOM启示录》

虽然曾经认为游戏就是自己的人生,甚至考虑走上职业玩家的道路,但是大学毕业后,痛定思痛,加上生活的压力,彻底的与游戏诀别,即便自己处于游戏开发的岗位上,却几乎不玩游戏,认为那是浪费自己的时间,因为我以前浪费了太多时间,所以此时如何还能再浪费?因为这个原因,开发游戏的时候我也选择了一个尽量远离真实游戏开发的岗位,服务器端的程序开发,这样,怎么说离游戏远一点,将游戏视为害人之物的我心里面的内疚也许会少一点吧。

阅读全文....

Windows下的进程创建API--CreateProcess使用经验漫谈


Windows下的进程创建API--CreateProcess使用经验漫谈

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

讨论新闻组及文件

Windows下工作久了,虽然不想多么深入的去了解它,但是实际还是多少懂了一点。这里说说问题比较多用的也非常多的一个APICreateProcess,这里不准备举太多例子了,《Windows核心编程》和MSDN上讲的都比较详细了,这里只谈谈使用的经验。

基本的应用上,这里用MSDN的一个例子讲解:

#include <windows.h>

#include <stdio.h>

#include <tchar.h>

 

void _tmain( VOID )

{

    STARTUPINFO si;

    PROCESS_INFORMATION pi;

    LPTSTR szCmdline=_tcsdup(TEXT("MyChildProcess"));

 

    ZeroMemory( &si, sizeof(si) );

    si.cb = sizeof(si);

    ZeroMemory( &pi, sizeof(pi) );

 

    // Start the child process.

    if( !CreateProcess( NULL,   // No module name (use command line)

       szCmdline,      // Command line

       NULL,           // Process handle not inheritable

       NULL,           // Thread handle not inheritable

       FALSE,          // Set handle inheritance to FALSE

       0,              // No creation flags

       NULL,           // Use parent's environment block

       NULL,           // Use parent's starting directory

       &si,            // Pointer to STARTUPINFO structure

       &pi )           // Pointer to PROCESS_INFORMATION structure

       )

    {

       printf( "CreateProcess failed (%d)./n", GetLastError() );

       return;

    }

 

    // Wait until child process exits.

    WaitForSingleObject( pi.hProcess, INFINITE );

 

    // Close process and thread handles.

    CloseHandle( pi.hProcess );

    CloseHandle( pi.hThread );

}

 

1.LPTSTR szCmdline;一句很重要,而且是必须得有的,这里需要注意,因为CreateProcess的第二参数是一个非const字符串,这点比较意外,因为事实上,我也没有看到MS改动过此字符串,从理论上来讲,改动commandline也是很让人惊讶的事情。

2. CloseHandle( pi.hProcess );

    CloseHandle( pi.hThread );

后,子进程就与父进程彻底脱离关系了,在Windows下进程之间的关系比较弱,不仅没有父进程收割子进程退出状态这一回事,甚至连getppid这样的API也没有。这又导致了两个现象,

1.Windows下不用当心Linux下的僵死进程问题。

2.)当失去句柄仅仅知道进程ID时,Windows下甚至无法判断此进程是否就是原有进程,(用OpenProcess打开的句柄无法判定是否就是原来的进程)假如此进程已经结束,也无法获取到进程的退出状态。(在Windows下获取进程退出状态必须得保留进程的句柄,然后调用GetExitCodeProcess

3.现在一般的游戏都已经不允许直接运行了,这点的目的很简单,加大别人用反编译软件调试游戏主程序的难度。按照CreateProcess的默认参数的直接创建原游戏主程序时,会碰到一个问题,及当发生原程序发生缺少动态库等情况时,原有系统弹出的提示对话框会被调用CreateProcess的进程吞掉,使得这类错误被掩盖,因为此时CreateProcess返回值实际是成功的。这时候,将CreateProcess的参数Process Creation Flags设为CREATE_DEFAULT_ERROR_MODE就可以让原有的提示窗口弹出来。

4. Process Creation Flags设为CREATE_SUSPENDED时,可以将欲创建进程挂起,这时想对新进程干啥都行,甚至可以尝试更改其代码段以影响程序运行,(但是大部分带监控的杀毒软件会有警告)。然后用ResumeThread API去让原进程的主线程运行起来。

5.CreateProcess创建的进程句柄实际代表的是一个Windows核心对象,适用于Windows核心对象的操作都可以对进程句柄进行,(核心对象的概念请参考《WIndows核心编程》,其中包括WaitForSingleObject等同步API

 

 

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

 

阅读全文....

异常处理与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

阅读全文....