|
using System;
using System.Collections;
using System.IO;
using System.Runtime.InteropServices;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Windows.Forms;
namespace vbAccelerator.Components.Win32
{
public delegate void DataReceivedEventHandler(object sender, DataReceivedEventArgs e);
/// <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 : NativeWindow, IDisposable
{
/// <summary>
/// Event raised when data is received on any of the channels
/// this class is subscribed to.
/// </summary>
public event DataReceivedEventHandler DataReceived;
[StructLayout(LayoutKind.Sequential)]
private struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
private const int WM_COPYDATA = 0x4A;
private const int WM_DESTROY = 0x2;
#region Member Variables
private CopyDataChannels channels = null;
private bool disposed = false;
#endregion
/// <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 override void WndProc (ref System.Windows.Forms.Message m )
{
if (m.Msg == WM_COPYDATA)
{
COPYDATASTRUCT cds = new COPYDATASTRUCT();
cds = (COPYDATASTRUCT) Marshal.PtrToStructure(m.LParam, typeof(COPYDATASTRUCT));
if (cds.cbData > 0)
{
byte[] data = new byte[cds.cbData];
Marshal.Copy(cds.lpData, data, 0, cds.cbData);
MemoryStream stream = new MemoryStream(data);
BinaryFormatter b = new BinaryFormatter();
CopyDataObjectData cdo = (CopyDataObjectData) b.Deserialize(stream);
if (channels.Contains(cdo.Channel))
{
DataReceivedEventArgs d = new DataReceivedEventArgs(cdo.Channel, cdo.Data, cdo.Sent);
OnDataReceived(d);
m.Result = (IntPtr) 1;
}
}
}
else if (m.Msg == WM_DESTROY)
{
// WM_DESTROY fires before OnHandleChanged and is
// a better place to ensure that we've cleared
// everything up.
channels.OnHandleChange();
base.OnHandleChange();
}
base.WndProc(ref m);
}
/// <summary>
/// Raises the DataReceived event from this class.
/// </summary>
/// <param name="e">The data which has been received.</param>
protected void OnDataReceived(DataReceivedEventArgs e)
{
DataReceived(this, e);
}
/// <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 override void OnHandleChange ()
{
// need to clear up everything we had set.
channels.OnHandleChange();
base.OnHandleChange();
}
/// <summary>
/// Gets the collection of channels.
/// </summary>
public CopyDataChannels Channels
{
get
{
return this.channels;
}
}
/// <summary>
/// Clears up any resources associated with this object.
/// </summary>
public void Dispose()
{
if (!disposed)
{
channels.Clear();
channels = null;
disposed = true;
GC.SuppressFinalize(this);
}
}
/// <summary>
/// Constructs a new instance of the CopyData class
/// </summary>
public CopyData()
{
channels = new CopyDataChannels(this);
}
/// <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>
~CopyData()
{
Dispose();
}
}
/// <summary>
/// Contains data and other information associated with data
/// which has been sent from another application.
/// </summary>
public class DataReceivedEventArgs
{
private string channelName = "";
private object data = null;
private DateTime sent;
private DateTime received;
/// <summary>
/// Gets the channel name that this data was sent on.
/// </summary>
public string ChannelName
{
get
{
return this.channelName;
}
}
/// <summary>
/// Gets the data object which was sent.
/// </summary>
public Object Data
{
get
{
return this.data;
}
}
/// <summary>
/// Gets the date and time which at the data was sent
/// by the sending application.
/// </summary>
public DateTime Sent
{
get
{
return this.sent;
}
}
/// <summary>
/// Gets the date and time which this data item as
/// received.
/// </summary>
public DateTime Received
{
get
{
return this.received;
}
}
/// <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>
internal DataReceivedEventArgs(string channelName, object data, DateTime sent)
{
this.channelName = channelName;
this.data = data;
this.sent = sent;
this.received = DateTime.Now;
}
}
/// <summary>
/// A strongly-typed collection of channels associated with the CopyData
/// class.
/// </summary>
public class CopyDataChannels : DictionaryBase
{
private NativeWindow owner = null;
/// <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 new System.Collections.IEnumerator GetEnumerator ( )
{
return this.Dictionary.Values.GetEnumerator();
}
/// <summary>
/// Returns the CopyDataChannel at the specified 0-based index.
/// </summary>
public CopyDataChannel this[int index]
{
get
{
CopyDataChannel ret = null;
int i = 0;
foreach (CopyDataChannel cdc in this.Dictionary.Values)
{
i++;
if (i == index)
{
ret = cdc;
break;
}
}
return ret;
}
}
/// <summary>
/// Returns the CopyDataChannel for the specified channelName
/// </summary>
public CopyDataChannel this[string channelName]
{
get
{
return (CopyDataChannel) this.Dictionary[channelName];
}
}
/// <summary>
/// Adds a new channel on which this application can send and
/// receive messages.
/// </summary>
public void Add(string channelName)
{
CopyDataChannel cdc = new CopyDataChannel(owner, channelName);
this.Dictionary.Add(channelName , cdc);
}
/// <summary>
/// Removes an existing channel.
/// </summary>
/// <param name="channelName">The channel to remove</param>
public void Remove(string channelName)
{
this.Dictionary.Remove(channelName);
}
/// <summary>
/// Gets/sets whether this channel contains a CopyDataChannel
/// for the specified channelName.
/// </summary>
public bool Contains(string channelName)
{
return this.Dictionary.Contains(channelName);
}
/// <summary>
/// Ensures the resources associated with a CopyDataChannel
/// object collected by this class are cleared up.
/// </summary>
protected override void OnClear()
{
foreach (CopyDataChannel cdc in this.Dictionary.Values)
{
cdc.Dispose();
}
base.OnClear();
}
/// <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 override void OnRemoveComplete ( Object key , System.Object data )
{
( (CopyDataChannel) data).Dispose();
base.OnRemove(key, data);
}
/// <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 void OnHandleChange()
{
foreach (CopyDataChannel cdc in this.Dictionary.Values)
{
cdc.OnHandleChange();
}
}
/// <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>
internal CopyDataChannels(NativeWindow owner)
{
this.owner = owner;
}
}
/// <summary>
/// A channel on which messages can be sent.
/// </summary>
public class CopyDataChannel : IDisposable
{
#region Unmanaged Code
[DllImport("user32", CharSet=CharSet.Auto)]
private extern static int GetProp(
IntPtr hwnd ,
string lpString);
[DllImport("user32", CharSet=CharSet.Auto)]
private extern static int SetProp(
IntPtr hwnd ,
string lpString,
int hData);
[DllImport("user32", CharSet=CharSet.Auto)]
private extern static int RemoveProp(
IntPtr hwnd,
string lpString);
[DllImport("user32", CharSet=CharSet.Auto)]
private extern static int SendMessage(
IntPtr hwnd,
int wMsg,
int wParam,
ref COPYDATASTRUCT lParam
);
[StructLayout(LayoutKind.Sequential)]
private struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
private const int WM_COPYDATA = 0x4A;
#endregion
#region Member Variables
private string channelName = "";
private bool disposed = false;
private NativeWindow owner = null;
private bool recreateChannel = false;
#endregion
/// <summary>
/// Gets the name associated with this channel.
/// </summary>
public string ChannelName
{
get
{
return this.channelName;
}
}
/// <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 int Send(object obj)
{
int recipients = 0;
if (disposed)
{
throw new InvalidOperationException("Object has been disposed");
}
if (recreateChannel) // handle has changed
{
addChannel();
}
CopyDataObjectData cdo = new CopyDataObjectData(obj, channelName);
// Try to do a binary serialization on obj.
// This will throw and exception if the object to
// be passed isn't serializable.
BinaryFormatter b = new BinaryFormatter();
MemoryStream stream = new 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:
int dataSize = (int)stream.Length;
if (dataSize > 0)
{
// 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.
byte[] data = new byte[dataSize];
stream.Seek(0, SeekOrigin.Begin);
stream.Read(data, 0, dataSize);
IntPtr ptrData = Marshal.AllocCoTaskMem(dataSize);
Marshal.Copy(data, 0, ptrData, dataSize);
// Enumerate all windows which have the
// channel name, send the data to each one
EnumWindows ew = new EnumWindows();
ew.GetWindows();
// Send the data to each window identified on
// the channel:
foreach(EnumWindowsItem window in ew.Items)
{
if (!window.Handle.Equals(this.owner.Handle))
{
if (GetProp(window.Handle, this.channelName) != 0)
{
COPYDATASTRUCT cds = new COPYDATASTRUCT();
cds.cbData = dataSize;
cds.dwData = IntPtr.Zero;
cds.lpData = ptrData;
int res = SendMessage(window.Handle, WM_COPYDATA, (int)owner.Handle, ref cds);
recipients += (Marshal.GetLastWin32Error() == 0 ? 1 : 0);
}
}
}
// Clear up the data:
Marshal.FreeCoTaskMem(ptrData);
}
stream.Close();
return recipients;
}
private void addChannel()
{
// Tag this window with property "channelName"
SetProp(owner.Handle, this.channelName, (int)owner.Handle);
}
private void removeChannel()
{
// Remove the "channelName" property from this window
RemoveProp(owner.Handle, this.channelName);
}
/// <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 void OnHandleChange()
{
removeChannel();
recreateChannel = true;
}
/// <summary>
/// Clears up any resources associated with this channel.
/// </summary>
public void Dispose()
{
if (!disposed)
{
if (channelName.Length > 0)
{
removeChannel();
}
channelName = "";
disposed = true;
GC.SuppressFinalize(this);
}
}
/// <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>
internal CopyDataChannel(NativeWindow owner, string channelName)
{
this.owner = owner;
this.channelName = channelName;
addChannel();
}
~CopyDataChannel()
{
Dispose();
}
}
/// <summary>
/// A class which wraps the data being copied, used
/// internally within the CopyData class objects.
/// </summary>
[Serializable()]
internal class CopyDataObjectData
{
/// <summary>
/// The Object to copy. Must be Serializable.
/// </summary>
public object Data;
/// <summary>
/// The date and time this object was sent.
/// </summary>
public DateTime Sent;
/// <summary>
/// The name of the channel this object is being sent on
/// </summary>
public string Channel;
/// <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 CopyDataObjectData(object data, string channel)
{
Data = data;
if (!data.GetType().IsSerializable)
{
throw new ArgumentException("Data object must be serializable.", "data");
}
Channel = channel;
Sent = DateTime.Now;
}
}
}
|
By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.
If a file you wish to view isn't highlighted, and is a text file (not binary), please
let us know and we'll add colourisation support for it.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.