设为首页收藏本站Access中国

Office中国论坛/Access中国论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

12下一页
返回列表 发新帖
查看: 9627|回复: 13
打印 上一主题 下一主题

[API] Access菜鸟邪门武器之四--msvbvm60.tlb揭开VBA的盖子使用指针和读写内存

[复制链接]
跳转到指定楼层
1#
发表于 2015-6-13 10:52:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 ganlinlao 于 2015-6-19 22:22 编辑

这是我在vb6吧无意中看到的一个好东西,网友狠人yjryym制作的msvbvm60.tlb。
在看本文之前,你要有点指针概念,要有点com概念。
在看本文之前,你要放弃所谓的实用性,因为本文并不为追求实用的人准备。
我不能保证msvbvm60.tlb和access的VBA的兼容性,甚至也不能保证msvbvm60.tlb的完全正确性
在任何使用msvbvm60.tlb之前,务必要先保存自己的VBA代码,因为一旦出错,vb编辑器必崩溃。
tlb只是一个类型库,tlb里面没有任何附加的函数或类,msvbvm60.tlb只是为了让msvbvm60.dll里面一些隐藏函数
直接可用,为了方便内存读写,并附加了一点API声明。

1、指针的取址和指针的取值:
VBA库里有VarPtr(变量)、StrPtr(字符串)、ObjPtr(对象)三个取地址函数,这是很多人都知道的。
在VBAX库里还增加了VarsPtr(数组变量)、ArrPtr(数组)、FunPtr(函数,配合Addressof用于传函数地址给变量)。
现在不仅增加了取地址函数,还提供了模拟指针的内存操作属性(功能正好是与取地址相反,所以在命名时我也就把名字给反过来表示,读取Integer值 = PtrInt(地址): PtrLng(地址) = 写入Long值: Set PtrObj(地址) = 修改对象引用)。StrPtrEx、ObjPtrEx在原有的基础增加修改地址的功能(比如字符串快速交换可以这样写:Dim t As IntPtr: t = StrPtr(字符串1): StrPtrEx(字符串1) = StrPtr(字符串2): StrPtrEx(字符串2) = t)。
为了与普通整数类型进行区分,还提供了一个IntPtr类型专门用来表示指针类型用(空指针常量为vbNullPtr),另外提供了一个SizeT类型表示内存大小用。
2、数据类型
SplitWord(把Integer拆成两个Byte)、SplitDWord(把Long拆成两个Integer)、SplitDWordLong(把Currency拆成两个Long),还有MakeWord(把两个Byte合并成一个Integer)、MakeDWordLong(把两个long合并成一个Currency)。
还有三个比较实用强大的函数分别是:ArrMove、StrMove、VarMove。在c++11的标准中增加了右值引用和移动赋值的概念,也就是说对于临时对象赋值变量时不需要进行深拷贝,把临时对象的数据浅拷贝过,并清除掉临时对象的数据就可以了,这种看起来就像是对象进行了移动,而C艹11允许通过强制转化为右值类型的方式来直接让两个变量直接进行移动拷贝,VB6底层帮我们实现了临时对象的移动赋值功能,但是并未将移动拷贝函数公开,导致无法在两个变量之间进行强制移动赋值,所以我在tlb里把移动赋值函数重声明出来,以便需要时使用。
3、内存读写
觉得VB6的隐藏函数还不够用,所以在tlb里加了一点API进行扩展,不过按照更接近VB6库函数的命名规则重新命名了(别打我,我一是为避免与以后出Windows.tlb时重名,二是这样看起来才像是对VB6的扩展)。前面提到了有内存操作,理所当然我也提供了内存分配函数,AllocMem分配内存,ReallocMem重分配内存,FreeMem回收内存。内存操作上除了PtrXXX这些基本内存操作属性外,还提供了批量内存操作函数:CopyBytes(拷贝内存,用于两段单独的内存)、CopyBytesZero(拷贝内存后清除原始内存)、MoveBytes(安全拷贝内存,用于同一段内存之间拷贝(效率略低于CopyBytes))、FillBytes(填充内存)、ZeroBytes(内存清0)。对于少量内存来说用PtrXXX和CopyMemX的基本内存操作效率相对较高,对于大量内存来说用批量内存操作效率更高。
4、数组结构
以前数组判空和维数获取见过使用过各种奇葩方式的,现在请用 If vbNullPtr = ArrPtr(数组) 的方式进行数组判空,使用 ArrDim(ArrPtr(数组)) 来获取数组维数,Elemsize(ArrPtr(数组)) 获取数组单个元素大小,ArrType(ArrPtr(数组)) 获取数组元素类型,GetLBound、GetUBound用来在只有数组地址的情况下获取数组上下界,AccessData(ArrPtr(数组))可以获取数组数据首地址,但是用完要记得使用UnaccessData(ArrPtr(数组))进行解锁,或者可以直接使用ArrStruct(ArrPtr(数组)) 获取数组结构(在数组结构中包含了数组维数、单个元素大小、锁定次数、数据地址),注意使用ArrStruct(ArrPtr(数组)).DataPtr获得的数据址不需要解锁。
5、doEvents反向
VBA库有个DoEvents函数,用于在大循环中处理事件,以防止UI卡住,我在VBAX库里提供了一个刚好相反的WaitEvents,等待事件的产生。

