【问题标题】:Why resourceReader.GetResourceData return Data of type "ResourceTypeCode.Stream" that is offset by 4为什么resourceReader.GetResourceData返回偏移4的“ResourceTypeCode.Stream”类型的数据
【发布时间】:2015-10-01 15:09:48
【问题描述】:

在我的函数 GetAssemblyResourceStream(下面的代码)中,我使用“assembly.GetManifestResourceStream”和“resourceReader.GetResourceData”从 Dll 读取资源。

当我从资源的字节数组中设置我的内存流时,我必须包含 4 个字节的偏移量:

const int OFFSET = 4;
resStream = new MemoryStream(data, OFFSET, data.Length - OFFSET);

这个偏移的原因是什么?它是从哪里来的?

参考:MSDN ResourceReader Class 末尾的示例

另外:我制作了一个测试应用来更好地了解资源。该应用程序显示了我在偏移方面遇到的问题。我的小测试应用程序可在Github (VS 2015)

2015-10-05 10h28 更新由于答案非常低,我怀疑存在错误和/或未记录的行为。我在Connect.Microsoft.com 报告了一个错误,并会看到结果。

2015-10-07 更新我删除了这个错误。我仍然认为它没有得到很好的记录和/或可能被视为一个错误,但我高度怀疑他们会在不做任何事情的情况下关闭我的请求。我希望没有人会遇到和我一样的问题。

代码:

   // ******************************************************************
    /// <summary>
    /// The path separator is '/'.  The path should not start with '/'.
    /// </summary>
    /// <param name="asm"></param>
    /// <param name="path"></param>
    /// <returns></returns>
    public static Stream GetAssemblyResourceStream(Assembly asm, string path)
    {
        // Just to be sure
        if (path[0] == '/')
        {
            path = path.Substring(1);
        }

        // Just to be sure
        if (path.IndexOf('\\') == -1)
        {
            path = path.Replace('\\', '/');
        }

        Stream resStream = null;

        string resName = asm.GetName().Name + ".g.resources"; // Ref: Thomas Levesque Answer at:
        // http://stackoverflow.com/questions/2517407/enumerating-net-assembly-resources-at-runtime

        using (var stream = asm.GetManifestResourceStream(resName))
        {
            using (var resReader = new System.Resources.ResourceReader(stream))
            {
                string dataType = null;
                byte[] data = null;
                try
                {
                    resReader.GetResourceData(path.ToLower(), out dataType, out data);
                }
                catch (Exception ex)
                {
                    DebugPrintResources(resReader);
                }

                if (data != null)
                {
                    switch (dataType) // COde from 
                    {
                        // Handle internally serialized string data (ResourceTypeCode members).
                        case "ResourceTypeCode.String":
                            BinaryReader reader = new BinaryReader(new MemoryStream(data));
                            string binData = reader.ReadString();
                            Console.WriteLine("   Recreated Value: {0}", binData);
                            break;
                        case "ResourceTypeCode.Int32":
                            Console.WriteLine("   Recreated Value: {0}", BitConverter.ToInt32(data, 0));
                            break;
                        case "ResourceTypeCode.Boolean":
                            Console.WriteLine("   Recreated Value: {0}", BitConverter.ToBoolean(data, 0));
                            break;
                        // .jpeg image stored as a stream.
                        case "ResourceTypeCode.Stream":
                            ////const int OFFSET = 4;
                            ////int size = BitConverter.ToInt32(data, 0);
                            ////Bitmap value1 = new Bitmap(new MemoryStream(data, OFFSET, size));
                            ////Console.WriteLine("   Recreated Value: {0}", value1);

                            const int OFFSET = 4;
                            resStream = new MemoryStream(data, OFFSET, data.Length - OFFSET);

                            break;
                        // Our only other type is DateTimeTZI.
                        default:
                            ////// No point in deserializing data if the type is unavailable.
                            ////if (dataType.Contains("DateTimeTZI") && loaded)
                            ////{
                            ////    BinaryFormatter binFmt = new BinaryFormatter();
                            ////    object value2 = binFmt.Deserialize(new MemoryStream(data));
                            ////    Console.WriteLine("   Recreated Value: {0}", value2);
                            ////}
                            ////break;
                            break;
                    }

                    // resStream = new MemoryStream(resData);
                }
            }
        }

        return resStream;
    }

