2012年11月27日 星期二

Head First Object-Oriented Analysis and Design(深入淺出物件導向分析與設計)

我一直都很喜歡深入淺出系列的書籍。這本深入淺出物件導向分析與設計就是一本深入淺出系列經典的例子,用實際生活上的例子穿插有趣的圖案一步一步帶領讀者進入OOAD的世界。書中介紹了一些基本的OO原則、部份Design Patten、UML以及軟體開發的流程,幾乎可以說涵蓋了大部分軟體開發的基本議題,很適合當OOAD初學者的第一本入門書。

其中我最喜歡書中一步一步帶你思考的模式,作者會先從不好的設計開始講起,一步一步帶你運用一些OO的基本原則修改程式架構。此外書中舉的例子也十分有趣,搭配作者幽默的文字會讓你有種在讀故事書的感覺,不知不覺就看完整本書。

書中列出開發偉大軟體的三步驟,我覺得分類的非常好。開發軟體不脫離這三個步驟:
  1. 確認你的軟體做客戶要它做的事。
  2. 應用基本的OO原則,增加軟體的彈性。
  3. 努力達成可維護、可重利用的設計。
不過這本書也像其他深入淺出系列一樣,是屬於入門類型的書籍,比較不適合有豐富OO經驗的軟體工程師。此外書中範例程式都是使用Java,讀者可能需要一些Java基本的OO知識才能閱讀。

總而言之,這是一本很棒的OOAD入門書。看完這本書後可以接著針對書中的各個議題去看進階的書籍。比如說Design Patten可以接著看深入淺出系列另外一本有名的書深入淺出設計模式。

2012年11月1日 星期四

