虽然VB中没有原生的内联汇编支持,但是通过轻量级COM对象可以间接实现。
严格的说这不是真正的内联汇编,因为你不能在任何地方使用,只能在轻量级对象的虚表(VTable)中使用,而且与其说是内联汇编,倒不如说是内联机器码。
为什么要使用内联汇编?比较“低级”的C语言编译器一般都支持内联汇编功能,可除了一些底层的系统,又有多少程序要用到内联汇编?更何况比较“高级”的VB?
C程序用不上内联汇编,是因为C语言提供了足够强大的功能,或者说C语言本身已经比较底层了。而VB隐藏了太多底层的东西,所以有些功能必须通过汇编来实现。别的先不说,就拿移位运算来说,除了VB以外,你见过哪钟语言没有移位运算符的?虽然可以用乘除法来模拟,但是要充分考虑各种溢出并且效率低下,我们今天就用内联汇编来实现移位运算。
首先用.idl文件定义好接口,用midl.exe编译成.tlb文件并添加引用:
[
uuid(6BEE1180-1F4A-47D9-B192-5FF90DDF8827),
version(1.0)]library BitLib{
importlib("stdole2.tlb");
[
odl,
uuid(E690B781-B712-4E59-A643-AA4AE9EE78BE),
nonextensible
]
interface IBit : IUnknown {
long _stdcall LeftShift(
[in] long a,
[in] long b);
long _stdcall RightShift(
[in] long a,
[in] long b);
};};
然后添加一个标准模块:
Option Explicit'VB6 bit shift lightweight object'By Demon'https://demon.twPrivate Declare Function CoTaskMemAlloc Lib "ole32.dll" (ByVal cb As Long) As LongPrivate Declare Sub CoTaskMemFree Lib "ole32.dll" (ByVal pv As Long)Private Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
Destination As Any, Source As Any, ByVal Length As Long)Private Declare Sub ZeroMemory Lib "kernel32" Alias "RtlZeroMemory" ( _
dest As Any, ByVal numBytes As Long)Private Type BitVTable
VTable(4) As LongEnd TypePrivate Type Bit
pVTable As Long
cRefs As LongEnd TypePrivate Type LeftShift
ASM(3) As LongEnd TypePrivate Type RightShift
ASM(3) As LongEnd TypePrivate m_VTable As BitVTablePrivate m_pVTable As LongPrivate m_LeftShift As LeftShiftPrivate m_RightShift As RightShiftPublic Function CreateBit() As IUnknown
Dim Struct As Bit
Dim ThisPtr As Long
If m_pVTable = 0 Then
With m_VTable
.VTable(0) = FuncAddr(AddressOf QueryInterface)
.VTable(1) = FuncAddr(AddressOf AddRef)
.VTable(2) = FuncAddr(AddressOf Release)
With m_LeftShift
.ASM(0) = &H824448B
.ASM(1) = &HC244C8B
.ASM(2) = &HCC2E0D3
.ASM(3) = &HCCCCCC00
'8B4424 08 mov eax, dword ptr [esp+8]
'8B4C24 0C mov ecx, dword ptr [esp+0C]
'D3E0 shl eax, cl
'C2 0C00 retn 0C
'CC int3
'CC int3
'CC int3
End With
.VTable(3) = VarPtr(m_LeftShift)
With m_RightShift
.ASM(0) = &H824448B
.ASM(1) = &HC244C8B
.ASM(2) = &HCC2E8D3
.ASM(3) = &HCCCCCC00
'8B4424 08 mov eax, dword ptr [esp+8]
'8B4C24 0C mov ecx, dword ptr [esp+0C]
'D3E8 shr eax, cl
'C2 0C00 retn 0C
'CC int3
'CC int3
'CC int3
End With
.VTable(4) = VarPtr(m_RightShift)
m_pVTable = VarPtr(.VTable(0))
End With
End If
ThisPtr = CoTaskMemAlloc(LenB(Struct))
If ThisPtr = 0 Then Err.Raise 7
With Struct
.pVTable = m_pVTable
.cRefs = 1
End With
CopyMemory ByVal ThisPtr, Struct.pVTable, LenB(Struct)
ZeroMemory Struct.pVTable, LenB(Struct)
CopyMemory CreateBit, ThisPtr, 4End FunctionPrivate Function QueryInterface(This As Bit, riid As Long, pvObj As Long) As Long
With This
pvObj = VarPtr(.pVTable)
.cRefs = .cRefs + 1
End WithEnd FunctionPrivate Function AddRef(This As Bit) As Long
With This
.cRefs = .cRefs + 1
AddRef = .cRefs
End WithEnd FunctionPrivate Function Release(This As Bit) As Long
With This
.cRefs = .cRefs - 1
Release = .cRefs
If .cRefs = 0 Then
DeleteThis This
End If
End WithEnd FunctionPrivate Function FuncAddr(ByVal pfn As Long) As Long
FuncAddr = pfnEnd FunctionPrivate Sub DeleteThis(This As Bit)
Dim tmp As Bit
Dim pThis As Long
pThis = VarPtr(This)
CopyMemory ByVal VarPtr(tmp), ByVal pThis, LenB(This)
CoTaskMemFree pThis
End Sub
再添加一个标准模块:
Sub Main()
Dim IBit As IBit
Set IBit = Bit.CreateBit
Debug.Print Hex$(IBit.LeftShift(1, 31))
Debug.Print Hex$(IBit.RightShift(&H80000000, 1))
End Sub
运行一下看看效果,输出80000000和40000000,还不错。
跟普通的轻量级对象一样,Bit对象VTable的前三个指针都是QueryInterface、AddRef和Release;而后两个指针却指向一个结构的地址,而该结构的内容是一串神奇的数字:
With m_LeftShift
.ASM(0) = &H824448B
.ASM(1) = &HC244C8B
.ASM(2) = &HCC2E0D3
.ASM(3) = &HCCCCCC00
'8B4424 08 mov eax, dword ptr [esp+8]
'8B4C24 0C mov ecx, dword ptr [esp+0C]
'D3E0 shl eax, cl
'C2 0C00 retn 0C
'CC int3
'CC int3
'CC int3End With.VTable(3) = VarPtr(m_LeftShift)With m_RightShift
.ASM(0) = &H824448B
.ASM(1) = &HC244C8B
.ASM(2) = &HCC2E8D3
.ASM(3) = &HCCCCCC00
'8B4424 08 mov eax, dword ptr [esp+8]
'8B4C24 0C mov ecx, dword ptr [esp+0C]
'D3E8 shr eax, cl
'C2 0C00 retn 0C
'CC int3
'CC int3
'CC int3
End With
.VTable(4) = VarPtr(m_RightShift)
很显然,这些神奇的数字就是机器码,下面注释是对应的汇编代码。将汇编转成机器码的方法很多,可以用NASM、MASM、Visual C++等。
简单介绍一下用Visual C++将汇编代码转成相应机器码的方法:
int main(){
_asm
{
mov eax, dword ptr [esp+8]
mov ecx, dword ptr [esp+0Ch]
shl eax, cl
retn 0Ch
}
return 0;}
调试上面的代码,在汇编窗口中就能看到对应的机器码,不过复制到VB中时要注意字节的顺序并用CC(int 3指令,当然也可以用nop指令)补齐:
23: _asm
24: {
25: mov eax, dword ptr [esp+8]
00401028 8B 44 24 08 mov eax,dword ptr [esp+8]
26: mov ecx, dword ptr [esp+0Ch]
0040102C 8B 4C 24 0C mov ecx,dword ptr [esp+0Ch]
27: shl eax, cl
00401030 D3 E0 shl eax,cl
28: retn 0Ch
00401032 C2 0C 00 ret 0Ch
29: }
为什么VB会执行我们构造出来的机器码呢?接口中定义的方法对应着对象VTable的指针,VTable(3)指向m_LeftShift结构的地址,当调用LeftShift方法时,VB编译器生成的是一条call指令,所以该地址的内容,也就是我们构造的机器码会被执行。