顯示具有 程式設計 標籤的文章。 顯示所有文章
顯示具有 程式設計 標籤的文章。 顯示所有文章

星期二, 9月 01, 2009

指定Java Application使用的語言

開發Java程式時, 可以利用resource的方式, 將程式的訊息以及ui等設計成支援多國語言, 然後執行時加上-Duser.language=locale_name或-Duser.country=country_name就可以切換不同的語言介面.

完整的指令為

顯示英文介面:
java -Duser.language=en -jar test.jar或
java -Duser.country=us -jar test.jar

顯示中文介面:
java -Duser.language=zh -jar test.jar或
java -Duser.country=TW -jar test.jar

星期日, 8月 09, 2009

struct成員的記憶體位址alignmentt

一般來說, compiler編譯c程式時, 遇到struct時會對成員資料的位址進行alignment的動作, 以增加記憶體讀取的效率. 所以struct往往會佔有比所有資料成員大小和還要多的記憶體空間. 例如下列的struct data的記憶體大小為12.
struct data {
char num;
int size;
void *ptr;
};

如果因為特殊需要, 需要節省記憶體, 則可以在程式碼中需要進行pack memory的struct前後, 採用#pragma pack來告訴compiler是否要進行pack memory的動作, 例如下列的struct packed_data的記憶體大小則為9, 不是12.
#pragma pack(1)
struct packed_data {
char num;
int size;
void *ptr;
};
#pragma pack()

星期二, 3月 17, 2009

Windows Service學習心得

Windows service與一般Windows的應用程式一樣, 也是一個執行檔. 不同的是, Windows service程式是以console application的形式在系統背景中執行. 既然Windows service是以console application的形式存在, 所以在撰寫Windows service時, source code中第一個最重要的地方就是main(), 也是整個service program的進入點.

在main中, 我們必須完成的事情很簡單, 就是將每個service的名稱以及其對應的serivce procedure註冊就好了. 方法很簡單, 就是先將SERVICE_TABLE_ENTRY的內容填好, 然後呼叫StartServiceCtrlDispatcher()就可以了.

#include <windows.h>
#include <stdio.h>

#define SLEEP_TIME 5000
#define LOGFILE "c:\\Temp\\memstatus.txt"

int WriteToLog(char* str)
{
    FILE* log;
    log = fopen(LOGFILE, "a+");
    if(log == NULL)
        return -1;
    fprintf(log, "%s\n", str);
    fclose(log);
    return 0;
}

SERVICE_STATUS ServiceStatus;
SERVICE_STATUS_HANDLE hStatus;

void ServiceMain(int argc, char** argv);
void ControlHandler(DWORD request);
int InitService();

void main()
{
    SERVICE_TABLE_ENTRY ServiceTable[2];

    ServiceTable[0].lpServiceName = "MemoryStatus";
    ServiceTable[0].lpServiceProc = (LPSERVICE_MAIN_FUNCTION)ServiceMain;
    ServiceTable[1].lpServiceName = NULL;    // 最後一個service entry table欄位必須為NULL
    ServiceTable[1].lpServiceProc = NULL;

    // Start the control dispatcher thread for our service
    StartServiceCtrlDispatcher(ServiceTable);
}

有幾點必須要說明一下, 首先, 一個service program不一定只能包含一種service, 如果有多個service, 則上列的ServiceTable陣列的element則會有多個. 只要記住最後一個element的service name和service procedure都必須為NULL表示陣列結束就可以了. 如此一來, service program就是負責作為SCM (Service Configuration Manager)與service之間溝通的橋樑, 也就是所謂的control dispatcher, 負責將SCM傳送過來的request傳遞給相對應的service, 並且監控service的執行.

另外, SCM啟動service program後, 必須在30秒內呼叫StartServiceCtrlDispatcher(), 否則會回報為啟動錯誤. 所以如果每個service的啟動有一些啟動程序必須執行, 應該寫在service procedure中, 也就是上例中的ServiceMain.

ServiceMain是service的entry point, 也就是我們在service program中為每個service所註冊的service procedure. 每個service的service procedure會由dispatcher產生一個新的thread來執行. 在service procedure中我們首先必須盡快為service註冊control handler, 也就是呼叫RegisterServiceCtrlHandler()來註冊該service名稱所對應的service control handler. 不管service的狀態是否有改變, 在control handler中都要回報service status, 透過呼叫SetServiceStatus()回報service status給SCM.