6、还有当我们使用API进行一些字符串相关的操作时,返回的都是些C串,VB6不能直接使用,这时就可以用AllocStr来把C串转成B串(BSTR)(注意必需使用W版函数)。
Option Explicit
Private Declare Function GetCommandLine Lib "kernel32" Alias "GetCommandLineW" () As IntPtr
Private Declare Function CommandLineToArgvW Lib "shell32" (ByVal lpCmdLine As IntPtr, pNumArgs As Long) As IntPtr
Private Declare Function LocalFree Lib "kernel32" (ByVal hMem As IntPtr) As Long
Private Property Get GetArgv(ByVal Index As Long) As String
Dim argc As Long, argv As IntPtr
argv = CommandLineToArgvW(GetCommandLine, argc)
If Index >= 0 And Index < argc Then
GetArgv = AllocStr(ByVal PtrPtr(argv + vbPtrSize * Index))
End If
LocalFree argv
End Property
Private Sub Command1_Click()
Print GetArgv(0)
End Sub

事实上,读完上面的文字,估计你依然会有一头雾水的感觉。没关系,如果有时间,再慢慢琢磨。msvbvm60.tlb都是比较底层的东西,其中大部分的封装不错,少部分的封装还有点不够。比如少了Vtableptr。但毫无疑问题,它在某些局部使用上,会有很大的便利性。



************************************************************
接下一楼的贴子内容。因为下一楼的字数超1万了。晕!!!!
************************************************************
说明3:string作为参数,进行数据传递时,如何不触发ansi/unicode的自动转换,可能是一个非常非常的难点。这也包括将字符串split成数组,将数组中的字符串,join成一个字符串,都将自动触发vb进行ansi/unicode的自动转换。目前的msvbvm60.tlb还没有哪一个函数,能防止vb的ansi/unicode的自动转换。copymemory函数在传递字符串时,vb也会进行ansi/unicode转换,虽然表面上它传的是指针。

说明4:msvbvm60.tlb原版中getLen()和getLenb()存在错误,我先把它修正过来。

说明5:应不应该增加dispcallfunc这个很重要的api函数?让VBA拥有调用cdecl、std、vtable方式来分别调用c、c++、com的函数
            vbrichclient当中已经分别实现了cdeclCall,stdCall,VtableCall。vtableCall对于vba实现轻量级的com类,有很大的帮助,但调用方式不那么直观。不过性能真的能比class写的类快几十到几百倍。而且无须IDL帮助。

评分

参与人数 1经验 +18 收起 理由
tmtony + 18 原创好贴!

查看全部评分

本帖被以下淘专辑推荐:

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
收藏收藏1 分享分享 分享淘帖1 订阅订阅
2#
 楼主| 发表于 2015-6-13 10:55:44 | 只看该作者
本帖最后由 ganlinlao 于 2015-6-15 11:47 编辑

1、在鸡窝前,我永远站在蛋的这一边----vba内部的数据类型。

       无论你多么的不熟悉VBA,只要你开始学习VBA,毫无疑问,数据类型,可能会是你首先学习的第一个内容。事实上,你可能会轻易地略过数据类型的字节(没印象了吧?),甚至会很疑惑,为什么要强调数据类型的字节。
