【问题标题】:Split Double to Long loword and Long hiword in VB6在 VB6 中将 Double 拆分为 Long loword 和 Long hiword
【发布时间】:2019-09-29 11:26:18
【问题描述】:

我需要使用 kernel32 的 SetFilePointer 函数来读取磁盘的一个扇区,该扇区的地址包含在 double 中,以解决大小问题。 我知道 ReadFile 函数接受 loword as long 和 hiword 作为 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 或十六进制字符串以尝试“手动”字节移位,但没有成功。

我已经看到了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)

但它对我不起作用。

【问题讨论】:

  • 你永远不想存储指针in a double
  • VB6 没有LongLong 数据类型,因此没有可拆分的来自Double 虽然也占用 8 个字节,但其内部结构与整数完全不同,因此您不能只复制字节。有Decimal,但它也有错误的大小。使用SetFilePointer 时,您通常开始 使用两个Longs 作为地址。如果它是一个预定义的常量,则将其声明为两个 Longs 开头。如果从文件中读取,则将其读取为两个Longs。
  • 实际上,VB6 Currency 数据类型实际上是一个 8 字节整数类型。小数点只是隐含的。如果您正确设置了声明,则在使用 API 时可以忽略它。但是 GSerg 绝对正确,您不应该尝试将任何类型的指针存储在 double 中。
  • 感谢您指出我不要使用 Double,也许是货币。我可以尝试在乘以 512 之前将扇区号一分为二,但我必须研究如何管理拆分结果以具有与从 INI 读取的扇区号相对应的正确字节数。我在问题的末尾添加了更多详细信息。

标签: vb6


【解决方案1】:

与@tcarvin noted 一样,Currency 数据类型也是 8 个字节,它具有与 LongLong 相同的内部结构,但带有隐含的十进制逗号。它也是签名的,这很好,因为SetFilePointer 也接受签名的Longs 作为地址部分。

如果你能保证你从文件中读取的扇区号永远不会大于922,337,203,685,477(十六进制346DC5D638865),那么你可以直接使用Currency并简单地调整内置的10000缩放以获得相同的二进制表示就像两个多头一样:

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,但使用 @ 987654333@:

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

【讨论】:

  • 很好的答案。我不确定 VB6 允许将多大的字符串十六进制值解释为数字。几乎认为必须创建一个自定义的 hex-to-long-long(这很容易,但执行起来无疑要慢得多)。
  • @tcarvin 实际上,无论如何都需要一个自定义解析器,因为有一个有效的 LongLong 值范围会导致 CCur 由于 10000 缩放而溢出。如果你使用自定义解析器,那么输出两个 Long 会更容易。
  • 我从没想过我必须回去学习基础知识,但也许我最好这样做。您的答案是完整的,随后添加的自定义解析器很有趣。我很高兴看到我还没有走这么远,但我只需要更好地研究数据类型。现在一切都按预期工作。谢谢。
【解决方案2】:

根据您的编辑,我做了一些谷歌搜索,看看是否有 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

祝你好运!

【讨论】:

  • 非常感谢您的回复和cmets。我不详细了解货币类型是如何考虑的,我正在使用 double 爬上镜子。我对您的答案投了赞成票,但我选择了@GSerg 答案,因为它设法就我的具体问题提供了完整且非常集中的信息。而且它是纯vb6,没有进一步声明。但是,对于练习,我也尝试了您的解决方案,没关系。谢谢。
  • @Silvio 不用担心,我也赞成 GSerg 的回答。他花了额外的时间在一个写得很好的答案中真正列出了你需要的一切。
猜你喜欢
  • 2019-02-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-04
  • 2014-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多