News:

MASM32 SDK Description, downloads and other helpful links
MASM32.com New Forum Link
masmforum WebSite

Floating point arithmetic and DLLs

Started by Merrick, February 22, 2005, 06:58:01 PM

Previous topic - Next topic

Merrick

Hi,

I'm trying to develop a DLL to perform floating point math operations in order to speed up
a visual basic application. I have had no problem developing a prototype that performs
operations on integers, but I am having difficulty passing floating point numbers properly
between VB and the DLL. Does anyone know of a good book or coding example that
might help me solve this problem?

Thanks,
Merrick

AeroASM

If you are passing a Single or Long, your DLL must receive a DWORD or REAL4, but if you are passing a Double, your DLL must receive a QWORD or REAL8. Does this help? If this is correct, it may be because VB might store floats differently to integers.

As for books and code examples, I have not seen any.

Merrick

Hi, thanks for the input. I am passing everything in and (hopefully!) out by reference, rather than values. This seems to work just fine with
integers, as I've said, but I get VERY odd results with floating point numbers. And it's not simply the way in which floating point numbers
are represented, as each time I run the application I get different values back from the same input parameters. As far as I can tell it's probably
BOTH an addressing and structure problem.

Anybody else run into this problem??

AeroASM

When calling external APIs from VB, you have to use ByVal. (Don't know exactly why)

I think that when you use ByRef, VB does gives the DLL the pointer to the variable rather than the value itself. This is why you get different answers for the same question: the variable just has a different address each time.

Merrick

AeroASM - It is true that API calls must be made by value - but that's simply because it is the way the API's are written (it's the default C calling convention), and is not the way my DLL is written. As I said, this works perfectly correctly for integers - that couldn't be true if the address were changing every time I called the DLL. Actually, now that I think about it, VB doesn't (nor does C) always use ByVal to pass data to and receive data from the external APIs - that can't work for either VB or C when you are passing arrays - and both use indirect addressing when passing arrays. My eventual goal is to pass arrays, and so have passed all of the data by reference from the beginning know that is how I eventually will have to do it in the end.

To solve the floating point problem, however, I have tried every combination of sending all of the arguments by value and by reference I can come up with (and, of course, modifying the DLL code to reflect that). They all behave differently and incorrectly. It's more than a little frustrating, as I'm sure it's a totally trivial thing I'm overlooking.

Thanks for your input.

AeroASM

So your DLL deferences the parameters given to it? I think the most likely trivial thing you might have got wrong is the derefencing part. Could you post some code?

TheoMcC

I agree with AeroASM, I suspect your troubles maybe a simple one, ie, proper de-referencing of passed values. Suggest you post some code examples.

Also, if your trying to return a (Single or Double) FUNCTION value, the return value must be left on the x87 stack, specifically, in ST(0). Visual Basic will then take the value from the x87 and return the value to your variable.

AeroASM

I never knew that; how did you find out?

MichaelW

If you pass a double from VB to the DLL, VB may pass it on the stack with the upper dword pushed onto the stack first. I have seen CRTL functions that expect this, but I don't recall testing VB. And at least for the CRTL, leaving the result on the FPU stack is the normal method of returning a floating-point value.
eschew obfuscation

Merrick

Hi all,

Thanks for your input - yes, I think that a large part of my problem is in dereferencing. I'm at work right now, and this project is not an "officially funded" project for work, so I am working on it in my off time. I'll put some code that works for integers (not using the FPU) and some code that doesn't work for floating points (using the FPU) as soon as I can.

In the end, I'm not going to want to pop the values off the FPU stack, however, as I intend to send array references to work on (very lage!) lists of numbers in the DLL.

Thanks again,
Merrick

Merrick

OK. I'll post these separately:

Here is a DLL that does integer math mostly ByRef, but with some ByVal referencing as well. I have three routines: one is a function that leaves the result in AX for retrieval; one is a subroutine that places the answer back into a variable referenced by passed address; and one is a subroutine that passes the address of the first elements of two input arrays and one output array by address and the length of the arrays by value.

The VB declarations:
Private Declare Function Power2Ref Lib "dllMath1" (ByRef int1 As Integer, ByRef int2 As Integer) As Integer
Private Declare Sub Power2Sub Lib "dllMath1" (ByRef int1 As Integer, ByRef int2 As Integer, ByRef int3 As Integer)
Private Declare Sub Power2Loop Lib "dllMath1" (ByVal count As Integer, ByRef int1 As Integer, ByRef int2 As Integer, ByRef int3 As Integer)

The DLL code:
    .386
    .model flat, stdcall
    option casemap :none

    include \masm32\include\windows.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\kernel32.lib

.code

LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD

        .if reason == DLL_PROCESS_ATTACH
            ret
            ; -----------------------------
            ; If error at startup, return 0
            ; System will abort loading DLL
            ; -----------------------------

        .elseif reason == DLL_PROCESS_DETACH

        .elseif reason == DLL_THREAD_ATTACH

        .elseif reason == DLL_THREAD_DETACH
           
        .endif

        ret

LibMain Endp

Power2Ref proc factorRef:DWORD, powerRef:DWORD
   mov ebx, factorRef
   mov ax, word ptr[ebx]
   mov ebx, powerRef
   mov cx, word ptr[ebx]
   shl ax, cl
   ret
Power2Ref    ENDP

Power2Sub proc factorSub:DWORD, powerSub:DWORD, resultSub:DWORD
   mov ebx, factorSub
   mov ax, word ptr[ebx]
   mov ebx, powerSub
   mov cx, word ptr[ebx]
   shl ax, cl
   mov ebx, resultSub
   mov word ptr [ebx],ax
   ret
Power2Sub    ENDP

Power2Loop proc arrayCount:WORD, factorArray:DWORD, powerArray:DWORD, resultArray:DWORD

   mov ax, arrayCount
   shl ax, 1
   jz done
   mov arrayCount, ax
next:
   mov ebx, factorArray
   add bx, arrayCount
   sub bx, 2
   mov ax, word ptr[ebx]
   mov ebx, powerArray
   add bx, arrayCount
   sub bx, 2
   mov cx, word ptr[ebx]
   shl ax, cl
   mov ebx, resultArray
   add bx, arrayCount
   sub bx, 2
   mov word ptr [ebx], ax
   sub arrayCount, 2
   jz done
   loop next
done:
   ret
Power2Loop    ENDP

End LibMain

Merrick

Here is the code that does the integer division without the FPU. The quotient is in the low byte and the remainder is in the high byte when returned.

The VB declarations:
Private Declare Function DivideRef Lib "dllMath1a" (ByRef int1 As Integer, ByRef int2 As Integer) As Integer
Private Declare Sub DivideSub Lib "dllMath1a" (ByRef int1 As Integer, ByRef int2 As Integer, ByRef int3 As Integer)

The DLL code:
    .386
    .model flat, stdcall
    option casemap :none

    include \masm32\include\windows.inc
    include \masm32\include\kernel32.inc
    includelib \masm32\lib\kernel32.lib

.code

LibMain proc hInstDLL:DWORD, reason:DWORD, unused:DWORD

        .if reason == DLL_PROCESS_ATTACH
            ret
            ; -----------------------------
            ; If error at startup, return 0
            ; System will abort loading DLL
            ; -----------------------------

        .elseif reason == DLL_PROCESS_DETACH

        .elseif reason == DLL_THREAD_ATTACH

        .elseif reason == DLL_THREAD_DETACH
           
        .endif

        ret

LibMain Endp

DivideRef proc factorRef:DWORD, powerRef:DWORD
   mov ebx, factorRef
   mov ax, word ptr[ebx]
   mov ebx, powerRef
   mov cx, word ptr[ebx]
   sub ax, cx
   idiv cl
   ret
DivideRef    ENDP

DivideSub proc factorRef:DWORD, powerRef:DWORD, resultRef:DWORD
   mov ebx, factorRef
   mov ax, word ptr[ebx]
   mov ebx, powerRef
   mov cx, word ptr[ebx]
   sub ax, cx
   idiv cl
   mov ebx, resultRef
   mov word ptr[ebx], ax
   ret
DivideSub    ENDP

End LibMain

AeroASM

One glaring mistake is that you haven't preserved ebx. Push it at the beginning of each proc and pop it back off at the end, or simply use edx, which doesn't need to be preserved.

Merrick

AeroASM - thanks for the correction! Sorry I haven't put up the floating point stuff yet. I put those others up late last night, and I realized that I had tried so many different modifications to that code to try to figure out what was going on that I didn't have any idea what version did what - and need to sort that out before posting it (and needed to get to bed last night!). I'll try to put that up tonight and hopefully a similar silly mistake will pop right up!

Thanks again.

Merrick

OK. Here are some examples using the FPU, some of which work (those which pass the result back on the stack), some of which don't (those which attempt to pop the result into a memory location before returning to VB). The VB declare statements are:

Private Declare Function Divide2 Lib "dllMath5" (ByVal long1 As Long, ByVal long2 As Long) As Double
Private Declare Sub Divide2Sub Lib "dllMath5" (ByVal long1 As Long, ByVal long2 As Long, ByVal double1 As Double)
Private Declare Function Divide2Float1 Lib "dllMath5" (ByVal double1 As Double, ByVal double2 As Double) As Double
Private Declare Sub Divide2Float2 Lib "dllMath5" (ByVal double1 As Double, ByVal double2 As Double, ByVal double3 As Double)

The DLL code is (leaving out the usual stuff):

Divide2 proc numerator:dword, denominator:dword
   finit
   fild numerator
   fild denominator
   ;fsub
   fdiv
   ret
Divide2    ENDP

Divide2Sub proc numerator:dword, denominator:dword, result:real8
   finit
   fild numerator
   fild denominator
   ;fsub
   fdiv st[0], st[1]
   fst result
   ret
Divide2Sub    ENDP

Divide2Float1 proc numerator:real8, denominator:real8
   finit
   fld numerator
   fld denominator
   ;fsub
   fdiv
   ret
Divide2Float1    ENDP

Divide2Float2 proc numerator:real8, denominator:real8, result:real8
   finit
   fld numerator
   fld denominator
   ;fsub
   fdiv st[0], st[1]
   fst result
   ret
Divide2Float2    ENDP

The functions Divide2 and Divide2Float1 work properly, but the commented "fsub" commands are there and commented out because that is the actual calculation I want to do - (a-b)/b - and though thexse functions return the appropriate results when I compile the code without the fsub command, they produce an "Error 16 - Expression too complex" message when I include it.

The subroutines cause no errors, but what ever values is in the variable passed to result remains in that variable upon return from the DLL. I'm confident this is because I need to pass the return variable by address and not by value in order for that to work, but was hoping to get confirmation of that.

Eventually, I hope to have this working via subroutine with indirect addressing as I want to pass two arrays of numbers to the DLL and have the DLL terminate with a third array containing the results.

I'll try to get the code which passes addresses posted before to long.

Thanks in advance for any suggestions.