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 |