[C#]建立資料夾範例程式

C#可透過System.IO.Directory.CreateDirectory(dirPath)來建立資料夾
如果dirPath中有不存在的資料夾,CreateDirectory會順便建立此資料夾。
下列範例會在C:\底下建立一個TestDir
using System;
using System.IO;

public class Program
{
    public static void Main()
    {
        string dirPath = @"C:\TestDir";
        if (Directory.Exists(dirPath))
        {
            Console.WriteLine("The directory {0} already exists.", dirPath);
        }
        else
        {
            Directory.CreateDirectory(dirPath);
            Console.WriteLine("The directory {0} was created.", dirPath);
        }
    }
}

[C#]在C#呼叫Native DLLs (P/Invoke)

在C#我們可以透過PInvoke(Platform Invocation Services)來access unmanaged DLL中的function, structs甚至是callbacks function。以下是一個簡單的例子,示範如何在C#中呼叫Windows DLL user32.dll。
MessageBox在user32.dll宣告長這樣:
MessageBox(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
C#範例程式:
using System;
using System.Runtime.InteropServices;    // For StructLayout, DllImport

class MsgBoxTest
{
    [DllImport("user32.dll"]
    static extern int MessageBox(IntPtr hWnd, String text, String caption, int type);

    public static void Main()
    {
        MessageBox(IntPtr.Zero, "Text", "Caption", 0);
    }
}
有三個地方要注意。DllImport定義function位於哪個dll中,static定義這是一個global method,extern則是告訴CLR此function是實做在外部。
看到這邊好像覺得P/Invoke沒什麼,繼續看下去就知道P/Invoke為什麼這麼讓人頭痛。

如果Native function的參數是struct該怎麼處理?讓我們看一個例子:
void GetSystemTime(LPSYSTEMTIME lpSystemTime);

typedef struct _SYSTEMTIME {
  WORD wYear;
  WORD wMonth;
  WORD wDayOfWeek;
  WORD wDay;
  WORD wHour;
  WORD wMinute;
  WORD wSecond;
  WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
要呼叫這個function,我們必須定義一個C#的Class和C Struct有一樣的結構。
using System;
using System.Runtime.InteropServices;     


[ StructLayout( LayoutKind.Sequential )]
public class SystemTime 
{
   public ushort year;
   public ushort month;
   public ushort weekday;
   public ushort day;
   public ushort hour;
   public ushort minute;
   public ushort second;
   public ushort millisecond;
}
StructLayout告訴marshaler如何把每個C# class中的field對應到C Struct中,LayoutKind.Sequential表示每個field是根據pack-size aligned sequentially。預設的pack-size是8,也就是說所有的field都會aligned成8 bytes。我們可以在StructLayout中加入Pack attribute來修改pack-size。
[StructLayout(LayoutKind.Sequential, Pack = 1)]
GetSystemTime範例由於剛好是8 byte aligned所以不需更改pack size。
要呼叫GetSystemTime很簡單:
using System;
using System.Runtime.InteropServices;

class MsgBoxTest
{
    [DllImport("Kernel32.dll")]
    public static extern void GetSystemTime(SystemTime st);

    public static void Main()
    {
        SystemTime t = new SystemTime();
        GetSystemTime(t);
        Console.WriteLine(t.Year);
    }
}
如果不知道native function如何在C#中宣告,可以去PINVOKE.NET搜尋。此網站也提供一個方便的Visual Studio add-in可讓你在VS中直接查詢。

2012年10月30日 星期二

[C#]取得現在日期和時間(DateTime Format String)

在C#中,可以用DataTime取得目前的時間以及日期。
Console.Write(DateTime.Now.TimeOfDay);
21:15:56.0797894
Console.Write(DateTime.Now.ToShortTimeString());
下午 09:16
也可以更進一步用ToString()來把DataTime轉成我們要得格式
Console.Write(DateTime.Now.ToString("HH:mm ss tt"));
21:18 01 下午
大寫HH表示用24小時制表示,也可以改成小寫hh用12小時制
Console.Write(DateTime.Now.ToString("HHmm"));
2118
日期也可以用DataTime取得,以下是範例:
Console.Write(DateTime.Now.ToShortDateString());
2012/10/30
同樣也可以用ToString()轉成我們要得格式
Console.Write(DateTime.Now.ToString("MM-dd-yyyy"));
10-30-2012
Console.Write(DateTime.Now.ToString("ddd dd MMM, yyyy"));
週二 30 十月, 2012

2012年10月26日 星期五

Bit-Banding

什麼是Bit-Banding?

Bit-Banding是一種讓使用者atomic修改memory中某個bit的方法,比如說常見的bit set或是bit clear指令。Bit-Banding把一塊比較大memory中的每個word都對應到一塊較小memory中的每個單一bit,如下圖所示:
上圖把一塊32MB SRAM的每個word都對應到一塊1MB SRAM中的每個bit。其中比較大的memory叫做bit-band alias region,比較小的memory叫做bit-band region。當我們access bit-band alias region中的一個word時,會自動對應到bit-band region的bit。而此Read-Modify-Write的過程被設計成不可被中斷的。因此當我們做bit set以及bit clear時是atomic。

以上圖來說明,當我們把0x23FFFFE0設定成1就相當於把0x200FFFFF的bit[0]設定成1。相反地,當我們把0x23FFFFE0設定成0就相當於把0x200FFFFF的bit[0]設定成0。附帶一提,把值寫到0x23FFFFE0的bit[31:1]是沒有意義的,不會產生任何作用。

Bit-Banding的好處

  1. 減少code size,不需要使用mask對特定bit做修改
  2. 由於atomic的特性,可用來實做mutex以及semaphore

2012年10月18日 星期四

Debug-Later Programming v.s. Test-Driven Development

  • Debug-Later Programming(DLP)
    • Traditional way of programming
    • 設計架構之後開始寫程式
    • 當程式寫完後開始進行測試以及Debug
      • 測試和Debug的過程會佔據軟體開發超過一半的時間
      • late feedback(bug需要花好幾天、數周甚至超過一個月才會讓程式開發者知道)
      • 軟體開發時間拉長,而且難以估計時間
  • Test-Driven Development
    • 在寫程式前先撰寫unit test
      • Tests are small
      • 自動化測試,自動測試是TDD的關鍵
    • 通過所有測試即完成程式
    • 新增功能的步驟
      • 加入一個小的測試程式
      • 跑一遍所有的測資,檢查是否通過所有測項
      • 修改產品的程式碼到通過測項
    • TDD的好處
      • Bug變少
      • debug時間變少
      • 減少發生bug的side effect
      • 測試程式本身就是軟體文件
      • 睡覺睡得安穩,週末不會被打擾
      • 監控專案進度
      • TDD is fun

    2012年7月18日 星期三

    Python 學習手冊

    Learning Python是一本很有名的Python入門書。目前最新版是第四版,但是中文版只有翻譯到第三版,蠻可惜的(這本書是Python書籍裡面我覺得翻譯很好得一本書)。第四版和第三版最大的差別在於多了一些Python 3.0的介紹,其餘部份都大同小異。這本書已經出很久了,但是在Amazon上的評價一直不是很好。不過我覺得這本書其實沒這麼差,今天我來幫他平反一下。

    首先,有兩種人不適合看這本書:

    第一種人是程式語言新手,我甚至覺得Python新手也不適合看這本書。為什麼呢?因為這本書寫得很冗長、很多廢話,而且又喜歡在前面的章節就開始講一些比較進階和細節的技術(像是Python Object Model)。如果完全不會Python的話你一定沒辦法消化這些東西,更不用說之前沒學過程式語言的人。這也就是為什麼在Amazon評分會這麼低的原因之一。

    第二種人是已經有其他程式語言底子,但是想快速學會Python的人,這本磚頭書可是有700多頁,又很多廢話。等你看完都不知道什麼時候了。這種人我會推薦他看The Quick Python Book,一本精簡又好閱讀的入門書。

    講了這麼多缺點,來講講這本書的優點。這本書最大的優點就是他寫得夠詳細。書中提到的一些Python的細節是我在其他書裡頭比較少看到的。比如說這本書完整的介紹Python的reference以及Python Object Model,這些都是初學者學Python很容易搞混的一點。很多書籍都只有簡單帶過或是完全不提。其他像是bound unbound這類比較進階的議題也都有提到。因此這本書很適合當第二本Python的書籍。

    所以不要被他書名給騙了,初學者真的不適合看這本書。反而推薦這本書給有時間並且有一點Python基礎的人,看完後絕對會更了解Python。




    2012年6月29日 星期五

    [C#]OpenFileDialog在XP會更改working directory

    今天碰到一個奇怪的bug。我寫了一個C#的tool用來parsing一些binary的檔案,此程式運作流程如下:

    1. 用OpenFileDialog讓使用者選取parsing的binary檔案
    2. 程式讀取一個parsing過程會用到的參數設定檔(放在和執行檔同一個目錄下)
    3. parsing binary並且把結果寫到一個新的檔案

    看起來很正常的流程,在第二步讀取設定檔時卻會一直回傳找不到檔案的錯誤訊息。而且很奇怪的是這段程式在Win7上可以執行,在XP上卻不能執行。找了很久才發現在XP上執行OpenFileDialog會更改working directory,造成我用相對路徑開檔時會找不到設定檔。而Win7的working directory在執行OpenFileDialog並不會被更改。

    以下是一個簡單的範例程式可以看出working directory在執行OpenFileDialog後會被改掉:
     var dlg = new Microsoft.Win32.OpenFileDialog();
     workingDirectory = Directory.GetCurrentDirectory(); // Correct
     if (open.ShowDialog() == DialogResult.OK) {
            workingDirectory = Directory.GetCurrentDirectory(); // Wrong!!
     }
    
    上面例子執行後會發現workingDirectory最後會被設定成OpenFileDialog選取檔案的那個資料夾。
    還好網路上蠻多人碰到這個問題,解決方法很簡單,只要把RestoreDirectory設成true就可以了,這個Property在WPF以及Windows Form的FileDialog中都有實做:
     var dlg = new Microsoft.Win32.OpenFileDialog();
     dlg.RestoreDirectory = true;
     workingDirectory = Directory.GetCurrentDirectory(); 
     if (open.ShowDialog() == DialogResult.OK) {
            workingDirectory = Directory.GetCurrentDirectory(); 
     }
    
    執行後可以發現執行完OpenFileDialog後working directory不會被改掉了。至於為什麽Win7的working directory不會被改掉,推測是因為Win7的RestoreDirectory預設就是true,而XP預設為false。(此推測是錯誤的,原因是因為系統底層的差異)

    2012年6月26日 星期二

    [C#]Nullable Type

    在C#中,你不能把一個int或bool變數設定成null。比如說以下這個範例:
    bool b = null;  //以下程式編譯時會出現錯誤訊息
    int i = null;     
    DateTime date = null;   
    
    但是有時候我們使用的變數不一定有值怎麼辦? 這時候就可以用Nullable type。只要在宣告變數時候在型態後面加一個問號,此變數就變成可以被設成null的Nullable type。
    bool? nullableBool = null;
    int? nullableInt = null;
    DateTime? nullableDate = null;
    
    一般的變數可以自動轉換成nullable type:
    int i = 5566;
    int? j = i;
    
    如果要把nullable type轉換成一般變數,就需要強制轉換:
    int? i = 5566;
    int j = (int)i;
    
    當一個變數是nullable type時,會自動多一個HasValue的特性。可以用來判斷變數是不是null。如果是null的話HasValue會是false。反之有值不是null的話,就會是true。
    int? num = null;
    if (num.HasValue == true)
    {
        System.Console.WriteLine("num = " + num.Value);
    }
    
    使用nullable type最大的優點就是可以讓你程式比較不容易當掉,一些由user輸入的變數可以宣告成nullable type。如此一來當user輸入錯誤資料時候,可以把變數設定成null之後再做處理。

    2012年6月8日 星期五

    ELF中的.bss section和COMMON section

    大家都知道未初始化的global變數會被配置在.bss section中。不過在ELF中沒有這麼簡單,ELF多了一個COMMON section專門用來存放未初始化的global變數。怎麼會有兩個地方都用來存放未初始化的global變數呢?其實是有差別的,以下我們舉個例子來說明:
    int a = 1;            //.data section
    int b = 0;            //.bss section
    int c;                //COMMON section
    
    由上面例子可以知道,如果我們把global變數初始化為非0的值,會被放在.data section。如果把global變數初始化為0,會被直接放到.bss section。如果完全沒初始化的話就會被放到COMMON section。為什麽要多一個COMMON section呢?其實跟gcc linker的運作有關係。

    以上面例子為例,global變數c其實是一個弱型別。也就是說,如果我們在不同的檔案中都宣告global變數c而沒有初始化的話,gcc linker在做linking時後並不會產生error。
    //test1.c
    int c;
    
    //test2.c
    int c;
    
    上面例子是完全合法的,我們也可以定義一個強型別來把弱型別蓋掉。
    //test1.c
    int c;
    
    //test2.c
    int c;
    
    //test3.c
    int c = 1;    //strong type
    
    上面例子gcc linker在做linking時候如果讀到test1.c中的int c,linker會先把c放到COMMON section中。讀到test2.c的c時,因為發現重複定義,所以不會做處理。讀到test3.c中的c由於是強型別,因此會把之前在COMMON的c蓋掉,在.data section建立一個初始值為1的c。因次最後的結果我們只會在.data section中找到c這個變數,不會在COMMON中看到c這個變數。

    當然如果同時定義兩個強型別變數的話,Linker就會跟你抗議了。
    //test1.c
    int c = 1;    //strong type
    
    //test2.c
    int c = 2;    //strong type
    
    test2.o:(.data+0x0): multiple definition of `c'
    test1.o:~/sway/test1.c:4: first defined here
    collect2: ld returned 1 exit status
    
    由上面這些例子可以知道,COMMON其實主要的用途是用來讓linker做merge用的。因此uninitialized的global變數會被暫時放在COMMON section,等Linker做完merge之後再看情況搬到正確的section中,也可能繼續留在COMMON section。因為這種特性,大多數embedded project的linker script檔都會把COMMON section放在.bss裡頭。這樣當程式啟動時可以一併把COMMON也清為0。
    .bss { *(.bss) *(COMMON) }