vbAccelerator - Contents of code file: SimpleInterprocessCommunicationsVB_CopyData.vb

Namespace vbAccelerator.Components.Win32

    Public Delegate Sub DataReceivedEventHandler(ByVal sender As Object, ByVal
     e As DataReceivedEventArgs)

    ''' <summary>
    ''' A class which wraps using Windows native WM_COPYDATA
    ''' message to send interprocess data between applications.
    ''' This is a simple technique for interprocess data sends
    ''' using Windows.  The alternative to this is to use
    ''' Remoting, which requires a network card and a way
    ''' to register the Remoting name of an object so it
    ''' can be read by other applications.
    ''' </summary>
    Public Class CopyData
        Inherits NativeWindow
        Implements IDisposable

        ''' <summary>
        ''' Event raised when data is received on any of the channels 
        ''' this class is subscribed to.
        ''' </summary>
        Public Event DataReceived As DataReceivedEventHandler

        <System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServi
        ces.LayoutKind.Sequential)> _
        Private Structure COPYDATASTRUCT
            Public dwData As IntPtr
            Public cbData As Integer
            Public lpData As Integer
        End Structure

        Private Const WM_COPYDATA As Integer = &H4A
        Private Const WM_DESTROY As Integer = &H2

#Region "Member Variables"
        Private m_channels As CopyDataChannels = Nothing
        Private m_disposed As Boolean = False
#End Region

        ''' <summary>
        ''' Override for a form's Window Procedure to handle WM_COPYDATA
        ''' messages sent by other instances of this class.
        ''' </summary>
        ''' <param name="m">The Windows Message information.</param>
        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            If (m.Msg = WM_COPYDATA) Then
                Dim cds As COPYDATASTRUCT = New COPYDATASTRUCT()
                cds =
                 System.Runtime.InteropServices.Marshal.PtrToStructure(m.LParam,
                 cds.GetType())
                If (cds.cbData > 0) Then
                    Dim data(cds.cbData - 1) As Byte
                    Dim source As IntPtr = New IntPtr(cds.lpData)
                    Dim length As Integer = cds.cbData
                    System.Runtime.InteropServices.Marshal.Copy(source, data,
                     0, length)
                    Dim stream As System.IO.MemoryStream = New
                     System.IO.MemoryStream(data)
                    Dim b As
                     System.Runtime.Serialization.Formatters.Binary.BinaryFormat
                    ter = New
                     System.Runtime.Serialization.Formatters.Binary.BinaryFormat
                    ter()
                    Dim cdo As CopyDataObjectData = b.Deserialize(stream)

                    If (channels.Contains(cdo.Channel)) Then
                        Dim d As DataReceivedEventArgs = New
                         DataReceivedEventArgs(cdo.Channel, cdo.Data, cdo.Sent)
                        OnDataReceived(d)
                        m.Result = New IntPtr(1)
                    End If
                End If
            ElseIf (m.Msg = WM_DESTROY) Then
                ' WM_DESTROY fires before OnHandleChanged and is
                ' a better place to ensure that we've cleared 
                ' everything up.
                channels.OnHandleChange()
                MyBase.OnHandleChange()
            End If
            MyBase.WndProc(m)
        End Sub

        ''' <summary>
        ''' Raises the DataReceived event from this class.
        ''' </summary>
        ''' <param name="e">The data which has been received.</param>
        Overridable Sub OnDataReceived(ByVal e As DataReceivedEventArgs)
            RaiseEvent DataReceived(Me, e)
        End Sub

        ''' <summary>
        ''' If the form's handle changes, the properties associated
        ''' with the window need to be cleared up. This override ensures
        ''' that it is done.  Note that the CopyData class will then
        ''' stop responding to events and it should be recreated once
        ''' the new handle has been assigned.
        ''' </summary>
        Protected Overrides Sub OnHandleChange()
            ' need to clear up everything we had set.
            channels.OnHandleChange()
            MyBase.OnHandleChange()
        End Sub

        ''' <summary>
        ''' Gets the collection of channels.
        ''' </summary>
        Public ReadOnly Property Channels() As CopyDataChannels
            Get
                Return m_channels
            End Get
        End Property

        ''' <summary>
        ''' Clears up any resources associated with this object.
        ''' </summary>
        Public Sub Dispose() _
            Implements IDisposable.Dispose
            If Not (m_disposed) Then
                Channels.Clear()
                m_channels = Nothing
                m_disposed = True
                GC.SuppressFinalize(Me)
            End If
        End Sub

        ''' <summary>
        ''' Constructs a new instance of the CopyData class
        ''' </summary>
        Public Sub New()
            m_channels = New CopyDataChannels(Me)
        End Sub

        ''' <summary>
        ''' Finalises a CopyData class which has not been disposed.
        ''' There may be a minor resource leak if this class is finalised
        ''' after the form it is associated with.
        ''' </summary>
        Protected Overrides Sub Finalize()
            MyBase.Finalize()
            Dispose()
        End Sub
    End Class

    ''' <summary>
    ''' Contains data and other information associated with data
    ''' which has been sent from another application.
    ''' </summary>
    Public Class DataReceivedEventArgs

        Private m_channelName As String = ""
        Private m_data As Object = Nothing
        Private m_sent As DateTime
        Private m_received As DateTime

        ''' <summary>
        ''' Gets the channel name that this data was sent on.
        ''' </summary>
        Public ReadOnly Property ChannelName() As String
            Get
                Return m_channelName
            End Get
        End Property

        ''' <summary>
        ''' Gets the data object which was sent.
        ''' </summary>
        Public ReadOnly Property Data() As Object
            Get
                Return m_data
            End Get
        End Property

        ''' <summary>
        ''' Gets the date and time which at the data was sent
        ''' by the sending application.
        ''' </summary>
        Public ReadOnly Property Sent() As DateTime
            Get
                Return m_sent
            End Get
        End Property

        ''' <summary>
        ''' Gets the date and time which this data item as
        ''' received.
        ''' </summary>
        Public ReadOnly Property Received() As DateTime
            Get
                Return m_received
            End Get
        End Property

        ''' <summary>
        ''' Constructs an instance of this class.
        ''' </summary>
        ''' <param name="channelName">The channel that the data was received
         from</param>
        ''' <param name="data">The data which was sent</param>
        ''' <param name="sent">The date and time the data was sent</param>
        Friend Sub New(ByVal channelName As String, ByVal data As Object, ByVal
         sent As DateTime)
            m_channelName = channelName
            m_data = data
            m_sent = sent
            m_received = DateTime.Now
        End Sub
    End Class

    ''' <summary>
    ''' A strongly-typed collection of channels associated with the CopyData
    ''' class.
    ''' </summary>
    Public Class CopyDataChannels
        Inherits DictionaryBase

        Private m_owner As NativeWindow = Nothing

        ''' <summary>
        ''' Returns an enumerator for each of the CopyDataChannel objects
        ''' within this collection.
        ''' </summary>
        ''' <returns>An enumerator for each of the CopyDataChannel objects
        ''' within this collection.</returns>
        Public Shadows Function GetEnumerator() As
         System.Collections.IEnumerator
            Return Me.Dictionary.Values.GetEnumerator()
        End Function

        ''' <summary>
        ''' Returns the CopyDataChannel at the specified 0-based index.
        ''' </summary>
        Default Public ReadOnly Property Item(ByVal Index As Integer) As
         CopyDataChannel
            Get
                Dim ret As CopyDataChannel = Nothing
                Dim i As Integer = 0
                Dim cdc As CopyDataChannel
                For Each cdc In Me.Dictionary.Values
                    i += 1
                    If (i = index) Then
                        ret = cdc
                        Exit For
                    End If
                Next
                Return ret
            End Get
        End Property

        ''' <summary>
        ''' Returns the CopyDataChannel for the specified channelName
        ''' </summary>
        Default Public ReadOnly Property Item(ByVal channelName As String) As
         CopyDataChannel
            Get
                Return Me.Dictionary(channelName)
            End Get
        End Property

        ''' <summary>
        ''' Adds a new channel on which this application can send and
        ''' receive messages.
        ''' </summary>
        Public Sub Add(ByVal channelName As String)
            Dim cdc As CopyDataChannel = New CopyDataChannel(m_owner,
             channelName)
            Me.Dictionary.Add(channelName, cdc)
        End Sub

        ''' <summary>
        ''' Removes an existing channel.
        ''' </summary>
        ''' <param name="channelName">The channel to remove</param>
        Public Sub Remove(ByVal channelName As String)
            Me.Dictionary.Remove(channelName)
        End Sub

        ''' <summary>
        ''' Gets/sets whether this channel contains a CopyDataChannel
        ''' for the specified channelName.
        ''' </summary>
        Public Function Contains(ByVal channelName As String) As Boolean
            Return Me.Dictionary.Contains(channelName)
        End Function

        ''' <summary>
        ''' Ensures the resources associated with a CopyDataChannel
        ''' object collected by this class are cleared up.
        ''' </summary>
        Protected Overrides Sub OnClear()
            Dim cdc As CopyDataChannel
            For Each cdc In Me.Dictionary.Values
                cdc.Dispose()
            Next
            MyBase.OnClear()
        End Sub

        ''' <summary>
        ''' Ensures any resoures associated with the CopyDataChannel object
        ''' which has been removed are cleared up.
        ''' </summary>
        ''' <param name="key">The channelName</param>
        ''' <param name="data">The CopyDataChannel object which has
        ''' just been removed</param>
        Protected Overrides Sub OnRemoveComplete(ByVal key As Object, ByVal
         data As Object)
            Dim cdc As CopyDataChannel
            cdc = data
            cdc.Dispose()
            MyBase.OnRemove(key, data)
        End Sub

        ''' <summary>
        ''' If the form's handle changes, the properties associated
        ''' with the window need to be cleared up. This override ensures
        ''' that it is done.  Note that the CopyData class will then
        ''' stop responding to events and it should be recreated once
        ''' the new handle has been assigned.
        ''' </summary>
        Public Sub OnHandleChange()

            Dim cdc As CopyDataChannel
            For Each cdc In Me.Dictionary.Values
                cdc.OnHandleChange()
            Next

        End Sub

        ''' <summary>
        ''' Constructs a new instance of the CopyDataChannels collection.
        ''' Automatically managed by the CopyData class.
        ''' </summary>
        ''' <param name="owner">The NativeWindow this collection
        ''' will be associated with</param>
        Friend Sub New(ByVal owner As NativeWindow)
            m_owner = owner
        End Sub
    End Class


    ''' <summary>
    ''' A channel on which messages can be sent.
    ''' </summary>
    Public Class CopyDataChannel
        Implements IDisposable

