Windows CE 編程的十點忠告

來源:互聯網
上載者:User

[文章導讀]
 
本文是根據作者在Windows CE上編寫Raima 資料管理器的經驗總結出來的,值得CE開發人員借鑒

[本文]
最近兩周我們花了大部分時間將已有的應用程式移植到Microsoft Windows CE中。一般說來,這個計劃不是太難。我們起步於Microsoft Win32代碼,當然Windows CE是基於Win32應用程式介面(API)的。有利的是,我們的應用程式(即Raima 資料管理器)有方便的使用介面,並包含一個大約由150個子函數組成的庫,這些函數都是由C語言寫成,可以用來建立、管理和訪問資料庫。

  按建立應用程式的方式來說,我們原以為將它移植到Windows CE中是一項相對簡單的C語言編程練習。然而,我們不久便遇到好些困難。從粗心大意的錯誤開始,比如在基於Windows NT 的Windows CE模擬器上使用Microsoft Windows NT庫,接著又違背Windows CE的編程戒律,如"千萬不要給Unicode(國際標準組織10646標準)字元分配奇數記憶體位址"。

  大約有百分之九十的問題或多或少地與Unicode有關。儘管Unicode編程不難,但是,當給單位元組字元編寫代碼時,很容易出錯(我有過許多次錯誤)。

  下面這些忠告是根據我們在Windows CE上編寫Raima 資料管理器的經驗總結出來的,但我相信,在做任何其它Windows CE程式之前,它們都值得借鑒。畢竟大多數Windows開發人員,當他們建立第一個Windows CE應用程式時,真正運用的是已掌握的Win32知識。

  1. 不要在模擬器上使用Windows NT庫

  這裡所討論的第一個錯誤實在太愚蠢了,但我還是陷了進去,也許你也會。當用Microsoft VC++(5.0版)建立一個Windows CE程式時,你會發現,包含路徑(include)、 庫路徑(library)、及可執行程式路徑被自動調整以匹配反應目標環境的選擇。因此,比如說為Windows CE模擬器建立應用程式時,你會發現,include路徑沒有指向Win32的包含檔案(在VC目錄下),而是指向Windows CE包含檔案(在WCE目錄下)。千萬別去修改。

  由於Windows CE在Windows NT下運行,所以模擬器上啟動並執行程式能夠調用任一Windows NT動態連結程式庫(DLL)中的函數,即使這個DLL不是模擬器的成員也一樣。顯然,這不是很好的事,因為相同的函數也許在手持PC(H/PC)或Windows CE裝置上不可用,而你的軟體最終要能在這些裝置上運行。

  第一次將非Unicode應用程式裝入Windows CE模擬器時,你會發現,許多正在使用的函數它都不支援,例如美國國家標準協會(ANSI)定義的字元函數strcpy()。這也許引誘你去連結Windows NT 已耗用時間庫,以便能解決所有問題。

  如果你是剛開始用Windows CE編程,可能你能用的包含檔案和庫檔案是明顯的。答案就是,你不要採用那些在寫普通Win32或非Windows CE程式時使用的包含檔案和庫檔案。

2. 不要混淆TCHARs和bytes

  如果你正在Windows CE上寫非Unicode應用程式,你或許要將所有的字串從單個字元(chars)轉換為寬字元(widechars)(例如,C變數類型whcar_t)。幾乎所有Windows CE支援的Win32和已耗用時間庫函數都要求寬字元變數。Windows 95不支援Unicode,然而,為了使程式碼具有可移植性,你要儘可能採用tchar.h中定義的TCHAR類型,不要直接使用wchar_t。

  TCHAR是定義為wchar_t還是char,取決於前置處理器的符號UNICODE是否定義。同樣,所有有關字串處理函數的宏,如_tcsncpy宏,它是定義為Unicode函數wcsncpy還是定義為ANSI函數strncpy,取決於UNICODE是否定義。

  在現存的Windows應用程式中,有些代碼也許暗示字元長為單位元組。這在給字串分配記憶體時經常用到,例如:

