Matthew Curland's VB function pointer call

zhaozj2021-02-17  57

Introduction to Matthew Curland: Visual Studio develops group members, participated in the development of VB's IntelliSense and Object Browser. He is a senior expert in VB, which has a very deep research on VB, which is a VB master. "Advanced Visual Basice" is a good book to elaborate VB advanced programming skills. This article "Call Function Pointers" in February 2000, "Visual Basic Program) (VB Programmer Month), this is one of his wings, the same name in Chapter 11 and this article in his book, This article should be the essence of this chapter.

This article is recommended because it integrates many technologies in VB. We can see the profound understanding of the Matt master, and the comprehensive application of the technology is reflecting his deep skill.

This article original: http://www.devx.com/premier/mgznarch/vbpj/2000/02FEB00/MC0200/MC0200.ASP (To first register into premier users) This article is supported code: http://www.devx.com/ Free / Mgznarch / VBPJ / CODE / 2000 / 02FEB00 / VB0002MC_P.ZIP

Keywords: Function pointer, COM, object, interface, vtalbe, VB assembly, dynamic DLL call. Level: Advanced Requirements: Understand the VB object programming, understand assembly.

The call function pointer is dynamically inserted into different behavior functions in the code, so that the code has the ability to dynamically change its own behavior.

Author: Matther Curland requirements: use the sample code for this article, you need to Professional or Enterprise Edition of VB5 or VB6. From Visual Basic 5.0, the Basic language introduces an important feature: AddRessof operator. This operator allows VB programmers to experience the pleasure to send their own function pointers. For example, we can get a list of system fonts in VB, which we can use standard API calls for subclass. In a word, we can finally use the Win32 API as you are in the document. However, this new toy can only bring us a short pleasure because this gift is incomplete. We can send a function pointer, but no one can send the function pointer to us. In fact, we can't even give us a function of the function, which makes us unable to experience the real fun of the gift (the translator: Oh, the light greece can't get rid of it. " Addressof let us see a corner of the vast world, but VB does not allow us to fully explore it, because VB does not let us call function pointers, we can only provide function pointers (translator: you can give the function pointer to the API , Then let the API call to the function pointer to complete the function of the function pointer call, but this still wants to give the gift to others). In fact, we can implement the function of calling the function pointer, we can manually turn a VTable binding call to the COM interface into a function pointer call. The most wonderful thing is that we can write the code for calling the modified function pointer in pure VB without any auxiliary DLL.

Tell the compiler function pointer what is like, is the key to enable VB to call any function. Get the parameter type and return value type to the VB compiler, allow the compiler to compile our function call into our program, so that the program will know how to deactify the function at runtime. After the program is compiled, a function is a series of compiletable characteristics in memory, and the implementation of our program is performed through the CPU. Call a function pointer, first requiring a program to get a pointer to this functional zonal stream, and then transfer the current command pointer (the IP register of the X86 assembly) through the X86 assembly instruction CALL. After the function is complete, return to the program that calls this function with the RET instruction to continue. I will mention the method you will mention below, using VB's own function call mode, so let me first explain how VB is to implement function calls. Three function pointers inside the VB, but in essence, the method of calling them is the same regardless of how VB is positioned. The VB compiler must know the accurate function prototype to generate the code of the call function.

The first class, the most common function pointer type is the normal pointer used by VB to call the function, which is defined in the standard module (or the friend function in the class module and the private function). When calling a friend function and a private function, call the instruction to locate an offset address of the current command pointer, or first jump to a lookup table recording the function location, then jump to the function (Translator: ie first "CALL The absolute address "jumps to a jump table, each entry in the table is a" JMP "to function). These functions are in the same project, and the coupon always links all the modules together, so it always knows where to find the VB internal function, so when the transfer controls the internal function, it is very small. of.

VB's call to certain function pointers is more difficult for the other two types of function pointers, and VB must make additional work at runtime. The second class, VB calls a method in a COM object interface. We may think that the work of establishing a COM object is quite complicated. If you completely use VB to build all components of your COM, but in fact is not the case. According to the binary standard of COM, a COM object is a pointer. This pointer points to a structure, the first element of this particular structure is a pointer to the array of function pointer. The first three pointers in this function pointer (also called virtual function table, referred to as vtable) must be standard QueryInterface, addRef, release function. The functions next to the VTable conform to the function definition in the given COM object interface definition (see Figure 1)