void ServiceMain(int argc, char** argv)
{
    int error; 

    
    hStatus = RegisterServiceCtrlHandler("MemoryStatus", (LPHANDLER_FUNCTION)ControlHandler);
    if(hStatus == (SERVICE_STATUS_HANDLE)0)
    {
        // Registering Control Handler failed
        return;
    }
   
    ServiceStatus.dwServiceType = SERVICE_WIN32;
    ServiceStatus.dwCurrentState = SERVICE_START_PENDING;
    ServiceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN;
    ServiceStatus.dwWin32ExitCode = 0;
    ServiceStatus.dwServiceSpecificExitCode = 0;
    ServiceStatus.dwCheckPoint = 0;
    ServiceStatus.dwWaitHint = 0;
    SetServiceStatus(hStatus, &ServiceStatus);

    // Initialize Service
    error = InitService();
    if(error)
    {
        // Initialization failed
        ServiceStatus.dwCurrentState = SERVICE_STOPPED;
        ServiceStatus.dwWin32ExitCode = -1;
        SetServiceStatus(hStatus, &ServiceStatus);
        return;
    }

    // We report the running status to SCM.
    ServiceStatus.dwCurrentState = SERVICE_RUNNING;
    SetServiceStatus(hStatus, &ServiceStatus);

    MEMORYSTATUS memory;
    // The worker loop of a service
    while(ServiceStatus.dwCurrentState == SERVICE_RUNNING)
    {
        char buffer[16];
        GlobalMemoryStatus(&memory);
        sprintf(buffer, "%d", memory.dwAvailPhys);

        int result = WriteToLog(buffer);
        if(result)
        {
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            ServiceStatus.dwWin32ExitCode = -1;
            SetServiceStatus(hStatus, &ServiceStatus);
            return;
        }

        Sleep(SLEEP_TIME);
    }
    return;
}

// Service initialization
int InitService()
{
    int result;
    result = WriteToLog("Monitoring started.");
    return result;
}

在service control handler中, 會針對SCM所送出的request呼叫相對應的處理程式. 可以接受的request種類則是在ServiceMain註冊service control handler時所設定. 基本上SERVICE_CONTROL_STOP和SERVICE_CONTROL_SHUTDOWN的處理方式一樣, 用來關閉service, 差別在於SERVICE_CONTROL_SHUTDOWN是在系統關機時, 由SCM發送給每個service.

void ControlHandler(DWORD request)
{
    switch(request)
    {
        case SERVICE_CONTROL_STOP:
            WriteToLog("Monitoring stopped.");
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            SetServiceStatus(hStatus, &ServiceStatus);
            return;
        case SERVICE_CONTROL_SHUTDOWN:
            WriteToLog("Monitoring stopped.");
            ServiceStatus.dwWin32ExitCode = 0;
            ServiceStatus.dwCurrentState = SERVICE_STOPPED;
            SetServiceStatus(hStatus, &ServiceStatus);
            return;
        default:
            break;
    }

    // Report current status
    SetServiceStatus(hStatus, &ServiceStatus);
}

要安裝service可以利用系統提供的command line utility, 如下所示. 要注意的是, binpath=後面必須有一個空格.

sc create MemoryStatus binpath= c:\Temp\MemoryStatus.exe

要刪除service也同樣可以使用系統提供工具, 如下所示.

sc delete MemoryStatus

 

參考資料:

在Windows XP上安裝Windows CE模擬器出現錯誤

安裝Microsoft eMbedded Visual C++在Windows XP上時出現了"虛擬 PC / Windows CE 模擬器' 將造成 Windows 變得不穩定。 Windows 已禁止載入這些驅動程式。"這個訊息, 則無法開啟Windows CE模擬器來測試所撰寫的Windows CE程式. 要解決這個問題很簡單, 只要稍微修改一下C:\Boot.ini這個檔案的內容就可以了. 修改步驟如下:

  1. 在桌面上"我的電腦"的圖示上按滑鼠右鍵, 選擇內容開啟"系統內容"
  2. 選擇"進階"頁面
  3. 在視窗下方的"啟動及修復"方塊中按一下"設定"
  4. 在出現的"啟動及修復"視窗中按一下"編輯"
  5. 然後將"/noexecute=optin"刪除, 並加上"/execute"
  6. 存檔後重新開機就大功告成了

參考資料:

星期五, 3月 06, 2009

Static initialization block and initializer block

Static initialization block在類別載入時會執行該區塊中的程式碼, 而且整個程式的執行過程中只會執行一次.