但涉及到指针操作,数据类型的字节变得非常重要。一旦字节搞错了,后果很严重。
以下是VBA常用的数据类型和字节:
类型 byte BoolenIntegerLongSingleDoubleCurrencyDecimalDateStringVariantObjectLongLong
字节 1字节 2字节
2 字节4 字节4 字节8 字节8 字节 12 字节8 字节 4字节 16字节4字节
8字节
注:Decimal因为只能包含在Variant中,所以我个人理解应该是16字节。指针intptr都是4字节,在vba中也可以用long表示。
看起来数据类型很是不少。有意思的是,在msvbvm60.dll中,它没有这些概念,它只有mem1,mem2,mem4,mem8对应的是1字节,2字节,4字节,8字节,至于Arrary数组和Variant都是一种特殊的结构。真是让人大吃一惊。

*******************************************************
接下来部分是msvbvm60.tlb中的函数说明
********************************************************
内存操作:
Sub CopyBytes(Length As SizeT, Destination As Any, Source As Any)
移动指定长度的字节流
Sub CopyBytesZero(Length As SizeT, Destination As Any, Source As Any)
移动指定长度的字节流,并清零
Sub MoveBytes(Destination As Any, Source As Any, Length As SizeT)
安全拷贝指定长度的字节流
Sub CopyMem1(Source As Any, Destination As Any)
拷贝任意变量1字节值
Sub CopyMem2(Source As Any, Destination As Any)
拷贝任意变量2字节值
Sub CopyMem4(Source As Any, Destination As Any)
拷贝任意变量4字节值
Sub CopyMem8(Source As Any, Destination As Any)
拷贝任意变量8字节值
Sub CopyMemArr(Destination As IntPtr, Source As IntPtr)
拷贝内存数组结构
Sub CopyMemObj(Source As Any, Destination As Any)
拷贝任意变量引用
Sub CopyMemPtr(Source As Any, Destination As Any)
拷贝任意变量指针长度值
Sub CopyMemVar(Source As Any, Destination As Any)
拷贝任意变量变体值
Sub CopyMemVars(Destination As IntPtr, Source As IntPtr)
拷贝数组内存值
FunPtr(Address As IntPtr) As IntPtr
返回一个函数或过程的地址。必须配合addressof。但因为addressof 只能对标准模块中的函数有用,
而且vb无法在内部使用函数指针,所以这个意义变得不大
GetLen(StringAddress As IntPtr)
根据字符串地址获取长度
GetLenB(StringAddress As IntPtr)
根据字符串地址获取字节数
sub GetMem1(Address As IntPtr, Value As Any)
读取内存1字节值
Sub GetMem2(Address As IntPtr, Value As Any)
Sub GetMem4(Address As IntPtr, Value As Any)
Sub GetMem8(Address As IntPtr, Value As Any)
Sub GetMemArr(Value() As Any, Address As IntPtr)
Sub GetMemObj(Address As IntPtr, Value As Any)
Sub GetMemPtr(Address As IntPtr, Value As Any)
Sub GetMemStr(Address As IntPtr, Value As Any)
Sub GetMemVars(Value() As Any, Address As IntPtr)
GetPtrObj(Address As IntPtr) As Object
GetPtrStr(Address As IntPtr) As String
Function GetRetStr(Address As IntPtr) As String
返回一个临时字符串,并自动析构字符串

ObjPtr(Object As Unknown) As IntPtr
ObjPtrEx(Object As Unknown) As IntPtr
VarMove(LeftValue, RightValue)
从一个变体移到另一个变体
VarPtr(Expression As Any) As IntPtr
返回一个变量的地址或一个常量的临时地址