【问题讨论】:

    标签: c# character-encoding stream resources offset


    【解决方案1】:

    byte[] 开头的 4 个字节是大小后面的数据的大小。但它完全没用,因为它是 byte[] 的一部分,并且 byte[] 的大小是已知的。此外,流的内容只是一个项目,其中 4 个字节的偏移量不能用于指示第一个项目相对于后续项目的大小,因为不可能有。

    阅读ResourceReader.GetResourceData Method documentation 后:我尝试了 BinaryReader 和 BinaryFormatter 都没有成功。我将继续以之前的方式读取资源的内容(绕过大小并使用 BitConverter 直接转换为流)。

    感谢“嘿,你”让我想到了朝那个方向看。

    仅供参考。这是我的代码,但它可能没有应有的准确......它适用于我,但没有经过深入测试。只是作为一个开始。

    // ******************************************************************
    /// <summary>
    /// Will load resource from any assembly that is part of the application.
    /// It does not rely on Application which is specific to a (UI) frameowrk.
    /// </summary>
    /// <param name="uri"></param>
    /// <param name="asm"></param>
    /// <returns></returns>
    public static Stream LoadResourceFromUri(Uri uri, Assembly asm = null)
    {
        Stream stream = null;
    
        if (uri.Authority.StartsWith("application") && uri.Scheme == "pack")
        {
            string localPath = uri.GetComponents(UriComponents.Path, UriFormat.UriEscaped);
    
            int indexLocalPathWithoutAssembly = localPath.IndexOf(";component/");
            if (indexLocalPathWithoutAssembly == -1)
            {
                indexLocalPathWithoutAssembly = 0;
            }
            else
            {
                indexLocalPathWithoutAssembly += 11;
            }
    
            if (asm != null) // Take the provided assembly, do not check for the asm in the uri.
            {
                stream = GetAssemblyResourceStream(asm, localPath.Substring(indexLocalPathWithoutAssembly));
            }
            else
            {
                if (uri.Segments.Length > 1)
                {
                    if (uri.Segments[0] == "/" && uri.Segments[1].EndsWith(";component/"))
                    {
                        int index = uri.Segments[1].IndexOf(";");
                        if (index > 0)
                        {
                            string assemblyName = uri.Segments[1].Substring(0, index);
    
                            foreach (Assembly asmIter in AppDomain.CurrentDomain.GetAssemblies())
                            {
                                if (asmIter.GetName().Name == assemblyName)
                                {
                                    stream = GetAssemblyResourceStream(asmIter, localPath.Substring(indexLocalPathWithoutAssembly));
                                    break;
                                }
                            }
                        }
                    }
                }
    
                if (stream == null)
                {
                    asm = Assembly.GetCallingAssembly();
                    stream = GetAssemblyResourceStream(asm, localPath.Substring(indexLocalPathWithoutAssembly));
                }
            }
        }
        return stream;
    }
    
    // ******************************************************************
    /// <summary>
    /// The path separator is '/'.  The path should not start with '/'.
    /// </summary>
    /// <param name="asm"></param>
    /// <param name="path"></param>
    /// <returns></returns>
    public static Stream GetAssemblyResourceStream(Assembly asm, string path)
    {
        // Just to be sure
        if (path[0] == '/')
        {
            path = path.Substring(1);
        }
    
        // Just to be sure
        if (path.IndexOf('\\') == -1)
        {
            path = path.Replace('\\', '/');
        }
    
        Stream resStream = null;
    
        string resName = asm.GetName().Name + ".g.resources"; // Ref: Thomas Levesque Answer at:
        // http://stackoverflow.com/questions/2517407/enumerating-net-assembly-resources-at-runtime
    
        using (var stream = asm.GetManifestResourceStream(resName))
        {
            using (var resReader = new System.Resources.ResourceReader(stream))
            {
                string dataType = null;
                byte[] data = null;
                try
                {
                    resReader.GetResourceData(path.ToLower(), out dataType, out data);
                }
                catch (Exception)
                {
                    DebugPrintResources(resReader);
                }
    
                if (data != null)
                {
                    switch (dataType) // COde from 
                    {
                        // Handle internally serialized string data (ResourceTypeCode members).
                        case "ResourceTypeCode.String":
                            BinaryReader reader = new BinaryReader(new MemoryStream(data));
                            string binData = reader.ReadString();
                            Console.WriteLine("   Recreated Value: {0}", binData);
                            break;
                        case "ResourceTypeCode.Int32":
                            Console.WriteLine("   Recreated Value: {0}", BitConverter.ToInt32(data, 0));
                            break;
                        case "ResourceTypeCode.Boolean":
                            Console.WriteLine("   Recreated Value: {0}", BitConverter.ToBoolean(data, 0));
                            break;
                        // .jpeg image stored as a stream.
                        case "ResourceTypeCode.Stream":
                            ////const int OFFSET = 4;
                            ////int size = BitConverter.ToInt32(data, 0);
                            ////Bitmap value1 = new Bitmap(new MemoryStream(data, OFFSET, size));
                            ////Console.WriteLine("   Recreated Value: {0}", value1);
    
                            const int OFFSET = 4;
                            resStream = new MemoryStream(data, OFFSET, data.Length - OFFSET);
    
                            break;
                        // Our only other type is DateTimeTZI.
                        default:
                            ////// No point in deserializing data if the type is unavailable.
                            ////if (dataType.Contains("DateTimeTZI") && loaded)
                            ////{
                            ////    BinaryFormatter binFmt = new BinaryFormatter();
                            ////    object value2 = binFmt.Deserialize(new MemoryStream(data));
                            ////    Console.WriteLine("   Recreated Value: {0}", value2);
                            ////}
                            ////break;
                            break;
                    }
    
                    // resStream = new MemoryStream(resData);
                }
            }
        }
    
        return resStream;
    }
    

    【讨论】:

    • 我没有找到任何文档。它仅基于少数测试,其中 4 个字节恰好是流中数据的大小。尽管内容是大图像或小文本,但 4 个字节(通过 BitConverter)始终是精确大小。
    • 嘿!我应该感谢您尝试帮助并推动我朝着调查偏移值的方向发展。谢谢!!!
    【解决方案2】:

    要了解数据偏移,了解数据流的基础知识很重要:
    What is a byte stream actually? 以及它们的一些用途:Transmission Control Protocol

    数据偏移量是流中为其他值保留的字节数 实际数据。它们是为数据提供格式的字节值。

    因此需要包含偏移量,因此这 4 个字节不会作为数据读取。这是一个指令 告诉编译器如何读取字节。

    对于未压缩的图像数据,偏移量为 4。

    像素被打包成字节并排列为扫描线。每次扫描 行必须以 4 字节的边界结束,因此 1、2 或 3 个字节 每条扫描线后面都有填充。

    所有编码数据都以 0 开头。 编码的位图数据将从不作为位图数据读取的代码字节开始, 而是解释以下数据的格式。

    Bytes of 04 AA 表示有 4 个字节的解码数据值为 AA 或 AA AA AA AA 如果解码字节数为奇数,则运行将以填充 00 开始和结束。

    所以 00 03 AA 00 将被解码为 AA AA AA

    00 00 是显示扫描线结束的标记 00 01 是位图数据结束的标记

    运行偏移标记(也称为增量或矢量代码)为 4 个字节。 如果存在,则以值开头:

    00 02 XX YY 后跟两个字节,X 和 Y 值。

    前两个字节00 02是指示指示
    -> 即以下两个字节 是光标应该移动以读取下一行数据的位置。

    此运行偏移标记指示位图中的位置,其中 应该写入下一个解码的像素运行。例如,运行 偏移标记值 00 02 05 03 表示 位图光标应该在扫描线下移动五个像素,三个 向前行,并写出下一次运行。然后光标继续 从新位置开始写入解码数据。

    我的大部分信息来源和引用自:

    Image and Data Compression 在 Microsoft Windows 位图文件格式摘要


    根据反馈进行编辑,不仅仅是图片

    基本上只能有 19 种数据类型中的一种。对于 Stream,添加了偏移量,因为假设文件类型可能具有编码标记,以描述文件类型/格式。由于 Stream 可能被编码,在这种情况下无法按原样读取数据,因此需要对其进行解码,这就是那些偏移字节中提供的内容。

    你可以说 xml 是一个字符串类型,但是如果它被检测为一个流,就会有一些嵌入的标记表明它不是一个字符串,而是一个编码的流。

    在使用 bmps 的情况下,保留的偏移量为 4,但 4 个字节并不总是被填充,这些字节向编译器提供下一步去哪里或如何解释这些下一个可读字节的指令,以及何时该指令完成(即使它小于 4)读取将继续。

    这是一个警告,不要将这些第一个字节作为数据读取!开头的 00 是一个标志。与读取 int 类型中的 00 并转换为 int 不同。

    xml 文件可以嵌入标记,bmps 也是如此。

    Chunk-based formats
    在这种文件结构中,每一块 数据被嵌入到一个以某种方式识别数据的容器中。 容器的范围可以通过 某种,通过某处的显式长度字段,或通过固定 文件格式定义的要求。

    这是资源阅读器背后的代码,我发现它很有用。如果你看这个,你会发现只有 19 种数据类型。

    reference source Microsoft resourcereader

    我希望这提供了一些明确性。

    我不认为这是一个错误。


    为了清晰起见,编辑添加更多解释

    码类型Stream其实就是找到StreamWrapper Class的时候。

    See here line 565:

    else if (type == typeof(StreamWrapper))
        return ResourceTypeCode.Stream; 
    

    来自 MSDN 文档。

    此类将 IStream 接口包装到 System.IO.Stream 类中。 由于此代码用于分类器,因此无需写入 提供的流,因为管道只提供只读流 分类器。因此,修改流的方法不是 实施。

    希望这已经回答了你的问题

    【讨论】:

    • 谢谢,但数据不是位图。数据是一个流。流的内容可以是任何东西。在我的情况下,它是一个 xml 文件。感谢您的尝试。您似乎是位图专家,但我看不出我的问题与位图有何关系。我从微软那里获取代码,他们从流中提取位图,但它可能是其他任何东西。
    • 可能有 1 或 2 个不可见字符来判断哪种编码(unicode 等)。但我怀疑 4 会是这个数字。但主要是,流中可能有任何东西,我认为偏移量与它包含的数据无关。我认为这是一个固定的偏移量,无论流的内容如何,​​它始终存在。
    • 这4个字节可能是资源是什么类型的数据的标识符。例如:0x001:位图,0x002:xmlFile。如果是这样的话,应该有描述这4字节枚举左右的文档。
    • 我不确定...让我有时间消化一下...您对 resourceReader 的引用可能会带来一些启发。
    【解决方案3】:

    我遇到了同样的问题,决定不使用 GetResourceData 方法。 我找到了其他解决方案,例如:

    public static byte[] GetResource(string resourceName)
        {
            try
            {
                var asm = Assembly.LoadFrom("YOUR_ASSEMBLY");
                Stream str = asm.GetManifestResourceStream($@"YOUR_ASSEMBLY.Properties.Resources.resources"); 
                using (var reader = new ResourceReader(str))
                {
                    var res = reader.GetEnumerator();
                    while (res.MoveNext())
                    {
                        if (res.Key.ToString() == resourceName)
                        {
                            if(res.Value is byte[])
                                return res.Value as byte[]; 
                            else if (res.Value is string)
                                return Encoding.UTF8.GetBytes(res.Value as string); 
    // TODO other types
    
                        }
                    }
                    return null;
                }
            }
            catch (Exception ex)
            {
                // message
                return null;
            }
        }
    

    【讨论】:

    • 谢谢。我没有我的代码来测试它,所以我不能假设它对我来说很好。我想应该没问题。所以我没有将其标记为答案,但我投票赞成。谢谢...如果其他用户认为这是一个更好的解决方案,他们肯定会投票赞成。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-03-27
    • 1970-01-01
    • 1970-01-01
    • 2019-02-07
    • 2013-07-24
    • 2021-12-15
    • 1970-01-01
    相关资源
    最近更新 更多