MakeObjectInstance的前世今生(关键是ECX的何时入栈以及Self指针何时存储的)

时间:2023-12-10 12:42:32

高手们的文章有很大启发,但是总有些小错,也有没交代清楚的,以下是我的理解:

编译器编译MainWndProc的时候,它是一个正常Delphi普通函数,MakeObjectInstance对它做变换是运行期的事情,
它有两个参数的:SELF,TMESSAGE,编译的时候仍然按照register规则编译。从而被翻译为一大堆汇编命令的集合。
它的头一句汇编就已经开始工作,至于函数参数的准备,由编译器在外部给它完成。函数自己不会为自己准备参数,也无法为自己准备参数。
MakeObjectInstance在运行期处理它的时候,仍然是一堆冷冰冰的汇编命令。
此外,MakeObjectInstance也是一个正常的Delphi函数,而且规则也是register,所以任何地方调用它,编译器都会提前为它准备好参数 EAX = Method(一个函数指针)
当VCL使用SetWindowLong把Delphi实例的窗口函数设为FObjectInstance,并将这个新窗口函数与Handle联系起来,Windows想才不管呢,有消息来了就往新窗口函数那边送,哪怕新窗口函数地址是错的。
并且windows也得提前准备好参数(不是编译器准备,因为数据来源就是来自于Windows,而不是程序员的代码),而且是按照stdcall准备好数据。
所以Windows准备好了
HANDLE
MESSAGE
WPARAM
LPARAM
本来想请MainWndProc的汇编语句直接去享用。但是MainWndProc说,它要的两个参数self和TMessage在哪里?在EAX,EDX和ECX里吗?Windows回答说没有准备好,你自己想办法。
这时FObjectInstance跳出来了,对MainWndProc说,我来帮你。于是FObjectInstance稍微计算了一下当前手里的货,然后记下了MainWndProc实际地址,但是先不使用它。
当系统需要使用这个MainWndProc的时候,FObjectInstance正式出场,它先自己做了1件事情:CALL Offset(其中这个offset就是刚才提前算好的数据),找到了它的管家Block
这个管家说,你先POP ECX,然后就JMP 一下找StdWndProc帮忙了。这时栈里的数据第一项仍然是HANDLE

现在的问题是,StdWndProc很容易找到,但POP ECX还是有点奇怪。
就是CALL OFFSET的下一句是Method,也就是MainWndProc的第一条语句。在CALL调用之前,必须将这个地址(不是语句)压栈。这样POP ECX就可以弹出了。
-- 当然由于在Call这之前会将下一条指令入栈,所以这里弹出的就是指向对象方法的指针。
POP ECX,使得 ECX = 注意是对象方法的指针。我感觉:这并不是什么Self的指针。但是可以通过[ECX+4]找到Self指针

MOV EDX,ESP // 在准备参数(保存栈顶值),将堆栈中构造的记录TMessage指针传递给EDX,也就是MainWndProc的第二个参数。而且因为是var类型,所以直接传递参数的地址值到寄存器中,也是正确的,而不像没有var的情况下会把参数本身传递到寄存器中。
MOV EAX,[ECX].Longint[4] // 准备参数,([ECX+4]是什么?就是Self),也就是MainWndProc的第一个参数。为什么说[ECX+4]是Self?因为ECX是MainWndProc函数的地址,可是这个函数第一件要做的事情,就是把不带var的形参复制一遍(编译器替它做的,或者说提前插入的),MainWndProc是register调用,所以顺序是从左到右复制(stdcall会从右到左复制吗?),所以MainWndProc的真正第一条语句就是定义局部变量并赋值为Self的地址。问题又来了,这是定义变量并赋值Self值的语句,不是Self本身的值。

Good process review it again, the Windows callback what is it? In fact, is to go to and implementation of a dynamically generated code: First, the implementation of the Call OFFSET offset turn to the implementation Pop ECX course, will Call before the next instruction is pushed onto the stack, so the pop-up is to point to the object's methods pointer. Next is to execute jmp [StdWndProc], in which the stack structure the record TMessage pointer is assigned to the EDX to combine TMethod According to the above explanation to understand, it is easy to understand
MOV EAX, [ECX]. Longint [4]; passing the Self pointer to EAX class Self pointer is pointing to the VMT entry address
CALL [ECX] Pointer; call MainWndProc methods

