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) }