关于VB的指针以及VB和C/C++间的参数传递

首先,我们使用C/C++创建一个WIN32 DLL,这样VB才可以使用C/C++的代码。

此前我对DLL导出函数已有专文讲述,这里就不再赘述,当然为了各位能够去验证,我还是把步骤截图上来:

打开VC6,新建一个Win32 DLL工程(使用VS.NET的朋友因为有中文版MSDN我就不截图了,我也不喜欢用.NET)

选择空白的DLL就可以了,代码我们自己写。

创建头文件和代码文件,其实最重要的是#include <windows.h>,标准化吧。

创建模块定义文件(关于.DEF文件上一篇已经有说明)

头文件的代码如下:

#ifndef __HOOK_H_
#define __HOOK_H_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#define WIN32_LEAN_AND_MEAN
//#define EXPORT_API extern "C" __declspec(dllexport)
#define EXPORT_API __declspec(dllexport)

#include <windows.h>	// ...

typedef union _unTagPack{
	BYTE Byte;
	SHORT Integer;
	LONG Long;
	DWORD Pointer;
	float Single;
	double Double;
	char Text[256];
}TAGPACK, *PTAGPACK;

/*
//{{AFX_INSERT_LOCATION}}
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
*/

#endif


程序代码如下:

#include "Hook.h"

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
	switch(ul_reason_for_call )
	{
	case DLL_PROCESS_ATTACH:
		break;
	case DLL_THREAD_ATTACH:
	case DLL_THREAD_DETACH:
	case DLL_PROCESS_DETACH:
		break;
	}
	return TRUE;
}

EXPORT_API DWORD __stdcall fnHook(DWORD dwIndex)
{	return dwIndex;
}

EXPORT_API DWORD __stdcall fnVarptr(void *dwArg, DWORD dwFlag)
{	TAGPACK Arg;
	switch(dwFlag)
	{	
	case 0:
		Arg.Pointer = (DWORD)dwArg;
		Arg.Byte = *(BYTE*)dwArg;
		break;
	case 1:
		Arg.Pointer = (DWORD)dwArg;
		Arg.Integer = *(SHORT*)dwArg;
		break;
	case 2:
		Arg.Pointer = (DWORD)dwArg;
		Arg.Long = *(LONG*)dwArg;
		*(LONG*)dwArg = 0x10000000;
		break;
	case 3:
		Arg.Pointer = (DWORD)dwArg;
		Arg.Single = *(float*)dwArg;
		break;
	case 4:
		Arg.Pointer = (DWORD)dwArg;
		Arg.Double = *(double*)dwArg;
		break;
	case 5:
		Arg.Pointer = (DWORD)dwArg;
		Arg.Pointer = *(DWORD*)dwArg;
		strcpy(Arg.Text, (const char*)dwArg);
		break;
	}
	//*dwArg = 0x80000001;
	return 0;
}


模块定义文件输出这两个函数就可以了。

EXPORTS
fnHook
fnVarptr


那么,我们给两个函数做个断点,方便等下调试。

 

OK!现在我们编写VB的代码,并生成Project1.exe,就一个窗口:

Option Explicit

Private Declare Function fnHook Lib "Hook.dll" (ByVal dwIndex As Long) As Long
Private Declare Function fnVarptr Lib "Hook.dll" (ByVal dwPtr As Long, ByVal dwFlag As Long) As Long

Private Type T_ARG
    b As Byte
    i As Integer
    l As Long
    s As Single
    d As Double
    t(32) As Byte
End Type

Dim a As T_ARG

Private Sub Form_Load()
    '
    Dim ret As Long, s As String
    a.b = 255
    a.i = 32767
    a.l = &H7FFFFFFF
    a.s = 12345.6789
    a.d = 0.123456789
    a.t(0) = Asc("D")
    a.t(1) = Asc("L")
    a.t(2) = Asc("L")
    a.t(3) = 0
    s = "Fuck You!"
    
    Call fnHook(0)
    ret = fnVarptr(VarPtr(a.b), 0)
    Call fnHook(1)
    ret = fnVarptr(VarPtr(a.i), 1)
    Call fnHook(2)
    ret = fnVarptr(VarPtr(a.l), 2)
    Call fnHook(3)
    ret = fnVarptr(VarPtr(a.s), 3)
    Call fnHook(4)
    ret = fnVarptr(VarPtr(a.d), 4)
    Call fnHook(5)
    ret = fnVarptr(VarPtr(a.t(0)), 5)
    Call fnHook(6)
    ret = fnVarptr(StrPtr(s), 5)
    Call fnHook(7)
End Sub

现在,我们在VC++的环境中配置好Project1.exe的路径,F5直接运行就会在这个断点停下:

你可能已经明白,这个函数只是方便我们定位VB的代码。不错,看汇编窗口的代码:

17:   EXPORT_API DWORD __stdcall fnHook(DWORD dwIndex)
18:   {   return dwIndex;
10001070   push        ebp
10001071   mov         ebp,esp
10001073   sub         esp,40h
10001076   push        ebx
10001077   push        esi
10001078   push        edi
10001079   lea         edi,[ebp-40h]
1000107C   mov         ecx,10h
10001081   mov         eax,0CCCCCCCCh
10001086   rep stos    dword ptr [edi]
10001088   mov         eax,dword ptr [ebp+8]
19:   }
1000108B   pop         edi
1000108C   pop         esi
1000108D   pop         ebx
1000108E   mov         esp,ebp
10001090   pop         ebp
10001091   ret         4

呵呵,所以说不是必要最好别定义函数,你至少多有10条汇编指令要运行。而且,这只是在C/C++。

一旦运行到ret 4,以后的代码就是VB的代码了。

当然,我们“装”聪明一点,跳开这个函数回到主调函数,那么主调函数的代码就是VB的代码(DLL函数是VB调用的)。