PtrBool(Address As IntPtr) As Boolean
指针取值
PtrByte(Address As IntPtr) As Byte
PtrCur(Address As IntPtr) As Currency
PtrDate(Address As IntPtr) As Date
PtrDbl(Address As IntPtr) As Double
PtrInt(Address As IntPtr) As Integer
PtrLng(Address As IntPtr) As Long
PtrObj(Address As IntPtr) As Object
PtrPtr(Address As IntPtr) As IntPtr
PtrSng(Address As IntPtr) As Single
PtrStr(Address As IntPtr) As String
PtrVar(Address As IntPtr)
设置或返回一个内存地址所指向的variant值
Sub PutMem1(Address As IntPtr, Value As Any)
写入内存1字节的值
Sub PutMem2(Address As IntPtr, Value As Any)
Sub PutMem4(Address As IntPtr, Value As Any)
Sub PutMem8(Address As IntPtr, Value As Any)
Sub PutMemObj(Address As IntPtr, Value As Any)
Sub PutMemPtr(Address As IntPtr, Value As Any)
Sub PutMemStr(Address As IntPtr, Value As Any)
Sub PutMemVar(Address As IntPtr, Value As Any)
Sub PutMemVars(Address As IntPtr, Value() As Any)
SetMemObj(Address As IntPtr, Value As Any)
SetMemVar(Address As IntPtr, Value As Any)


为方便使用API,合并数字类型
MakeWord(LoByte As Byte, HiByte As Byte, [_Invalid As Byte]) As Integer
两个字节合成一个字值
MakeDWordLong(LoDWord As Long, HiDWord As Long, [_Invalid As LongLong]) As LongLong
两个双字合成一个四字值
SplitDWord(DWord As Long) As DWord
SplitDWordLong(DWordLong As LongLong) As DWordLong
SplitRGB(Color As ColorConstants) As ColorRGBA
SplitWord(Word As Integer) As Word
说明:假乎漏了一个MakeDword

字符串处理:
StrMove(LeftValue As String, RightValue As String)
StrPtr(String As String) As IntPtr
StrPtrEx(String As String) As IntPtr
StrToAnsi(Destination As String) As String
StrToUnicode(Destination As String) As String
AllocStr(String As Any) As String
以C风格unicode字符串创建vb6字符串(vbnullChar结束)
AllocStrLen(String As Any, Length As SizeT) As String
以C风格unicode字符串创建vb6字符串(字符数)
AllocStrLenB(String As Any, LenBits As SizeT) As String
以C风格unicode字符串创建vb6字符串(字节数)

数组处理:
ArrPtr(Array() As Any) As IntPtr
返回一个数组结构的内存地址
VarsPtr(Array() As Any) As IntPtr
返回一个数组变量的内存地址
AccessData(ArrayAddress As IntPtr) As IntPtr
锁定并返回数组的数据地址
ArrDim(ArrayAddress As IntPtr) As SizeT
返回数组维数
ArrStruct(ArrayAddress As IntPtr) As SafeArray
返回数组结构
ArrType(ArrayAddress As IntPtr) As VbVarType
返回数组的元素类型
Elemsize(ArrayAddress As IntPtr) As SizeT
返回数组元素大小
Sub ArrMove(LeftValue() As Any, RightValue() As Any)
从一个数组移动到另一个数组
GetLBound(ArrayAddress As IntPtr, [Dim As SizeT = vbByteSize]) As Long
返回数组的索引下限
GetUBound(ArrayAddress As IntPtr, [Dim As SizeT = vbByteSize]) As Long
返回数组的索引上限
UnaccessData(ArrayAddress As IntPtr)
解锁并结束数组的数据地址
ArrIID(ArrayAddress As IntPtr) As UUID
设置或返回数组的接口UUID
RandomUUID
随机生成一个UUID,并返回数组的索引下界
StringUUID(lpsz As String) As UUID
将string形式转成UUID,并返回数组的索引下界


图片处理:
LoadPicture(FileName, [WidthDesired As Long], [HeightDesired As Long], [Flags As LoadPictureConstants]) As StdPicture
从文件中加载图片
LoadStmPicture(Stream As IStream, Size As SizeT, Runmode As BOOLAPI, IID As UUID) As StdPicture
从流中加载一张图片
NewPicture(PICTDESC As PICTDESC, IID As UUID, [Own As BOOLAPI]) As StdPicture
创建新的图片
SavePicture(Picture As StdPicture, FileName As String)
保存图片到文件中

内存管理:
AllocMem(Bytes As SizeT) As IntPtr
分配指定大小的内存
FreeMem(Address As IntPtr)
释放内存
Malloc([MemMode As MallocMode = vbTaskMalloc]) As IMalloc
返回内存管理对象
ReallocMem(Address As IntPtr, Bytes As SizeT) As IntPtr
重分配指定大小的内存