http://www.programdevelop.com/2823252/

Good process review it again, the Windows callback what is it? In fact, is to go to and implementation of a dynamically generated code: First, the implementation of the Call OFFSET offset turn to the implementation Pop ECX course, will Call before the next instruction is pushed onto the stack, so the pop-up is to point to the object's methods pointer. Next is to execute jmp [StdWndProc], in which the stack structure the record TMessage pointer is assigned to the EDX to combine TMethod According to the above explanation to understand, it is easy to understand
MOV EAX, [ECX]. Longint [4]; passing the Self pointer to EAX class Self pointer is pointing to the VMT entry address
CALL [ECX] Pointer; call MainWndProc methods

--------------------------------------------------------------------

StdTimerProc和MakeTimerObjectInstance

http://*.com/questions/18991697/setwindowshookex-inside-thread-instability

--------------------------------------------------------------------

http://www.lebeausoftware.org/articles/bcbj_vol12_num5.1.pdf

--------------------------------------------------------------------

TWndMethod 是一种过程类型,它指向一个接收 TMessage 类型参数的过程,但它不是一般的静态过程,它是对象相关(object related)的。TWndMethod 在内存中存储为一个指向过程的指针和一个对象的指针,所以占用8个字节。

TMessage 中并没有窗口句柄,因为这个句柄已经在窗口创建之后保存在 TWinControl.Handle 之中

http://blog.163.com/as_liaokun/blog/static/6492896120092514029260

MakeObjectInstance 在内存中生成了一小段汇编代码,这段代码的内容就是一个标准的窗口过程。这段汇编代码中同时存储了两个参数,一个是 MainWndProc 的地址,一个是 Self (对象的地址)。这段汇编代码的功能就是使用 Self 参数调用 TWinControl.MainWndProc 函数。

这样,如果 TWinControl 对象所创建的窗口收到消息后(形象的说法),会被 Windows 回调 TWinControl.FObjectInstance,而 FObjectInstance 会呼叫该对象的 TWinControl.MainWndProc 函数。就这样 VCL 完成了对象的消息处理过程与 Windows 要求的回调函数格式差异的转换。注意,在转换过程中,Windows 回调时传递进来的第一个参数 HWND 被抛弃了。因此 Delphi 的组件必须使用 TWinControl.Handle (或 protected 中的 WindowHandle) 来得到这个参数。Windows 回调函数需要传回的返回值也被替换为 TMessage 结构中的最后一个字段 Result。

// Windows 准备回调

Windows 准备回调 TWinControl.FObjectInstance 前在堆栈中设置参数:

push LPARAM

push WPARAM

push UINT

push HWND

push (eip.Next)             ; 把Windows 回调前下一条语句的地址

; 保存在堆栈中

jmp FObjectInstance.Code    ; 调用 TWinControl.FObjectInstance

FObjectInstance.Code 只有一句 call 指令:

call ObjectInstance.offset

push eip.Next

jmp InstanceBlock.Code      ; 调用 InstanceBlock.Code

InstanceBlock.Code:

pop ecx                     ; 将 eip.Next 的值存入 ecx, 用于

; 取 @MainWndProc 和 Self

jmp StdWndProc              ; 跳转至 StdWndProc

http://blog.163.com/as_liaokun/blog/static/6492896120092513932753/

-----------------------------------------------------------------------

替换后窗口过程入口的代码应该如下: 
   Call Near Ptr @StdWndProc 
   //这将使后面TMethod的入口指针入栈 
   stdWndProc: 
     Pop ECX //因此ECX 中是TMethod 的入口 
谢谢各位的参与!

http://www.yourdelphi.com/topic_33512_5a95.htm

-----------------------------------------------------------------------

MakeObjectInstance通过StdWndProc过程实现了两个功能:
⑴将窗口过程(即窗口的回调函数)转换为对象方法;
⑵将Windows的TMsg消息结构转换Delphi的TMessage消息结构;

XOR EAX,EAX

PUSH EAX

PUSH LParam

PUSH WParam

PUSH Message

以上五条命令表示将Windows的TMsg消息结构转换Delphi的TMessage消息结构;

http://ymg97526.blog.163.com/blog/static/17365816020134392837997/