Initializer block則是在每個物件被建立時執行該區塊中的程式碼, 接著才執行contructor中的程式碼.

class ClassA {
    // static initialization block
    static {
        System.out.println("This is static initialization blocks");
    }

    // initializer block
    {
        System.out.println("This is initializer block");
    }

    public ClassA()
    {
        System.out.println("Constructor A");
    }

    public void print()
    {
        System.out.println("I am A");
    }

    public static void staticPrint()
    {
        System.out.println("Static print");
    }
}

public class Test01 {
    public static void main(String[] args) {
        System.out.println("Start testing...");
        ClassA.staticPrint();
        ClassA a = new ClassA();
        a.print();
    }
}

星期日, 3月 01, 2009

Java的String pool

Java執行時會維護一個String pool, 對於一些可以共享的字串物件, 會先在String pool中尋找, 有相同的字串內容就直接傳回, 減少記憶體的耗用.

public class test02 {
    public static void main(String args[])
    {
        String a = new String("abc");
        String b = new String("abc");
        String c = "abc";
        String d = "abc";
        String e = "def";
        String f = "abcdef";
        String g = c+e;
        String h = (c+e).intern(); 
        String i = new String("abc").intern();

        System.out.println(a == b);    // false
        System.out.println(c == d);    // true
        System.out.println(f == g);    // false
        System.out.println(f == h);    // true
    }
}

星期四, 2月 05, 2009

Java中的foreach

J2SE 5.0之後新增了foreach的語法, 可用於存取陣列元素. 以下為一些簡單的範例:

一維陣列

public class test02 {
    public static void main(String args[])
    {
        int[] a = new int[] {1, 2, 3, 4, 5};

        for(int v : a)
            System.out.println(v);
    }
}

二維陣列

public class test02 {
    public static void main(String args[])
    {
        int[][] a = new int[][] {
            {1, 2, 3, 4, 5},
            {10, 11, 12}
        };

        for(int[] row : a) {
            for(int element : row)
                System.out.print(element + " ");
            System.out.println();
        }
    }
}

Autoboxing in Java

自動boxing/unboxing是J2SE 5.0之後提供的新功能, 可以由compiler視程式需要自動在primitive data type以及object之間進行轉換. Boxing就是將primitive data type轉成object, 而unboxing則是把object轉成對應的primitive data type. 如下範例所示:

public class test02 {
    public static void main(String args[])
    {
        Integer i1;
        int a = 100;

        i1 = new Integer(a); // boxing
        i1 = a/2; // auto boxing

        a = i1.intValue()/5; // unboxing
        a = i1/2; // auto unboxing
    }
}

雖然這功能非常方便, 但是有幾點是必須要注意的. 首先, 因為這動作是compiler time的動作, 所以有可能會發生程式可以成功編譯但是執行時卻發生執行錯誤的現象. 例如:

public class test02 {
    public static void main(String args[])
    {
        Integer i = null;
        int a;

        a = i / 2; // null pointer exception
    }
}

另外, 數值大小不同也會使boxing的行為有些不同, 例如:

public class test02 {
    public static void main(String args[])
    {
        Integer i = 100;
        Integer j = 100;

        if(i == j)
            System.out.println("equal");
        else
            System.out.println("not equal");
    }
}

此程式會得到equal. 但是下一個範例則會得到not equal.

public class test02 {
    public static void main(String args[])
    {
        Integer i = 200;
        Integer j = 200;

        if(i == j)
            System.out.println("equal");
        else
            System.out.println("not equal");
    }
}

這兩個範例唯一的差別在於程式中所指定的數值, 一個為100, 另一個為200, 但是結果卻也不同. 這是因為auto boxing 在數值介於-128到127之間時, 記憶體會重複使用, 所以第一個範例的i與j會是相同的物件. 反之, 第二個範例則會是兩個不同的物件, 所以兩支程式的執行結果也就不一樣.

星期五, 6月 27, 2008

找出程式出現segmentation fault的方法

寫程式出現segmentation fault是常有的事。如果這個bug是固定會出現的,那還算幸運,因為你已經知道發生segmentation fault的地方在哪哩,只要好好看source code應該就可以解決了。但是,如果segmentation fault是三不五時才出現,而且沒有一定的規則會使這個錯誤出現,那就很麻煩了。不過,我們其實可以利用signal handler的方式,攔截segmentation fault發生時的錯誤處理方式,就可以輕鬆的找到錯誤發生的那行程式碼了。

