【问题标题】:Conversion from VB6 string data to .NET byte array从 VB6 字符串数据转换为 .NET 字节数组
【发布时间】:2009-05-07 08:06:20
【问题描述】:

我正在编写一个 C# 应用程序,它从 VB6 代码生成的 SQL 数据库中读取数据。数据是一个 Singles 数组。我正在尝试将它们转换为 float[]

下面是在数据库中写入数据的VB6代码(不能更改此代码):

  Set fso = New FileSystemObject
  strFilePath = "c:\temp\temp.tmp"

  ' Output the data to a temporary file
  intFileNr = FreeFile
  Open strFilePath For Binary Access Write As #intFileNr
  Put #intFileNr, , GetSize(Data, 1)
  Put #intFileNr, , GetSize(Data, 2)
  Put #intFileNr, , Data
  Close #intFileNr

  ' Read the data back AS STRING
  Open strFilePath For Binary Access Read As #intFileNr
  strData = String$(LOF(intFileNr), 32)
  Get #intFileNr, 1, strData
  Close #intFileNr

  Call Field.AppendChunk(strData)

如您所见,数据被放在一个临时文件中,然后作为VB6 String 读回并写入数据库(dbLongBinary 类型的行)

我尝试了以下方法:

进行块复制

byte[] source = databaseValue as byte[];
float [,] destination = new float[BitConverter.ToInt32(source, 0), BitConverter.ToInt32(source, 4)];
Buffer.BlockCopy(source, 8, destination, 0, 50 * 99 * 4);

这里的问题是 VB6 二进制到字符串的转换。 VB6 字符串 char 有 2 个字节宽,我不知道如何将其转换回我可以处理的二进制格式。

下面是 VB6 代码生成的临时文件的转储: alt text http://robbertdam.nl/share/dump%20of%20text%20file%20generated%20by%20VB6.png

这是我从数据库中读取的数据转储(=VB6 字符串): alt text http://robbertdam.nl/share/dump%20of%20database%20field.png

【问题讨论】:

  • 你确定你有正确的二进制转储吗?我可以看到 LINQ 为某些序列引入的 unicode 填充,但它们并不完全匹配。
  • 但是两者都是相同大小的数组 a (0 到 2, 0 到 12) 所以我认为如果将它复制到另一个字节数组中并跳过每隔一个字节你应该对其余的代码很好我发布了。
  • 那么 C# 代码必须处理每个字节和每个浮点数的字节数?
  • 如果我错了,请原谅我,但这与您 4 月 24 日的问题不完全相同吗? stackoverflow.com/questions/785181/…
  • 哦,我刚看到你的PS。也许版主可以为你合并问题,如果有的话。我相信他们有这种力量。

标签: c# .net vb.net vb6


【解决方案1】:

我看到的一种可能方法是:

  1. 以 System.Char[] 的形式读回数据,它是 Unicode,就像 VB BSTR 一样。
  2. 通过 Encoding.ASCII.GetBytes() 将其转换为 ASCII 字节数组。这有效地删除了所有交错的 0。
  3. 将此 ASCII 字节数组复制到最终的浮点数组中。

类似这样的:

char[] destinationAsChars = new char[BitConverter.ToInt32(source, 0)* BitConverter.ToInt32(source, 4)];
byte[] asciiBytes = Encoding.ASCII.GetBytes(destinationAsChars);
float[] destination = new float[notSureHowLarge];
Buffer.BlockCopy(asciiBytes, 0, destination, 0, asciiBytes.Length);

现在目的地应该包含原始花车。警告:不确定 VB6 Singles 的内部格式是否与 System.Float 的内部格式二进制兼容。如果没有,所有赌注都取消。

【讨论】:

【解决方案2】:

这是我从上面的答案中得出的解决方案。

将文件作为 unicode char[] 读入,然后重新编码为我的默认系统编码生成可读文件。

internal void FixBytes()
{
    //Convert the bytes from VB6 style BSTR to standard byte[].

    char[] destinationAsChars = 
    System.Text.Encoding.Unicode.GetString(File).ToCharArray();

    byte[] asciiBytes =  Encoding.Default.GetBytes(destinationAsChars);
    byte[] newFile = new byte[asciiBytes.Length];
    Buffer.BlockCopy(asciiBytes,0, newFile, 0, asciiBytes.Length);
    File = newFile;
}

