星期二, 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月 13, 2009

Install VMware workstation v6.0.0.45731 on Ubuntu 8.0.4

After installing VMware workstation on Ubuntu 8.0.4, a compiler error which says "conflicting types for ‘poll_initwait’" will raise. The same error will also generate when you run vmware-config.pl to configure the VMware. We can fix the problem in the following steps:

  1. Download the patch from Petr Vandrovec at http://knihovny.cvut.cz/ftp/pub/vmware/vmware-any-any-update115.tar.gz.
  2. tar xfz vmware-any-any-update115.tar.gz
  3. Change the working directory into vmware-any-any-update115
  4. sudo ./runme.pl

Then, you will get another compiler error which says "only <linux/bitops.h> can be included directly". Fix the probelm in the following steps:

  1. cd /usr/lib/vmware/modules/source
  2. cp vmmon.tar vmmon.tar.orig
  3. sudo tar xvf vmmon.tar
  4. cd vmmon-only/include/
  5. sudo vi vcpuset.h
  6. change line 74 from: #include "asm/bitops.h" to #include "linux/bitops.h"
  7. rm vmmon.tar
  8. sudo tar cvf vmmon.tar vmmon-only/
  9. sudo rm -rf vmmon-only/
  10. sudo vmware-config.pl

星期五, 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串接

String物件的長度是固定的, 不能改變它的內容. 在程式中, 使用+來串接字串實際上會產生新的字串物件. 如果程式中此動作非常頻繁, 程式會變得比較沒有效率.

在J2SE 5.0之前, 可以使用StringBuffer來做這些字串的處理, 從J2SE 5.0開始, 也可以使用StringBuilder來做相同的事情. 這兩個類別的差別在於多執行緒的同步管理. 如果程式是多執行緒程式, 則使用StringBuffer, 因為StringBuilder沒有處理同步的問題.

public class AppendStringTest {
    public static void main(String[] args) {
        StringBuilder builder = new StringBuilder("");
        long beginTime = System.currentTimeMillis();
        for(int i=0; i<10000; i++)
            builder.append(String.valueOf(i));
        long endTime = System.currentTimeMillis();
        System.out.println("執行時間: "+(endTime-beginTime));
        String text = "";
        beginTime = System.currentTimeMillis();
        for(int i=0; i<10000; i++)
            text = text + i;
        endTime = System.currentTimeMillis();
        System.out.println("執行時間: "+(endTime-beginTime));
    }
}

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