none
Passing Image objects across AppDomains RRS feed

  • Question

  • I have run into a brick wall when attempting to return a System.Drawing.Image from my plugin which is loaded in another AppDomain (see previous post):

    Remoting cannot find field 'nativeImage' on type 'System.Drawing.Image'.

    Googling this error suggests it's a common problem, but I couldn't find an appropriate solution anywhere.

    As this is for a plugin interface for my application, I would like to keep it as simple as possible, and allow developers to simply return an Image object that can be accessed from the default AppDomain. That is, I would ideally like to avoid requiring the use of streams or such nonsense on the "client" side, however doing so on the server (host app) side would be acceptable.

    Thanks in advance.
    Wednesday, March 19, 2008 2:48 AM

Answers

  • Hi,
    The image class if marshalbyref.  So your image is being passed MarshalByRef when you need MarshalByValue.  The image class implements ISerializable and if you look at the code we see that just the byte[] is serialized.  You should probably just do the same and pass a byte[]  instead of passing the image or maybe a wrapper class with the byte array that you can pass marshal by value.  In the wrapper class you can have a ToImage method or something like that.

     

    // Something like this?

    [Serializable()]

           public class ImageWrapper
            {
                private byte[] _Bytes;

                public ImageWrapper(Image img)
                {
                    if (img == null)
                    {
                        throw new ArgumentNullException("img");
                    }
                    using (MemoryStream stream = new MemoryStream())
                    {
                        img.Save(stream);
                        _Bytes = stream.ToArray();
                    }
                   
                }

                public Image ToImage()
                {
                    return Image.FromStream(new MemoryStream(_Bytes));
                }
            }

     

     

    // Image serialization code.

    internal Image(SerializationInfo info, StreamingContext context)
    {
        SerializationInfoEnumerator enumerator = info.GetEnumerator();
        if (enumerator != null)
        {
            while (enumerator.MoveNext())
            {
                if (string.Equals(enumerator.Name, "Data", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        byte[] buffer = (byte[]) enumerator.Value;
                        if (buffer != null)
                        {
                            this.InitializeFromStream(new MemoryStream(buffer));
                        }
                    }
                    catch (ExternalException)
                    {
                    }
                    catch (ArgumentException)
                    {
                    }
                    catch (OutOfMemoryException)
                    {
                    }
                    catch (InvalidOperationException)
                    {
                    }
                    catch (NotImplementedException)
                    {
                    }
                    catch (FileNotFoundException)
                    {
                    }
                }
            }
        }

     

    [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
    void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            this.Save(stream);
            si.AddValue("Data", stream.ToArray(), typeof(byte[]));
        }
    }


     

     

    Wednesday, March 19, 2008 6:13 PM

