Encapsulate the callback function -- construct an entry in the form of a common function (a function without the this pointer in the parameter) for the object method (the function with the this pointer in the parameter ).

Source: Internet
Author: User

File: mfunentry. Bas
Function: encapsulate the callback function -- construct an entry in the form of a common function (a function without the this pointer in the parameter) for the object method (the function with the this pointer in the parameter ).
Author: zyl910
Version: V1.0
Date: 2005-6-24

It is very troublesome to Use callback functions in VB. It must be written in modules and difficult to encapsulate. This module is used to solve this problem.

 

Principle: Structure of the VB object
~~~~~~~~~~~~~~~~~~

Object variable object interface function finger- ━━━ ━━ ━ ┓ ┏ [[[[[── ① 0 (4) the pointer to the interface was → ② 0 (4) * pvtable was → ③ 0 (4) QueryInterface was → │ ━ ━━ ━ 4 4 4 4 4 ┃ 4 (?) Custom Data 1 ┃ 4 (4) addref ┃ →│ │ ┃? (?) Custom Data 2 Release Phase 8 (4) Release Phase → │ release? (?) Custom Data 3 using Objective C (4) gettypeinfocount using-→ │ using? (?) ...... Limit limit 10 (4) gettypeinfo limit → │ limit 14 (4) getidsofnames limit → │ limit 18 (4) invoke functions → │ ┃ 1C ┃ 1C 1C (4) Object User-Defined Function 1 → │ object implementation │ ┃ 20 (4) object User-Defined Function 2 → │ ┃ 24 (?) ...... The object variable of the │ interface was too large and too small to exceed the upper limit.] ━━ ━┓ │ ① 0 (4) the pointer of the interface was → ② 0 (4) * pvtable was → ③? (?) ...... │ ┗ ━ ┛ ┃ ┗ ━━ [[[[[│ ┣ ━━ ━ [Interface 2] ━━ ━ ┫ ┏ [[ [interface 2] ━━ ┓ │ ┃ ...... Too many? (?) ...... ┃ ╰ ── ╯ ① = Varptr (object variable) Address ② = objptr (object variable) '// This pointer address ③: it can only be obtained indirectly through copymemory: call copymemory (lngaddr, byval objptr (object variable), 4) when the implements statement is used to inherit multiple interfaces, this pointer of interfaces forms a table, all are stored in the memory area of the object and the interface function pointers of the interfaces are connected to the stack before the function call: when there are too many requests when ESP + 00 then the returned address when there are too many requests when using ESP + 00 then the returned address when using ESP + 04 then this pointer then ESP + 00 then returns the address ┃ ESP + 04 then this pointer then ESP + 08 then userdata then ESP + 04 then parameter 0 then ESP + 08 then parameter 0 then ESP + 0C callback parameter 0 callback ESP + 08 when parameter 1 when ESP + 0C when parameter 1 when ESP + 10 when parameter 1 when ...... Too many ...... Too many ...... Random (). normal function (B ). member functions (c ). stack in function call with userdata parameter: there are two major causes for this problem. EBP + 04 bytes returned address ┃ EBP + 00 bytes old EBP ┃ EBP + 04 bytes returned address ┃ EBP + 08 bytes this pointer ┃ EBP + 04 bytes returned address ┃ EBP + 08 bytes this pointer implements EBP + 0C ┃ userdata ┃ EBP + 08 callback parameter 0 ┃ EBP + 0C callback parameter 0 ┃ EBP + 10 callback parameter 0 ┃ EBP + 0C callback parameter 1 ┃ EBP + 10 callback parameter 1 javasebp + 14 callback parameter 1 callback ...... Too many ...... Too many ...... Struct (a). Common Function (B). member function (c). Return Value of the member function with the userdata parameter ~~~~~~~~~~~~~~~~ For the process (VB syntax): Sub proc (byval N as long) VB is actually processed as (IDL syntax): hresult proc ([in] int N ); after the this pointer is added, it is actually changed to (C syntax): hresult proc (void * This, int N); the return value of the function is placed in eax, while the process caller ignores the returned value, therefore, this operation will not cause problems. For functions (VB syntax): function proc (byval N as long) as longvb is actually processed as (IDL syntax): hresult proc ([in] int N, [out, retval] int *); after adding this pointer, it actually becomes (C syntax): hresult proc (void * This, [in] int N, [out, retval] int *); because a return value parameter is added, the stack is unbalanced, so illegal operations may occur.

Return values of member functions
~~~~~~~~~~~~~~~~

For the process (VB syntax ):
Sub proc (byval N as long)

Actually processed by VB (IDL syntax ):
Hresult proc ([in] int N );

After the this pointer is added, it actually becomes (C syntax ):
Hresult proc (void * This, int N );

The function return value is placed in eax, while the process caller ignores the returned value, so this operation will not cause problems.

 

For functions (VB syntax ):
Function proc (byval N as long) as long

Actually processed by VB (IDL syntax ):
Hresult proc ([in] int N, [out, retval] int *);

After the this pointer is added, it actually becomes (C syntax ):
Hresult proc (void * This, [in] int N, [out, retval] int *);

Because a return value parameter is added, the stack is unbalanced, so illegal operations may occur.

 

Module code
~~~~~~~~

Mfunentry. Bas
Option explicit module: function entry function: encapsulate callback functions -- Object method (a function with the this pointer in the parameter) construct an entry in the form of a common function (a function without the this pointer in the parameter) 'Author: zyl910' version: v1.0' Date: 'Global compilation constant (Set "Conditional compilation Parameters" in the Project Properties dialog box ")'~~~~~~~~~~~~ 'Isrelease: whether the release mode is used. If it is in release mode, the program does not check whether the fef_checkide constant 'apitypelib: whether there is an api library. 'Compile constants in this module '~~~~~~~~~~~~~~ '# Const debugasm = true' indicates whether int3 is added to the Command (for debugging convenience) #################################### ############## 'writing process '~~~~~~~~ ''Writing callback functions using the addressof operator is too troublesome. You also need to write code in the module, which is not in line with the spirit of object-oriented programming. In addition, some callback 'functions cannot be interrupted in the debugging environment of vb ide, such as subclass, Hook, and timer. ''I have tried many methods to encapsulate callback functions, but they are not ideal. For example, the previous 'attempt: writing functions based on subclasses, hooks, timers, callback functions without return values, and callback functions with 'Return values, which are easy to use. Besides, the Code is embedded with the assembly code, which is very efficient. However, there is a problem: All functions are separated, not only 'encoding is complex, but also the size is huge. For example, for a subclass-based class, each object's embedded 'assembly code requires 519 bytes of memory, plus the memory usage of the object itself, 'a lot of memory. ''So I want to improve this: each function entry has only a small amount of Jump code and some ''parameters. The jump code is used to jump to a common entry function. The common entry function adjusts the function call protocol based on the number of parameters to call the corresponding processing function (for example, you can add the this pointer to the parameter column to call the object method) ''then, considering that the object is allocated from the heap, the speed is slow, and the VB object occupies a larger memory ratio. So we finally decided to use struct as the function entry. 'A function entry is a functionentryblock struct variable. The 'struct is composed of the functionentryheader structure and the functionentrydata structure composite. Use the funentryfillblock function to initialize the structure. The 'functionentryheader 'structure is located in the header of the functionentryblock structure. 'The content of this struct is the compilation jump code. Since the last line of the assembly code is the 'call instruction, the address of the next instruction -- functionentrydata struct' is pushed to the stack, so that the parameter is passed. Use the funentryfillheader function to bind the structure to the entry function. 'Usage method '~~~~~~~~ ''1. Use feattachproc to bind the functionentryblock struct to the function you want to call. However, we generally use interfaces to process callback functions. In this way, we can 'implement Object-Oriented Programming: '1. define interfaces. Make the function prototype of the interface consistent with that of the callback function (this pointer is added in reality ). You can use the class module definition of VB or the Type Library definition. 'Only when you use the Type Library definition, note that the function with the returned value must be in the retval format 'to process the returned value. '2. This interface is implemented in the class module (or window module or user-defined control module. '3. Define the functionentryblock struct variable in the appropriate position and use 'feattachiunknow (for interfaces inherited directly from iunknow. This interface can only be declared with a Type Library) or feattachidispatch (for an interface directly inherited from iunknow. This interface can be declared through the VB class module) to fill the struct with embedded 'assembly code and some key data. These functions are very powerful, so in general, you only need to adjust the Fed. fflags field and fed. the pfnoldproc field is required only when the 'fef_checkide flag exists. pfnoldproc field. '4. The entry address is the first address of the FEH member in the functionentryblock structure. 'You can obtain this address through the varptr function. Because Feh is the first field of the structure, you can get the port address in the form of "varptr ([functionentryblock variable. For convenience, feattachproc, feattachiunknow, and 'feattachidispatch both return the entry address. 'Technical information '~~~~~~~~ '1. ebmode function' this function is an export function of vba6. you can call this function to determine whether it is in the debugging mode of VB ide. However, this function cannot be called through the VB statement. This is because when the VB statement is executed, the vb ide is no longer in the interrupt mode, it is in running mode because 'the VB statement needs to be parsed and executed. ''Function prototype: int ebmode (void); 'Return value: '0: After compilation, run '1: Run '2 in VB ide: interrupted in VB ide 'references '~~~~~~~~ '1. matthcw Curland, translated by Tu Xiangyun, advanced Visual Basic programming (Advanced Visual Basic 6: power techniques for everyday programs ). China Power Press, 2001.5 '2. Li Wei, "VCL Architecture Analysis". Electronic Industry Press, book 2004.1 '3. Luo yunbin, 32-bit assembly language programming in windows. Electronic Industry Press, 2002.10 # If apitypelib = 0 then' ## API ############################# ########################' = module ============ ============================================================== private declare function getmodulehandle lib "Kernel32" alias "getmodulehandlea" (byval lpmodulename as string) as longprivate declare function getprocaddress lib "Kernel32" (byval hmodule as long, byval lpprocname as string) as longprivate cons T str_module_vba6 as string = "vba6" Private const str_module_vba5 as string = "vba5" Private const str_ebmode as string = "ebmode" '= memory ======== ========================================================== = private declare sub copymemory lib "Kernel32" alias "rtlmovememory" (destination as any, source as any, byval length as long) # end if '# If apitypelib = 0 then '############################# ########################## #### '= Functionentry ================================ ========== public Enum functionentryflags fef_checkide = 1' whether to check vb ide (by calling pfncheckide ). If it is in release mode (compiling constant isrelease), the program will not check the fef_checkide constant. If it is in debug mode of vb ide, it will try to call pfnoldproc. 'If pfnoldproc does not exist, modify the stack and return it. Fef_structptr = 2' indicates whether to pass the address of the functionentrydata structure. 'If this flag exists, it indicates that the first parameter in the parameter column is the address of the functionentrydata structure, rather than the this pointer. 'The flag is used in some complex designs, so that you can write a forwarding function in the standard module to handle the complexity. Whether the returned value exists in fef_retval = & h80000000. 'Com's return value is in the stack and needs special processing (so this flag is required ). Note that only the simple type can be returned (that is, the returned value can be placed in the eax register ). 'This flag is not required for traditional functions that return results through the eax register. End Enum 'function entry structure header. Storage function jump code public type functionentryheader '16byte asmcodejmp (0 to 3) as long' Assembly jump code end type' standard data of the function entry. Used to call functions correctly. You can add your own project after the cargs project to handle complex situations. Public type functionentrydata '32byte asmcoderet (0 to 7) as byte' stores the return command, because the fflags as long 'flag constant pfncheckide as long' needs to be returned from the function with different parameter numbers to check the VB ide function (this member is automatically set when createfunctionentry () is called) pfnproc as long' function to be called (parameter columns will be adjusted, such as inserting this pointer) pfnoldproc as long' original function (parameter columns will not be adjusted, consistent with the original function prototype) pobjthis as long 'this pointer (Return Value of varptr (object variable) cargs as long' number of parameters (the number of DWORD parameters of the function) end type' function entry block public type fun Ctionentryblock '48byte Feh as functionentryheader fed as functionentrydataend type' note: the struct is adjusted carefully so that it is subject to a 16-byte boundary. '= ASM code: genentry = for the code, see ASM/funentry. asmprivate const asmsize_genentry as long = 168 # If isrelease thenprivate const asm_genentry as string = "Signature Category "# elseprivate const asm_genentry as string =" category Else "# end if 'Note: The release version changes 74 25 (JZ _ no_stopped) at the & H1B position to EB 25 (JMP _ no_stopped) 'You cannot directly put the code in a module-level array. 'Because the space of the module-level array is allocated in the heap, the memory will be released when the process is terminated, resulting in a suspended pointer. 'So the code should be placed in the struct. This is because the module-level struct memory is private type asmcodeblock_genentry code (0 to asmsize_genentry-1) in the static memory zone (data segment not initialized) as byteend typeprivate m_asmcode_genentry as asmcodeblock_genentry '======================== ============================== private m_pfngenentry as long 'address of the universal entry function private m_pfnebmode as long 'Check the VB ide function private sub pvinit () dim hmod as long dim I as long if m_pfngenentry <> 0 Then exit Sub with m_asmcode_genentry 'read command for I = 0 to asmsize_genentry-1. code (I) = cbyte ("& H" & Mid $ (asm_genentry, I * 2 + 1, 2) Next I 'Bind Address m_pfngenentry = varptr (. code (0) end with 'get the address of the ebmode function hmod = getmodulehandle (str_module_vba5) 'vb5? If hmod = 0 then hmod = getmodulehandle (str_module_vba6) 'vb6? If hmod then' is in the debugging mode of VB ide m_pfnebmode = getprocaddress (hmod, str_ebmode) else 'After compilation, execute m_pfnebmode = 0 end if end sub' to fill in the built-in assembly code of the functionentryheader structure public sub fefillheader (byref Feh as functionentryheader, optional byval pfncall as long = 0) 'The default function is m_pfngenentry if pfncall = 0 then if m_pfngenentry = 0 then pvinit pfncall = m_pfngenentry end if 'Fill command with Feh # If debugasm then. asmcodejmp (0) = & h90 9090cc 'CC: INT 3 # Else. asmcodejmp (0) = & h90909090'90: NOP # end if. asmcodejmp (1) = & hb8909090. asmcodejmp (2) = pfncall. asmcodejmp (3) = & hd0ff9090 end with '-- code -- '90 nop' 90 nop' 90 nop' B8 imm32 mov eax, imm32 '90 nop' 90 nop' FF D0 call eaxend sub' fill in the built-in assembly code of the functionentrydata structure public sub fefilldata (byref fed as functionentrydata, optional byval cargs as long =-1) with Fed 'calculates the returned byte if cargs <0 then' indicates using the cargs field cargs = In the struct. cargs Else. cargs = cargs end if cargs = cargs * 4 'convert the number of parameters into bytes if (cargs and & hffff0000) then' beyond the word range err. raise 6 'overflow exit sub end if 'Fill code '61 popad '8b 45 F8 mov eax, dword ptr [ebp-8] 'c9 leave 'c2 34 12 RET 1234 H. asmcoderet (0) = & H61. asmcoderet (1) = & h8b. asmcoderet (2) = & h45. asmcoderet (3) = & hf8. asmcoderet (4) = & hc9. asmcoderet (5) = & hc2. asmcoderet (6) = (Cargs and & HFF ). asmcoderet (7) = (cargs and & hff00 &)/& h100 & 'Fill in the pfncheckide field. pfncheckide = m_pfnebmode end withend sub fill the functionentryblock Structure Embedded Assembly Code (fill the header and data structure) Public sub fefillblock (byref Feb as functionentryblock, optional byval cargs as long =-1) with feb' fill functionentryheader call fefillheader (. feh) 'fill functionentrydata call fefilldata (. fed, cargs) end withend sub 'get the address of the object method function 'parameter: 'Pobj: the address of the object, that is, the return value of objptr 'ioffest: This method is offset in the vtable table. If it is based on iunknow (can only be declared using the Type Library), this value is generally 4. If it is based on idispatch (which can be declared using the VB class module), this value is generally 7. Public Function fegetmethodptr (byval pobj as long, optional byval ioffest as long = 7) As long dim pvtable as long call copymemory (pvtable, byval pobj, 4) Call copymemory (fegetmethodptr, byval (pvtable + ioffest * 4), 4) end function 'Bind the function entry to a function. 'parameter: 'feb: functionentryblock struct address' pobj: object address, both the objptr return value 'pfnproc: function to be called 'cargs: number of parameters in the parameter column' Return Value: Entry address (essentially this struct. feh sub-structure first address) Public Function feattachproc (_ byref Feb as functionentryblock, _ byval pobj as long, _ byval pfnproc as long, _ optional byval cargs as long =-1) as long with feb' fill in functionentryheader call fefillheader (. feh) 'fill functionentrydata call fefilldata (. fed, cargs) 'adjust pointer. fed. pobjthis = pobj. pfnproc = pfnproc end with feattachproc = varptr (. feh) end with end function 'Bind the function entry to the 'parameter: 'feb: functionentryblock struct address 'pobj: object address on the interface pointer (iunknow version, both the objptr return value 'cargs: number of parameters in the parameter column 'offest: This method is offset in the vtable 'Return Value: Entry address (essentially this struct. feh sub-structure first address) Public Function feattachiunknow (_ byref Feb as functionentryblock, _ byval pobj as long, _ optional byval cargs as long =-1, _ optional byval ioffest as long = 4) as long feattachiunknow = feattachproc (Feb, pobj, fegetmethodptr (pobj, ioffest), cargs) end function 'Bind function entry to interface pointer (idispatch) 'parameter: 'feb: the address of the functionentryblock struct is 'pobj: object variable 'cargs: number of parameters in the parameter column 'ioffest: This method offsets 'Return value: Entry address (essentially this struct. feh sub-structure first address) Public Function feattachidispatch (_ byref Feb as functionentryblock, _ byval pobj as object, _ optional byval cargs as long =-1, _ optional byval ioffest as long = 7) as long feattachidispatch = feattachproc (Feb, objptr (pobj), fegetmethodptr (objptr (pobj), ioffest), cargs) end Function
ASM/funentry. ASM
Option Casemap: none; # case sensitive.map; # create 32 bit code. model flat, stdcall; #32 bit memory model. code; stack status before the code is called; esp content; 0 functionentrydata struct address; + 4 original function return address; + 8 original function parameter 1; + C original function parameter 2; ......; Stack status when the code is called; EBP content ;-? All 32-bit registers saved by pushad;-8 function return values;-4 functionentrydata struct address; 0 old EBP; + 4 original function return address; + 8 original function parameter 1; + C parameter 2 of the original function ;......; Stack before calling a common function (non-retval); esp content;-8 function to be called;-4 This pointer or functionentrydata struct address; 0 original function return address; + 4 This pointer or functionentrydata structure address; + 8 original function parameter 1; + C original function parameter 2 ;...... Fef_checkide = 1; Whether to check vb ide (by calling pfncheckide ). If it is in the debugging mode of vb ide, it tries to call pfnoldproc. If pfnoldproc does not exist, modify the stack and return it. Fef_structptr = 2; Whether to pass the address of the functionentrydata structure.; If this flag exists, it indicates that the first parameter in the parameter column is the address of the functionentrydata structure, rather than the this pointer. This flag is used for some complex designs, so that you can write a forwarding function in the standard module to handle complex situations. Fef_retval = 80000000 h; whether the returned value exists. The return value of COM is in the stack and needs special processing (so this flag is required ). Note that only the simple type can be returned (that is, the returned value can be placed in the eax register ). This flag is not required for traditional functions that return results through the eax register. Functionentrydatastrucasmcoderetdq ?; Store the return command because fflagsdd? Is returned from the function with different parameter numbers ?; Constant pfncheckidedd ?; Check the function of vb ide (this member is automatically set when createfunctionentry () is called) pfnprocdd ?; The function to be called (the parameter column will be adjusted, such as inserting this pointer) pfnoldprocdd ?; The original function (No parameter column adjustment, consistent with the original function prototype) pobjthisdd ?; This pointer (Return Value of varptr (object variable) cargsdd ?; Functionentrydataends; asmcoderet field code:; 61 popad; 8B 45 F8 mov eax, dword ptr [ebp-8]; C9 leave; c2 34 12 RET 1234 h; parameter _ reteip $ = 4; return address _ pthis $ = 4; address of this pointer or functionentrydata struct _ arg0 $ = 8; parameter 0; local Variable _ pdata $ =-4; functionentrydata struct address or this pointer (only when the retval function calls the pfnproc function) _ lresult $ =-8; return value. Only the retval function _ pfncall $ =-8; Address of the called function (because popad can only pass data through the stack ). Start: only for non-retval functions and fef_checkide processing; prepare NOP for debugging; restore stack popeax; put the data structure address into the eax register; Save the pushebpmovebp and ESP environment; allocate local variables subesp, 8; all registers into the stack pushad; initialize the local variables movebx, eax; then access the data structure movdword PTR _ pdata $ [EBP], eax through EBX; write the data structure address to the memory xoreax, eax; initialize the returned value movdword PTR _ lresult $ [EBP], eax; check the fef_checkide constant (jump without sign) moveax, [EBX + functionentrydata. fflags] testeax, fef_checkidejz_no_stopped; check the pfncheckide function moveax, [EBX + functionentrydata. pfncheckide] testeax, callback; call the pfncheckide function calleaxcmpeax, 2jne_no_stopped; interrupt mode in the VB ide runtime environment _ is_stopped:; check the pfnoldproc function moveax, [EBX + functionentry. pfnoldproc] testeax, javasptr _ pfncall $ [EBP], eax; put the function address into the stack; call popadmoveax, dword ptr _ pfncall $ [EBP] movesp, ebppopebpjmpeax; no Default handler function _ no_oldproc:; The Returned Code leaeax, [EBX + functionentrydata. asmcoderet] jmpeax; it is not an interruption mode in the VB ide runtime environment _ no_stopped:; check the fef_retval constant (jump with a flag) moveax, [EBX + functionentrydata. fflags] testeax, fef_retvaljnz_is_retval_fun; common functions (no return value or return value can be stored in the eax register) _ is_normal_fun:; Save the address of the called function in the stack movedx, [EBX + Function. pfnproc] movdword PTR _ pfncall $ [EBP], EDX; check the fef_structptr constant (jump with a flag) moveax, [EBX + functionentrydata. fflags] testeax, fef_structptrjnz_call_normal_fun; save this pointer in the stack moveax, [EBX + functionentrydata. pobjthis] movdword PTR _ pdata $ [EBP], eax; call common function _ call_normal_fun:; restore stack popadmovesp, ebppopebp; fill this pointer push [esp]; moveax, dword ptr _ pdata $ [esp] movdword PTR _ pthis $ [esp], eax; call moveax, dword ptr _ pfncall $ [esp] jmpeax; function with COM return value _ is_retval_fun:; press the return value address into Stack leaeax, dword ptr _ lresult $ [EBP] pusheax; adjust ECx to make it equal to the number of Dwords to be copied. Calculate the number of bytes to be copied (eax), the number of double words (ECx) movedx, and [EBX + functionentrydata. cargs] moveax, edxmovcl, 2; now it is a 32-bit code, each parameter occupies 4 bytes -- both shifts left two places shleax, clmovecx, EDX; empty-out stack (using serial commands to copy parameters) subesp, eax; empty-out stack; copy parameter CLD; clear direction bit, so that the address is adding leaesi, dword ptr _ arg0 $ [EBP]; calculation source pointer movedi, esp; Set destination pointer repmovsd; copy; check fef_structptr constant (jump with flag) movedx, EBX; the default value is fef_structptr mode moveax and [EBX + functionentrydata. fflags] testeax, fef_structptrjnz_call_retval_fun; change edX to this pointer movedx, [EBX + functionentrydata. pobjthis] _ call_retval_fun:; call pushedxcalldword PTR [EBX + functionentrydata. pfnproc]; returns leaeax, [EBX + functionentrydata. asmcoderet] jmpeaxend start

 

Package download
~~~~~~~~

Download (note the modified extension)

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.