Figure 1: How does the function pointer agent work? Click Here

When VB calls a method or attribute of a COM object through a variable of an object type, this variable is placed in this variable. When VB is to locate the function, first get the pointer to the VTALBE through the first element referenced by COM, and then locate the function pointer in the vTable. For a vTable call, the compiler provides the offset of the COM reference and function pointer in the vTable. This allows the function pointer to be dynamically selected at runtime. This two-way indirect way - two pointers must be calculated (translation: pointing to VTALBE's pointer and function pointer in vtable) must be determined at runtime) - Make VTable calls slower than the same project Many, because the direct call does not require any pointers that can be performed at runtime.

VB Treats the public method of the class in the same engineering and the method of treating the external COM object, you need to find vTable, which is why you call a friend function in the same object will be much more reasonable than calling a public function. . However, looking for VTable is the basis of COM, which allows VB to use COM objects loaded from the external library, but also the implementation of the programming concept like IMPLEMENTS. Dynamic load cannot be achieved by static linkages, looking for VTABLE spending is the cost of using dynamic loads must pay. The post-binding calls performed by the Object type variable are different from the VTABLE binding call. Of course, this difference is not that Vb is useless, this difference is because of the second binding call VB uses different VTABLE. When the later binding call is performed, the compiler calls GetIDSOFNEMES and INVOKE of the IDispatch interface. This requires VTABLE calls and quite a few parameters, so this is very slow, and you must constantly locate the invoke, you can call to real function pointers through type information (translator: truly slow cause or invoke Parameter adjustment. When you have interface type library information for the corresponding object, VB will make another post-binding -dispid binding, which only needs to call GetIDSOFNemes when accessing objects, to get all properties and methods of DISPID The future call only needs to be a VTALBE call for Invoke, but because Invoke is slow, DISPID binding is not much better than the normal post-binding.). Undoubtedly, when calling the COM object in the same thread, the later binding will be slower than the VTALBE binding (translator: slow hundreds of times in the same line. Due to the inter-boundary distribution overhead, cross-thread, Cross-process, cross-machine, two binding methods will be getting smaller and smaller)

Third categories, use the DECLARE statement to use the function pointer. Declare allows VB to dynamically load specific DLLs through the LoadLibraray API and get a specific function pointer in DLL through the getProcAddress API and function name (or function alias). Declare the function pointer in the type library is loaded by the Import Table when the program is loaded, and the function pointer declared through the declare statement is to be loaded when this function is first called (translator: this There are advantages and disadvantages in two ways. Use Declare to load when calling, one of the VB runtime, using simple, two to the DLL that needs to be loaded, can be handled by error capture at runtime. Type library one-time load, one is to increase load time, and the second is that the program cannot start when the corresponding DLL can not find the time, but the Type library calls API can bypass the DLL load process of VB runtime dynamics. This is necessary at some point).

Dynamic Specify Function Points Whether it is Declare or a library, the way the VB call function pointer is the same as the function is loaded. The pointer has been loaded due to the previous call, so the second call will be faster, and the speed is close to the function of calling the static link. The DECLARE statement is the most natural way to call dynamically loaded function pointers in VB. However, the function pointer is determined by VB instead of being specified (the translator: this is true translation, it should be: function pointer can only specify before compilation, by VB to load, not specified by us at runtime Dynamically loaded function pointer), so we cannot use a Declare statement to call any function pointer. The limit of the DECLARE statement allows us to load only the functions specified by the lib and alias words when designing.

Here, I have explained how VB is to call your function pointer. During the function of the VB itself, it should be implemented through the tools provided by VB itself (translator: It seems that the author matt is a VB purely supporter). Static coupling does not need to consider - If you like to modify the PE file header, please pay attention (the translator: About the method of modifying the peer to the hook input function, in February 1998 MSJ column bugslayer, John Robbins master with pure VB implements hookimportedfunctionsByname, but it is used to call the function pointer that is a killing chicken with a cow knife. We cannot specify the function pointer statically, so the declare statement is not considered. However, we can use loadLibaray and getProcadDress in VB to get the function pointer from the external DLL, just like Declare to do it for us. The VTable call is the only way to call the VB from the binding function. Our task is to build a structure that meets the COM binary standard, and then put this manually established COM object in a variable of an object type, then call manually established VTABLE ports. By calling the function in this VTABLE, you can directly process the function pointer to call. I call this object for FunctionDelegotor (Function Agent). This method requires us to solve three unique problems. First, vtalbe calls with additional parameters (this pointer), we don't want to pass it to our function pointer. So we need a generic proxy function to handle this extra Than the pointer before you can call. Second, we need to build a COM object with this proxy function in vTable. Third, we need an interface definition to let the VB compiler look like our function pointer. Interface definitions should also include function prototypes in vtable, and the proxy function is the same in the object vtable (the translator: When calling the function pointer through the interface, only this will allow the proxy function to process the function parameters The THIS pointer in the stack).

We can use the assembly code to write a proxy function (translator: It is really easy for the author matt, because he has a relatively deep research on the assembly code inserted in the line in VB. In fact, the author is easy to relatively From the Alpha platform). In the Intel platform, all parameters that pass to COM objects or standard API calls are passed through the stack. Unfortunately, the VB of the Alpha platform is not like this. It does not provide a simple method to write assembly code for the same function (translation: Alpha platform is a RISC thin instruction set system, its parameters pass multiple direct use registers It is necessary to handmade the assembly code on this platform. From his directory, you know that he is specifically taken out in the book to introduce the assembly code under the Alpha platform).

As long as we know what the stack is like, we can clearly know what the assembly code needs to do. VB only supports functions that meet the StdCall call specification. This call specification is always in the stack from the right direction, and is the cleaning of the stack by the caller. The obligation to clean up is nothing to do with this article, but the order of stack is very important. In particular, it is important to note that the THIS pointer in the COM class (referred to in the VB class), which is always stack as the leftmost parameters. When the function is called, the function returns the address (where the function returns the program continues to execute) is also pressed into the stack by the Call instruction itself. Before any COM interface output function is executed, the stack looks as follows:

Parameter N (Nth Parameters, Rayed Parameters) ... Parameter 2Parameter 1 (1st Parameters) This Pointer (Hidden this pointer is the most left-front parameter) Return Address (return address), but we just want Call the function pointer does not require a hidden associated THIS pointer. Calling a function that meets the VTable call but does not have additional parameters, you need to squeeze the THIS pointer from the stack before you can transfer the control to the target function pointer. The benefits of letting the THIS pointer in the stack are because it points to the structure. Consider we define a structure, its second member is a function pointer. The offset of this member is from 4 bytes. Then point this function to squeeze out and call it through the proxy function as follows:

; Pop up the return address to the temporary ECX register, and then restore it later. POP ECX

; From the stack, the THIS pointer (the translation: is the base address of the back jump) POP EAX

Re-return the return address stack in the ECX register; so that the function pointer is called, know where to return to PUSH ECX

Transfer control to function pointer; it offers 4 bytes after the THIS pointer. JMP DWORD PTR [EAX 4]

6 bytes of these four instructions are required: 59 58 51 FF 60 04. We will make up two int3 instructions (CC CC) to make 8 bytes, which is just in a VB's Currency variable. Such a Currency variable will be placed in the address of the Magic Number - 368956918007638.6215 @ - This Currency variable is a function pointer to the proxy function. This proxy function is squeezed off the THIS pointer and can jump to any function without considering the parameters of the function. That is to say, we can use the same assembly code to proxy any function pointer. We now need a vTable to include this pointer to the word stream, which is actually a function. (Translator: That is, using a VTABLE's entry contains a proxy function pointer). Use the proxy function to use a structure, which offset 4 bytes is the function pointer we want to call. We also need it to shift 0 bytes of a pointer to the vTable, so that this structure is like a COM object, only this VB can call the function in the VTABLE. We don't have to assign memory in the stack for a simple function pointer; in contrast, we only need to declare a variable of a FunctionDelegator structure in a place to call the code. Although we offer AddRef and Release functions, they don't do anything, but they are only moving VB (translator: VB she strictly tracks our objects. Whenever we add a reference to our object, She will call the addRef in our object to accurately calculate the number of times the object is referenced; whenever we are a reference and object break up, she will call Release to notify us to reduce the reference count. VB she did In order to break up all our references and objects, the object is cleanly abandoned in memory. In order to move the habit of VB, even if we hand-established objects do not dynamically allocate memory, our objects must also provide AddRef and RELEASE. So the fourth VTABLE entry is a pointer to the assembly code of the proxy function. The function agent's code declares a UDT to include a VTALBE array pointer. (Code see Listing1)

Convert structure to a COM object When we pass a pointer to the legitimate VTable to the FunctionDelegator structure, copy this structure into an object variable, this structure is a legitimate COM object. This object's QueryInterface function believes that the function prototype of the fourth entry of the interface Vtalbe we required is always consistent with the function pointer. If the required interface is not supported, the QI function typically returns an E_NOIInterface error. This error status is in VB, which is incorrect in the type of stopped on the SET statement. This trust design of the FunctionDelegator object requires that we must guarantee type security, we will never ask this object to request an interface that does not comply with the function pointer. If we destroy this rule, the punishment for us will be collapsed instead of the type does not match the error (the translator: To experience this penalty, you can try to return the interface in the Listing1 code to any interface in VB To reference, such as using Shape, due to its fourth interface definition does not match, crash). The VTALBE of the FunctionDelegator does not count any reference count, so we don't have to write any Tear-Down (severe error handling) or memory release code. When the stack is in its scope (Translator: SCOPE here means the variable range of the FunctionDelegator object variable, that is, the declaration and the use of its process-level or module level range), the memory used by the COM object will automatically start from the stack Clear, which means that the COM object returned by InitDelegator must be destroyed before the structure is destroyed (or). There is another step before VB can call the proxy function: We must define an interface for the function pointer we want to call. By using the mktylib tool to generate an object definition language (ODL) file, we can do this very easily. Although mktylib.exe is an official functional simplified version of MIDL.exe, mktylib.exe is relatively easier when we want to generate a strict type library used by VB. Moreover, it is different from MIDL.EXE, mktylib.exe it is sold with separate VB products. Our interface definition must inherit from IunkNown and have an additional function. When we only use ODL to treat, we can avoid OLE automation to write unnecessary registration keys in the HKCR / Interface primary key in the registry. Although our Qi function ignores UUID, it still needs us to build type library. (Translator: Although you can generate components containing type libraries through ActiveX projects, you can generate type libraries without external tools, but all components in VB support OLE automation, they must register key values ​​in the registry More importantly, the interfaces generated by VB have inherited from Idispatch. Its vTable does not meet the requirements of this article. If you don't want to use the object definition language, you must modify the realization of the agent function with more pure VB. Because inheriting to idispatch, we can only place a representation function pointer in the eighth entrance of VTable. Although this practice is feasible, it is very complicated because it is necessary to handle the idispatch that can be moved to vb, and this is never like this This article is so simple to establish an iUnknown interface. Although it is possible, this bend is around it)

As an example, three functions are defined here. The first is a comparison function prototype of the standard in the sort algorithm. The second function pointer call can return COM HRESULT error code, such as DllRegisterServer. The third is a function that does not return value without the parameters. We can join the function declaration in accordance with your needs. Save the funcdecl.odl files we modified, and perform Mktylib FuncDecl.odl, then join the reference to our project. (See the ODL in Listing2) We can see that by calling the following pair of functions, we can call the function pointer in real time, and for a long time, for the VB programmer, I want to use this pair function. Possible, this is a function of DllregisterServer and DllunregisterServer. By accessing these two standard ActiveX DLLs and OCX entry functions, our EXE can be positioned and registered with their own components in accordance with their own needs (translator: This technology is still quite value. Although the regrr32 can be called via the shell statement. EXE to register components, but it only supports standard portals: DllRegisterServer and Dllunregister, and use the technology here, we can call non-standard portals, change two output functions in the ATL project, we are in VB It can still be registered, so simple operation can function as a certain protection component). For such an external function, we are called through LoadLibrary and getProcAddress calls to get the function pointer from the external DLL and move the function pointer to the FunctionDelegator structure so that we can call this function pointer itself. (See Listing3)

Use the function pointer to sort (translator: Here the original use of how to demonstrate how to sort by the method of the function pointer callback. Only the function pointer to the function pointer is similar to the Listing 3. Because these paragraphs are omitted here.)

We can use this call function pointer technology in many ways. For example, we can dynamically change the behavior of a certain code by inserting functions with different behavior at runtime. We can also achieve Type Casting in VB through this technology (translator: get a variable without a variable through Varptr, then use this pointer as a parameter, pass this pointer to different type conversion functions Mandatory type conversion can be implemented by pointer and call it. I can't list all possible applications, but here I will demonstrate a small program.

We often want to jump to the debugger while commissioning the compiled VB component. The standard method is to run the int3 command. At this time, a system exception dialog box will make us choose whether to start the debugger or the procedure of the collapse. We need to run the function with two assembly instructions: Break (int3) and return (re-ret). The corresponding ASM instruction is CC and C3. With the following code to implement such a FunctionDelegator: Dim FDVoid As FunctionDelegator Dim CallVoid As ICallVoid Dim Int3Ret As Integer Int3Ret = & HC3CC Set CallVoid = InitDelegator (_ FDVoid, VarPtr (Int3Ret)) 'interrupt and enters the debugger CallVoid.Void

In-line assembly (line assembly) code in VB provides unlimited possibilities for VB expressive (translator: actually there is very different compilation in this line in C, we can only insert machine code) , I think this is called in-line machine code in in-line machine code more appropriate). The function we demonstrated here is actually the same as the functionality of DebugBreak this API (translator: "Translator is not as good as DEBUGBREAK), but it is not so simple to implement other features. If we need more bytes, you can use a long or currency array to extract the throttle and use Varptr to get a pointer to the 0th element of the array as a function pointer. (Full text) Listing 1 This code converts a FunctionDelegator into a COM object that supports a specific function pointer. This is a special COM object because it does not require any memory allocation and the request for our interface is always blindly cooperated. The requested only correct interface is our responsibility.

'THE MAGIC NUMBERPRIVATE CDELEGATEASM_AS currency = -368956918007638.6215 @

'The auxiliary function of the use of Private Declare Sub CopyMemory _ lib "kernel32" Alias ​​"RTLMOVEMEMORY" _ (PDEST AS Any, PSRC As Any, Byval Bytelen As Long)

Private M_DelegateAsm As Currency

'vTable Type Declaration Private Type DelegatorVTables' Okqi Vtable In 0 To 3, Failqi VTable In 4 To 7 vTable (7) As longend Type

Private M_VTables As DelegatorVTables

'Pointing to vTable's pointer, success Qiprivate M_PVTableokqi As Long

'Pointing to vTable pointer, failed qiprivate m_pvtablefailqi as long

'Function Pointer Agent Structure Declaration Public Type FunctionDelegator PVTable As Long' this Has To Stay At Offset 0 Pfn As Long 'this Has To stay at Offset 4nd Type

'Initialization FunctionDelegator structure, and a pointer to it' as a COM object returns .Public Function InitDelegator (_ Delegator As FunctionDelegator, _ Optional ByVal pfn As Long) As IUnknown 'first access initialization vTable If m_pVTableOKQI = 0 Then InitVTables WITH DELEGATOR. PVTable = m_pvtableokqi .pfn = Pfn End with CopyMemory InitDelegator, Varptr (Delegator), 4END Function

'Initialization vTablePrivate Sub InitVTables () Dim pAddRefRelease As Long With m_VTables .VTable (0) = _ FuncAddr (AddressOf QueryInterfaceOK) .VTable (4) = _ FuncAddr (AddressOf QueryInterfaceFail) pAddRefRelease = FuncAddr (AddressOf AddRefRelease) .VTable (1) = pAddRefRelease .VTable (5) = pAddRefRelease .VTable (2) = pAddRefRelease .VTable (6) = pAddRefRelease m_DelegateASM = cDelegateASM .VTable (3) = VarPtr (m_DelegateASM) .VTable (7) = .VTable (3) m_pVTableOKQI = VarPtr ( .Vtable (0)) m_pvtablefailqi = varptr (.vtable (4)) End welhend sub 'success qiprivate function queryinterfaceok (_ this as functiondeelegator, _ riid as long, pvobj asse line) as long' to the first request always blindly blindly Cooperation pvobj = varptr (this) 'is swapped into failure vtable, only when the call function pointer returns HRESULT error code', of course, it is always safer. This.pvtable = m_PVTABLEFAILQIEND FUNCTION

Private function addRefrelease (_ byval this as long) nothing does not do, no reference count. END FUNCTION

'Failed Qiprivate Function QueryinterfaceFail (_ Byval this as long, _ riid as long, pvobj asse line) as long' said to any request: "No" pvobj = 0 queryinterfacefail = & h80004002 'E_NOIINTERFACEEND FUNCTION

'Return Function Pointer Auxiliary Function PRIVATE FUNCADDR (Byval Pfn As Long) As long Funcaddr = Pfnend Function

Translator: The above code has been modified after the original text has been published, so the original text does not mention why the above code requires two different VTABLE. Matt explains this reason in the ReadMe file of the updated sample code. I will submit this reason as follows: This is because when the modified function pointer needs to return HRESULT error code, VB will request a reference to the object to the object again to the object again. However, since QI in the original code fully uses the trust of blind cooperation, it always returns to the object itself interface pointer, even if it does not support the required interface. Since ISUPPORTERRORINFO is not supported, ISUPPORTERRORINFO is not supported, the program will crash when VB tries to collect error messages when the VB is trying to collect error messages. The solution is to provide two vTable. When the QI in the initialized VTABLE is called, it takes trust mode to return the interface pointer and exchange VTABLE containing failure Qi before returning. The QI of this next access will be a failure QI, and the failure QI rejects all interface requests, which is effective to block the subsequent QI request, including the request for iSupp PorterrorInfo. In the following list, we can see that once we increase the reference, there will be types that do not match errors. There is also a bug on the processing of ERR objects, that is, when VB requests an ISUPPORTERRORINFO interface with QI to fail, ERR objects always reserve references to this object. Since our VTALBE will release the ERR object, there is a pended reference in the ERR object, and the program will crash when the ERR object is released. The solution is to trigger a new error with Err.raise before the end of the program. Specific practice, see source code. Listing 2 is used to tell VB to compile how to call our function pointer's external ODL file. Without the description of this interface, although we can still generate a COM object to the correct function pointer, there is no way to call the function in the vTable.

[UUID (57EC3F60-5425-11D3-AB5C-D41203C10000), Helpstring ("Function Pointer Declarations"), LCID (0x0), Version (1.0)] library funcdecllib {importlib ("stdole2.tlb"); [UUID (57ec3f61- 5425-11D3-AB5C-D41203C10000), ODL] Interface iCAllCompare: iUnknown {long compare ([in] long elem1, [in] long elem2);} [507ec3f62-5425-11d3-ab5c-d41203c10000), ODL] Interface Iunknown {HRESULT CALL ();} [UUID (57ec3f63-5425-11d3-ab5c-d41203c10000), ODL] Interface iCallvoid: iunknown {void void ();}}

Listing 3 In order to achieve standard ActiveX DLL and OCX registration, we need to load the DLL into memory and find the entry function pointer used to register, and then call this pointer. By using the FunctionDelegator object, we can do the same operation to any DLL.

Private Declare Function LoadLibrary _ Lib "kernel32" Alias ​​"LoadLibraryA" _ (ByVal lpFileName As String) As LongPrivate Declare Function FreeLibrary Lib "kernel32" _ (ByVal hModule As Long) As LongPrivate Declare Function GetProcAddress Lib "kernel32" _ (ByVal hModule As Long, _ Byval LPPROCNAME AS STRING) AS Longpublic Sub DllRegisterServer (Dllname As String) CallDllRegentry Dllname, "DLLREGISTERSERVER" End Sub

Public Sub DllunRegisterServer (Dllname As String) CallDllRegentry Dllname, "DllunregisterServer" End Sub

Private Sub CallDllRegEntry (DllName As String, _ EntryPoint As String) Dim pCall As ICallHRESULTNoParamsDim Delegator As FunctionDelegatorDim hMod As LongDim pfn As Long 'Load the dll hMod = LoadLibrary (DllName) If hMod = 0 Then Err.Raise 5

'Error Trap to make Sure We free the library on error Goto ERROR

'Find function pointer PFN = getProcaddress (hmod, entrypoint) if Pfn = 0 Then Err.raise 5

'Initialization and get a reference to the agent COM object. Set PCall = InitDelegator (Delegator, PFN)

'Call function pointer PCALL.CALL

'' ***** Translator: Cancel Note The following section can come to experience the wrong and crash 'set pcall = nothing' DIM PIUN AS IUNKNOWN, PSHAPE2 AS Shape 'Set Piun = InitDelegator (delegator, PFN)' DIM PCallvoid The as ICallvoid 'set pcallvoid = piun' '' type does not match errors, because Qi has been replaced with failed QI. '' Set pshape2 = piun 'set piun = Nothing' set pcallvoid = Nothing '' crash, because interface definitions and function pointers do not match '' set pshape2 = initdelegator (delegator, PFN) '******** ********************************************************

转载请注明原文地址:https://www.9cbs.com/read-28389.html

New Post(0)