Re: Unsigned 32 bit
From: Tony Proctor (tony_proctor_at_aimtechnology_NoMoreSPAM_.com)
Date: 09/26/04
- Next message: Hapticz: "Re: Poor VB Programming"
- Previous message: DaveO: "Text on web page form"
- In reply to: Ulrich Korndoerfer: "Re: Unsigned 32 bit"
- Next in thread: Ulrich Korndoerfer: "Re: Unsigned 32 bit"
- Reply: Ulrich Korndoerfer: "Re: Unsigned 32 bit"
- Messages sorted by: [ date ] [ thread ]
Date: Sun, 26 Sep 2004 10:33:13 +0100
Interesting stuff Ulrich. However, it must have been a lot of effort to go
through in researching it, understanding it, and implementing it. As you
say, the benefits for intensive numerical calculations (e.g.
encryption/decryption) would make it worth while. However, for my own
personal usage, I would probably go for the C version, or even the "ignore
errors" version if I was in a big hurry. It comes down to a trade-off
between performance and maintainability (not just for me but for others in
our team)
Tony Proctor
"Ulrich Korndoerfer" <ulrich_wants_nospam@prosource.de> wrote in message
news:4155EC1C.1B54B33B@prosource.de...
> Hi,
>
> Tony Proctor wrote:
> >
> > I agree Ulrich that ignoring errors is still slow, but faster than
using
> > String- or Decimal-based solutions.
>
> This is correct, but one does not have to resort to Strings or Decimal
> based solutions. One can use Currencies or just plain longs like in:
>
> Private Function UAdd(ByVal Base As Long, ByVal Offset As Long) As Long
> UAdd = (((Base Xor &H80000000) + (Offset And &H7FFFFFFF)) Xor
> &H80000000) Or (Offset And &H80000000)
> End Function
>
> This should be faster than using Decimals or Strings (yikes:-))
>
> > I was going to recommend writing a
> > C-based DLL (compiled with _STDCALL of course), and reference it from VB
> > with Declare statements.
> >
> > However, you seem to be suggesting that this would be slower than using
a
> > TypeLib. Is that correct? If so, can you explain a little more?
>
> Ok, here we go.
>
> I have a helper dll called ukVBHLP.dll, written in pure assembly
> language. One could use C too, and in most cases a good compiler, if all
> is set up correct, would produce equivalent or nearly equivalent machine
> code. But one looses transparency and would have to check the produced
> machine code for efficiency as well. So, and because the routines are
> short and simple, I prefer to use assembly language.
>
> This helper dll (a plain standard dll) has the base adress 0x02400000.
> It exports many routines, among them is:
>
>
;***************************************************************************
****
> ; Function : GetAdr
> ; VB signature: Function GetAdr(ByVal Param As Long) As Long or many
> other variations
> ; Purpose : VarPtr like function (code is identical to the VarPtr
> code in MSVBVM60.DLL!)
> ; Returns : Param
> ; Remarks : Use it for getting the adresses of variables of any kind
> and of functions
> ; and methods in a VB module or for type casting
>
;***************************************************************************
****
>
> ALIGN
> GetAdr proc
>
> mov eax, [esp+4] ;returns the address
> retn 4 ;returns and cleans the stack
>
> GetAdr endp
>
> For this helper dll I wrote a typelib in IDL called ukVBHLB.tlb. This
> has an entry
>
> [entry("GetAdr"), helpstring("Returns ...")]
> long _stdcall GetAdrVar([in] void* AnyNonArrVar);
>
> which declares a function GetAdrVar. Its equivalent VB declare is
>
> Private Declare Function GetAdrVar _
> Lib "ukVBHLP" Alias "GetAdr" _
> (ByRef AnyNonArrVar As Any) As Long
>
> Now lets make a very simple VB program, containing one module with one
> method:
>
> Public Sub Main()
> Dim MyVar As Long, AdressOfMyVar1 As Long, AdressOfMyVar2 As Long
>
> AdressOfMyVar1 = GetAdrVar(MyVar)
> AdressOfMyVar2 = VarPtr(MyVar)
> If AdressOfMyVar1 = AdressOfMyVar2 Then Beep
>
> End Sub
>
> where VarPtr is the intrinsic VB function.
>
> First let's reference the typelib in the project and compile it (with
> all advanced options checked, but with the main option "no optimization"
> selected). Then we go disassembling using a debugger. Note that the
> resulting code is strongly dependend on which compile options are used.
> In this case we use "no optimization" because the code produced then is
> easier to follow. If we had used "optimize for speed", we would get
> faster, but different, more complicated code. For the sake of the
> comparison of using a typelib versus using VB declares however the
> former compiler options are sufficient to show the basic differences.
>
> The disassembly of Sub Main is (VB programs are loaded at 0x00400000):
>
> 01: 00401595 8BEC MOV EBP, ESP
> 02: 00401597 6A 0C PUSH 0C
> 03: 00401599 58 POP EAX
> 04: 0040159A E8 01FBFFFF CALL Projekt1.___vbaChkstk
> 05: 0040159F 8D45 FC LEA EAX, [LOCAL.MyVar]
> 06: 004015A2 50 PUSH EAX
> 07: 004015A3 E8 94FBFFFF CALL Projekt1.___vba@001DFC3C
> 08: 004015A8 8945 F8 MOV [LOCAL.AdressOfMyVar1], EAX
> 09: 004015AB 8D45 FC LEA EAX, [LOCAL.MyVar]
> 10: 004015AE 50 PUSH EAX
> 11: 004015AF E8 82FBFFFF CALL Projekt1.@__vba@001DFBE8
> 12: 004015B4 8945 F4 MOV [LOCAL.AdressOfMyVar2], EAX
> 13: 004015B7 8B45 F8 MOV EAX, [LOCAL.AdressOfMyVar1]
> 14: 004015BA 3B45 F4 CMP EAX, [LOCAL.AdressOfMyVar2]
> 15: 004015BD 75 05 JNZ SHORT Projekt1.004015C4
> 16: 004015BF E8 6CFBFFFF CALL Projekt1.@__vba@001DFBB0
> 17: 004015C4 C9 LEAVE
> 18: 004015C5 C3 RETN
>
> Lines 1 to 4 sets up the stack for local variables.
> In lines 5 to 8 "AdressOfMyVar1 = GetAdrVar(MyVar)" is executed.
> In lines 9 to 12 "AdressOfMyVar2 = VarPtr(MyVar)" is executed.
> In lines 13 to 16 "" is executed.
> Line 17 resets the stack and line 18 exits.
>
> Note that the GetAdrVar of the helper dll ukVBHLP is called in exactly
> the same way like the VarPtr function located in the msvbvm60.dll: the
> adress of MyVar is loaded into eax, eax is pushed on the stack, project
> internal adresses are called (labeled ___vba@001DFC3C and
> @__vba@001DFBE8) and then eax (which contains the return value) is
> stored in AdressOfMyVarX.
>
> Both project internal adresses are inside the jump table VB has build.
> This jump table has jumps to the methods of both used dlls, but only to
> those methods which are *used* in the program, not to *all* methods the
> dlls offer.
>
> The relevant part of the jump table is:
>
> 01: 00401136 @__vba@001DFBE8 JMP DWORD PTR
> DS:[<&MSVBVM60.#644>]
> 02: 0040113C ___vba@001DFC3C JMP DWORD PTR
> DS:[<&ukVBHLP.GetAdr>]
>
> Line 1 is a jump to a VB function, Line 2 to a ukVBHLP function. Now
> lets look at these adresses, which reside inside the adress space of the
> dlls (for msvbvm60.dll the base adress is 0x):
>
> The jump from line 1 goes to:
>
> 660E3BBD VarPtr MOV EAX, DWORD PTR SS:[ESP+4]
> 660E3BC1 RETN 4
>
> So, this is already the function to call.
>
> The jump from line 2 goes to another jump table (inside ukVBHLP.dll),
> which does contain jumps to all exported methods of this dll:
>
> 024010FA GetAdr JMP ukVBHLP.GetAdr
>
> Now this is the last jump, it goes to the function:
>
> 02401270 GetAdr MOV EAX, DWORD PTR SS:[ESP+4]
> 02401274 RETN 4
>
> Ok, lets resume:
>
> when using a typelib, the call to the function in the helper dll first
> goes to a jump, this jumps to another jump which jumps to the function.
> To reach the function, we have 1 call and two jumps. VB internal
> functions have 1 call and one jump.
>
> The memory consumed inside the VB program for the necessary housekeeping
> for a call to a ukVBHLP function is 1 entry (6 bytes long) in the
> programs jump table. And memory is consumed only for those functions of
> the dll, which are actually used in the program. If the dll has 100
> functions, of which only 3 are used by the program, only 3 entries in
> the program's jump table would exist.
>
> Now lets change the VB program. We remove the reference to the typelib
> and add an equivalent VB declare:
>
> Private Declare Function GetAdrVar _
> Lib "ukVBHLP" Alias "GetAdr" _
> (ByRef AnyNonArrVar As Any) As Long
>
> We compile using the same settings and disassemble:
>
> 01: 004015F4 >/. 55 PUSH EBP
> 02: 004015F5 |. 8BEC MOV EBP, ESP
> 03: 004015F7 |. 6A 10 PUSH 10
> 04: 004015F9 |. 58 POP EAX
> 05: 004015FA |. E8 A1FAFFFF CALL Projekt1.___vbaChkstk
> 06: 004015FF |. 8D45 FC LEA EAX, [LOCAL.MyVar]
> 07: 00401602 |. 50 PUSH EAX
> 08: 00401603 |. E8 38FDFFFF CALL Projekt1.___vba@001EAD98
> 09: 00401608 |. 8945 F0 MOV [LOCAL.unnamed_var1], EAX
> 10: 0040160B |. E8 32FBFFFF CALL Projekt1.___vbaSetSystemError
> 11: 00401610 |. 8B45 F0 MOV EAX, [LOCAL.unnamed_var1]
> 12: 00401613 |. 8945 F8 MOV [LOCAL.AdressOfMyVar1], EAX
> 13: 00401616 |. 8D45 FC LEA EAX, [LOCAL.MyVar]
> 14: 00401619 |. 50 PUSH EAX
> 15: 0040161A |. E8 1DFBFFFF CALL Projekt1.@__vba@001EAE28
> 16: 0040161F |. 8945 F4 MOV [LOCAL.AdressOfMyVar2], EAX
> 17: 00401622 |. 8B45 F8 MOV EAX, [LOCAL.AdressOfMyVar1]
> 18: 00401625 |. 3B45 F4 CMP EAX, [LOCAL.AdressOfMyVar2]
> 19: 00401628 |. 75 05 JNZ SHORT Projekt1.0040162F
> 20: 0040162A |. E8 07FBFFFF CALL Projekt1.@__vba@001EADF0
> 21: 0040162F |> C9 LEAVE
> 22: 00401630 \. C3 RETN
>
> Up to line 5 the stack is set up again.
> Now lines 6 to 12 are used for executing "AdressOfMyVar1 =
> GetAdrVar(MyVar)". This are 3 lines more than when using a typelib.
>
> Lines 6 to 8 are identical to the former first 3 lines: the adress of
> MyVar is loaded into eax, eax is pushed on the stack and a program
> internal adress (labeled ___vba@001EAD98 now) is called.
>
> But now at line 9, on return the result is not stored in AdressOfMyVar1
> but in a temporary var on the stack (unnamed_var1). Then in line 10 a vb
> dll function is called (___vbaSetSystemError). Then on return from this
> function, in line 11 the temp var is loaded into eax and in line 12 eax
> is stored to AdressOfMyVar1.
>
> So one difference is: each time we call a VB declared function this
> takes 3 lines more of assembler code as it would take if the function
> would have been declared using a typelib. Each time the return value of
> the function first is temporarily stored, ___vbaSetSystemError is called
> and then the register is loaded back from memory.
>
> This is one part of the reason, why VB declared functions use more
> memory and are slower than typelib declared functions.
>
> What's going on there? Now, VB is told to call a function in an external
> dll. One usual way of external functions to communicate errors is to use
> the SetLastError-API. It can use this method, but it must not. But VB
> does not know, if the called function uses this method or not. So for
> being on the safe side, it queries GetLastError after each call of such
> a function (using its internal method called ___vbaSetSystemError).
> ___vbaSetSystemError sets the LastDllError property of the VB error
> object and uses GetLastError. Here is its code:
>
> 660D7324 __vbaSetSystemError PUSH ESI
> 660D7325 CALL DWORD PTR
> DS:[<&KERNEL32.GetLast>; KERNEL32.GetLastError
> 660D732B PUSH DWORD PTR DS:[66110EC0]
> 660D7331 MOV ESI, EAX
> 660D7333 CALL DWORD PTR
> DS:[<&KERNEL32.TlsGetV>; KERNEL32.TlsGetValue
> 660D7339 MOV DWORD PTR DS:[EAX+9C], ESI
> 660D733F POP ESI
> 660D7340 RETN
>
> One can not tell via a VB declare to the VB compiler, wether a method
> uses SetLastError or not. But IDL can. So if the IDL-entry is correctly
> formulated, it can tell VB, that the function does not use SetLastError.
> And then VB also does not query GetLastError. This is one reason, why
> typelib declared functions spare memory and execution time.
>
> Now lets move on. The line
>
> 08: 00401603 |. E8 38FDFFFF CALL Projekt1.___vba@001EAD98
>
> called our function. Following it, we come to a table entry (line 24
> below) in the programs adress space:
>
> 01: 00401314 ___vba@001EAD78 ASCII "ukVBHLP",0
> 02: 0040131C DB 07
> 03: 0040131D DB 00
> 04: 0040131E DB 00
> 05: 0040131F DB 00
> 06: 00401320 ___vba@001EAD88 ASCII "GetAdr",0
> 07: 00401327 DB 00
> 08: 00401328 DD Projekt1.___vba@001EAD78
> ; ASCII "ukVBHLP"
> 09: 0040132C DD Projekt1.___vba@001EAD88
> ; ASCII "GetAdr"
> 10: 00401330 DB 00
> 11: 00401331 DB 00
> 12: 00401332 DB 04
> 13: 00401333 DB 00
> 14: 00401334 INT3
> 15: 00401335 ASCII ""@",0
> 16: 00401338 DB 00
> 17: 00401339 DB 00
> 18: 0040133A DB 00
> 19: 0040133B DB 00
> 20: 0040133C DB 00
> 21: 0040133D DB 00
> 22: 0040133E DB 00
> 23: 0040133F DB 00
> 24: 00401340 ___vba@001EAD98 MOV EAX, DWORD PTR DS:[4022D4]
> 25: 00401345 OR EAX, EAX
> 26: 00401347 JE SHORT Projekt1.0040134B
> 27: 00401349 JMP EAX
> 28: 0040134B PUSH Projekt1.00401328
> 29: 00401350 MOV EAX, Projekt1.@__vba@001DFBB0
> ; JMP to MSVBVM60.DllFunctionCall
> 30: 00401355 CALL EAX
> ; Projekt1.@__vba@001DFBB0
> 31: 00401357 JMP EAX
>
> Lines 1 to 31 shows VB's housekeeping for VB declared functions in
> external dlls. They start declaring the dll including its name, followed
> by entries for *each* of the VB declared functions in the program,
> wether they are actually used in the program or not. For each function
> entry the function name (null terminated ASCII string) and some bytes of
> other housekeeping data are used (lines 6 to 23). And there is a code
> stub for each entry (lines 24 to 31) which slows done execution too.
>
> In line 24, from another table entry (at 0x4022D4) in the programs .data
> space its content is moved to the eax register. This content is either
> zero or equal to the dynamically calculated adress of the entry point in
> the external dll. Line 25 and 26 test, wether this entry is zero. This
> is done *on each call* to the function. When it is not zero, line 27
> jumps to this adress. If it is zero, line 28 and 29 call a VB internal
> function. This function determines, wether the dll is loaded (dlls whose
> functions are used via typelib are always loaded on program start, dlls
> whose functions are declared via VB declare are loaded on demand: when
> the first call to such a function is executed). If the dll is not
> loaded, it is loaded then. Then the VB internal function dynamically
> determines the entry adress (using GetProcAddress) and writes this
> adress to the table entry at 0x4022D4.
>
> So more overhead (memory and execution time) is added.
>
> Extra memory in this case for GetAdr is from 0x00401320 to 0x00401357
> (56 bytes). The amount of memory depends on the length of the function
> name. The longer the name, the more memory is used. And such extra
> memory is allocated for *each* VB declaration, no matter wether it is
> actually used or not.
>
> And execution is slower, not only when the function is called the first
> time, but on each function call.
>
> I hope that I could make clear, that using typelibs is preferrable to
> using VB declares.
>
> > P.S. your first link (STDCALL.txt) seems to be broken
>
> Thanks. I fixed it.
>
> --
> Ulrich Korndoerfer
>
> VB tips, helpers, solutions -> http://www.proSource.de/Downloads/
>
- Next message: Hapticz: "Re: Poor VB Programming"
- Previous message: DaveO: "Text on web page form"
- In reply to: Ulrich Korndoerfer: "Re: Unsigned 32 bit"
- Next in thread: Ulrich Korndoerfer: "Re: Unsigned 32 bit"
- Reply: Ulrich Korndoerfer: "Re: Unsigned 32 bit"
- Messages sorted by: [ date ] [ thread ]
Relevant Pages
|