|
  
- 帖子
- 1825
- 精华
- 4
- 威望
- 2370
- 擂点
- 11
- 人气
- 284
- 注册时间
- 2008-3-4
|
1楼
发表于 2009-3-10 22:21
| 只看该作者
Part 1. BSTR 结构
在 VB6 里, 我们使用 String 类型来处理字符串. String (不考虑定长字符串的情况) 实际上是一个指向 BSTR 的指针, 让我们来看一下它的结构.- Sub Main()
- Dim x As String
- Dim a As Long
- a = &H70514
- x = "Hello World!"
- MsgBox "a = " & Hex(a) & vbCrLf & _
- "VarPtr(a) = " & Hex(VarPtr(a)) & vbCrLf & _
- "VarPtr(x) = " & Hex(VarPtr(x)) & vbCrLf & _
- "StrPtr(x) = " & Hex(StrPtr(x)) & vbCrLf
- End Sub
复制代码 将这段程序编译成 EXE, 并用 Windbg 载入. go~ 出现如下对话框:---------------------------
工程1
---------------------------
a = 70514
VarPtr(a) = 12FD80
VarPtr(x) = 12FD7C
StrPtr(x) = 14FB7C
---------------------------
确定
--------------------------- Ctrl+Break~ 我们来看一下 VarPtr(x) 对应的内存.0:001> dd 12fd7c l 8
0012fd7c 0014fb7c 00070514 0012fea8 004010a6
0012fd8c 0012fb2c 00401088 0012fed4 7339a030 由此我们可以知道, String 在栈上的部分只有四字节. 并且这四字节存放的内容等于 StrPtr(x).
继续看 StrPtr(x), 下面直接给出结果.0:001> db 14fb7c - 4 l 40
0014fb78 18 00 00 00 48 00 65 00-6c 00 6c 00 6f 00 20 00 ....H.e.l.l.o. .
0014fb88 57 00 6f 00 72 00 6c 00-64 00 21 00 00 00 ad ba W.o.r.l.d.!.....
0014fb98 ab ab ab ab ab ab ab ab-00 00 00 00 00 00 00 00 ................
0014fba8 05 00 07 00 43 07 18 00-0a 00 00 00 37 00 30 00 ....C.......7.0. 这个指针指向一个以 0000 结尾的 Unicode 字符串, 在这个字符串的前面有 4 个字节, 记录这个字符串的长度.
-
Part 2. 字符串处理相关函数
VB 使用 SysAllocString 和 SysFreeString 来给分配和销毁字符串内存, 这和 HeapAlloc 略有不同. 由于一些程序编写者经常滥用诸如字符串连接之类的操作, OLEAUT32 用内存池特别优化了有关内存分配的函数.
具体的算法可以参考这篇文章 (Heap Feng Shui in JavaScript):
http://bbs.pediy.com/showthread.php?t=55879
我们不用研究这么高深的东西, 只需要知道, 在 VB 中, 下面这种操作是相当低效的 (假设 a, b 都是字符串):上面这两句是想把 b 转移给 a, 但是编译器并不知道这一点, 它先给 a 分配一块和 b 字符串一样大小的内存, 再把 b 里的内容复制过去, 最后将 b 指向的内存释放. 如果从时间复杂度角度考虑, 就是本来 O(1) 的操作变成了 O(n) (n 为字符串长度).
从效率上考虑, 这显然是不可接受的. 能不能找出一种更好的方法呢?
-
Part 3. VB 的指针相关函数
在 VB 中, 我们有 VarPtr, StrPtr, ObjPtr 等函数, 分别用于获得不同类型变量的地址. 在 kernel32 或 ntdll 导出的 RtlMoveMemory 函数的帮助下, 几乎无所不能.
用 RtlMoveMemory 来复制 4 字节的内存, 似乎显得太慢. 不过 msvbvm60 里导出了一组函数, 能够快速地复制小内存.
这组函数包括 GetMem1, GetMem2, GetMem4, GetMem8, PutMem1, PutMem2, PutMem4 和 PutMem8. 我们只讨论 GetMem4 和 PutMem4, 因为我们这里要复制的所有东西都是 4 字节的.
从函数名字上来理解, GetMem4 是用来“读内存”, PutMem4 是用来“写内存”, 不过你大可不必这么理解. 这样理解会更为恰当: GetMem4 是从指针到指针的复制, 而 PutMem4 是从值到指针的复制. 我们可以给这两个函数起一个别名, 以避免在编程时受到函数名称的误导.
我们这样声明:- Declare Sub RefToRef Lib "msvbvm60.dll" Alias "GetMem4" (ByRef SrcRef As Long, ByRef DstRef As Long)
- Declare Sub RefFromValue Lib "msvbvm60.dll" Alias "PutMem4" (ByRef DstRef As Long, ByVal SrcValue As Long)
复制代码 你可以将这三个 Ref 排列组合地换成 Ptr, 产生 6 个不同的函数, 你也可以在需要用指针的时候使用 ByVal. 我们举一个例子说明这个问题:- Dim a As Long, b As Long
- b = 70514
- RefToRef b, a
- RefToRef ByVal VarPtr(b), a
- RefToRef b, ByVal VarPtr(a)
- RefToRef ByVal VarPtr(b), ByVal VarPtr(a)
- RefFromValue a, b
- RefFromValue ByVal VarPtr(a), b
- MsgBox a
复制代码 这六句话是等价的.
-
Part 4. 将这些东西应用到字符串上
我们可以先用一个最简单的例子来说明这项技术的可行性. 新建一个工程, 移除 Form1, 添加一个模块, 输入如下代码:- Declare Sub RefToRef Lib "msvbvm60.dll" Alias "GetMem4" (ByRef SrcRef As Long, ByRef DstRef As Long)
- Declare Sub RefFromValue Lib "msvbvm60.dll" Alias "PutMem4" (ByRef DstRef As Long, ByVal SrcValue As Long)
- Sub Main()
- Dim a As String, b As String
- a = "This is a string"
- RefFromValue ByVal VarPtr(b), StrPtr(a) ' BSTR Attach
- MsgBox a & vbCrLf & b
- RefFromValue ByVal VarPtr(b), 0 ' BSTR Detach
- End Sub
复制代码 不想写字了, 下面的代码请大家自行理解. 如果有疑问我改天补充说明一下.- Function StringDetach(ByRef x As String) As Long
- StringDetach = StrPtr(x)
- RefFromValue ByVal VarPtr(x), 0
- End Function
- Function StringAttach(ByRef x As String, ByVal p As Long) As Long
- StringAttach = StrPtr(x)
- RefFromValue ByVal VarPtr(x), p
- End Function
复制代码 Example: 交换字符串- Sub StringSwap(ByRef a As String, ByRef b As String)
- Dim p As Long
- p = StringDetach(a)
- p = StringAttach(b, p)
- StringAttach a, p
- End Sub
- Sub Main()
- Dim a As String, b As String
- a = "World"
- b = "Hello"
- StringSwap a, b
- MsgBox a & " " & b
- End Sub
复制代码 此技术在 PsNull、IcyListView 等程序中广泛使用...
[ 本帖最后由 iceboy 于 2009-3-10 22:29 编辑 ] |
-
7
评分次数
-
|