一些API拓展:
Sub Beep2([Sound As VbMsgBoxStyle])
播放系统提示音
Beep3(Freq As Long, Duration As Long)
播放指定频率和时间的蜂鸣声
Clock() As Long
获得运行时间
FillBytes(Destination As Any, Length As SizeT, Fill As Byte)
填充指定长度的字节流
MoveBytes(Destination As Any, Source As Any, Length As SizeT)
安全拷贝指定长度的字节流
Sleep(MilliSeconds As Long)
暂停当前线程,毫秒为单位
Sub WaitEvents()
等待当前线程的事件产生,与Doevents相反
ZeroBytes(Destination As Any, Length As SizeT)
清空指定长度的字节流
HLSToRGB(Hue As Integer, Luminance As Integer, Saturation As Integer) As ColorConstants
HLs颜色转RGB颜色
Sub RGBToHLS(RGB As ColorConstants, Hue As Integer, Luminance As Integer, Saturation As Integer)


SHfunction拓展:
Sub AtomicUnknownRelease([ppunk As Any])
Function QueryUnknownService(punk As Unknown, Service As UUID, IID As UUID) As Unknown
Sub SetUnknown(ppunk As Unknown, [punk As Unknown])
Property SetUnknownSite(punk As Unknown) As Unknown
Property UnknownSite(punk As Unknown, [riid As UUID]) As Unknown
Property UnknownWindow(punk As Unknown) As OLE_HANDLE

字节流处理:
Sub CopyStream(pstmFrom As IStream, pstmTo As IStream, cb As SizeT)
Property CreateMemStream(Init As Byte, Size As SizeT) As IStream
从内存创建流
Property CreateStream(FileName As String, [Mode As StreamMode], [Attributes As Long], [Create As BOOLAPI], [Template As IStream]) As IStream
Sub MakeHash(Data As Any, DataSize As SizeT, Hash As Any, HashSize As SizeT)
Sub ReadStream(pstm As IStream, pv As Any, cb As SizeT)
Sub ReadStreamStr(pstm As IStream, ppsz As String)
Sub ResetStream(pstm As IStream)
Property StreamSize(pstm As IStream, pui As LongLong)
Sub WriteStream(pstm As IStream, pv As Any, cb As SizeT)
Sub WriteStreamStr(pstm As IStream, psz As String)

说明1:在使用copyMem*,getMem*,putMem*等函数时,你一定要非常清楚自己在做什么。
这些函数都不做类型安全检查。
所以字节大小有没有匹配变得很重要。如果不匹配,编辑器不会提示类型溢出,但结果很可能是错误的。
这些函数都比普通的“=”赋值,性能要快。因为它省掉了很多步骤。

说明2:小心定长数组和定长字符串,它们在作为参数传递的时候,vb会强制把它们转成动态数组和动态字符串,也就是说vb会自动生成一个临时变量。这时候varsptr或strptr获得的地址都是这个临时变量的地址,而不是真正的定长数组和定长字符串的地址。(vb的坑真不少呀)














3#
 楼主| 发表于 2015-6-13 11:37:12 | 只看该作者
本帖最后由 ganlinlao 于 2015-6-16 13:53 编辑

2、指针的故事——渐行渐远渐无声,破处床头皆是恨

指针的运算
在vb中,几乎没有指针的概念,尽管在vb里指针无处不在,但你根本是看不见的。这里谈的仅仅是在VB中适度使用指针,如果你完全没有指针概念,可以百度一下c的指针教程,随处可见。不要和我争辩,如果用指针,不如学c/c++之类的东西。
vb中适度使用指针,在某些场合,会显得更方便,仅此而已,我从不鼓励别人使用指针。而且我对c/c++一无所知。
指针所指向的内存地址只有移动,复制。所以那个copyMemory函数(原名RTLMoveMemory),在VB里
那是赫赫有名。它的本质是内存地址的移动。
指针运算符
指针运算符有两种:
  •                 取地址函数:Varptr(),strPtr(),objPtr()……
  •                 取值函数:PtrStr(),Ptrint(),Ptrlng()……

