Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Split Double to Long loword and Long hiword in VB6

Tags:

vb6

I need to use the SetFilePointer function of kernel32 to read a sector of the disk whose address is contained in a double for size problems. I know that the ReadFile function accepts the loword as long and hiword as long parameters, but I couldn't split my double address into two words.

I tried several methods using Mod and Fix, but in the end I only had overflow errors.

LoWord = CLng(dNum Mod CDbl(4294967295)) 'Dont care the number I use, I always get overflow error

or

LoWord = CLng(FMod(dNum, 4294967295#))
HiWord = CLng(dNum - (FMod(dNum, 4294967295#)))   'tryed different number to see the behaviour, don't care

where

Public Function FMod(a As Double, b As Double) As Double
   FMod = a - Fix(a / b) * b

   'http://en.wikipedia.org/wiki/Machine_epsilon
   'Unfortunately, this function can only be accurate when `a / b` is outside [-2.22E-16,+2.22E-16]
   'Without this correction, FMod(.66, .06) = 5.55111512312578E-17 when it should be 0
   If FMod >= -2 ^ -52 And FMod <= 2 ^ -52 Then '+/- 2.22E-16
       FMod = 0
   End If
End Function

I tried to convert the double to byteArray or hexadecimal string to try a "manual" byte shift, but with no luck.

I already see the Convert Double into 8-bytes array, but the sample without modification always convert dNum=1 in [0, 0, 0, 0, 0, 0, 240, 63] as result and it don't seem the right one.

Do you have some tip or some other way to read sectors with a big address from a disk in VB6?

Thank you all for reading my question.

To better specify what I'm doing: I know that maybe vb6 isn't the best choice, but now I've started with this ... I read a sector number from an INI file (is variable) in hexadecimal format (as a string) which I convert to Long (but should it be carried in double, or what?), considering 512 bytes per sector. The number of bytes I have to read from the disk, starting from that sector on, is a constant.

When I use the function

Call SetFilePointer(hDevice, iStartSec * BytesPerSector, 0, FILE_BEGIN)

I have to specify the number of bytes and then I have to multiply by 512. This cause me the overflow I'm trying to bypass.

I tried also this method:

Private Type TKK_Dbl
    Value As Double
End Type

Private Type Dbl2Long
    LowVal As Long
    HighVal As Long
End Type

Private D As TKK_Dbl
Private L As Dbl2Long

in function...

D.Value = CDbl(iStartSec) * CDbl(BytesPerSector)
LSet L = D
Call SetFilePointer(hDevice, L.LowVal, L.HighVal, FILE_BEGIN)

But it does not worked for me.

like image 641
Silvio Avatar asked Dec 23 '22 21:12

Silvio


2 Answers

As @tcarvin noted, the Currency data type is also 8 bytes and it has the same internal structure as a LongLong, but with an implied decimal comma. It is also signed, which is good because SetFilePointer also accepts signed Longs for the address parts.

If you can guarantee that your sector number read from the file will never be greater than 922,337,203,685,477 (hex 346DC5D638865), then you can use Currency directly and simply adjust for the built-in 10000 scaling to get the same binary representation as two Longs would have:

dim sector_number_as_string as string
sector_number_as_string = "B3A73CF8186"  ' Read from file; decimal 12345678987654

dim iStartSec as currency
iStartSec = CCur("&h" & sector_number_as_string) / 10000@

'At this point iStartSec = 1234567898.7654

dim offset as currency
offset = iStartSec * 512@

However, if your values can be greater than 346DC5D638865, you will need a custom parser because CCur will overflow:

Public Type TKK_Cur
    Value As Currency
End Type

Public Type Cur2Long
    LowVal As Long
    HighVal As Long
End Type

' Returns already scaled value, no need to divide by 10000
Public Function ParseLongHex(ByVal s As String) As Currency
    Dim c As TKK_Cur
    Dim l As Cur2Long

    l.LowVal = CLng("&h" & Right$(s, 8))
    If Len(s) > 8 Then l.HighVal = CLng("&h" & Left$(s, Len(s) - 8))

    LSet c = l
    ParseLongHex = c.Value
End Function
dim sector_number_as_string as string
sector_number_as_string = "B3A73CF8186"  ' Read from file; decimal 12345678987654

dim iStartSec as currency
iStartSec = ParseLongHex(sector_number_as_string)

'At this point iStartSec = 1234567898.7654

dim offset as currency
offset = iStartSec * 512@

If SetFilePointer accepted two pointers to two values, you could already call it with that, but as it accepts lodword by value and hidword by reference, you have to do the LSet like you already did, but with Currency:

Private Type TKK_Cur
    Value As Currency
End Type

Private Type Cur2Long
    LowVal As Long
    HighVal As Long
End Type

Private C As TKK_Cur
Private L As Cur2Long
C.value = offset
lset l = c
like image 103
GSerg Avatar answered Dec 30 '22 15:12

GSerg


Based upon your edits, I did a bit of googling to see if there is a Win API to do a left shift, since multiplication by 512 is just a bitwise left shift by 9.

I ran across RtlLargeIntegerShiftLeft that will do just that ( found here https://learn.microsoft.com/en-us/windows-hardware/drivers/kernel/rtlenlargedintegermultiply).

Next, just to save you some time, I found a VB6 sample of the declare that uses it (here: http://www.xbeat.net/vbspeed/c_ShiftLeft.htm)

Private Type LARGEINT
  Long1 As Long
  Long2 As Long
End Type

Private Declare Function RLIShiftLeft Lib "ntdll" Alias "RtlLargeIntegerShiftLeft" _
    (ByVal Val1 As Long, ByVal Val2 As Long, ByVal ShiftCount As Long) As LARGEINT

Best of luck!

like image 45
tcarvin Avatar answered Dec 30 '22 17:12

tcarvin