File: System\Messaging\ActiveXMessageFormatter.cs
Project: ndp\cdf\src\NetFx20\System.Messaging\System.Messaging.csproj (System.Messaging)
//------------------------------------------------------------------------------
// <copyright file="ActiveXMessageFormatter.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>                                                                
//------------------------------------------------------------------------------
 
namespace System.Messaging
{
    using System.Runtime.Serialization.Formatters;
    using System.Runtime.InteropServices;
    using System.Text;
    using System.Runtime.Serialization;
    using System.Diagnostics;
    using System;
    using System.IO;
    using System.Globalization;
    using System.ComponentModel;
    using System.Messaging.Interop;
 
    /// <include file='doc\ActiveXMessageFormatter.uex' path='docs/doc[@for="ActiveXMessageFormatter"]/*' />
    /// <devdoc>
    ///    <para>
    ///       Formatter class that serializes and deserializes
    ///       primitives, classes, enumeration, and other objects into and from <see cref='System.Messaging.MessageQueue'/>
    ///       messages using binary format.
    ///    </para>
    /// </devdoc>
    public class ActiveXMessageFormatter : IMessageFormatter
    {
        internal const short VT_ARRAY = 0x2000;
        internal const short VT_BOOL = 11;
        internal const short VT_BSTR = 8;
        internal const short VT_CLSID = 72;
        internal const short VT_CY = 6;
        internal const short VT_DATE = 7;
        internal const short VT_I1 = 16;
        internal const short VT_I2 = 2;
        internal const short VT_I4 = 3;
        internal const short VT_I8 = 20;
        internal const short VT_LPSTR = 30;
        internal const short VT_LPWSTR = 31;
        internal const short VT_NULL = 1;
        internal const short VT_R4 = 4;
        internal const short VT_R8 = 5;
        internal const short VT_STREAMED_OBJECT = 68;
        internal const short VT_STORED_OBJECT = 69;
        internal const short VT_UI1 = 17;
        internal const short VT_UI2 = 18;
        internal const short VT_UI4 = 19;
        internal const short VT_UI8 = 21;
        internal const short VT_VECTOR = 0x1000;
        private byte[] internalBuffer;
        private UnicodeEncoding unicodeEncoding;
        private ASCIIEncoding asciiEncoding;
        private char[] internalCharBuffer;
 
        /// <include file='doc\ActiveXMessageFormatter.uex' path='docs/doc[@for="ActiveXMessageFormatter.CanRead"]/*' />
        /// <devdoc>
        ///    When this method is called, the formatter will attempt to determine 
        ///    if the contents of the message are something the formatter can deal with.
        /// </devdoc>
        public bool CanRead(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
 
            int variantType = message.BodyType;
            if (variantType != VT_BOOL && variantType != VT_CLSID &&
                variantType != VT_CY && variantType != VT_DATE &&
                variantType != VT_I1 && variantType != VT_UI1 &&
                variantType != VT_I2 && variantType != VT_UI2 &&
                variantType != VT_I4 && variantType != VT_UI4 &&
                variantType != VT_I8 && variantType != VT_UI8 &&
                variantType != VT_NULL && variantType != VT_R4 &&
                variantType != VT_I8 && variantType != VT_STREAMED_OBJECT &&
                variantType != VT_STORED_OBJECT &&
                variantType != (VT_VECTOR | VT_UI1) &&
                variantType != VT_LPSTR && variantType != VT_LPWSTR &&
                variantType != VT_BSTR && variantType != VT_R8)
                return false;
 
            return true;
        }
 
        /// <include file='doc\ActiveXMessageFormatter.uex' path='docs/doc[@for="ActiveXMessageFormatter.Clone"]/*' />
        /// <devdoc>
        ///    This method is needed to improve scalability on Receive and ReceiveAsync scenarios.  Not requiring 
        ///     thread safety on read and write.
        /// </devdoc>
        public object Clone()
        {
            return new ActiveXMessageFormatter();
        }
 