加减算术运算
         对于指向数组的指针变量,可以加上或减去一个整数n。指针变量加或减一个整数n的意义是把指针指向的当前位置(指向某数组元素)向前或向后移动n个位置。应该注意,数组指针变量向前或向后移动一个位置和地址加1或减1在概念上是不同的。因为数组可以有不同的类型,各种类型的数组元素所占的字节长度是不同的。如指针变量加1,即向后移动1 个位置表示指针变量指向下一个数据元素的首地址。而不是在原地址基础上加1。
指针变量的加减运算只能对数组指针变量进行,对指向其它类型变量的指针变量作加减运算是毫无意义的

两个指针变量之间的运算
只有指向同一数组的两个指针变量之间才能进行运算,否则运算毫无意义

1) 两指针变量相减
两指针变量相减所得之差是两个指针所指数组元素之间相差的元素个数。实际上是两个指针值(地址)相减之差再除以该数组元素的长度(字节数)。例如pf1和pf2是指向同一浮点数组的两个指针变量,设pf1的值为2010H,pf2的值为2000H,而浮点数组每个元素占4个字节,所以pf1-pf2的结果为(2000H-2010H)/4=4,表示pf1和 pf2之间相差4个元素。两个指针变量不能进行加法运算。例如,pf1+pf2是什么意思呢?毫无实际意义。

2) 两指针变量进行关系运算
指向同一数组的两指针变量进行关系运算可表示它们所指数组元素之间的关系。例如:

    pf1=pf2 表示pf1和pf2指向同一数组元素;
    pf1>pf2 表示pf1处于高地址位置;
    pf1<pf2 表示pf1处于低地址位置。

指针变量还可以与0比较。设p为指针变量,则p=0表明p是空指针,它不指向任何变量;p<>0表示p不是空指针。
在MSvbvm60.tlb中空指针定义为VBnullPtr,空指针是由对指针变量赋予0值而得到的。
在这里我将以VBA的简单一维数组作为例子开头,本例子代码在excel的VBA中测试通过:
Sub ptr运算()
    Dim myArr(4) As Long, p As VBAX.IntPtr, i&
    myArr(0) = 10
    myArr(1) = 11
    myArr(2) = 12
    myArr(3) = 13
    myArr(4) = 14
    p = VBAX.ArrStruct(ArrPtr(myArr)).DataPtr     '获取一维数组的数据首地址
    For i = 0 To 4
        Debug.Print PtrLng(p + vbLngSize * i)  '因为VB压根就没有[]这样的下标运算符,所以我们只能将就着用这样难看的写法
    Next
End Sub
说明:vb的数组只能说是一种极其变态的结构,变态到让人实在无语

用指针访问多维数组
在用指针访问二维数组时,一般是采用把二维转成一维的访问方式。具体的公式如下:
设二维数组有n行m列,则第(i,j)个元素在一维数组中的下标是
(i-1)*m+j
数组在正常使用中,最好是采用从0开始。一般vb的二维数组是先行后列,采用指针访问是先列后行
以下的例子在excel的VBA中测试通过
Sub 二维数组指针运算()
    Dim myarr(4, 4) As Long, p As VBAX.IntPtr, i&, k&, l1&, l2&
    For i = 0 To 4
        For k = 0 To 4
            myarr(i, k) = 10 * (i + 1) + k
        Next k
    Next i
    p = ArrStruct(ArrPtr(myarr)).DataPtr
    l1 = UBound(myarr, 1) - LBound(myarr, 1)
    l2 = UBound(myarr, 2) - LBound(myarr, 2)
    For k = 0 To 4
        For i = 0 To 4
            Debug.Print PtrLng(p + (i * 5 + k) * vbLngSize)
        Next
    Next
End Sub
指针数组
指针数组是一组有序的指针的集合。 指针数组的所有元素都必须是具有相同存储类型和指向相同数据类型的指针变量
在vb中的表示方式:dim myArr() as vbax.intptr
指针函数
函数类型是指函数返回值的类型。允许一个函数的返回值是一个指针(即地址),这种返回指针值的函数称为指针型函数
function myfunction() as vbax.intptr
指针的指针
如果一个指针变量存放的又是另一个指针变量的地址,则称这个指针变量为指向指针的指针变量。在前面已经介绍过,通过指针访问变量称为间接访问。由于指针变量直接指向变量,所以称为“单级间址”。而如果通过指向指针的指针变量来访问变量则构成“二级间址”。正常来说,你最多用到二级指针,即指针的指针。如果超过二级以上,很容易开始变得混乱不堪,除非你的神经足够大条,你可以无限级使用下去。即指针的指针的指针……
函数指针
很不幸的是,VB不能使用从basic到basic的函数指针。所以绝大多数人都会对着addressof望而兴叹。函数指针很重要!即使vbscript也有函数指针。vb.net的委托其实也是一种函数指针。可惜了,VB不能简单地使用函数指针。