int myfunc(char *p)
{
char *pszFileName;

pszFileName = malloc(MAXFILELEN);
if(pszFileName)
strncpy(pszFileName, p, MAXFILELEN);
/*etc*/

  在這段代碼中,分配的記憶體塊應該寫作(MAXFILELEN * sizeof(char)),但是大多數程式員喜歡將它簡化為MAXFILELEN,因為對於所有的平台來說sizeof(char)的值等於1。然而,當你用TCHARS代替多個字元時,很容易忘記這種固有的概念,於是將代碼編寫成下面的形式:

int myfunc(TCHAR *p)
{
TCHAR *pszFileName;

PszFileName = (TCHAR*)malloc(MAXFILELEN);
If (pszFileName)
tcsncpy(pszFileName, p, MAXFILELEN);
/*etc*/

  這是不行的。它馬上會導致出錯。這裡的錯誤在於malloc函數中指定變數大小為bytes,然而_tcsncpy函數中使用的第三個變數卻指定為TCHARs而不是bytes。當UNICODE被定義時,一個TCHAR等於兩個位元組數(bytes)。

  上述程式碼片段應該改寫為:

int myfunc(TCHAR *p)
{
TCHAR *pszFileName;

PszFileName = (TCHAR*)malloc(MAXFILELEN * sizeof(TCHAR));
if(pszFileName)
tcsncpy(pszFileName, p, MAXFILELEN);
/*etc*/

  3. 不要將Unicode 字串放入奇數記憶體位址

  在Intel系列處理器上,你可以在一奇數記憶體位址儲存任何變數或數組,不會導致任何致命的錯誤影響。但在H/PC上,這一點不一定能行 ? 你必須對大於一個位元組的資料類型小心謹慎,包括定義為無符號短型(unsigned short) 的wchar_t。當你設法訪問它們的時候,將它們置於奇地址會導致溢出。

  編輯器經常在這些問題上提醒你。你無法管理堆棧變數地址,並且編輯器會檢查確定這些地址與變數類型是否相匹配。同樣,已耗用時間庫必須保證從堆中分配的記憶體總是滿足一個word邊界 ,所以你一般不必擔心那兩點。但是,如果應用程式含有用memcpy()函數拷貝記憶體地區的代碼,或者使用了某種類型的指標算術以確定記憶體位址,問題也許就出現了。考慮下面的例子:

int send_name (TCHAR * pszName)
{
char *p, *q;
int nLen=(_tcslen(pszName) + 1) * sizeof(TCHAR);

p=maloc(HEADER_SIZE + nLen);
if(p)
{
q = p + HEADER_SIZE;
_tcscpy((TCHAR*)q, pszName);
}
/* etc */

  這段代碼是從堆中分配記憶體並複製一個字串,在字串的開頭留一個HEADER_SIZE的大小。假設UNICODE定義了,那麼該字串就是一個widechar字串。如果HEADER_SIZE是一個偶數,這段代碼就會正常工作,但如果HEADER_SIZE為奇數,這段代碼就會出錯,因為q指向的地址也將為奇數。

  注意,當你在Intel系列處理器中的Windows CE模擬器上測試這段代碼時,這個問題是不會發生的。

  在這個例子中,只要確保HEADER_SIZE為偶數,你就可以避免問題的發生。然而,在某些情況下你也許不能這麼做。例如,如果程式是從一台式PC輸入資料,你也許不得不採用事先定義過的二進位格式,儘管它對H/PC不適合。在這種情況下,你必須採用函數,這些函數用字元指標控制字元串而不是TCHAR指標。如果你知道字串的長度,就可以用memcpy()複製字串。因此,採用逐個位元組分析Unicode字串的函數也許足以確定字串在widechars中的長度。

  4. 在ANSI和Unicode字串之間進行翻譯

  如果你的Windows CE應用程式介面於台式PC,也許你必須操作PC機中的ANSI字串資料(例如,char字串)。即使你在程式中只用到Unicode字串,這都是事實。

  你不能在Windows CE上處理一個ANSI字串,因為沒有操縱它們的庫函數。最好的解決辦法是將ANSI字串轉換成Unicode字串用到H/PC上,然後再將Unicode字串轉換回ANSI字串用到PC上。為了完成這些轉換,可採用MultiByteToWideChar()和WideCharToMultiByte () Win32 API 函數。

5. 對於Windows CE 1.0的字串轉換,劈開(hack)

  在Windows CE 1.0 版本中,這些Win32API函數還沒有完成。所以如果你想既要支援CE 1.0又能支援CE 2.0,就必須採用其它函數。將ANSI字串轉換成Unicode字串可以用wsprintf(),其中第一個參數採用一widechar字串,並且認識"%S"(大寫),意思是一個字串。由於沒有wsscanf() 和 wsprintfA(),你必須想別的辦法將Unicode字串轉換回ANSI字串。由於Windows CE 1.0不在國家語言支援(NLS)中,你也許得求助於hack,如下所示:

/*
Definition / prototypes of conversion functions
Multi-Byte (ANSI) to WideChar (Unicode)

atow() converts from ANSI to widechar
wtoa() converts from widechar to ANSI
*/
#if ( _WIN32_WCE >= 101)

#define atow(strA, strW, lenW) /
MultiByteToWidechar (CP_ACP, 0, strA, -1, strW, lenW)

#define wtoa(strW, strA, lenA) /
WideCharToMutiByte (CP_ACP, 0, strW, -1, strA, lenA, NULL, NULL)

#else /* _WIN32_WCE >= 101)*/

/*
MultiByteToWideChar () and WideCharToMultiByte() not supported o-n Windows CE 1.0
*/
int atow(char *strA, wchar_t *strW, int lenW);
int wtoa(wchar_t *strW, char *strA, int lenA);

endif /* _WIN32_WCE >= 101*/

#if (_WIN32_WCE <101)

int atow(char *strA, wchar_t *strW, int lenW)
{
int len;
char *pA;
wchar_t *pW;

/*
Start with len=1, not len=0, as string length returned
must include null terminator, as in MultiByteToWideChar()
*/
for(pA=strA, pW=strW, len=1; lenW; pA++, pW++, lenW--, len++)
{
*pW = (lenW = =1) ? 0 : (wchar_t)( *pA);
if( ! (*pW))
break;
}
return len;
}

int wtoa(wxhar_t *strW, char *strA, int lenA)
{
int len;
char *pA;
wchar_t *pW;
/*
Start with len=1,not len=0, as string length returned
Must include null terminator, as in WideCharToMultiByte()
*/
for(pA=strA, pW=strW, len=1; lenA; pa++, pW++, lenA--, len++)
{
pA = (len==1)? 0 : (char)(pW);
if(!(*pA))
break;
}
return len;
}

#endif /*_WIN32_WCE<101*/

  這種適合於Windows CE 1.0的實現辦法比使用wsprintf()函數要容易,因為使用wsprintf()函數更難以限制目標指標所指向的字串的長度。

  6. 選擇正確的字串比較函數

  如果你要分類Unicode標準字串,你會有以下幾個函數可供選擇:

wcscmp(), wcsncmp(), wcsicmp(), 和wcsnicmp()

wcscoll(), wcsncoll(), wcsicoll(),和wcsnicoll()

CompareString()

  第一類函數可用來對字串進行比較,不參考當地(Locale)或外文字元。如果你永遠不想支援外文,或者你僅僅想測試一下兩個字串的內容是否相同,這類函數非常好用。

  第二類函數使用現有的當地設定(current locale settings)(系統設定,除非你在字串比較函數之前調用了wsetlocale()函數)來比較兩個字串。這些函數也能正確分類外文字元。如果當地的字元"C"("C" locale)被選定,這些函數與第一類函數就具有了相同的功能。

  第三類函數是Win32函數CompareString()。這個函數類似於第二類函數,但是它允許你指定當地設定(the locale)作為一個參數,而不是使用現有的當地設定(current locale settings)。CompareString()函數允許你選擇性地指定兩個字串的長度。你可以將第二個參數設定為NORM_IGNORECASE,從而使函數比較字串時不比較大小寫。

  通常,即使不將第二個參數設定為NORM_IGNORECASE,CompareString()函數也不用來區分大小寫。我們經常用wcsncoll()函數來區分大小寫,除非使用當地的字元"C"("C" locale)。所以,在我們的代碼中,不使用CompareString()函數來區分大小寫,而用wcsncoll()函數來區分大小寫

  7. 不要使用相對路徑

  與Windows NT不一樣,Windows CE沒有目前的目錄這個概念,因此,任何路徑只是相對於根目錄而言的。如果你的軟體給檔案或目錄使用相對路徑,那麼你很可能把它們移到別的地方了。例如,路徑"./abc"在Windows CE中被當作"/abc"看待。

  8.移走了對calloc()和 time()函數的調用

  C運行庫中的calloc()函數不能使用,但是malloc()函數可以代替calloc()函數。並且不要忘記,calloc()函數初始化時分配的記憶體為零,而malloc()函數不一樣。同樣,time()函數也不能使用,但你可以使用Win32函數GetSystemTime()函數代替time()函數。

  經過以上的警告後,你會高興地學習最後令你驚訝的兩點忠告。

  9. 不需要改變Win32 輸入/輸出(I/O)檔案的調用

  Win32的輸入輸出函數,Windows CE也支援。允許你象訪問Win32檔案系統那樣訪問對象。CreateFile()函數在Windows CE中不能辯認標誌FILE_FLAG_RANDOM_ACCESS,但是這個標誌僅用作可選的磁碟訪問,並且不影響函數調用的功能。

  10. 不要擔心位元組的狀態

  當我們把應用程式寫入Windows CE時,有了一個美好的發現,那就是Windows CE的數字資料類型的位元組狀態與Intel結構的位元組狀態一樣,在所有的處理器上,Windows CE均支援。

  幾乎象所有的資料庫引擎一樣,Raima資料庫管理員在資料庫檔案中以二進位形式儲存數字資料。這就意味一個記錄無論何時寫入資料庫或從資料庫讀出,均被當作一系列的位元組來處理,不管它域的內容。只要資料庫檔案不要傳給別的任何系統,數字資料的位元組狀態問題就解決了。如果資料庫檔案被一個來自原始系統且帶有不同位元組狀態的處理器訪問,數字資料將被誤解。

  無論何時,當你在擁有不同處理器的機器上傳輸檔案時,就會出現這個問題。在這個問題上,值得高興的是所有類型的處理器都使用相同的位元組狀態。

  在使用Windows CE時,這10點忠告應該引起你足夠的重視,避免學習時走彎路。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.