技术宅的结界

 找回密码
 立即注册→加入我们

QQ登录

只需一步,快速开始

搜索
热搜: 下载 VB C 实现 编写
查看: 565|回复: 5
收起左侧

【VB6】在VB6里实现“指针类型”——像C语言的[]那样用()来读写内存中的数组!

[复制链接]

995

主题

2207

帖子

5万

积分

用户组: 管理员

一只技术宅

UID
1
精华
197
威望
261 点
宅币
16463 个
贡献
32446 次
宅之契约
0 份
在线时间
1565 小时
注册时间
2014-1-26
发表于 2017-10-18 05:10:34 | 显示全部楼层 |阅读模式

欢迎访问技术宅的结界,请注册或者登录吧。

您需要 登录 才可以下载或查看,没有帐号?立即注册→加入我们

x
以前我提到过各种VB6里面使用指针的方法比如用VarPtr取得变量的地址然后用CopyMemory(实际上是RtlMoveMemory)来把指定地址的数据复制到自己的地方或者把自己的数据复制到指定地方。现在可以不用那么麻烦了:通过设置SAFEARRAY来直接像访问自家数组一样去读写一个指定的内存区域的数据。

VB6的数组是SAFEARRAY安全数组,而实际的数组的数据是存储在SAFEARRAY结构体的pvData成员指向的地方的。SAFEARRAY的结构如下:
[C] 纯文本查看 复制代码
typedef struct tagSAFEARRAYBOUND {
  ULONG cElements;
  LONG  lLbound;
} SAFEARRAYBOUND, *LPSAFEARRAYBOUND;

typedef struct tagSAFEARRAY {
  USHORT         cDims;
  USHORT         fFeatures;
  ULONG          cbElements;
  ULONG          cLocks;
  PVOID          pvData;
  SAFEARRAYBOUND rgsabound[1];
} SAFEARRAY, *LPSAFEARRAY;
我就想:如果我自己搞个傀儡数组,然后我修改它的pvData成员的值,是不是可以指哪打哪了?经过我的测试发现它还真是这样。

那么在VB6里面如何才能找出一个数组它的SAFEARRAY结构体的存储位置呢?经过多次尝试我发现:

声明API的时候,如果你把某个参数的定义写成“xxxx() As Any”,那么VB6就会在调用这个API的时候把你提供的数组它的SAFEARRAY结构体的地址传给这个API。所以又到了使用某个傀儡函数的时候:VarPtr。
VB6的msvbvm60.dll导出了一个傀儡函数VarPtr,它的实现其实就是把自己参数列表的第一个参数原样返回给你。IDA里面已经看到了,这个VarPtr的反汇编是这么写的:
[Asm] 纯文本查看 复制代码
mov eax,[esp+4]
ret 4
所以我们可以手动声明它的API,并且修改它的名字(别名)和参数的定义。我是这样写的:
[Visual Basic] 纯文本查看 复制代码
Declare Function ArrayPtr Lib "msvbvm60.dll" Alias "VarPtr" (ptr() As Any) As Long
为了观察VB6自身是如何分配地址存储SAFEARRAY结构体的内容和数据的内容,我写了个测试程序,用了这样的代码来测试:
[Visual Basic] 纯文本查看 复制代码
Private Sub Command3_Click()
Cls

Dim Arr_Int_Fixed(111) As Integer '固定大小Integer数组
Dim Arr_Int_Alloc() As Integer '可变大小Integer数组
ReDim Arr_Int_Alloc(111)

Dim Arr_Long_Fixed(111) As Integer '固定大小Long数组
Dim Arr_Long_Alloc() As Integer '可变大小Long数组
ReDim Arr_Long_Alloc(111)

Dim Arr_Long_Member As TestType '在结构体里定义固定大小Long数组
'结构体声明:
'Private Type TestType
'    Something As Long
'    SomeArr(111) As Long
'End Type

Dim Arr_VarPtr As Long '数组变量自身的地址
Dim Arr_Ptr As Long 'SAFEARRAY结构体的地址
Dim Arr_Body As SAFEARRAY 'SAFEARRAY结构体的内容

Arr_VarPtr = ArrayPtr(Arr_Int_Fixed)
CopyMemory Arr_Ptr, ByVal Arr_VarPtr, 4
CopyMemory Arr_Body, ByVal Arr_Ptr, Len(Arr_Body)

Print "Arr_Int_Fixed:"
GoSub PrintArrData

Arr_VarPtr = ArrayPtr(Arr_Int_Alloc)
CopyMemory Arr_Ptr, ByVal Arr_VarPtr, 4
CopyMemory Arr_Body, ByVal Arr_Ptr, Len(Arr_Body)

Print "Arr_Int_Alloc:"
GoSub PrintArrData

Arr_VarPtr = ArrayPtr(Arr_Long_Fixed)
CopyMemory Arr_Ptr, ByVal Arr_VarPtr, 4
CopyMemory Arr_Body, ByVal Arr_Ptr, Len(Arr_Body)

Print "Arr_Long_Fixed:"
GoSub PrintArrData

Arr_VarPtr = ArrayPtr(Arr_Long_Alloc)
CopyMemory Arr_Ptr, ByVal Arr_VarPtr, 4
CopyMemory Arr_Body, ByVal Arr_Ptr, Len(Arr_Body)

Print "Arr_Long_Alloc:"
GoSub PrintArrData