All replies

  • System.Drawing.Image class declaration implies that this class can be passed cross aplication domains.

    Also System.Drawing.Image is abstract class therefore problem can be caused by derived one.

     

    Anyway, I believe classes from System.Drawing namespace are not intended for data transferring. In my opinion you should other class to transfer your image from one app domain to another.

    Wednesday, March 19, 2008 10:20 AM
  • Can you recommend an easy-to-use approach that would allow developers to embed images as resources within a DLL and return them via an interface?

    Image would have been perfect, had it worked.

    If there's some property of Image a developer could return, like Image.GetBytes(), that would be ok too, but there doesn't appear to be any such property.

    Perhaps some assembly-level resource object?

    Does Microsoft have a recommended way of transmitting image data across AppDomains? Perhaps simply byte[], but as I mentioned I'd like to avoid requiring developers to use streams.
    Wednesday, March 19, 2008 10:31 AM
  • Actually, problem is caused by System.Drawing.Bitmap class. I believe your developers use this class.

    Actually, there is mentioned in MSDN this kind of problem when working with Bitmap class cross app domains:

     

    The Bitmap class is not accessible across application domains. For example, if you create a dynamic AppDomain and create several brushes, pens, and bitmaps in that domain, then pass these objects back to the main application domain, you can successfully use the pens and brushes. However, if you call the DrawImage method to draw the marshaled Bitmap, you receive the following exception.

    Remoting cannot find field "native image" on type "System.Drawing.Image".

     

     

    http://msdn2.microsoft.com/en-us/library/system.drawing.bitmap.aspx
    Wednesday, March 19, 2008 11:25 AM
  • Hi,
    The image class if marshalbyref.  So your image is being passed MarshalByRef when you need MarshalByValue.  The image class implements ISerializable and if you look at the code we see that just the byte[] is serialized.  You should probably just do the same and pass a byte[]  instead of passing the image or maybe a wrapper class with the byte array that you can pass marshal by value.  In the wrapper class you can have a ToImage method or something like that.

     

    // Something like this?

    [Serializable()]

           public class ImageWrapper
            {
                private byte[] _Bytes;

                public ImageWrapper(Image img)
                {
                    if (img == null)
                    {
                        throw new ArgumentNullException("img");
                    }
                    using (MemoryStream stream = new MemoryStream())
                    {
                        img.Save(stream);
                        _Bytes = stream.ToArray();
                    }
                   
                }

                public Image ToImage()
                {
                    return Image.FromStream(new MemoryStream(_Bytes));
                }
            }

     

     

    // Image serialization code.

    internal Image(SerializationInfo info, StreamingContext context)
    {
        SerializationInfoEnumerator enumerator = info.GetEnumerator();
        if (enumerator != null)
        {
            while (enumerator.MoveNext())
            {
                if (string.Equals(enumerator.Name, "Data", StringComparison.OrdinalIgnoreCase))
                {
                    try
                    {
                        byte[] buffer = (byte[]) enumerator.Value;
                        if (buffer != null)
                        {
                            this.InitializeFromStream(new MemoryStream(buffer));
                        }
                    }
                    catch (ExternalException)
                    {
                    }
                    catch (ArgumentException)
                    {
                    }
                    catch (OutOfMemoryException)
                    {
                    }
                    catch (InvalidOperationException)
                    {
                    }
                    catch (NotImplementedException)
                    {
                    }
                    catch (FileNotFoundException)
                    {
                    }
                }
            }
        }

     

    [SecurityPermission(SecurityAction.LinkDemand, Flags=SecurityPermissionFlag.SerializationFormatter)]
    void ISerializable.GetObjectData(SerializationInfo si, StreamingContext context)
    {
        using (MemoryStream stream = new MemoryStream())
        {
            this.Save(stream);
            si.AddValue("Data", stream.ToArray(), typeof(byte[]));
        }
    }


     

     

    Wednesday, March 19, 2008 6:13 PM
  • Hi anon1, thanks so much for the detailed response. This looks like just what I need, however could I get you to explain the "Image serialization code" section... specifically what (and where) "internal Image(SerializationInfo info, StreamingContext context)" is?

    I'm not too familiar with serialization, but it looks like this is a constructor for Image... is this a .NET 3 trick or something because how am I supposed to define a constructor for Image? Or is this a System.Drawing.Image-derived class...

    I understand what you're trying to do, however I would have thought that would be part of the ImageWrapper class.

    Thanks,

    Logan
    Wednesday, March 19, 2008 10:29 PM
  • Thats the code for the actual System.Drawing.Image. I used reflector to look at it. I just used the serialization attribute on the sample imagewrapper class.

     

    Wednesday, March 19, 2008 10:31 PM
  • Oh! Doh I see what you are doing now, showing me how the actual Image serialization is implemented. Sorry for the lapse on my part.

    Logan
    Wednesday, March 19, 2008 10:32 PM
  • // Something like this?

    [Serializable()]

           public class ImageWrapper
            {
                private byte[] _Bytes;

                public ImageWrapper(Image img)
                {
                    if (img == null)
                    {
                        throw new ArgumentNullException("img");
                    }
                    using (MemoryStream stream = new MemoryStream())
                    {
                        img.Save(stream);
                        _Bytes = stream.ToArray();
                    }
                   
                }

                public Image ToImage()
                {
                    return Image.FromStream(new MemoryStream(_Bytes));
                }
            }

    Good call on the wrapper. I used a similar class with an implicit System.Drawing.Image conversion operator to provide somewhat-transparent image support across remoting boundaries.
    Wednesday, May 20, 2009 8:09 PM