Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why resourceReader.GetResourceData return Data of type "ResourceTypeCode.Stream" that is offset by 4

In my function GetAssemblyResourceStream (code below), I read resource from Dll using "assembly.GetManifestResourceStream" and "resourceReader.GetResourceData".

When I set my memory stream from the byte array of the resource, I have to include an offset of 4 bytes:

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

What is the reason of that offset? where does it came from?

Reference: Sample at end of MSDN ResourceReader Class

Also: I made a test app to better understand resources. That app show the problem I had with the offset. My little test app is available at Github (VS 2015)

Update 2015-10-05 10h28 Due to very low answers, I suspected a bug and/or undocumented behavior. I reported a bug at Connect.Microsoft.com and will see the result.

Update 2015-10-07 I removed the bug. I still think it is not well documented and/or could be considered as a bug but I highly suspect they will close my request without doing anything. I hope nobody will fall in the same problem I did.

Code:

   // ******************************************************************
    /// <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;
    }
like image 857
Eric Ouellet Avatar asked Oct 01 '15 15:10

Eric Ouellet


1 Answers

The 4 bytes at the start of the byte[] is the size of the data that follow the size. But it is totally useless because it is part of a byte[] and a byte[] size is already known. Moreover, the content of the stream is only one item where the 4 bytes offset could not serve to indicate the size of the first item in regards to following ones because there can't be any.

After reading ResourceReader.GetResourceData Method documentation : I tried both BinaryReader and BinaryFormatter without success. I will continue to read the content of the resource the same way I did earlier (bypass the size and convert directly to a stream with BitConverter).

Thanks to "Hey you" who gave me the idea to look in that direction.

Just for reference. This is my code but it could be not as accurate as it should be... It works for me but not deeply tested. Just to give a start.

// ******************************************************************
/// <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;
}
like image 188
Eric Ouellet Avatar answered Oct 12 '22 01:10

Eric Ouellet