        /// <include file='doc\ActiveXMessageFormatter.uex' path='docs/doc[@for="ActiveXMessageFormatter.InitStreamedObject"]/*' />
        /// <devdoc>
        ///    <para>[To be supplied.]</para>
        /// </devdoc>
        public static void InitStreamedObject(object streamedObject)
        {
            IPersistStreamInit persistStreamInit = streamedObject as IPersistStreamInit;
            if (persistStreamInit != null)
                persistStreamInit.InitNew();
        }
 
        /// <include file='doc\ActiveXMessageFormatter.uex' path='docs/doc[@for="ActiveXMessageFormatter.Read"]/*' />
        /// <devdoc>
        ///    This method is used to read the contents from the given message 
        ///     and create an object.
        /// </devdoc>
        public object Read(Message message)
        {
            if (message == null)
                throw new ArgumentNullException("message");
 
            Stream stream;
            byte[] bytes;
            byte[] newBytes;
            int size;
            int variantType = message.BodyType;
            switch (variantType)
            {
                case VT_LPSTR:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    size = message.properties.GetUI4(NativeMethods.MESSAGE_PROPID_BODY_SIZE);
 
                    if (this.internalCharBuffer == null || this.internalCharBuffer.Length < size)
                        this.internalCharBuffer = new char[size];
 
                    if (asciiEncoding == null)
                        this.asciiEncoding = new ASCIIEncoding();
 
                    this.asciiEncoding.GetChars(bytes, 0, size, this.internalCharBuffer, 0);
                    return new String(this.internalCharBuffer, 0, size);
                case VT_BSTR:
                case VT_LPWSTR:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    size = message.properties.GetUI4(NativeMethods.MESSAGE_PROPID_BODY_SIZE) / 2;
 
                    if (this.internalCharBuffer == null || this.internalCharBuffer.Length < size)
                        this.internalCharBuffer = new char[size];
 
                    if (unicodeEncoding == null)
                        this.unicodeEncoding = new UnicodeEncoding();
 
                    this.unicodeEncoding.GetChars(bytes, 0, size * 2, this.internalCharBuffer, 0);
                    return new String(this.internalCharBuffer, 0, size);
                case VT_VECTOR | VT_UI1:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    size = message.properties.GetUI4(NativeMethods.MESSAGE_PROPID_BODY_SIZE);
                    newBytes = new byte[size];
                    Array.Copy(bytes, newBytes, size);
 
                    return newBytes;
                case VT_BOOL:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    newBytes = new byte[1];
                    Array.Copy(bytes, newBytes, 1);
                    if (bytes[0] != 0)
                        return true;
 
                    return false;
                case VT_CLSID:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    newBytes = new byte[16];
                    Array.Copy(bytes, newBytes, 16);
                    return new Guid(newBytes);
                case VT_CY:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    newBytes = new byte[8];
                    Array.Copy(bytes, newBytes, 8);
                    return Decimal.FromOACurrency(BitConverter.ToInt64(newBytes, 0));
                case VT_DATE:
                    bytes = message.properties.GetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY);
                    newBytes = new byte[8];
                    Array.Copy(bytes, newBytes, 8);
                    return new DateTime(BitConverter.ToInt64(newBytes, 0));
                case VT_I1:
                case VT_UI1:
                    stream = message.BodyStream;
                    bytes = new byte[1];
                    stream.Read(bytes, 0, 1);
                    return bytes[0];
                case VT_I2:
                    stream = message.BodyStream;
                    bytes = new byte[2];
                    stream.Read(bytes, 0, 2);
                    return BitConverter.ToInt16(bytes, 0);
                case VT_UI2:
                    stream = message.BodyStream;
                    bytes = new byte[2];
                    stream.Read(bytes, 0, 2);
                    return BitConverter.ToUInt16(bytes, 0);
                case VT_I4:
                    stream = message.BodyStream;
                    bytes = new byte[4];
                    stream.Read(bytes, 0, 4);
                    return BitConverter.ToInt32(bytes, 0);
                case VT_UI4:
                    stream = message.BodyStream;
                    bytes = new byte[4];
                    stream.Read(bytes, 0, 4);
                    return BitConverter.ToUInt32(bytes, 0);
                case VT_I8:
                    stream = message.BodyStream;
                    bytes = new byte[8];
                    stream.Read(bytes, 0, 8);
                    return BitConverter.ToInt64(bytes, 0);
                case VT_UI8:
                    stream = message.BodyStream;
                    bytes = new byte[8];
                    stream.Read(bytes, 0, 8);
                    return BitConverter.ToUInt64(bytes, 0);
                case VT_R4:
                    stream = message.BodyStream;
                    bytes = new byte[4];
                    stream.Read(bytes, 0, 4);
                    return BitConverter.ToSingle(bytes, 0);
                case VT_R8:
                    stream = message.BodyStream;
                    bytes = new byte[8];
                    stream.Read(bytes, 0, 8);
                    return BitConverter.ToDouble(bytes, 0);
                case VT_NULL:
                    return null;
                case VT_STREAMED_OBJECT:
                    stream = message.BodyStream;
                    ComStreamFromDataStream comStream = new ComStreamFromDataStream(stream);
                    return NativeMethods.OleLoadFromStream(comStream, ref NativeMethods.IID_IUnknown);
                case VT_STORED_OBJECT:
                    throw new NotSupportedException(Res.GetString(Res.StoredObjectsNotSupported));
                default:
                    throw new InvalidOperationException(Res.GetString(Res.InvalidTypeDeserialization));
            }
        }
 
