VB中String的用法及原理


在各種不同開發語言中,字符串類型顯然是最常見,也是最常用的。 
    常用代表它最易用,是這樣嗎?未必,越簡單,越普通,你會忽視,內里隱藏着的陷井更容易使你中招。它往往是絆腳石,或者程序中性能的瓶頸。 
    本身,我對VB語言及相關應用並不太熟,只不過近期編碼用到,有些體會。

一: 先來總結一下,常用編程語言的字串表達方式: 
C:      char(wchat_t) * 或 []:  字符數組來表示字符串,以0結尾,無長度標識。
          配一堆操作函數,不好記,不好用。 
C++:  std::string  basic_string<>的特化版本. 注意:其內在的數據區由默認的內存管理器分配。
         對象哈,提供還算好用的方法。 
MFC: CString 標准的C++類. 數據項:LPTSTR 一堆可用的方法。 
Windows普通API DLL:       LPTSTR 采用C標准的0結束字串 
Windows擴展COM中:         BSTR  OLE標准, 頭上帶長度標識的字符串. 
Java中:                            ;" /> VB中:                              String   實際上就是OLE標准的BSTR 
好了,我們關心的是BSTR。 
看看BSTR到底是什么東東。
1.0:看看在Windows SDK中的定義:
typedef OLECHAR* BSTR;
typedef WCHAR OLECHAR;
typedef wchar_t WCHAR;
所以,它實際上是寬字符指針。

1.1:它的內存結構呢?很容易查到資料。
前置四字節,內置字串的長度,后面是字串內容,原則上並不以'/0'結尾,長度由前置值決定。
所以,它又不簡簡單單就是寬字符指針。但從基本類型定義上來看,它與寬字符指針是可以划等號的。

1.2:那VB中的String也就明白了。實際上是地址,是字串的首地址(注意:不是長度的首地址) 
另外,String是可以裝載中間帶'/0'字符的字串的,只不過,一些顯示函數可能將其省略。
如:dim str as string
     str = "ab" & chr(0) & "cd"
     MsgBox str   '輸出:ab
     Debug.Print str '輸出 ab cd

1.3:可以看出,它很特珠,Windows提供幾個函數,用來分配和釋放它。
分配:SysAllocStringLen      SysAllocString
釋放:SysFreeString
取長度:SysStringLen
注意:分配得到的BSTR,實際上仍然是以'/0'結尾。SysStringLen似乎有點兒英雄無用武之地,呵。
比如:SysAllocStringLen 的說明文檔:
Allocates a new string, copies cch characters from the passed string into it, and then appends a null character.
比如:SysAllocString,我們可以通過例子:
 BSTR bstrVal = ::SysAllocString(L"abcd");
 for(int n=0;n<::SysStringLen(bstrVal)+1;n++)
 {
  TRACE(_T("/n%d"),*(bstrVal+n));
 }
 ::SysFreeString(bstrVal);
輸出:
97
98
99
100
0
仍然有0。

1.4:VC中的用法:
無論用MFC,還是ATL,實際上,由於BSTR並不是基本類型,而它的相關操作函數也是沿用的以'/0'結尾的函數,所以,雖然它在字串中可以帶'/0',但實際上,經過相關操作后,'/0'后的內容會丟掉,這要小心。比如,我們看看CString的構造:
CString::CString(LPCWSTR lpsz)
{
 Init();
 int nSrcLen = lpsz != NULL ? wcslen(lpsz) : 0;
 if (nSrcLen != 0)
 {
  AllocBuffer(nSrcLen*2);
  _wcstombsz(m_pchData, lpsz, (nSrcLen*2)+1);
  ReleaseBuffer();
 }
}

小結:BSTR有長度標識,但是MS在實現時,為了兼容以前的字串操作,再加上BSTR並不是基本類型,所以,它的分配函數和一些操作函數都是把它當作'/0'結尾來處理。千萬要注意。最好也不要用它來裝載中間帶'/0'的字串,因為,說不定什么時候,它就丟掉了。

二:  VBr的應用中將麻煩的地方,應該集中在以下幾個方面: 
2.1: VB調用Windows API DLL
 
2.1.1: 字串作為輸入參數 
   這還用說嗎? 
   在API中如果是LPTSTR,在VB中直接轉為Byval s as String 
   原因?  為什么 BSTR可以直接轉為 LPTSTR ? 
   前面其實已經講了,再說一次吧。 
   BSTR實際上是指針,指向字串頭.所以,采用ByVal指向的剛好是字串的首地址,也就是LPSTR,嘿嘿,剛剛好,符合需要,真是巧啊。 

2.1.2: 字串作為輸出參數 
這個麻煩一點,舉個例子吧. 
如: 
    UINT GetWindowsDirectory( 
      LPTSTR lpBuffer, 
      UINT uSize 
   ); 
使用API Viewer得到聲明 
   Public Declare Function GetWindowsDirectory Lib "kernel32" Alias "GetWindowsDirectoryA" _ 
      (ByVal lpBuffer As String, ByVal nSize As Long) As Long 
代碼: 
    Dim sTemp As String * MAX_PATH 
    Dim lsize As Long 
    Dim str As String 
    lsize = GetWindowsDirectory(sTemp, MAX_PATH) 
    str = Left(sTemp, lsize) 
    MsgBox "Windows路徑:" & str & "長度:" & Len(str) 
