设为首页收藏本站Access中国

Office中国论坛/Access中国论坛

 找回密码
 注册

QQ登录

只需一步,快速开始

12下一页
返回列表 发新帖
查看: 9069|回复: 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 订阅订阅
14#
发表于 2017-12-14 00:46:41 | 只看该作者
同求: msvbvm60.tlb 下载
13#
发表于 2017-9-5 07:30:36 | 只看该作者
求 msvbvm60.tlb 下载,在百度也没有搜索到
12#
发表于 2015-11-30 10:15:51 | 只看该作者

感谢楼主分享,非常感谢~~
11#
发表于 2015-11-28 13:01:35 | 只看该作者
每次回帖、谢谢!辛苦了。
10#
发表于 2015-6-15 09:16:34 | 只看该作者
t小宝 发表于 2015-6-14 14:30
牛!武器差不多凑齐了

七大武器,还有三大武器,期待。。。。。。。。。。。。

点击这里给我发消息

9#
发表于 2015-6-14 14:30:52 | 只看该作者
牛!武器差不多凑齐了
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 锛堟妧鏈級鍘熷垱绮惧搧璇剧▼銆佸綍鍍忋.

查看全部评分

7#
发表于 2015-6-13 16:08:21 | 只看该作者
很想知道  PtrLng(地址) = 写入Long值 怎么实现地址写入的.?

我的理解:
"对于少量内存来说用PtrXXX和CopyMemX的基本内存操作效率相对较高,对于大量内存来说用批量内存操作效率更高。"
那么排除不是用copymemory, 那只能用"="赋值了,"
6#
发表于 2015-6-13 15:51:01 | 只看该作者
WHY?:Decimal12字节?
因为 Decimal是包含在Variant中,所以数据表达最大12字节,还有1个字节标识类型,另外3个字节是保留用。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-6-13 18:40 , Processed in 0.115580 second(s), 38 queries .

Powered by Discuz! X3.3

© 2001-2017 Comsenz Inc.

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