点击这里给我发消息

4#
发表于 2015-6-13 11:40:06 | 只看该作者
强大! 支持续篇!
5#
 楼主| 发表于 2015-6-13 11:40:16 | 只看该作者
本帖最后由 ganlinlao 于 2015-6-25 23:58 编辑

iUnknown的故事——妻不如妾,妾不如妓,妓不如偷

以后有时间再写了,但还有以后吗?












3、轻量级的Com对象
      在VB6中可以用类模块来实现COM对象,其实用UDT(用户定义类型)也可以。
COM只不过是一种二进制标准,只要能在内存中构造出符合这种标准的数据结构,就能实现COM对象。
如果结合msvbvm60.tlb,写一个轻量级的com会显得更简单易懂一点:下面的代码是一个iunknown的原型
Const E_NOINTERFACE As Long = &H80004002
Private
Type LightEmptyVTable     '构造一个对象lightEmpty的Vtalbe
VTable(
2) As vbax.intptr                 '任何一个iunknown至少需要三个函数。如果你的com对象有N个方法、属性,则Vtale(N+2)
End Type
Public
Type LightEmpty             '可以理解成定义一个lightEmpty的object对象,它其实是一个指针      
   pVTable As vbax.intptr             '定义一个指向Vtable的指针,
End
Type
Private
m_VTable As LightEmptyVTable  '实例化vtable
Private
m_pVTable As vbax.intptr            '定义指向m_Vtable的指针

Public Function
InitializeLightEmpty(LE As LightEmpty) As IUnknown   
If
m_pVTable = 0 Then          '这是典型的构造函数constructor,而且是带参数的构造函数
With
m_VTable            
        .VTable(
0) = funptr(AddressOf QueryInterface)            
        .VTable(
1) = Funptr(AddressOf AddRef)            
        .VTable(2) = Funptr(AddressOf Release)        '假设这个类中有add()方法,那么vtable(3)=funptr(addressof add),以此类推
        m_pVTable =
VarPtr(.VTable(0))        
End With   
End If
        
With
LE        
       .pVTable = m_pVTable        
       putMem4 InitializeLightEmpty,VarPtr(.pVTable)   
End With

End Function

Private Function
QueryInterface (This As LightEmpty, riid As Long, pvObj As Long) As long   
Debug.Assert False   
pvObj =
0   
QueryInterface = E_NOINTERFACE

End Function
Private Function AddRef(This As LightEmpty) As Long   
Debug.Assert
False
End Function
Private Function
Release(This As LightEmpty) As Long   
MsgBox "LightEmpty->Release"
End Function


Sub Main()   
Dim LE As LightEmpty   
InitializeLightEmpty LE

End Sub


通过上面的代码,我们可以看出一个com的类的最基本的构造。
问题1:看得出来,没有简单易用的函数指针,给VB带来很大很大的麻烦。
问题2:用type end type构造出来的类,最好只用于一个工程中。如果你想把这个类象普通的class写的类那样对外发布,必须自己写这个类的tlb。
问题3:有没有可能用collection来代替array?键名用方法名(或属性名),值是函数的指针
问题4:所谓的实现继承?是将父类的vtable复制到子类的vtable?

轻量级com会带来什么样的作用?
1、实现静态类。VBA本身就有大量的函数了,所以无须静态方法。但如果你有很多的函数,那如果能分门归纳成一个个静态类,这样的类库条理会更清晰。
2、继承。com实际上是没有继承这个概念的(我也不知道我这样说对不对?),com和继承相类似的叫聚合。




6#
发表于 2015-6-13 15:51:01 | 只看该作者
WHY?:Decimal12字节?
因为 Decimal是包含在Variant中,所以数据表达最大12字节,还有1个字节标识类型,另外3个字节是保留用。
7#
发表于 2015-6-13 16:08:21 | 只看该作者
很想知道  PtrLng(地址) = 写入Long值 怎么实现地址写入的.?

