在 VB6 中将 Double 拆分为 Long loword 和 Long hiword
Split Double to Long loword and Long hiword in VB6
由于大小问题,我需要使用kernel32的SetFilePointer函数来读取地址为double的磁盘扇区。
我知道 ReadFile 函数接受 loword as long 和 hiword as long 参数,但我无法将我的双地址拆分为两个词。
我尝试了几种使用Mod和Fix的方法,但最后只有溢出错误。
LoWord = CLng(dNum Mod CDbl(4294967295)) 'Dont care the number I use, I always get overflow error
或
LoWord = CLng(FMod(dNum, 4294967295#))
HiWord = CLng(dNum - (FMod(dNum, 4294967295#))) 'tryed different number to see the behaviour, don't care
哪里
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
我尝试将双精度数转换为 byteArray 或十六进制字符串以尝试 "manual" 字节移位,但没有成功。
我已经看到了 Convert Double into 8-bytes array,但是没有修改的样本总是将 dNum=1 转换为 [0, 0, 0, 0, 0, 0, 240, 63] 结果,但它没有似乎是对的。
你有什么技巧或其他方法可以在 VB6 中从磁盘读取大地址扇区吗?
感谢大家阅读我的问题。
为了更好地说明我在做什么:
我知道也许 vb6 不是最好的选择,但现在我已经开始这样做了......
我从 INI 文件(变量)中读取十六进制格式(作为字符串)的扇区号,我将其转换为 Long(但它应该以双精度形式携带,还是什么?),考虑到每个扇区 512 字节。
从该扇区开始,我必须从磁盘读取的字节数是一个常数。
当我使用函数时
Call SetFilePointer(hDevice, iStartSec * BytesPerSector, 0, FILE_BEGIN)
我必须指定字节数,然后我必须乘以 512。这导致我试图绕过的溢出。
我也试过这个方法:
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
函数中...
D.Value = CDbl(iStartSec) * CDbl(BytesPerSector)
LSet L = D
Call SetFilePointer(hDevice, L.LowVal, L.HighVal, FILE_BEGIN)
但这对我不起作用。
根据您的编辑,我进行了一些谷歌搜索,看看是否有 Win API 可以进行左移,因为乘以 512 只是按位左移 9。
我 运行 穿过 RtlLargeIntegerShiftLeft 就可以做到这一点(在此处找到 https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/rtlenlargedintegermultiply)。
接下来,为了节省您一些时间,我找到了一个使用它的声明的 VB6 示例(此处: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
祝你好运!
As @tcarvin , the Currency
数据类型也是 8 个字节,它具有与 LongLong 相同的内部结构,但带有隐含的十进制逗号。它也是有符号的,这很好,因为 SetFilePointer
也接受地址部分的有符号 Long
。
如果你能保证你从文件中读取的扇区号永远不会大于922,337,203,685,477(十六进制346DC5D638865),那么你可以直接使用Currency
并简单地调整内置的10000缩放比例得到与两个 Long 相同的二进制表示:
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@
但是,如果您的值可以大于 346DC5D638865,您将需要自定义解析器,因为 CCur
会溢出:
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@
如果 SetFilePointer
接受两个指向两个值的指针,您已经可以用它来调用它,但是由于它按值接受 lodword 并按引用接受 hidword,因此您必须像您一样执行 LSet
已经做了,但是 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
由于大小问题,我需要使用kernel32的SetFilePointer函数来读取地址为double的磁盘扇区。 我知道 ReadFile 函数接受 loword as long 和 hiword as long 参数,但我无法将我的双地址拆分为两个词。
我尝试了几种使用Mod和Fix的方法,但最后只有溢出错误。
LoWord = CLng(dNum Mod CDbl(4294967295)) 'Dont care the number I use, I always get overflow error
或
LoWord = CLng(FMod(dNum, 4294967295#))
HiWord = CLng(dNum - (FMod(dNum, 4294967295#))) 'tryed different number to see the behaviour, don't care
哪里
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
我尝试将双精度数转换为 byteArray 或十六进制字符串以尝试 "manual" 字节移位,但没有成功。
我已经看到了 Convert Double into 8-bytes array,但是没有修改的样本总是将 dNum=1 转换为 [0, 0, 0, 0, 0, 0, 240, 63] 结果,但它没有似乎是对的。
你有什么技巧或其他方法可以在 VB6 中从磁盘读取大地址扇区吗?
感谢大家阅读我的问题。
为了更好地说明我在做什么: 我知道也许 vb6 不是最好的选择,但现在我已经开始这样做了...... 我从 INI 文件(变量)中读取十六进制格式(作为字符串)的扇区号,我将其转换为 Long(但它应该以双精度形式携带,还是什么?),考虑到每个扇区 512 字节。 从该扇区开始,我必须从磁盘读取的字节数是一个常数。
当我使用函数时
Call SetFilePointer(hDevice, iStartSec * BytesPerSector, 0, FILE_BEGIN)
我必须指定字节数,然后我必须乘以 512。这导致我试图绕过的溢出。
我也试过这个方法:
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
函数中...
D.Value = CDbl(iStartSec) * CDbl(BytesPerSector)
LSet L = D
Call SetFilePointer(hDevice, L.LowVal, L.HighVal, FILE_BEGIN)
但这对我不起作用。
根据您的编辑,我进行了一些谷歌搜索,看看是否有 Win API 可以进行左移,因为乘以 512 只是按位左移 9。
我 运行 穿过 RtlLargeIntegerShiftLeft 就可以做到这一点(在此处找到 https://docs.microsoft.com/en-us/windows-hardware/drivers/kernel/rtlenlargedintegermultiply)。
接下来,为了节省您一些时间,我找到了一个使用它的声明的 VB6 示例(此处: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
祝你好运!
As @tcarvin Currency
数据类型也是 8 个字节,它具有与 LongLong 相同的内部结构,但带有隐含的十进制逗号。它也是有符号的,这很好,因为 SetFilePointer
也接受地址部分的有符号 Long
。
如果你能保证你从文件中读取的扇区号永远不会大于922,337,203,685,477(十六进制346DC5D638865),那么你可以直接使用Currency
并简单地调整内置的10000缩放比例得到与两个 Long 相同的二进制表示:
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@
但是,如果您的值可以大于 346DC5D638865,您将需要自定义解析器,因为 CCur
会溢出:
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@
如果 SetFilePointer
接受两个指向两个值的指针,您已经可以用它来调用它,但是由于它按值接受 lodword 并按引用接受 hidword,因此您必须像您一样执行 LSet
已经做了,但是 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