注意一下: 
A: 引用的Windows API是A版,而不是W版,如果是W版會有錯誤. 
原因,我的猜測是:   VB的API調用機制中,字串強制做了UNICODE到ANSI的轉換.API的DLL沒法告知調 
用端,它是Unicode版還是ANSI版.如果是OCX或COM就沒問題了,因為它們有標識。沒辦法,只能用一種了, MS選了A版。 
B: 參數是ByVal 
道理同作為輸入時,實際上傳的指針,所以,指向的內容是可以修改地,也就可以返回了。 
C: 根據API的使用說明,可得到 
[out] Pointer to the buffer to receive the null-terminated string containing the path. 
需要調用方分配空間,因此, sTemp需要給足夠長度的空間. 
D: 返回的0結尾的字串,可以用left達到目的. 
因為:VB的String對應的是BSTR類型,長度由前置字節決定,而不是0字符決定. 

2.1.3: 字串作為Return值 
這種API是不是存在呢?系統級的沒看見,一般不會這樣用,因為這存在內存分配的問題. 
但我自已好像寫過,處理方式是: 
VB聲明中可以使用long返回,然后用lstrcpy進行轉換, 
說起lStrCpy就不得不說說它的作用了,它實在是很關鍵,它很神奇,它可以把 
地址指向的字符串拿出來。 
我們來看看原理: 
lStrcpy本身只是簡單的復制字符串的函數: 
Public Declare Function lstrcpy Lib "kernel32" Alias "lstrcpyA" (ByVal lpString1 As String, ByVal lpString2 As String) As Long 如果按照API View中的聲明,是不行的.它確實就是字串拷字串,沒辦法把字址指向的字串拷到另一串. 
   好了,需要小小改動一下: 
   首先:目的地沒錯,確實應該是指向字串的首地址.用Byval String就可以.第一個參數OK。 
   源串呢? 因為你傳入的實際上不是String,而是long啊(上例返回的是long噢),那當然應該將聲明改成long,否則,傳入的String會變成啥,啥都不是呢?就這么簡單。 

2.1.4: LPTSTR 都可以用VB的byval String替代嗎? 
如果在結構體里,可就不是這樣了.
如:EnumForms  枚舉某個打印機的所有打印紙型。 
Public Declare Function EnumForms Lib "winspool.drv" Alias "EnumFormsA" (ByVal hPrinter As Long, _ ByVal Level As Long, ByRef pForm As FORM_INFO_1, ByVal cbBuf As Long, ByRef pcbNeeded As Long, _ 
 ByRef pcReturned As Long) As Long 
對於 FORM_INFO_1的定義: 
Public Type FORM_INFO_1 
        Flags As Long 
        pName As String 
        Size As SIZEL 
        ImageableArea As RECTL 
End Type 
    如果按上述的方法調用,會出現異常.估計是在拷貝數據時,String實際上是不同於LPTSTR的,因為它的前面還有四字節是標識長度的,如果定義為String,那在構造pName之前的長度,必將動到它不應該動到的地方,這樣,就錯了.想來不會異常退出的,但卻退出了,不知原因(我沒弄明白) 

  所以,此種情況,應該將pName變為long,然后再用2.1.3提到的方法,將字串拷貝出來使用. 
         這樣,就OK了。 

引申一下: 
    對於一些API中大的結構體中的指針類型,我們完全可以用long來取代它,然后傳遞0&,因為,大多數情況,我們是不需要傳入任何參數的,避免定義很多我們並不需要的類型.這樣就降低了API使用的復雜性。 

2.2 : VB調用COM組件(或OCX控件) 
這似乎沒啥可說的,COM組件的接口一般都是標准的BSTR,與VB的String完全兼容. 
VB調用端沒啥問題,倒是書寫COM的程序需要注意: 
2.2.1: 應用端可能會傳入空指針.比如傳入vbNullString,這需要留意. 
2.2.2: BSTR的釋放與普通LPTSTR可不同,使用前需要初始化,使用后要釋放,當然,VB無此煩惱. 

3: VB字串的連接操作. 
    當我們要序列化生成文本文件時,大都喜歡先將內容寫入字串,然后再一次寫了文件.否則多次寫入文件,似乎有效率低之嫌,但如果字串為多次累加而成,那么使用VB字串的連接操作,將是效率極差的做法,這也變成性能低下的重要原因. 
    分析一下原因: 
    對於String的連接,系統的做法一定是重分配空間,搬移到新空間,拷貝進新內容.如果連接次數較多,且較細,那將是災難性的。(類似MFC的CString的做法,假設我們要寫一個100K長度的字串,但每次添加一個字符,那你可以試試它的速度) 
   如何解決? 
   很簡單,采用預分配內存塊, 當內存塊不夠用時,自動增加指定塊的增量(當然每次增長的內存塊這個閥值需要慎重考慮) 
   這樣,減少搬移操作,效率自然提升。 
   具體實現:可以使用內存操作API實現.VB會麻煩一點(需要用API生成全局內存,自行完成搬移,拷貝), 
VC可以直接使用CMemFile簡單搞定. 

4: VB字串多語言化 
4.1: 一般的做法是: 將原生串與多語言串做成Key.Value Pair結構。多語言信息為Value,然后做一個簡單的Hashc ode來索引,或者干脆用Hash Map來實現 (為了更高效,更容易使用已有容器,最好借助於C++編寫相關支持)。
4.2: 為了使UI上的字串也能多語言化,需要保證字串信息為動態加載。 
4.3: 對於控件的字體編碼字符集也需要處理,以保證正確顯示


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM