1.首先我们必须知道C语言的调用约定为
__cdecl(即参数从右向左依次进栈,由调用者还原堆栈).
2.一条push指令最多压入4个字节,当不足4个字节时应补齐4个字节,超过4个字节时应该由低位到高位依次压栈.
3.pop指令也和push一样一次只能弹出4个字节.
4.我们需要一个CallStruct类型来储存一个参数.
1 class CallStruct
2 {
3 public:
4 INT Integer;
5 BOOL Bool;
6 DOUBLE Real;
7 WCHAR String[MAX_STRING];
8
9 enum TYPE
10 {
11 ctInteger,
12 ctString,
13 ctBool,
14 ctReal,
15 ctVoid,
16 }Type;
17
18 CallStruct() : Integer(0),Bool(FALSE),Real(0)
19 {
20 String[0] = 0;
21 }
22
23 CallStruct(const NAutoPtr<VirtualMachine::VarClass>& Var) : Integer(Var->Integer),Bool(Var->Bool),Real(Var->Real),Type((TYPE)Var->Type)
24 {
25 if(Type == ctString) wcscpy(String,Var->String);
26 }
27 };
5.我们需要一个列表来存放参数和一个返回值对象来存放返回值.
1 List<NAutoPtr<CallStruct>> VarList;
2 NAutoPtr<CallStruct> Return;
6.最后我们需要一个HMODULE和一个FARPROC分别存放dll的句柄和函数地址.
1 HMODULE hModule;
2 FARPROC FunctionPtr;
7.然后我们添加几个功能函数.
1 BOOL AddVar(NAutoPtr<VirtualMachine::VarClass>& Var);
2 BOOL SetReturnType(CallStruct::TYPE Type);
3 BOOL SetLibName(LPTSTR Name);
4 BOOL SetFunctionName(LPTSTR Name);
注意:GetProcAddress第二个参数只接受LPCSTR类型的字符串,应此如果传入的是Unicode编码的字符必须将其转换成ANSI的.
8.我们添加一个函数Run用于调用函数.
1 BOOL CallMacro::Run()
2 {
3 if(FunctionPtr == 0 || Return.Buffer() == 0) return FALSE;
4 union RealStruct
5 {
6 double Real;
7 struct
8 {
9 int Head,Tail;
10 };
11 };
12 NAutoPtr<CallStruct> cs;
13 int Integer;
14 BOOL Bool;
15 RealStruct Real; // Push指令一次只能压入4字节
16 LPTSTR String;
17
18 int iEsp;
19 __asm mov int ptr[iEsp],esp; // 保存esp
20 for(int i=0;i<VarList.Size();i++)
21 {
22 cs = VarList[i];
23 Integer = cs->Integer;
24 Bool = cs->Bool;
25 Real.Real = cs->Real;
26 String = cs->String;
27 switch(cs->Type)
28 {
29 case CallStruct::ctInteger:
30 __asm push Integer;
31 break;
32 case CallStruct::ctString:
33 __asm push String;
34 break;
35 case CallStruct::ctBool:
36 __asm push Bool;
37 break;
38 case CallStruct::ctReal:
39 __asm push Real.Tail;
40 __asm push Real.Head;
41 break;
42 }
43 }
44 FARPROC proc = FunctionPtr;
45 int Head,Tail;
46 __asm
47 {
48 call proc
49 mov int ptr[Head],edx
50 mov int ptr[Tail],eax
51 }
52 switch(Return->Type)
53 {
54 case CallStruct::ctInteger:
55 Return->Integer = Tail;
56 break;
57 case CallStruct::ctString:
58 wcscpy(Return->String,(LPCTSTR)Tail);
59 break;
60 case CallStruct::ctBool:
61 Return->Bool = Tail;
62 break;
63 case CallStruct::ctReal:
64 __asm fstp [Real.Real];
65 Return->Real = Real.Real;
66 break;
67 }
68 // __declspec调用约定,需要手工还原堆栈
69 __asm mov esp,int ptr[iEsp];
70 return TRUE;
71 }
Run函数首先检查是否已经装载了DLL并获得了函数地址,以及返回值类型是否已经定义.
然后根据类型依次将函数压栈.
然后调用call指令并保存返回值.(这里需要注意的是
当返回值类型为double或float类型时必须使用fstp指令从FPU寄存器栈的栈顶的值取出来)
最后还原堆栈.
然后我们来测试一下.
创建一个名为TestDLL的DLL工程并添加函数Test.
1 TESTDLL_API double Test(double d,double* d1,WCHAR* lpBuffer)
2 {
3 if(d == 123.456789) MessageBox(0,lpBuffer,L"",0);
4 *d1 = 789.654;
5 return 77777;
6 }
然后创建一个测试工程,添加相关文件.
在_tmain中添加以下代码.
1 int _tmain(int argc, _TCHAR* argv[])
2 {
3 double d;
4 CallMacro cm;
5 NAutoPtr<VirtualMachine::VarClass> Var;
6
7 Var = new VirtualMachine::VarClass;
8 Var->Type = VirtualMachine::VarClass::vtString;
9 wcscpy(Var->String,L"aaaaa");
10 cm.AddVar(Var);
11
12 Var = new VirtualMachine::VarClass;
13 Var->Type = VirtualMachine::VarClass::vtInteger;
14 Var->Integer = (INT)&d;
15 cm.AddVar(Var);
16
17 Var = new VirtualMachine::VarClass;
18 Var->Type = VirtualMachine::VarClass::vtReal;
19 Var->Real = 123.456789;
20 cm.AddVar(Var);
21
22 cm.SetLibName(L"TestDll.dll");
23 cm.SetFunctionName(L"Test");
24 cm.SetReturnType(CallMacro::CallStruct::ctReal);
25 cm.Run();
26
27 wprintf(L"%f %f\n",d,cm.Return->Real);
28 system("pause");
29 return 0;
30 }
编译并运行,可以看到Test函数调用成功,并成功输出d的值和返回值.
最后给出
完整代码.
posted on 2011-02-06 22:21
lwch 阅读(2591)
评论(0) 编辑 收藏 引用 所属分类:
NScript