我的理解:
"对于少量内存来说用PtrXXX和CopyMemX的基本内存操作效率相对较高,对于大量内存来说用批量内存操作效率更高。"
那么排除不是用copymemory, 那只能用"="赋值了,"
8#
 楼主| 发表于 2015-6-13 17:50:05 | 只看该作者
本帖最后由 ganlinlao 于 2015-6-30 12:43 编辑

如果有点指针使用概念,指针并不复杂。
例子代码在excel的VBA中测试通过:

Sub testPtrLng()
    Dim i As Long, p As VBAX.IntPtr         '声明p为指针
    p = VarPtr(i)                                        '在使用指针之前必须初始化,把p指向它指向的地址,即变量i的首地址
    i = 100
    Debug.Print i
    PtrLng(p) = 200                                 '因为VB中没有@用来表示取址,或*用来表示取值。所以这里ptrLng()相当*取值符
    MsgBox i                    '指针都是间接引用,变量名都是直接引用,看起来似乎是绕一个弯,但你想想c就知道,为什么c很猛
End Sub


*********************************************************************************************************
三、千金买美人,何处卖青春----string的故事

1、string的替换函数的区别:
Sub testReplace()
    Dim str As String, pStr As VBAX.IntPtr, pstr1 As VBAX.IntPtr
    str = "我是中国人"
    pStr = StrPtr(str)
    Debug.Print str
    Debug.Print pStr
    str = Replace(str, "是", "爱")
    pstr1 = StrPtr(str)
    Debug.Print str
    Debug.Print pstr1
End Sub
#############
Sub testMid()     ‘mid语句
    Dim str As String, pStr As VBAX.IntPtr, pstr1 As VBAX.IntPtr
    str = "我是中国人"
    pStr = StrPtr(str)
    Debug.Print str
    Debug.Print pStr
    Mid(str, 2, 1) = "爱"
    pstr1 = StrPtr(str)
    Debug.Print str
    Debug.Print "Mid语句的str内存地址没有发生变化:" & vbTab & pstr1
End Sub
##########
Sub testMidFunction()          'mid函数
    Dim str As String, pStr As VBAX.IntPtr, pstr1 As VBAX.IntPtr
    str = "我是中国人"
    pStr = StrPtr(str)
    Debug.Print str
    Debug.Print pStr
    str = Mid(str, 1)
    pstr1 = StrPtr(str)
    Debug.Print str
    Debug.Print "mid函数的str内存地址发生变化:" & vbTab & pstr1
End Sub


其实我们很容易理解vba中的所有string的处理函数,都会有一个返回值,而这个返回值是一个全新的ret字符串。
真正让我吃惊的是mid语句,str内存地址没有发生变化,说明str字符串在内部是可直接读写,而无须重新生存一个完全拷贝的ret字符串。
说明mid语句性能比Replace要高。
但事情没这么简单,接下来,就是我解决不了的疑惑:
Sub testpStr()
    Dim str As String, pStr As VBAX.IntPtr, pstr1 As VBAX.IntPtr
    str = "我是中国人"
    pStr = StrPtr(str)
    Debug.Print str
    Debug.Print pStr
    PutMem2 pStr + 2, "爱"   '这里即使是用putMem1把unicode当成一个字符,也同样是显示乱码
    pstr1 = StrPtr(str)
    Debug.Print "指针读写会显示乱码:" & str
    Debug.Print "str内存地址没有变化:" & pstr1
End Sub

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

评分

参与人数 1经验 +10 收起 理由
zpy2 + 10 锛堟妧鏈級鍘熷垱绮惧搧璇剧▼銆佸綍鍍忋.

查看全部评分

点击这里给我发消息

9#
发表于 2015-6-14 14:30:52 | 只看该作者
牛!武器差不多凑齐了
10#
发表于 2015-6-15 09:16:34 | 只看该作者
t小宝 发表于 2015-6-14 14:30
牛!武器差不多凑齐了

七大武器,还有三大武器,期待。。。。。。。。。。。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

QQ|站长邮箱|小黑屋|手机版|Office中国/Access中国 ( 粤ICP备10043721号-1 )  

GMT+8, 2024-11-1 13:29 , Processed in 0.129311 second(s), 37 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表