在VB 中调用动态连接库
| 在VB 中調(diào)用動(dòng)態(tài)連接庫(kù) |
| ? |
| 1 2 3 4 5 6 7 8 ?下一頁(yè) 作為一種簡(jiǎn)單易用的Windows開發(fā)環(huán)境,Visual Basic從一推出就受到了廣大編程人員的歡迎。它使 程序員不必再直接面對(duì)紛繁復(fù)雜的Windows消息,而可以將精力主要集中在程序功能的實(shí)現(xiàn)上,大大提高了編程效率。但凡事有利必有弊。VB中高度的封裝和模塊化減輕了編程者的負(fù)擔(dān),同時(shí)也使開發(fā)人員失去了許多訪問(wèn)低層API函數(shù)和直接與Windows交互的機(jī)會(huì)。因此,相比而言,VB應(yīng)用程序的執(zhí)行效率和功能比C/C++或Delphi生成的程序要差。為了解決這個(gè)問(wèn)題,在一個(gè)大型的VB開發(fā)應(yīng)用中,直接調(diào)用Windows API函數(shù)幾乎是不可避免的;同時(shí),還有可能需 要程序員自己用C/C++等開發(fā)一些動(dòng)態(tài)連接庫(kù),用于在VB中調(diào)用。本文主要討論在32位開發(fā)環(huán) 境Visual Basic 5.0中直接調(diào)用Windows 95 API函數(shù)或用戶生成的32位動(dòng)態(tài)連接庫(kù)的方法 與規(guī)則。Windows動(dòng)態(tài)連接庫(kù)是包含數(shù)據(jù)和函數(shù)的模塊,可以被其它可執(zhí)行文件(EXE、DLL、OCX 等)調(diào)用。動(dòng)態(tài)連接庫(kù)包含兩種函數(shù):輸出(exported)函數(shù)和內(nèi)部(internal)函數(shù)。輸出函數(shù)可以被其它模塊調(diào)用,而內(nèi)部函數(shù)則只能在動(dòng)態(tài)連接庫(kù)內(nèi)部使用。盡管動(dòng)態(tài)連接庫(kù)也能輸出 數(shù)據(jù),但實(shí)際上它的數(shù)據(jù)通常是只在內(nèi)部使用的。使用動(dòng)態(tài)連接庫(kù)的優(yōu)點(diǎn)是顯而易見的。將應(yīng) 用程序的一部分功能提取出來(lái)做成動(dòng)態(tài)連接庫(kù),不但減小了主應(yīng)用程序的大小,提高了程序 運(yùn)行效率,還使它更加易于升級(jí)。多個(gè)應(yīng)用程序共享一個(gè)動(dòng)態(tài)連接庫(kù)還能有效地節(jié)省系統(tǒng)資 源。正因?yàn)槿绱?#xff0c;在Windows系統(tǒng)中,動(dòng)態(tài)連接庫(kù)得到了大量的使用。 一般來(lái)說(shuō),動(dòng)態(tài)連接庫(kù)都是以DLL為擴(kuò)展名的文件,如Kernel32.dll、commdlg.dll等。但也有例外,如16位Windows的核心部件之一GDI.exe其實(shí)也是一個(gè)動(dòng)態(tài)庫(kù)。編寫動(dòng)態(tài)連接庫(kù)的工具很多,如VisualC++、BorlandC++、Delphi等,具體方法可以參見相關(guān)文檔。下面只以Visual C++5.0為例,介紹一下開發(fā)應(yīng)用于VisualBasic5.0的動(dòng)態(tài)連接庫(kù)時(shí)應(yīng)注意的問(wèn)題(本文中所有涉及C/C++語(yǔ)言或編譯環(huán)境的地方,都以VC5為例;所有涉及VisualBasic的地方都以VB5 為例)。 作為一種32位Windows應(yīng)用程序的開發(fā)工具,VB5生成的exe文件自然也都是32位的,通常情況下也只能調(diào)用32位的動(dòng)態(tài)連接庫(kù)。但是,并不是所有的32位動(dòng)態(tài)庫(kù)都能被VB生成的exe 文件正確地識(shí)別。一般來(lái)說(shuō),自己編寫用于VB應(yīng)用程序調(diào)用的動(dòng)態(tài)連接庫(kù)時(shí),應(yīng)注意以下幾個(gè)方面的問(wèn)題: 1、生成動(dòng)態(tài)庫(kù)時(shí)要使用__stdcall調(diào)用約定,而不能使用缺省的__cdecl調(diào)用約定;__stdcall 約定通常用于32位API函數(shù)的調(diào)用。 2、在VC5中的定義文件(.def)中,必須列出輸出函數(shù)的函數(shù)名,以強(qiáng)制VC5系統(tǒng)將輸出函數(shù)的裝飾名(decoratedname)改成普通函數(shù)名;所謂裝飾名是VC的編譯器在編譯過(guò)程中生成的輸出函數(shù)名,它包含了用戶定義的函數(shù)名、函數(shù)參數(shù)及函數(shù)所在的類等多方面的信息。由于在VC5中定義文件不是必需的,因此工程不包含定義文件時(shí)VC5就按自己的約定將用戶定義的輸出函數(shù)名修改成裝飾名后放到輸出函數(shù)列表中,這樣的輸出函數(shù)在VB生成的應(yīng)用程序中是不能正確調(diào)用的(除非聲明時(shí)使用Alias子句)。因此需要增加一個(gè).def文件,其中列出用戶需要的函數(shù)名,以強(qiáng)制VC5不按裝飾名進(jìn)行輸出。 3、VC5中的編譯選項(xiàng)"結(jié)構(gòu)成員對(duì)齊方式(structure member alignment)" 應(yīng)設(shè)成4字節(jié),其原因?qū)⒃诤笪脑敿?xì)介紹。 4、由于在C中整型變量是4個(gè)字節(jié),而VB中的整型變量依然只有2個(gè)字節(jié),因此在C中聲 明的整型(int)變量在VB中調(diào)用時(shí)要聲明為長(zhǎng)整型(long),而C中的短整型(short)在VB中則 要聲明成整型(integer);下表針對(duì)最常用的C語(yǔ)言數(shù)據(jù)類型列出了與之等價(jià)的Visual Basic 類型(用于32位版本的Windows)。 |
?
A.處理使用字符串的系統(tǒng)Windows API過(guò)程
如果調(diào)用的系統(tǒng)Windows API過(guò)程要使用字符串,那么聲明語(yǔ)句中必須增加一個(gè)Alias 子句,以指定正確的字符集。包含字符串的系統(tǒng)Windows API函數(shù)實(shí)際有兩種格式:ANSI和Unicode( 關(guān)于ANSI和Unicode兩種字符集的區(qū)別將在后面詳細(xì)闡述)。因此,在Windows頭文件中,每 個(gè)包含字符串的函數(shù)都同時(shí)有ANSI版本和Unicode版本。例如,下面是SetWindowText函數(shù) 的兩種C語(yǔ)言描述。可以看到,第一個(gè)描述將函數(shù)定義為SetWindowTextA,尾部的"A" 表明它是一個(gè)ANSI函數(shù):
| WINUSERAPI BOOL WINAPI SetWindowTextA(HWND hWnd, LPCSTR lpString); |
第二個(gè)描述將它定義為 SetWindowTextW, 尾部的"W" 表明它是一個(gè) Unicode 函數(shù):
| WINUSERAPI BOOL WINAPI SetWindowTextW(HWND hWnd, LPCWSTR lpString); |
因?yàn)閮蓚€(gè)函數(shù)實(shí)際的名稱都不是"SetWindowText",要引用正確的函數(shù)就必 須增加一個(gè)Alias子句:
| Private Declare Function SetWindowText Lib "user32" _ Alias "SetWindowTextA" (ByVal hwnd As Long, ByVal _ lpString As String) As Long |
應(yīng)當(dāng)注意,對(duì)于VB中使用的系統(tǒng)WindowsAPI函數(shù),應(yīng)該指定函數(shù)的ANSI版本,因?yàn)橹?有WindowsNT才支持Unicode版本,而Windows95不支持這個(gè)版本。僅當(dāng)應(yīng)用程序只運(yùn)行 在WindowsNT平臺(tái)上的時(shí)候才可以使用Unicode版本。
B.函數(shù)名是不標(biāo)準(zhǔn)的名稱
有時(shí),個(gè)別的DLL過(guò)程的名稱不是有效的標(biāo)識(shí)符。例如,它可能包含了非法的字符(如連 字符),或者名稱是VB的關(guān)鍵字(如GetObject)。在這種情況下,可以使用Alias關(guān)鍵字。例 如,操作環(huán)境DLLs中的某些過(guò)程名以下劃線開始。盡管在VB標(biāo)識(shí)符中允許使用標(biāo)識(shí)符,但是 下劃線不能作為標(biāo)識(shí)符的第一個(gè)字符。為了使用這種過(guò)程,必須先聲明一個(gè)名稱合法的過(guò)程, 然后用Alias子句引用過(guò)程的真實(shí)名稱:
| Declare Function lopen Lib "kernel32" Alias "_lopen" _ (ByVal lpPathName As String, ByVal iReadWrite _ As Long) As Long |
在上例中,lopen是VB中使用的過(guò)程名稱。而_lopen則是動(dòng)態(tài)連接庫(kù)中可以識(shí)別的名 稱。
C.使用序號(hào)標(biāo)識(shí)DLL過(guò)程
除了使用名稱之外,還可以使用序號(hào)來(lái)標(biāo)識(shí)DLL過(guò)程。某些動(dòng)態(tài)連接庫(kù)中不包含過(guò)程的名稱,在聲明它們包含的過(guò)程時(shí)必須使用序號(hào)。同使用名稱標(biāo)識(shí)的DLL過(guò)程相比,如果使用序號(hào),在最終的應(yīng)用程序中消耗的內(nèi)存將比較少,而且速度會(huì)快些。但是,一個(gè)具體的API的序號(hào) 在不同的操作系統(tǒng)中可能是不同的。例如GetWindowsDirectory在Win95下的序號(hào)為432,而在WindowsNT4.0下為338??偠灾?#xff0c;如果希望應(yīng)用程序能夠在不同的操作系統(tǒng)下運(yùn)行,那么最好不要使用序號(hào)來(lái)標(biāo)識(shí)API過(guò)程。如果過(guò)程不屬于API,或者應(yīng)用程序使用的范圍很有 限,那么使用序號(hào)還是有好處的。
要使用序號(hào)來(lái)聲明DLL過(guò)程,Alias子句中的字符串需要包含過(guò)程的序號(hào),并在序號(hào)的 前面加一個(gè)數(shù)字標(biāo)記字符(#)。例如,Windowskernel中的GetWindowsDirectory函數(shù)的序 號(hào)為432;可以用下面的語(yǔ)句來(lái)聲明該DLL過(guò)程:
| Declare Function GetWindowsDirectory Lib "kernel32" _ Alias "#432" (ByVal lpBuffer As String, _ ByVal nSize As Long) As Long |
在這里,可以使用任意的合法名稱作為過(guò)程的名稱,VB將用序號(hào)在DLL中尋找過(guò)程。
為了得到要聲明的過(guò)程的序號(hào),可以使用Dumpbin.exe等實(shí)用工具(Dumpbin.exe是Microsoft VisualC++提供的一個(gè)實(shí)用工具,它的使用說(shuō)明可以參見VC的文檔)。利用Dumpbin,可以提取出.dll文件中的各種信息,例如DLL中的函數(shù)列表,它們的序號(hào)以及與代碼有關(guān)的其它信息。
?
?
(2)、字符串參數(shù)的傳遞:
與簡(jiǎn)單數(shù)據(jù)類型相比,字符串類型(String、String*n)的參數(shù)傳遞要復(fù)雜得多,這主要是Windows 95 API和VB使用的字符串類型不同的緣故。VB使用被稱為BSTR的String數(shù)據(jù)類型,它是由自動(dòng)化(以前被稱為OLE Automation)定義的數(shù)據(jù)類型。一個(gè)BSTR由頭部和字符串組成,頭部包含了字符串的長(zhǎng)度信息,字符串中可以包含嵌入的null值。大部分的BSTR是 Unicode的,即每個(gè)字符需要兩個(gè)字節(jié)。BSTR通常以兩字節(jié)的兩個(gè)null字符結(jié)束。下圖表示 了一個(gè)BSTR類型的字符串。
(前綴)aTest/0
頭部BSTR指向數(shù)據(jù)的第一個(gè)字節(jié)
另一方面,大部分的DLL過(guò)程(包括Windows 95 API中的所有過(guò)程)使用LPSTR類型字符串,這是指向標(biāo)準(zhǔn)的以null結(jié)束的C語(yǔ)言字符串的指針,它也被稱為ASCIIZ字符串。LPSTR 沒(méi)有前綴。下圖顯示了一個(gè)指向ASCIIZ字符串的LPSTR。
aTest/0
LPSTR指向一個(gè)以null結(jié)尾的字符串?dāng)?shù)據(jù)的第一個(gè)字節(jié)
如果DLL過(guò)程需要一個(gè)LPSTR(指向以null結(jié)束的字符串的指針)作為參數(shù),可以在VB 中將一個(gè)字符串以傳值的方式傳遞給它。因?yàn)橹赶駼STR的指針實(shí)際指向以null值結(jié)束的字符串的第一個(gè)數(shù)據(jù)字節(jié),所以對(duì)于DLL過(guò)程來(lái)說(shuō),它就是一個(gè)LPSTR。這樣傳入動(dòng)態(tài)連接庫(kù)的字符串,DLL過(guò)程也可以對(duì)它進(jìn)行修改,盡管它是以傳值方式傳入的。只有當(dāng)DLL過(guò)程需要一個(gè)指向LPSTR的指針時(shí),才以傳址的方式傳入字符串,這時(shí)DLL過(guò)程得到的是一個(gè)指向字符串指針的指針(相當(dāng)于C/C++中的char**),而不是通常所用的字符串的首地址(相當(dāng)于C/C++中的char*)。
當(dāng)需要把一個(gè)字符串?dāng)?shù)組整個(gè)傳入動(dòng)態(tài)連接庫(kù)時(shí),情況就變得復(fù)雜多了,用傳遞簡(jiǎn)單數(shù)據(jù)類型數(shù)組的方式來(lái)傳遞字符串?dāng)?shù)組是行不通的。當(dāng)我們以傳值的方式將一個(gè)字符串?dāng)?shù)組的第一個(gè)元素傳進(jìn)動(dòng)態(tài)連接庫(kù)時(shí),DLL過(guò)程得到的實(shí)際上是該元素壓入堆棧段后的地址,而不是數(shù)據(jù)段中整個(gè)數(shù)組的首地址。也就是說(shuō),這時(shí)DLL過(guò)程只能得到數(shù)組的第一個(gè)元素,而無(wú)法訪問(wèn)整個(gè)數(shù)組。而以傳址方式傳入第一個(gè)元素時(shí),DLL過(guò)程只能得到指向該元素在堆棧段中地址的指針,同樣無(wú)法訪問(wèn)整個(gè)數(shù)組。這不能不說(shuō)是VB的一個(gè)不足。因此,在程序設(shè)計(jì)中,如果確實(shí)需要將整個(gè)字符串?dāng)?shù)組傳入動(dòng)態(tài)庫(kù),就必須采取其它方法。
我們知道,在VB中,有一種Byte數(shù)據(jù)類型。每個(gè)Byte型變量占一個(gè)字節(jié),不含符號(hào)位,因 此所能表示的范圍為0到255。這種數(shù)據(jù)類型是專門用于存放二進(jìn)制數(shù)據(jù)的。為了將整個(gè)字符 串?dāng)?shù)組傳進(jìn)動(dòng)態(tài)庫(kù),可以用字節(jié)數(shù)組來(lái)保存字符串。由于Byte是一種簡(jiǎn)單數(shù)據(jù)類型,因此字節(jié) 數(shù)組的傳遞是非常簡(jiǎn)單的。首先,需要把一個(gè)字符串正確地轉(zhuǎn)變成一個(gè)字節(jié)數(shù)組。這要涉及一 些字符集的知識(shí)。Windows 95和VB使用不同的字符集,Windows 95 API使用的是ANSI或DBCS 字符集,而VB使用的則是Unicode字符集。所謂ANSI字符集,是指每個(gè)字符都用一個(gè)字節(jié)表示, 因此最多只能有28=256個(gè)不同的字符,這對(duì)于英語(yǔ)來(lái)說(shuō)已經(jīng)足夠了,但不能完全支持其它語(yǔ) 言。DBCS字符集支持很多不同的東亞語(yǔ)言,如漢語(yǔ)、日語(yǔ)和朝鮮語(yǔ),它使用數(shù)字0-255表示ASCII 字符,其它大于255或小于0的數(shù)字表明該字符屬于非拉丁字符集;在DBCS中,ASCII字符的長(zhǎng) 度是一個(gè)字節(jié),而漢語(yǔ)、日語(yǔ)和其它東亞字符的長(zhǎng)度是2個(gè)字節(jié)。而Unicode字符集則完全用 兩個(gè)字節(jié)表示一個(gè)字符,因此最多可以表示216=65536個(gè)不同字符。也就是說(shuō),ANSI字符集中 所有的字符都只占一個(gè)字節(jié),DBCS字符集中ASCII字符占一個(gè)字節(jié),漢字占兩個(gè)字節(jié),Unicode 字符集中每個(gè)字符都占兩個(gè)字節(jié)。由于VB與WindowsAPI使用的字符集不同,因此在進(jìn)行字符 串到字節(jié)數(shù)組的轉(zhuǎn)換時(shí),當(dāng)用Asc函數(shù)取得一個(gè)字符的字節(jié)碼后,需要判斷它是否是一個(gè)ASCII 字符;如果是ASCII字符,則在轉(zhuǎn)換后的字節(jié)數(shù)組中就只占一個(gè)字節(jié),否則要占兩個(gè)字節(jié)。
下面給出了轉(zhuǎn)換函數(shù):GetChar Byte得到一個(gè)字符的高字節(jié)或低字節(jié),它的第一個(gè)參數(shù) 是一個(gè)字符的ASCII碼,第二個(gè)參數(shù)是標(biāo)志取高字節(jié)還是低字節(jié);StrToByte按DBCS或ANSI格 式將一個(gè)字符串轉(zhuǎn)換成一個(gè)字節(jié)數(shù)組,第一個(gè)參數(shù)是待轉(zhuǎn)換的字符串,第二個(gè)參數(shù)是轉(zhuǎn)換后的一個(gè)定長(zhǎng)字節(jié)數(shù)組,若該數(shù)組長(zhǎng)度不足以存放整個(gè)字符串,則截去超長(zhǎng)的部分;ChangeStrAryToByte 利用前兩個(gè)函數(shù)將字符串?dāng)?shù)組轉(zhuǎn)換成字節(jié)數(shù)組,第一個(gè)參數(shù)是定長(zhǎng)的字符串?dāng)?shù)組,其中每個(gè)元素都是一個(gè)字符串(各個(gè)元素包含的字符數(shù)可以不同),第二個(gè)參數(shù)是一個(gè)變長(zhǎng)的字節(jié)數(shù)組, 保存轉(zhuǎn)換后的結(jié)果。
?
Function GetCharByte(ByVal OneChar As Integer, ByVal IsHighByte As Boolean) As Byte ' 該函數(shù)獲得一個(gè)字符的高字節(jié)或低字節(jié)
If IsHighByte Then
If OneChar >= 0 Then
GetCharByte = CByte(OneChar / 256)
'右移8位,得到高字節(jié)
Else
GetCharByte = CByte((OneChar
And &H7FFF) / 256) Or &H80
End If
Exit Function
Else
GetCharByte = CByte(OneChar And &HFF)
'屏蔽掉高字節(jié),得到低字節(jié)
Exit Function
End If
End Function
Sub StrToByte(StrToChange As String, ByteArray() As Byte)
'該函數(shù)將一個(gè)字符串轉(zhuǎn)換成字節(jié)數(shù)組
Dim LowBound, UpBound As Integer
Dim i, count, length As Integer
Dim OneChar As Integer
count = 0
length = Len(StrToChange)
LowBound = LBound(ByteArray)
UpBound = UBound(ByteArray)
For i = LowBound To UpBound
ByteArray(i) = 0 '初始化字節(jié)數(shù)組
Next
For i = LowBound To UpBound
count = count + 1
If count <= length Then
OneChar = Asc(Mid(StrToChange, count, 1))
If (OneChar > 255) Or (OneChar < 0) Then
'該字符是非ASCII字符
ByteArray(i) = GetCharByte(OneChar, True) '得到高字節(jié)
i = i + 1
If i <= UpBound Then ByteArray(i)
= GetCharByte(OneChar, False)
'得到低字節(jié)
Else
'該字符是ASCII字符
ByteArray(i) = OneChar
End If
Else
Exit For
End If
Next
End Sub
Sub ChangeStrAryToByte(StrAry()
As String, ByteAry() As Byte)
'將字符串?dāng)?shù)組轉(zhuǎn)換成字節(jié)數(shù)組
Dim LowBound, UpBound As Integer
Dim i, count, StartPos, MaxLen As Integer
Dim TmpByte() As Byte
LowBound = LBound(StrAry)
UpBound = UBound(StrAry)
count = 0
ReDim ByteAry(0)
For i = LowBound To UpBound
MaxLen = LenB(StrAry(i))
ReDim TmpByte(MaxLen + 1)
ReDim Preserve ByteAry(count + MaxLen + 1)
Call StrToByte(StrAry(i), TmpByte) '轉(zhuǎn)換一個(gè)字符串
StartPos = count
Do
ByteAry(count) = TmpByte(count - StartPos)
count = count + 1
If ByteAry(count - 1) = 0 Then Exit Do
Loop '將每一個(gè)字符串對(duì)應(yīng)
的字節(jié)數(shù)組按順序填入結(jié)果數(shù)組中
ReDim Preserve ByteAry(count - 1)
Next i
End Sub
下面看一個(gè)轉(zhuǎn)換的例子:
| DimResultAry()asByte DimSomeStr(2)asString SomeStr(0)="測(cè)試1" SomeStr(1)="測(cè)試222" SomeStr(2)="測(cè)試33" CallChangeStrAryToByte (SomeStr,ResultAry)'轉(zhuǎn)換字符串?dāng)?shù)組 |
當(dāng)轉(zhuǎn)換完成以后,查看字節(jié)數(shù)組ResultAry,其中包含了21個(gè)元素,依次是:178,226,202,212,49,0,178,226,202,212,50,50,50,0,178,226,202,212,51,51,0。其中,[178,226]是"測(cè)"的字節(jié)碼,[202,112]是"試"的字節(jié)碼,49,50,51 分別為字符1、2、3的ASCII碼??梢?#xff0c;經(jīng)過(guò)轉(zhuǎn)換后,字符串?dāng)?shù)組中的各個(gè)元素按順序放在了字節(jié)數(shù)組中,相互間以終止符0分隔。
這樣,字符串?dāng)?shù)組就全部轉(zhuǎn)換成了字節(jié)數(shù)組,然后只要將字節(jié)數(shù)組的第一個(gè)元素以傳址的方式傳入動(dòng)態(tài)連接庫(kù),DLL過(guò)程就可以正確地訪問(wèn)數(shù)組中的所有字符串了。但是,使用這種方法,當(dāng)DLL過(guò)程處理結(jié)束返回VB時(shí),VB得到的仍然是字節(jié)數(shù)組。如果需要在VB中再次得到該字節(jié)數(shù)組表示的字符串,還要把整個(gè)字節(jié)數(shù)組重新以0為分割符分成多個(gè)子數(shù)組(每個(gè)子數(shù)組都對(duì)應(yīng)原來(lái)字符串?dāng)?shù)組中的一個(gè)元素),然后使用VB函數(shù)StrConv將每個(gè)子數(shù)組轉(zhuǎn)換成字符串(轉(zhuǎn)換時(shí)第二個(gè)參數(shù)選vbUnicode),就可以顯示或進(jìn)行其它操作了。例如,其中一個(gè)子數(shù)組的名字是SubAry,則函數(shù)StrConv(SubAry,vbUnicode)就返回了它所對(duì)應(yīng)的字符串。
總之,VB應(yīng)用程序和動(dòng)態(tài)庫(kù)間字符串參數(shù)的傳遞是一個(gè)比較復(fù)雜的過(guò)程,使用時(shí)要非常謹(jǐn)慎。同時(shí)應(yīng)盡可能避免傳遞字符串?dāng)?shù)組類型的參數(shù),因?yàn)檫@很容易引起下標(biāo)越界、堆棧溢出等嚴(yán)重錯(cuò)誤。
?
?
http://www.yesky.com/20011030/202753.shtml
?
總結(jié)
以上是生活随笔為你收集整理的在VB 中调用动态连接库的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 用户自定义类型(User-defined
- 下一篇: vb字符串在内存中的分布?