        /// <include file='doc\ActiveXMessageFormatter.uex' path='docs/doc[@for="ActiveXMessageFormatter.Write"]/*' />
        /// <devdoc>
        ///    This method is used to write the given object into the given message.  
        ///     If the formatter cannot understand the given object, an exception is thrown.
        /// </devdoc>
        public void Write(Message message, object obj)
        {
            if (message == null)
                throw new ArgumentNullException("message");
 
            Stream stream;
            int variantType;
            if (obj is string)
            {
                int size = ((string)obj).Length * 2;
                if (this.internalBuffer == null || this.internalBuffer.Length < size)
                    this.internalBuffer = new byte[size];
 
                if (unicodeEncoding == null)
                    this.unicodeEncoding = new UnicodeEncoding();
 
                this.unicodeEncoding.GetBytes(((string)obj).ToCharArray(), 0, size / 2, this.internalBuffer, 0);
                message.properties.SetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY, this.internalBuffer);
                message.properties.AdjustSize(NativeMethods.MESSAGE_PROPID_BODY, size);
                message.properties.SetUI4(NativeMethods.MESSAGE_PROPID_BODY_SIZE, size);
                message.properties.SetUI4(NativeMethods.MESSAGE_PROPID_BODY_TYPE, VT_LPWSTR);
                return;
            }
            else if (obj is byte[])
            {
                byte[] bytes = (byte[])obj;
                if (this.internalBuffer == null || this.internalBuffer.Length < bytes.Length)
                    this.internalBuffer = new byte[bytes.Length];
 
                Array.Copy(bytes, this.internalBuffer, bytes.Length);
                message.properties.SetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY, this.internalBuffer);
                message.properties.AdjustSize(NativeMethods.MESSAGE_PROPID_BODY, bytes.Length);
                message.properties.SetUI4(NativeMethods.MESSAGE_PROPID_BODY_SIZE, bytes.Length);
                message.properties.SetUI4(NativeMethods.MESSAGE_PROPID_BODY_TYPE, VT_UI1 | VT_VECTOR);
                return;
            }
            else if (obj is char[])
            {
                char[] chars = (char[])obj;
                int size = chars.Length * 2;
                if (this.internalBuffer == null || this.internalBuffer.Length < size)
                    this.internalBuffer = new byte[size];
 
                if (unicodeEncoding == null)
                    this.unicodeEncoding = new UnicodeEncoding();
 
                this.unicodeEncoding.GetBytes(chars, 0, size / 2, this.internalBuffer, 0);
                message.properties.SetUI1Vector(NativeMethods.MESSAGE_PROPID_BODY, this.internalBuffer);
                message.properties.SetUI4(NativeMethods.MESSAGE_PROPID_BODY_SIZE, size);
                message.properties.SetUI4(NativeMethods.MESSAGE_PROPID_BODY_TYPE, VT_LPWSTR);
                return;
            }
            else if (obj is byte)
            {
                stream = new MemoryStream(1);
                stream.Write(new byte[] { (byte)obj }, 0, 1);
                variantType = VT_UI1;
            }
            else if (obj is bool)
            {
                stream = new MemoryStream(1);
                if ((bool)obj)
                    stream.Write(new byte[] { 0xff }, 0, 1);
                else
                    stream.Write(new byte[] { 0x00 }, 0, 1);
                variantType = VT_BOOL;
            }
            else if (obj is char)
            {
                stream = new MemoryStream(2);
                byte[] bytes = BitConverter.GetBytes((Char)obj);
                stream.Write(bytes, 0, 2);
                variantType = VT_UI2;
            }
            else if (obj is Decimal)
            {
                stream = new MemoryStream(8);
                byte[] bytes = BitConverter.GetBytes(Decimal.ToOACurrency((Decimal)obj));
                stream.Write(bytes, 0, 8);
                variantType = VT_CY;
            }
            else if (obj is DateTime)
            {
                stream = new MemoryStream(8);
                byte[] bytes = BitConverter.GetBytes(((DateTime)obj).Ticks);
                stream.Write(bytes, 0, 8);
                variantType = VT_DATE;
            }
            else if (obj is Double)
            {
                stream = new MemoryStream(8);
                byte[] bytes = BitConverter.GetBytes((Double)obj);
                stream.Write(bytes, 0, 8);
                variantType = VT_R8;
            }
            else if (obj is Int16)
            {
                stream = new MemoryStream(2);
                byte[] bytes = BitConverter.GetBytes((short)obj);
                stream.Write(bytes, 0, 2);
                variantType = VT_I2;
            }
            else if (obj is UInt16)
            {
                stream = new MemoryStream(2);
                byte[] bytes = BitConverter.GetBytes((UInt16)obj);
                stream.Write(bytes, 0, 2);
                variantType = VT_UI2;
            }
            else if (obj is Int32)
            {
                stream = new MemoryStream(4);
                byte[] bytes = BitConverter.GetBytes((int)obj);
                stream.Write(bytes, 0, 4);
                variantType = VT_I4;
            }
            else if (obj is UInt32)
            {
                stream = new MemoryStream(4);
                byte[] bytes = BitConverter.GetBytes((UInt32)obj);
                stream.Write(bytes, 0, 4);
                variantType = VT_UI4;
            }
            else if (obj is Int64)
            {
                stream = new MemoryStream(8);
                byte[] bytes = BitConverter.GetBytes((Int64)obj);
                stream.Write(bytes, 0, 8);
                variantType = VT_I8;
            }
            else if (obj is UInt64)
            {
                stream = new MemoryStream(8);
                byte[] bytes = BitConverter.GetBytes((UInt64)obj);
                stream.Write(bytes, 0, 8);
                variantType = VT_UI8;
            }
            else if (obj is Single)
            {
                stream = new MemoryStream(4);
                byte[] bytes = BitConverter.GetBytes((float)obj);
                stream.Write(bytes, 0, 4);
                variantType = VT_R4;
            }
            else if (obj is IPersistStream)
            {
                IPersistStream pstream = (IPersistStream)obj;
                ComStreamFromDataStream comStream = new ComStreamFromDataStream(new MemoryStream());
                NativeMethods.OleSaveToStream(pstream, comStream);
                stream = comStream.GetDataStream();
                variantType = VT_STREAMED_OBJECT;
            }
            else if (obj == null)
            {
                stream = new MemoryStream();
                variantType = VT_NULL;
            }
            else
            {
                throw new InvalidOperationException(Res.GetString(Res.InvalidTypeSerialization));
            }
 
            message.BodyStream = stream;
            message.BodyType = variantType;
        }
 
        [ComVisible(false)]
        private class ComStreamFromDataStream : IStream
        {
            private Stream dataStream;
 
            // to support seeking ahead of the stream length...
            private long virtualPosition = -1;
 
            public ComStreamFromDataStream(Stream dataStream)
            {
                if (dataStream == null) throw new ArgumentNullException("dataStream");
                this.dataStream = dataStream;
            }
 
 
            private void ActualizeVirtualPosition()
            {
                if (virtualPosition == -1) return;
 
                if (virtualPosition > dataStream.Length)
                    dataStream.SetLength(virtualPosition);
 
                dataStream.Position = virtualPosition;
 
                virtualPosition = -1;
            }
 
            public IStream Clone()
            {
                NotImplemented();
                return null;
            }
 
            public void Commit(int grfCommitFlags)
            {
                dataStream.Flush();
                // Extend the length of the file if needed.
                ActualizeVirtualPosition();
            }
 
            public long CopyTo(IStream pstm, long cb, long[] pcbRead)
            {
                int bufSize = 4096;
                IntPtr buffer = Marshal.AllocHGlobal((IntPtr)bufSize);
                if (buffer == IntPtr.Zero) throw new OutOfMemoryException();
                long written = 0;
                try
                {
                    while (written < cb)
                    {
                        int toRead = bufSize;
                        if (written + toRead > cb) toRead = (int)(cb - written);
                        int read = Read(buffer, toRead);
                        if (read == 0) break;
                        if (pstm.Write(buffer, read) != read)
                        {
                            throw EFail(Res.GetString(Res.IncorrectNumberOfBytes));
                        }
                        written += read;
                    }
                }
                finally
                {
                    Marshal.FreeHGlobal(buffer);
                }
                if (pcbRead != null && pcbRead.Length > 0)
                {
                    pcbRead[0] = written;
                }
 
                return written;
            }
 
            public Stream GetDataStream()
            {
                return dataStream;
            }
 
            public void LockRegion(long libOffset, long cb, int dwLockType)
            {
            }
 
            protected static ExternalException EFail(string msg)
            {
                ExternalException e = new ExternalException(msg, NativeMethods.E_FAIL);
                throw e;
            }
 
            protected static void NotImplemented()
            {
                ExternalException e = new ExternalException(Res.GetString(Res.NotImplemented), NativeMethods.E_NOTIMPL);
                throw e;
            }
 
            public int Read(IntPtr buf, int length)
            {
                byte[] buffer = new byte[length];
                int count = Read(buffer, length);
                Marshal.Copy(buffer, 0, buf, length);
                return count;
            }
 
            public int Read(byte[] buffer, int length)
            {
                ActualizeVirtualPosition();
                return dataStream.Read(buffer, 0, length);
            }
 
            public void Revert()
            {
                NotImplemented();
            }
 
            public long Seek(long offset, int origin)
            {
                long pos = virtualPosition;
                if (virtualPosition == -1)
                {
                    pos = dataStream.Position;
                }
                long len = dataStream.Length;
                switch (origin)
                {
                    case NativeMethods.STREAM_SEEK_SET:
                        if (offset <= len)
                        {
                            dataStream.Position = offset;
                            virtualPosition = -1;
                        }
                        else
                        {
                            virtualPosition = offset;
                        }
                        break;
                    case NativeMethods.STREAM_SEEK_END:
                        if (offset <= 0)
                        {
                            dataStream.Position = len + offset;
                            virtualPosition = -1;
                        }
                        else
                        {
                            virtualPosition = len + offset;
                        }
                        break;
                    case NativeMethods.STREAM_SEEK_CUR:
                        if (offset + pos <= len)
                        {
                            dataStream.Position = pos + offset;
                            virtualPosition = -1;
                        }
                        else
                        {
                            virtualPosition = offset + pos;
                        }
                        break;
                }
                if (virtualPosition != -1)
                {
                    return virtualPosition;
                }
                else
                {
                    return dataStream.Position;
                }
            }
 
            public void SetSize(long value)
            {
                dataStream.SetLength(value);
            }
 
            public void Stat(IntPtr pstatstg, int grfStatFlag)
            {
                // GpStream has a partial implementation, but it's so partial rather 
                // restrict it to use with GDI+
                NotImplemented();
            }
 
            public void UnlockRegion(long libOffset, long cb, int dwLockType)
            {
            }
 
            public int Write(IntPtr buf, int length)
            {
                byte[] buffer = new byte[length];
                Marshal.Copy(buf, buffer, 0, length);
                return Write(buffer, length);
            }
 
            public int Write(byte[] buffer, int length)
            {
                ActualizeVirtualPosition();
                dataStream.Write(buffer, 0, length);
                return length;
            }
        }
    }
}