原理是這樣的,原本程式如果發生segmentation fault,作業系統在印出錯誤訊息之後就會將該程式結束。可是,這種處理方式對我們並沒有多大的幫助,因為該程式的process都已經從記憶體中一除了。所以,我們要做的就是,改變發生segmentation fault時處理process的方式。改變的方法就是利用signal()這個系統function。其使用方式很簡單,就是指定要攔截的signal以及處理該signal的handler就可以了。

在下面的程式碼中,我們寫了一個超級簡單的signal handler來代替原來系統預設的segmentation fault handler。就是寫一個無窮迴圈將程式停住,這樣我們就可以用gdb來看看到底是哪一行程式碼觸發segmentation fault了。

#include <signal.h>
#include <stdio.h>

void sighandler(int sig)
{
    while(1)
        ;
}

void error_func()
{
    char *msg = NULL;
    printf("%s\n", msg);  // 這裡會發生segmentation fault
}

int main()
{
    signal(SIGSEGV, sighandler);
    error_func();
    return 0;
}

glib的message logging utility

glib提供了一個message logging utility可以讓我們可以在程式執行時輸出一些訊息,顯示程式目前的狀況或是遇到的錯誤。跟printf比起來,glib提供的message logging utility更加多樣化,可以根據不同的執行狀況設定不同等級的訊息。以下列出一個簡單的範例:

#include <glib.h>

int main()
{
    g_debug("%s", "this is a debug message");
    g_log("mytest", G_LOG_LEVEL_CRITICAL, "%s", "This is a critical message");
    g_log("mytest", G_LOG_LEVEL_INFO, "%s", "This is an info message");
    g_log("mytest", G_LOG_LEVEL_DEBUG, "%s", "This is another debug message");
    g_log("mytest", G_LOG_LEVEL_ERROR, "%s", "This is an error message");
    return 0;
}

編譯時記得要加上glib的header file所在位置(用-I)以及library的所在位置(用-L),在我的系統上是以下列方式編譯的:

gcc -g -o test_glog test_glog.c -I/usr/include/glib-2.0 -I/usr/lib/glib-2.0/include  -lglib-2.0

執行結果如下所示:

** (process:24480): DEBUG: this is a debug message

(process:24480): mytest-CRITICAL **: This is a critical message
mytest-INFO: This is an info message
(process:24480): mytest-DEBUG: This is another debug message

mytest-ERROR **: This is an error message
aborting...
已經終止 (core dumped)

星期三, 5月 21, 2008

不安裝MS JVM也可以安裝Visual Studio 6.0

在Windows XP上安裝Visual Studio 6.0時,通常都會出現下列這個畫面,要求你先安裝Microsoft Virtual Machine for Java,重開機後才可以繼續安裝Visual Studio 6.0。

image

可是微軟已經不再Microsoft Visual Machine for Java,所以實在是不太想裝這個老舊的軟體在自己的電腦上。在網路上google了一下想找看看有沒有解決方法,結果運氣還不錯,一下子就找到了答案了,而且方法非常簡單,就是"騙",也就是騙Visual Studio 6.0安裝程式已經安裝了Microsoft Virtual Machine for Java在自己的機器上就可以順利安裝了。

接下來就簡單介紹一下如何騙。

  1. 開個dos視窗(就是黑黑的那個囉)
  2. cd \windows
  3. echo "" > msjava.dll

好了,就這麼簡單,已經可以安裝Visual Studio 6.0了。安裝完畢後,記得再把msjava.dll殺掉就可以了。

參考資料:

Installing VB6 without Java

星期日, 5月 18, 2008

讓Visual Studio 2005編譯出來的程式可以拿到沒有安裝VS2005的電腦上執行的方法

使用Visual Studio 2005所開啟Win32專案 或是Win32主控台控制專案,如果把所編譯的執行檔拿到沒有安裝.Net framework的電腦上去執行,不管是debug或是release組態的執行檔,都會出現應用程式設定不正確的錯誤訊息:

e1

本來還以為是Visual Studio 2005不支援編譯native code,可是又覺得怪怪的。經過一番研究之後,終於發現其實問題是出在專案的組態設定而已。

在Visual Studo 2005中建立Win32專案或是Win32主控台控制專案,在專案屬性設定視窗中的組態屬性->C/C++->程式碼產生設定頁,有一個設定是設定程式執行時程式庫的連結方式,在Debug組態預設是/MDd,image

而Release組態則預設是/MD。

image

