在工作上,常常需要把同樣的功能,porting 到不同的平台上去。有些 porting 很簡單,找出對應的 API 代換即可,但有些 porting,就真的很需要對各個平台,非常細緻的理解與熟習。

對應的 API 代換很簡單,例如 pthread_create() 換到 Win32 平台,可以用 _beginthreadex() 代替,頂多需要注意一下參數的不同即可。

有些 API 代換則比較複雜一些,牽涉到程式邏輯的不同。不過因為在功能面上是對等的,只是寫法不同,所以也不是什麼大問題。

例如,「列出目錄內容」這件事,在 UNIX 上要用 opendir/readdir/closedir 這組函式:

void list_dir(const char* dir)
{
    DIR* d = opendir(dir);
    dirent* e;
    while ((e = readdir(d)) != NULL) {
        printf("%s\n", e->d_name);
    }
    closedir(d);
}

但是到了 Windows 上,則要用 FindFirstFile/FindNextFile/FindClose 這組函式:

void list_dir(const char* dir)
{
    // prepare wildcard path
    char wild[MAX_PATH];
    strcpy(wild, dir);
    strcat(wild, "\\*");

    WIN32_FIND_DATA wfd;
    memset(&wfd, 0, sizeof(wfd));
    HANDLE h = FindFirstFile(wild, &wfd);
    if (h == INVALID_HANDLE_VALUE) {
        throw "FindFirstFile() failed.";
    }
    do {
        printf("%s\n", wfd.cFileName);
    } while (FindNextFile(h, &wfd));
    FindCloseFile(h);
}

兩組函式的寫法不太一樣,差別在於,第一筆資料在 UNIX 上,同其他筆資料,係由 readdir() 取得,但在 Windows 上,卻是第一筆資料由 FindFirstFile() 取得,其他筆資料由 FindNextFile() 取得。這點差異,導致了兩邊的程式寫法不太一樣。

不過如何,最近我碰到的這個例子,才真的叫難。

話說 socket programming 是由 BSD (UNIX) 發展出來的,之後才移植到各個平台,如 Windows 上的 WinSock。當一條 TCP 連線使用完畢時,我們可以呼叫 close() 將這個 socket 關閉,也可以用 shutdown() 取代 close()

在 UNIX 裡,close() 會做 reference counting,若有多個 socket 對應到同一條 TCP connection,則只有當最後一個 socket 被 close() 時,這條 TCP connection 才真的會被結束掉。這通常發生於使用 fork() 來處理 incoming connection 時,母行程 accept() 得到 socket 後,會呼叫 fork() 複製出子行程來處理。此時,母行程與子行程各自擁有一個 socket,同時對應到這條 TCP connection。當母行程 fork() 完畢後,會馬上呼叫 close() 將 socket 關閉,因為母行程再也不需要用到這條 TCP connection。如果這個 close() 真的把 TCP connection 給結束掉,那子行程就沒戲唱了。所以,close() 會做 reference counting,只有在子行程也 close() 時,才會真的把 TCP connection 關閉。

在 Windows 裡,socket 不能用 close() 關閉,要用一個特別版本的 closesocket() 才行。closesocket() 與 UNIX 上的 close(),功能相同。

至此,porting 仍處在 API 對應的等級,很簡單。可是,問題就出在可以取代 close()shutdown()

兩者之間的差別在於,shutdown() 將不理會 socket 的 reference counting,馬上送出 FIN 將 TCP connection 結束。使用 shutdown() 的好處顯而易見,我們可以確定這個 TCP connection 立刻結束,可加強資源控管。

依據《UNIX Network Programming》這本 socket programming 的聖經級書本所述:

If we really want to send a FIN on a TCP connection, the shutdown function can be used instead of close.

我們可以用 shutdown() 完全取代 close():用了 close() 就不要用 shutdown(),用了 shutdown() 就不要用 close()

可是,MSDN 卻說:

The shutdown function does not close the socket. Any resource attached to the socket will not be freed until closesocket is invoked.

意思是說,在 WinSock 裡,用了 shutdown() 之後,還要用 closesocket(),才能「關乾淨」把資源全部釋放。

就是因為這一點細節的不同,若是忽略了,就會產生 leak。要不是不小心看到 MSDN 的這一句,我也會疏忽掉,產生極難追蹤的 bug。

唯有通曉這類精微的細節,才能做到「真㊣的 porting」。面對浩如瀚海的 Windows APIs,我也只能兢兢業業地,一步一腳印,細讀 MSDN 的每一字每一句,希望能夠捕捉到,這些會害死人不償命的微光掠影。