#Region " Unmanaged Code"
        Private Declare Auto Function GetProp Lib "user32" ( _
         ByVal hwnd As IntPtr, _
         ByVal lpString As String) As Integer
        Private Declare Auto Function SetProp Lib "user32" ( _
         ByVal hwnd As IntPtr, _
         ByVal lpString As String, _
         ByVal hData As IntPtr) As Integer
        Private Declare Auto Function RemoveProp Lib "user32" ( _
         ByVal hwnd As IntPtr, _
         ByVal lpString As String) As Integer

        Private Declare Auto Function SendMessage Lib "user32" ( _
         ByVal hwnd As IntPtr, _
         ByVal wMsg As Integer, _
         ByVal wParam As Integer, _
         ByRef lParam As COPYDATASTRUCT _
         ) As Integer

        <System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServi
        ces.LayoutKind.Sequential)> _
        Private Structure COPYDATASTRUCT
            Public dwData As IntPtr
            Public cbData As Integer
            Public lpData As IntPtr
        End Structure

        Private Const WM_COPYDATA As Integer = &H4A
#End Region

#Region " Member Variables"
        Private m_channelName As String = ""
        Private m_disposed As Boolean = False
        Private m_owner As NativeWindow = Nothing
        Private m_recreateChannel As Boolean = False