要解決這個問題就是只要將Debug組態的/MDd改成/MTd,Release組態的/MD改成/MT,問題就迎刃而解了。 因為改成/MTd或是/MT的程式碼產生方式,會將程式所需要的MSVCRxx.dll改以static linking的方式編譯,非以dynamic linking的方式,所以程式執行時並不需要去找相關的dll檔案。

不過這樣做,會使執行檔的檔案大小變大一些些喔。

星期五, 8月 24, 2007

利用gdb進行shared library的除錯

寫程式時, 遇到bug最常使用的除錯工具就是gdb了. gdb會利用ld.so這個dynamic linker/loader, 自動將我們的程式載入記憶體, 並且在執行過程中將程式所使用到的shared library載入. 然而, 如果我們要debug的是已經compile成shared library的function時, 這種on-demand loading的運作方式則會造成我們的不便, 因為我們無法預先設定breakpoint以便在程式呼叫到shared library中的function時, 將程式執行暫停.

因此, 如果要以gdb對已經compile成shared library的function進行除錯時, 可以採用下列方式來進行在shared library中的function的除錯工作:

  1. 首先, 當然是在編譯shared library時, 必須有debug information, 也就是compile時要加上-g的option
  2. 再來就是告訴ld.so, 在載入程式時,一併載入我們所要debug的shared library, 只要利用LD_PRELOAD環境變數即可. LD_PRELOAD是告訴ld.so, 在程式一開始載入時, 就順便一起載入LD_PRELOAD環境變數所指定的shared library.
  3. export LD_PRELOAD=要debug的shared library路徑
  4. 執行gdb進行程式除錯, 並且先隨便設定一個中斷點
  5. break main
  6. 輸入run讓gdb開始執行程式, 此時gdb應該會一下子就暫停程式的執行. 這時候就可以確定一下我們要debug的shared library是否已經載入
  7. info share

此時應該就可以找到我們在shell中利用LD_PRELOAD環境變數要求ld.so載入的shared library了. 接下來就可以設定shared library中的function為中斷點並且進行debug了.

星期二, 8月 21, 2007

檔案比較工具

當我們需要比對兩個檔案的內容時,在Linux上,常常第一個想到的就是diff這個文字模式的工具。這個雖然牌子老、信用好,但是缺乏了圖形化介面總是比較不那麼方便。其實,我們可以有許多其他的選擇,例如:tkdiff、fldiff。


tkdiff是diff的圖形化front-end,其本質上仍然是使用diff進行檔案比比對的動作,只是幫diff包了一個漂亮的圖形外觀。除此之外,tkdiff還可以進行檔案merge的動作。還有一點值得特別強調一下,就是tkdiff不只可以對一般檔案進行比比對,也可以對source control system中的檔案進行比對動作。
tkdiff的使用方法很簡單,可以只輸入tkdif進入interactive模式,直接選取要比對的檔案,或是在命令列直接輸入要比較即可。

fldiff也是一個圖形化的檔案比對工具,其基本功能跟tkdiff差不多,都是可以比較一般的檔案以及source control system中的檔案。不過fldiff比tkdiff多了一個目錄內容比對功能,就是可以指定兩個目錄,然後針對目錄中相同名稱的檔案進行內容比對的功能。

tkdiff有已經編譯好的Windows版本,fldiff需要自己編譯。


相關連結:

星期日, 8月 12, 2007

好用的寫程式工具

在寫程式的時候常常需要作兩個檔案之間的比較,有一些商業軟體提供了相當不錯的功能,例如:UltraEdit。可是這些軟體通常需要花費不少的銀兩,對於可憐又貧窮的我,實在是無法負擔這些額外的支出,在這裡要跟大家推薦一下一些好用的免費軟體。

首先是程式編輯器。我個人比較現在常用的是Notepad++以及PSPad兩套軟體。這兩套軟體提供了大部分UltraEdit中的功能,而且都提供了中文使用介面。不過,PSPad有提供檔案比較功能,但是Notepad++好像沒有提供檔案比較的功能,這對於常常必須在檔案之間比較內容實在很不方便。所以就上網google一下,果然發現了一個好用、免費還open source的檔案比較工具,那就是WinMerge。

WinMerge是一個open source project,其除了提供了檔案比較的功能,也同樣有中文使用介面。

參考網址:
1. UltraEdit http://www.ultraedit.com/
2. Notepad++ http://notepad-plus.sourceforge.net/tw/site.htm
3. PsPad http://www.pspad.com/
4. WinMerge http://winmerge.org/