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.
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 Long
s 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
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With