比如第一次传入一个Byte的时候:

00401B43   push        0
00401B45   call        004018B0
00401B4A   mov         esi,dword ptr ds:[401014h]
00401B50   call        esi
00401B52   mov         edx,dword ptr [ebp-34h]
00401B55   mov         edi,dword ptr ds:[40105Ch]
00401B5B   push        edx
00401B5C   call        edi
00401B5E   push        0
00401B60   push        eax
00401B61   call        004018F4
00401B66   call        esi
00401B68   push        1
00401B6A   call        004018B0

也许你会认为,这个004018B0就是fnHook的地址,其实不是。从上面的汇编代码我们已经知道它的地址是10001070。

那么为什么不是PUSH 0,然后CALL 10001070呢?这是因为VB调用DLL是DllFunctionCall来完成的。

VB跟.NET一样,应该说.NET跟VB一样在虚拟机环境中运行,它编译后的代码很接近机器码。

这跟VB的历史有关,VB是伴随着ActiveX技术发展起来的,它的成功远超出了微软的预料,这也是.NET借鉴它的原因之一。

点到为止,回到正题。

 

我们不管此CALL 004018B0后做什么样的操作,最终都是调用fnHook然后返回,那么我们就得到了VB代码所对应的汇编代码:

00401B68   push        1
00401B6A   call        004018B0
00401B6F   call        esi
00401B71   mov         eax,dword ptr [ebp-38h]
00401B74   push        eax
00401B75   call        edi
00401B77   push        1
00401B79   push        eax
00401B7A   call        004018F4
00401B7F   call        esi
00401B81   push        2
00401B83   call        004018B0
00401B88   call        esi
00401B8A   mov         ecx,dword ptr [ebp-3Ch]
00401B8D   push        ecx
00401B8E   call        edi
00401B90   push        2
00401B92   push        eax
00401B93   call        004018F4
00401B98   call        esi
00401B9A   push        3
00401B9C   call        004018B0
00401BA1   call        esi
00401BA3   mov         edx,dword ptr [ebp-40h]
00401BA6   push        edx
00401BA7   call        edi
00401BA9   push        3
00401BAB   push        eax
00401BAC   call        004018F4
00401BB1   call        esi
00401BB3   push        4
00401BB5   call        004018B0
00401BBA   call        esi
00401BBC   push        ebx
00401BBD   call        edi
00401BBF   push        4
00401BC1   push        eax
00401BC2   call        004018F4
00401BC7   call        esi
00401BC9   push        5
00401BCB   call        004018B0
00401BD0   call        esi
00401BD2   mov         eax,dword ptr [ebp-44h]
00401BD5   push        eax
00401BD6   call        edi
00401BD8   push        5
00401BDA   push        eax
00401BDB   call        004018F4
00401BE0   call        esi
00401BE2   push        6
00401BE4   call        004018B0
00401BE9   call        esi
00401BEB   mov         ecx,dword ptr [ebp-1Ch]
00401BEE   push        ecx
00401BEF   call        dword ptr ds:[40105Ch]
00401BF5   push        5
00401BF7   push        eax
00401BF8   call        004018F4
00401BFD   call        esi
00401BFF   push        7
00401C01   call        004018B0
00401C06   call        esi
00401C08   mov         dword ptr [ebp-4],0
00401C0F   wait

代码中CALL esi及以后的东西是VB自己生成的指令,我们不用理会。

好,注意下第一次传递Byte时候汇编代码的红色部分,所调用的函数会变代码是这样的:

7345C195   push        esi
7345C196   call        dword ptr ds:[7339122Ch]
7345C19C   push        dword ptr ds:[7349EF94h]
7345C1A2   mov         esi,eax
7345C1A4   call        dword ptr ds:[73391278h]
7345C1AA   mov         dword ptr [eax+9Ch],esi
7345C1B0   pop         esi
7345C1B1   ret


注意红色那行,起跳转到这里执行:

7C92FE01   mov         eax,fs:[00000018]
7C92FE07   mov         eax,dword ptr [eax+34h]
7C92FE0A   ret

 

第二个CALL,我们不去理会它,你也可以继续跟踪,不过现在我们已经有了答案:

就是它就是VarPtr(),我们看VB IDE中函数的提示:


Ptr As Any中的Any就好比C/C++中的void*指针,既然是这样,就不能说VB完全不支持指针,而是把它和谐了。

当然,就一此说VB支持指针,也是错的。我们无法用*ptr = value 来操作指针,也无法给指针赋值。

然而,通过VarPtr我们得到了指针的值,把它传递给C/C++,我们看下以此传递进来后C/C++处理的结果:

将Byte的值传递给共用体的Byte成员。

 

将Integer赋值给共用体成员。

 

类似的Long也是一样:

 

单精度浮点型。

这里也许你会发现,VB中的12345.6789变成了12345.7,为什么?

这跟浮点型的存储方式有关,这里就不详细讲了。不过一点是,不要把long直接用内存复制(包括指针转换),赋值给Single

反之也是一样。虽然两者使用的内存都是4个字节。

 

双精度浮点型。

赋值后:

 

字符串,直接把字节数组传递进来:

 

字符串,用StrPtr传递的结果有所不同

少尉有点功底的都知道,这是Unicode编码的字符串。我们的DLL中使用strcpy,对此我们应该使用另外一个函数:wcscpy

 

最关键的是,到这里我们已经认识到,VB所谓VarPtr只是把指针搞成一个Long,字符串就多一点玄机。

只要认识到这一点,那么在处理VB和C/C++的DLL中数据的交换,或者换个说法叫函数调用,就了然了。

你还可以再去验证,把Byval As String传递给char*或者const char*是完全OK的。

 

Doc End!

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页