Introduction
When you want the Windows Explorer to open document files in your application you have no problem when your application is not already open. Explorer opens your app and passes the document file as parameter, but when your app is already open Explorer would open a new instance of your app! e.g.. in a MDI Application you want the file to open in only one instance.
If you have this problem, you have to use DDE to communicate with the instance of your application. DDE works with Windows messages and is a relict from Windows 3.1
How does it work?
The component creates a dummy window which has a message pump, and waits for a WM_DDE_*
messages. When WM_DDE_EXECUTE
is received it fires the DDEExecute
event.
public class NativeWindowWithMessages :
System.Windows.Forms.NativeWindow
{
public event MessageEventHandler ProcessMessage;
protected override void WndProc(ref Message m)
{
if (ProcessMessage!=null)
{
bool Handled=false;
ProcessMessage(this,ref m,ref Handled);
if (!Handled) base.WndProc(ref m);
}else base.WndProc(ref m);
}
}
This is the NativeWindowWithMessages
class which inherits from Windows.Forms.NativeWindow
and provides an event for message processing. The event handler handles the complete DDE process:
protected void MessageEvent(object sender,ref Message m,
ref bool Handled)
{
if ((m.Msg==(int)Win32.Msgs.WM_DDE_INITIATE))
{
System.Diagnostics.Debug.WriteLine("WM_DDE_INITIATE!");
ushort a1=Win32.Kernel32.GlobalAddAtom(
Marshal.StringToHGlobalAnsi(m_AppName));
ushort a2=Win32.Kernel32.GlobalAddAtom(
Marshal.StringToHGlobalAnsi(m_ActionName));
ushort s1 = (ushort)(((uint)m.LParam) & 0xFFFF);
ushort s2 = (ushort)((((uint)m.LParam) & 0xFFFF0000) >> 16);
if ((a1!=s1)||(a2!=s2)) return;
IntPtr po=Win32.User32.PackDDElParam(
(int)Msgs.WM_DDE_ACK,(IntPtr)a1,(IntPtr)a2);
Win32.User32.SendMessage(m.WParam,
(int)Msgs.WM_DDE_ACK,m_Window.Handle,po);
Win32.Kernel32.GlobalDeleteAtom(a1);
Win32.Kernel32.GlobalDeleteAtom(a2);
isInitiated=true;
Handled=true;
}
if ((m.Msg==(int)Win32.Msgs.WM_DDE_EXECUTE))
{
System.Diagnostics.Debug.WriteLine("WM_DDE_EXECUTE!");
if (!isInitiated) return;
IntPtr pV=Win32.Kernel32.GlobalLock(m.LParam);
string s3 =
System.Runtime.InteropServices.Marshal.PtrToStringAuto(pV);
Win32.Kernel32.GlobalUnlock(m.LParam);
IntPtr lP=Win32.User32.PackDDElParam(
(int)Win32.Msgs.WM_DDE_ACK,(IntPtr)1,m.LParam);
Win32.User32.PostMessage(m.WParam,
(int)Win32.Msgs.WM_DDE_ACK,m_Window.Handle,lP);
System.Diagnostics.Debug.WriteLine(s3);
string[] sarr=s3.Split(new char[]{'[',']'});
if (sarr.GetUpperBound(0)>-1)
{
if (OnDDEExecute!=null) OnDDEExecute(this,sarr);
}
Handled=true;
}
if (m.Msg==(int)Win32.Msgs.WM_DDE_TERMINATE)
{
System.Diagnostics.Debug.WriteLine("WM_DDE_TERMINATE");
if (!isInitiated) return;
Win32.User32.PostMessage(m.WParam,
(int)Win32.Msgs.WM_DDE_TERMINATE,m_Window.Handle,(IntPtr)0);
Handled=true;
isInitiated=false;
}
}
Native Win32 calls are used for Message processing (GlobalLock, GlobalUnlock, GlobalAddAtom, GlobalDeleteAtom, PackDDElParam
) and functions from System.Runtime.InteropService
for string conversion (Marshal.PtrToStringAuto, Marshal.StringToHGlobalAnsi
)
GlobalLock
/ GlobalUnlock
- Locks/Unlocks a windows handle to global memory
GlobalAddAtom
/ GlobalDeleteAtom
- a handle to a unique name
PackDDElParam
- a DDE function which packs data into a message parameter
Marshal.PtrToStringAuto
- Reads a string from a pointer, automatically handles Unicode or normal strings
Marshal.StringToHGlobalAnsi
- returns a pointer to an ANSI string
The AppName and ActionName properties
public string AppName
{
get{return m_AppName;}
set{m_AppName=value;}
}
public string ActionName
{
get{return m_ActionName;}
set{m_ActionName=value;}
}
The client which initiates the DDE conversation has to specify a Application Name and a Action Name which the DDEListener
Component has to check. This is important because the Windows Explorer sends the WM_DDE_Initiate
message to all windows, and waits for an answer. So the DDEListener
Component only answers to messages with equal AppName
and ActionName
, otherwise it would block other applications.
Setup
When you use this class or the sample project, remember that you have to register a file type to your application.
- Go to Explorer in Menu Extra open Folder-options and go to the tab "file types".
- Add or edit a file extension, add a new action and specify the executable of your project or the sample project.
- Check the DDE Checkbox and enter e.g. [open("%1")] as command
- You have to specify a Application Name that you also use in the project: CPDDETest
- You have to specify a Action Name that you also use in the project: System
Apply and open the file-type in explorer. The Sample project executable should start. When you open a file in explorer and the Sample project executable is running it should display a message box showing the command text.