为什么 resourceReader.GetResourceData 返回偏移量为 4 的 "ResourceTypeCode.Stream" 类型的数据



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

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

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

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

参考:示例在MSDN ResourceReader类的末尾

还有:我做了一个测试应用程序,以更好地了解资源。这个应用程序显示了我的偏移问题。我的小测试应用程序可在Github (VS 2015)

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

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

代码:

   // ******************************************************************
    /// <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;
    }

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

读取ResourceReader后。我尝试了BinaryReader和BinaryFormatter,但没有成功。我将继续以与之前相同的方式读取资源的内容(绕过大小并使用BitConverter直接转换为流)。

感谢"Hey you"给了我朝那个方向看的想法。

供参考。这是我的代码,但它可能不像它应该是准确的…它对我有用,但没有经过深入测试。只是给一个开始。

// ******************************************************************
/// <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。

像素被打包成字节并排列成扫描线。每一个扫描行必须以4字节的边界结束,所以是1、2或3个字节

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

Bytes of 04 AA表示有4个字节的解码数据值AA或AA 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表示位图光标应该沿着扫描线向下移动五个像素,三个向前行,并写出下一次运行。然后游标继续从新位置写入解码后的数据。

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

图像和数据压缩在Microsoft Windows位图文件格式摘要


编辑后反馈,不只是图片

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

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

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

这是一个警告,不要把第一个字节读为数据!开头的00是一面旗帜。而不是将int型中的00读入并转换为int型

xml文件可以嵌入标记,bmp文件也可以。


Chunk-based格式在这种文件结构中,每一块数据嵌入在以某种方式标识数据的容器中。的开始和结束标记可以标识容器的作用域某种形式,通过某个地方的显式长度字段,或者是固定的文件格式定义的要求。

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

参考源代码Microsoft resourcereader

我希望这能让你明白一些。

我不相信这是一个bug。


编辑以添加更多的解释以更清晰

代码类型Stream实际上是当StreamWrapper类被找到时。

见第565行:

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

来自MSDN文档。

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

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

我有同样的问题,并决定不使用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;
        }
    }

相关内容

  • 没有找到相关文章

最新更新