#End Region

        ''' <summary>
        ''' Gets the name associated with this channel.
        ''' </summary>
        Public ReadOnly Property ChannelName() As String
            Get
                Return m_channelName
            End Get
        End Property

        ''' <summary>
        ''' Sends the specified object on this channel to any other
        ''' applications which are listening.  The object must have the
        ''' SerializableAttribute set, or must implement ISerializable.
        ''' </summary>
        ''' <param name="obj">The object to send</param>
        ''' <returns>The number of recipients</returns>
        Public Function Send(ByVal obj As Object) As Integer

            Dim recipients As Integer = 0

            If (m_disposed) Then
                Throw New InvalidOperationException("Object has been disposed")
            Else

                If (m_recreateChannel) Then ' handle has changed
                    addChannel()
                End If

                Dim cdo As CopyDataObjectData = New CopyDataObjectData(obj,
                 m_channelName)

                ' Try to do a binary serialization on obj.
                ' This will throw and exception if the object to
                ' be passed isn't serializable.
                Dim b As
                 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
                 = New
                 System.Runtime.Serialization.Formatters.Binary.BinaryFormatter(
                )
                Dim stream As System.IO.MemoryStream = New
                 System.IO.MemoryStream()
                b.Serialize(stream, cdo)
                stream.Flush()

                ' Now move the data into a pointer so we can send
                ' it using WM_COPYDATA:
                ' Get the length of the data:
                Dim dataSize As Integer = stream.Length
                If (dataSize > 0) Then
                    ' This isn't very efficient if your data is very large.
                    ' First we copy to a byte array, then copy to a CoTask 
                    ' Mem object... And when we use WM_COPYDATA windows will
                    ' make yet another copy!  But if you're talking about 4K
                    ' or less of data then it doesn't really matter.
                    Dim data(dataSize) As Byte
                    stream.Seek(0, System.IO.SeekOrigin.Begin)
                    stream.Read(data, 0, dataSize)
                    Dim ptrData As IntPtr =
                     System.Runtime.InteropServices.Marshal.AllocCoTaskMem(dataS
                    ize)
                    System.Runtime.InteropServices.Marshal.Copy(data, 0,
                     ptrData, dataSize)

                    ' Enumerate all windows which have the
                    ' channel name, send the data to each one
                    Dim ew As EnumWindows = New EnumWindows()
                    ew.GetWindows()

                    ' Send the data to each window identified on
                    ' the channel:
                    Dim window As EnumWindowsItem
                    For Each window In ew.Items
                        If Not (window.Handle.Equals(m_owner.Handle)) Then
                            If (GetProp(window.Handle, Me.ChannelName) <> 0)
                             Then
                                Dim cds As COPYDATASTRUCT = New COPYDATASTRUCT()
                                cds.cbData = dataSize
                                cds.dwData = IntPtr.Zero
                                cds.lpData = ptrData
                                Dim res As Integer = SendMessage(window.Handle,
                                 WM_COPYDATA, m_owner.Handle.ToInt32(), cds)
                                recipients +=
                                 IIf(System.Runtime.InteropServices.Marshal.GetL
                                astWin32Error() = 0, 1, 0)
                            End If
                        End If
                    Next

                    ' Clear up the data:
                    System.Runtime.InteropServices.Marshal.FreeCoTaskMem(ptrData
                    )
                End If
                stream.Close()
            End If
            Return recipients
        End Function

        Private Sub addChannel()
            ' Tag this window with property "channelName"
            SetProp(m_owner.Handle, m_channelName, m_owner.Handle)
        End Sub

        Private Sub removeChannel()
            ' Remove the "channelName" property from this window
            RemoveProp(m_owner.Handle, m_channelName)
        End Sub

        ''' <summary>
        ''' If the form's handle changes, the properties associated
        ''' with the window need to be cleared up. This method ensures
        ''' that it is done.  Note that the CopyData class will then
        ''' stop responding to events and it should be recreated once
        ''' the new handle has been assigned.
        ''' </summary>
        Public Sub OnHandleChange()
            removeChannel()
            m_recreateChannel = True
        End Sub

        ''' <summary>
        ''' Clears up any resources associated with this channel.
        ''' </summary>
        Public Sub Dispose() _
            Implements IDisposable.Dispose
            If Not (m_disposed) Then
                If (ChannelName.Length > 0) Then
                    removeChannel()
                End If
                m_channelName = ""
                m_disposed = True
                GC.SuppressFinalize(Me)
            End If
        End Sub

        ''' <summary>
        ''' Constructs a new instance of a CopyData channel.  Called
        ''' automatically by the CopyDataChannels collection.
        ''' </summary>
        ''' <param name="owner">The owning native window</param>
        ''' <param name="channelName">The name of the channel to
        ''' send messages on</param>
        Friend Sub New(ByVal owner As NativeWindow, ByVal channelName As String)
            m_owner = owner
            m_channelName = channelName
            addChannel()
        End Sub

        Protected Overrides Sub Finalize()
            Dispose()
            MyBase.Finalize()
        End Sub
    End Class

    ''' <summary>
    ''' A class which wraps the data being copied, used
    ''' internally within the CopyData class objects.
    ''' </summary>
    <Serializable()> _
    Friend Class CopyDataObjectData
        ''' <summary>
        ''' The Object to copy.  Must be Serializable.
        ''' </summary>
        Public Data As Object
        ''' <summary>
        ''' The date and time this object was sent.
        ''' </summary>
        Public Sent As DateTime
        ''' <summary>
        ''' The name of the channel this object is being sent on
        ''' </summary>
        Public Channel As String

        ''' <summary>
        ''' Constructs a new instance of this object
        ''' </summary>
        ''' <param name="data">The data to copy</param>
        ''' <param name="channel">The channel name to send on</param>
        ''' <exception cref="ArgumentException">If data is not
         serializable.</exception>
        Public Sub New(ByVal theData As Object, ByVal theChannel As String)
            Data = theData
            If Not (Data.GetType().IsSerializable) Then
                Throw New ArgumentException("Data object must be
                 serializable.", "data")
            End If
            Channel = theChannel
            Sent = DateTime.Now
        End Sub
    End Class

end namespace