4

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.

Silvio
  • 43
  • 4
  • You never want to store your pointer [in a `double`](https://stackoverflow.com/q/588004/11683). – GSerg Sep 29 '19 at 13:02
  • 1
    VB6 doesn't have a `LongLong` data type, so there is nothing to split *from*. The `Double`, while also takes 8 bytes, has a completely different internal structure than an integer, so you cannot just copy the bytes. There is `Decimal`, but it also has a wrong size. When using `SetFilePointer`, you normally *start* with having two `Long`s for the address. If it's a predefined constant, declare it as two `Long`s to begin with. If it's read from a file, read it as two `Long`s. – GSerg Sep 29 '19 at 13:11
  • 1
    Actually, the VB6 Currency data type is a 8 byte integer type under the covers. The decimal point is merely implied. You can ignore it when working with APIs if you setup your declares correctly. But GSerg is definitely correct that you should not be trying to store a pointer of any kind in a double. – tcarvin Sep 30 '19 at 12:38
  • Thank for pointing me in not using a Double, maybe a currency. I can try to split sector number in two long before multiply by 512, but I have to study on how to manage the splitted results to have a correct byte number corresponding to the sector number readed from the INI. I added more details at the end of in my question. – Silvio Sep 30 '19 at 13:36

2 Answers2

5

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!

tcarvin
  • 10,715
  • 3
  • 31
  • 52
  • Thank you very much for your reply and comments. I didn't know in detail how the currency type was considered and I was climbing on the mirrors using double. I up-voted your answer, but I chose the @GSerg answer because it managed to give a complete and very focused information on my specific problem. Moreover it is pure vb6 without further declare. For exercise I have however tried also your solution and it is ok. Thanks. – Silvio Oct 01 '19 at 21:46
  • @Silvio No worries, I upvoted the answer from GSerg as well. He took extra time to really lay out everything you need in a well written answer. – tcarvin Oct 02 '19 at 13:17
5

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
GSerg
  • 76,472
  • 17
  • 159
  • 346
  • Great answer. I wasn't sure how large of a string hex value VB6 would allow to be interpreted as a number. Almost thought a custom hex-to-long-long would have to be created (which would be easy enough but undoubtedly much slower to execute). – tcarvin Oct 01 '19 at 12:40
  • @tcarvin Actually, a custom parser would be required anyway, because there is a range of valid LongLong values that would cause `CCur` to overflow due to the 10000 scaling. And if you go with a custom parser, then it's much easier to output two Longs to begin with. – GSerg Oct 01 '19 at 12:55
  • I never thought I'd have to go back to studying the basics, but maybe I'd better do it. Your answer is complete and the subsequent addition of the custom parser is interesting. I'm glad to see that I hadn't gone so far, BUT I just had to study the data types better. All now is working as expected. Thank you. – Silvio Oct 01 '19 at 21:53