Re: Unsigned 32 bit

From: Tony Proctor (tony_proctor_at_aimtechnology_NoMoreSPAM_.com)
Date: 09/26/04


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/
>



Relevant Pages

  • Re: Unsigned 32 bit
    ... This helper dll has the base adress 0x02400000. ... Its equivalent VB declare is ... Dim MyVar As Long, AdressOfMyVar1 As Long, AdressOfMyVar2 As Long ... Both project internal adresses are inside the jump table VB has build. ...
    (microsoft.public.vb.general.discussion)
  • Re: bad dll calling convention in debug only
    ... >> Are you also using the debug version of the MFC library? ... >> debugging the DLL. ... >> Create a typelib and use that to replace the Declare Directives. ... I also recommend Bruce McKinney's source for a WinAPI typelib. ...
    (microsoft.public.vb.general.discussion)
  • Re: Which assembler can handle the BIG stuff ?
    ... although I agree that a 16M jump table might be a table looking ... The worst OS DLL that I know of it the kernal and it has 864 entries ... four bytes (offset only) while others are six bytes. ... there is no jmp table in the kernal. ...
    (alt.lang.asm)
  • Re: Converting VB6 Structures to .NET
    ... This dll was written by a third party ... I will try ByRef. ... I am using a number of "Declare Function" statments to access the functions ... Public Structure zFuheader ...
    (microsoft.public.dotnet.languages.vb)
  • Re: Exported function mangaled name
    ... that header file; it must be declared as __declspec ... You have to declare the function the same way in your .cpp file as in ... #define LIBSPEC __declspec ... See my essay on The Ultimate DLL Header File on my MVP Tips site. ...
    (microsoft.public.vc.mfc)

Quantcast