【讨论】:

    【解决方案3】:

    您可能知道,VB6 端的编码非常糟糕。它试图做的是将 Single 数据(与 C# 中的 float 相同)转换为字符串。但是,虽然有更好的方法可以做到这一点,但一开始是一个非常糟糕的主意。

    主要原因是,将二进制数据读入 VB6 BSTR 会将数据从 8 位字节转换为 16 位字符,使用当前代码页。因此,这可能会在数据库中产生不同的结果,具体取决于它所运行的语言环境。(!)

    所以当你从数据库中读回它时,除非你指定写入时使用的相同代码页,否则你会得到不同的浮点数,甚至可能是无效的。

    这将有助于查看二进制(单个)和 DB(字符串)形式的数据示例,以十六进制显示,以验证正在发生的事情。

    来自后来的帖子:

    其实这不是“坏”的 VB6 代码。

    是的,因为它将二进制数据带入字符串域,这违反了现代 VB 编码的基本规则。这就是存在 Byte 数据类型的原因。如果您忽略这一点,当您创建的数据库跨越区域设置边界时,您很可能会得到无法破译的数据。

    他正在做的是存储数组 以紧凑的二进制格式并保存 它作为一个“块”进入数据库。 有很多正当的理由去做 这个。

    当然,他想要这个是有正当理由的(尽管您对“紧凑”的定义与传统定义不同)。目的是好的:选择的手段不是。

    致 OP:

    您可能无法更改作为输入数据提供的内容,因此上述内容主要是学术性的。如果还有时间更改用于创建 blob 的方法,让我们建议不涉及字符串的方法。

    在应用任何提供的解决方案时,请尽量避免使用字符串,如果不能,请使用与创建它们的代码页相匹配的特定代码页对其进行解码。

    【讨论】:

    • 谢谢,我已将示例数据添加到我的帖子中
    【解决方案4】:

    你能澄清一下文件的内容是什么(即一个例子)吗?作为二进制(可能是十六进制)还是字符?如果数据是 VB6 字符串,那么您必须使用 float.Parse() 来读取它。 .NET 字符串也是每个字符 2 字节,但从文件加载时,您可以使用 Encoding 进行控制。

    【讨论】:

    • 我已将示例数据添加到我的帖子中
    【解决方案5】:

    其实这不是“坏”的 VB6 代码。他正在做的是以紧凑的二进制格式存储数组并将其作为“块”保存到数据库中。这样做有很多正当理由。

    VB6 代码将其保存到磁盘并读回的原因是因为 VB6 不支持仅在内存中读取和写入文件。如果您想创建一大块二进制数据并将其填充到其他位置(例如数据库字段),这是常用算法。

    在 .NET 中处理这个不是问题。我拥有的代码在 VB.NET 中,因此您必须将其转换为 C#。

    修改为处理字节和 unicode 问题。

    Public Function DataArrayFromDatabase(ByVal dbData As byte()) As Single(,)
        Dim bData(Ubound(dbData)/2) As Byte
        Dim I As Long
        Dim J As Long
    
        J=0
        For I = 1 To Ubound(dbData) step 2
            bData(J) = dbData(I)
            J=1
        Next I
    
        Dim sM As New IO.MemoryStream(bData)
        Dim bR As IO.BinaryReader = New IO.BinaryReader(sM)
        Dim Dim1 As Integer = bR.ReadInt32
        Dim Dim2 As Integer = bR.ReadInt32
        Dim newData(Dim1, Dim2) As Single
    
        For I = 0 To Dim2
            For J = 0 To Dim1
                newData(J, I) = bR.ReadSingle
            Next
        Next
    
        bR.Close()
        sM.Close()
        Return newData
    End Function
    

    关键技巧是像在 VB6 中一样读取数据。我们有能力在 .NET 中使用 MemoryStreams,所以这相当容易。

    首先,我们跳过所有其他字节以消除 Unicode 填充。

    然后我们从字节数组创建一个内存流。然后使用 MemoryStream 初始化 BinaryReader。

    我们在数组的第一维中读取 VB6 Long 或 .NET Int32 我们在数组的第二维中读取 VB6 Long 或 .NET Int32

    读取循环以数组维度的相反顺序构造。 Dim2 是外环,Dim1 是内环。原因是这就是 VB6 以二进制格式存储数组的方式。

    返回newData,你已经成功恢复了VB6创建的原始数组!

    现在您可以尝试使用一些数学技巧。二维为 4 字节/字符,每个数组元素为 4 字节/字符。但是为了长期的可维护性,我发现使用内存流的字节操作更加明确。它需要更多的代码,但当你在 5 年后重新审视它时会更加清晰。

    【讨论】:

    • 感谢您的回答!您的回答首先假设我从数据库中读取了一个字符串,但该字段作为字节 [] 传递给我(由 Linq)。看我之前贴的代码:byte[] source = databaseValue as byte[];
    【解决方案6】:

    首先我们跳过每隔一个字节 消除 Unicode 填充。

    嗯...如果这是一个有效的策略,那么数据库字符串转储中的每一列都将只包含零。但是快速浏览第一个显示事实并非如此。事实上,这些列中有很多非零字节。我们能负担得起丢弃它们吗?

    这说明使用Strings导致的Unicode转换不是简单的添加'padding',而是改变了数据的字符。您所说的填充是 ASCII 范围(00-7F 二进制)映射到相同的 Unicode 范围这一事实的巧合。但这不适用于二进制 80-FF。

    看看第一个存储的值,它的原始字节值是 94 9A 27 3A。当转换为 Unicode 时,这些不会变成 94 00 97 00 27 00 3A 00。它们会变成 1D 20 61 01 27 00 3A 00。

    丢弃每隔一个字节会得到 1D 61 27 3A -- 而不是原来的 94 9A 27 3A。

    【讨论】:

    • 对,在问这个问题之前,我已经尝试过丢掉每个第二个字符 - 但正如你所预测的那样,这不起作用
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-11-04
    • 2021-11-11
    • 2016-10-28
    • 2023-04-09
    • 1970-01-01
    相关资源
    最近更新 更多