Arr_VarPtr = ArrayPtr(Arr_Long_Member.SomeArr)
CopyMemory Arr_Ptr, ByVal Arr_VarPtr, 4
CopyMemory Arr_Body, ByVal Arr_Ptr, Len(Arr_Body)

Print "Arr_Long_Member:"
GoSub PrintArrData

Exit Sub
PrintArrData:
    '打印数组变量自身的地址和这个数组变量所描述的SAFEARRAY结构体的地址
    Print "Address", Hex$(Arr_VarPtr); "->"; Hex$(Arr_Ptr)
    Print "cDims", Arr_Body.cDims '维数
    Print "fFeatures", Hex$(Arr_Body.fFeatures) '特性
    Print "cbElements", Arr_Body.cbElements '单个元素的大小
    Print "cLocks", Arr_Body.cLocks '是否有锁
    Print "pvData", Hex$(Arr_Body.pvData) '实际的数据的指针
    Print "cElements(0)", Arr_Body.rgsabound(0).cElements '1维元素总数
    Print "lLbound(0)", Arr_Body.rgsabound(0).lLbound '上标
    Return
End Sub
注意在VB6里面你的数组变量本身相当于一个SAFEARRAY*,也就是一个指针,指向一个SAFEARRAY结构体。但你自己声明的结构体内的固定大小数组则不是这样的情况。

这个测试的结果如下图所示:
20171016223631.png

可以发现以下特征:(有些虽然看不出,但结合我的测试它就是这种效果)
  • 固定大小数组变量自身和它指向的SAFEARRAY结构体都在栈上,但数据在堆上。
  • 可变大小数组变量在栈上,但它指向的SAFEARRAY结构体在堆上,经过测试我发现VB6会在这个数组变量生命周期结束后对这个SAFEARRAY结构体所占的内存进行了释放内存的操作,也就是类似C语言的“free()”的操作。此外可变大小数组的数据也是在堆上的,并且是单独分配的,而不是和SAFEARRAY结构体一起分配的。
  • 对于结构体内的固定大小数组,这个变量自身和它指向的SAFEARRAY结构体都在栈上,而数据则在结构体里。图中可以看到这个结构体自身是在栈上的。

其中我说的在堆上的玩意儿,经过我的测试就是不停地点按钮的话,那几个值特别大的数字它是不停地变化的。也就是它确实有个内存的分配和释放的操作在里面。

那么……如果我在某个地址上有个数据但我想要直接访问它而不经过CopyMemory的话,我是不是可以自己构建一个傀儡SAFEARRAY,来让我的数组“一出生”它就指向我要的数据,并且可以直接读写呢?经过我的尝试:这是完全可行的!
但要注意VB6会对生命周期结束的SAFEARRAY进行回收操作,一个阻止回收操作的方法就是把cLocks成员的值设为非零。这样它就会因为上了锁而不再尝试回收它。

我写了个一个Module,照抄里面的代码就能构建自己的指针类型。实测还是很方便的。

我通过构造一个结构体,前面做了个傀儡SAFEARRAY结构,后面是数组变量。用法就是直接用对应的函数初始化就行。对应的函数是xxxxPtr_Setup,其中xxxx表示类型。你自己可以通过照葫芦画瓢的方式抄我的结构体和这个函数的实现来实现自己的自定义类型的指针类型。
modArrayPtr.bas (3.67 KB, 下载次数: 3)

34

主题

133

帖子

6938

积分

用户组: 管理员

UID
77
精华
11
威望
112 点
宅币
6397 个
贡献
129 次
宅之契约
0 份
在线时间
90 小时
注册时间
2014-2-22
发表于 2017-10-18 10:47:32 | 显示全部楼层
膜拜LZ的超神技术。

0

主题

10

帖子

4729

积分

用户组: 技术宅的结界VIP成员

UID
2021
精华
0
威望
2 点
宅币
4715 个
贡献
0 次
宅之契约
0 份
在线时间
4 小时
注册时间
2016-10-21
发表于 2017-10-18 15:57:18 | 显示全部楼层
大神好腻害!

266

主题

438

帖子

4589

积分

用户组: 真·技术宅

UID
2
精华
61
威望
147 点
宅币
3427 个
贡献
125 次
宅之契约
0 份
在线时间
592 小时
注册时间
2014-1-25
发表于 2017-10-19 21:34:16 | 显示全部楼层
厉害,创造了一个VB-C语言!!

1

主题

6

帖子

32

积分

用户组: 初·技术宅

UID
2920
精华
0
威望
2 点
宅币
22 个
贡献
0 次
宅之契约
0 份
在线时间
1 小时
注册时间
2017-10-2
发表于 2017-10-19 21:35:55 | 显示全部楼层
元始天尊 发表于 2017-10-19 21:34
厉害,创造了一个VB-C语言!!

我过来看看学长们在干啥

1

主题

15

帖子

15

积分

用户组: 初·技术宅

UID
2735
精华
0
威望
0 点
宅币
0 个
贡献
0 次
宅之契约
0 份
在线时间
6 小时
注册时间
2017-7-28
发表于 2017-10-21 18:12:53 | 显示全部楼层
学习一下
回复

使用道具 举报

本版积分规则

QQ|申请友链|Archiver|手机版|小黑屋|技术宅的结界 ( 滇ICP备16008837号|网站地图

GMT+8, 2018-9-24 08:26 , Processed in 0.125349 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

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