Click here to Skip to main content
15,878,871 members
Articles / Programming Languages / C#
Article

DDEListener Component (open your Document Files with Explorer)

Rate me:
Please Sign up or sign in to vote.
4.75/5 (12 votes)
22 Oct 20022 min read 111K   1.3K   36   13
Handles DDE_EXECUTE messages

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.

C#
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:

C#
protected void MessageEvent(object sender,ref Message m,
                            ref bool Handled)
{
    //A client wants to Initiate a DDE connection
    if ((m.Msg==(int)Win32.Msgs.WM_DDE_INITIATE))
    {
        System.Diagnostics.Debug.WriteLine("WM_DDE_INITIATE!");
        //Get the ATOMs for AppName and ActionName
        ushort a1=Win32.Kernel32.GlobalAddAtom(
            Marshal.StringToHGlobalAnsi(m_AppName));
        ushort a2=Win32.Kernel32.GlobalAddAtom(
            Marshal.StringToHGlobalAnsi(m_ActionName));

        //The LParam of the Message contains the 
        //ATOMs for AppName and ActionName
        ushort s1 = (ushort)(((uint)m.LParam) & 0xFFFF);
        ushort s2 = (ushort)((((uint)m.LParam) & 0xFFFF0000) >> 16);

        //Return when the ATOMs are not equal.
        if ((a1!=s1)||(a2!=s2)) return;
        //At this point we know that this application 
        //should answer, so we send
        //a WM_DDE_ACK message confirming the connection
        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);
        //Release ATOMs
        Win32.Kernel32.GlobalDeleteAtom(a1);
        Win32.Kernel32.GlobalDeleteAtom(a2);
        isInitiated=true;
        Handled=true;
    }

    //After the connection is established the Client
    //should send a WM_DDE_EXECUTE message
    if ((m.Msg==(int)Win32.Msgs.WM_DDE_EXECUTE))
    {
        System.Diagnostics.Debug.WriteLine("WM_DDE_EXECUTE!");
        //prevent errors
        if (!isInitiated) return;
        //LParam contains the Execute string, so we must 
        //Lock the memory block passed and
        //read the string. The Marshal class provides helper functions
        IntPtr pV=Win32.Kernel32.GlobalLock(m.LParam);
        string s3 =
            System.Runtime.InteropServices.Marshal.PtrToStringAuto(pV);
        Win32.Kernel32.GlobalUnlock(m.LParam);
        //After the message has been processed, 
        //a WM_DDE_ACK message is sent
        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);
        //now we split the string in Parts (every command 
        //should have [] around the text)
        //the client could send multiple commands
        string[] sarr=s3.Split(new char[]{'[',']'});
        if (sarr.GetUpperBound(0)>-1)
        {
            //and fire the event, passing the array of strings
            if (OnDDEExecute!=null) OnDDEExecute(this,sarr);
        }
        Handled=true;
    }
    //After the WM_DDE_EXECUTE message the client should 
    //Terminate the connection
    if (m.Msg==(int)Win32.Msgs.WM_DDE_TERMINATE)
    {
        System.Diagnostics.Debug.WriteLine("WM_DDE_TERMINATE");
        if (!isInitiated) return;
        //Confirm termination
        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

C#
/// <SUMMARY>
/// The Application Name to listen for
/// </SUMMARY>
public string AppName
{
    get{return m_AppName;}
    set{m_AppName=value;}
}
/// <SUMMARY>
/// The Action Name to listen for
/// </SUMMARY>
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.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralDDE interaction with Word documents Pin
AjaySaic20-Mar-08 0:32
AjaySaic20-Mar-08 0:32 
GeneralNice Job Pin
Sprotty19-Jul-07 0:31
Sprotty19-Jul-07 0:31 
GeneralUnicode Ansi Pin
Member 6454431-Feb-07 3:07
Member 6454431-Feb-07 3:07 
GeneralI does not work Pin
Stefan Repas28-Apr-04 5:55
Stefan Repas28-Apr-04 5:55 
GeneralRe: I does not work Pin
Alexander Werner28-Apr-04 21:06
Alexander Werner28-Apr-04 21:06 
The instance of the class is created in the InitializeComponent() function created by the Windows Forms Designer...
Make sure that the Properties "ActionName" and "AppName" are set to the same values used for making the dde request!
GeneralAnother way... Pin
benjamin Raibaud9-Jan-04 8:03
benjamin Raibaud9-Jan-04 8:03 
GeneralActionName Pin
swede13-Aug-03 21:55
swede13-Aug-03 21:55 
QuestionDDEClient? Pin
K-Dub25-Jun-03 17:20
K-Dub25-Jun-03 17:20 
GeneralThere is another way... Pin
Richard Deeming22-Oct-02 22:44
mveRichard Deeming22-Oct-02 22:44 
GeneralRe: There is another way... Pin
Heath Stewart23-Jan-03 2:27
protectorHeath Stewart23-Jan-03 2:27 
GeneralRe: There is another way... Pin
sytelus27-Jan-03 9:16
sytelus27-Jan-03 9:16 
GeneralRe: There is another way... Pin
Heath Stewart10-Mar-03 2:15
protectorHeath Stewart10-Mar-03 2:15 
NewsRe: There is another way... Pin
RipplingCreek22-Sep-05 17:15
RipplingCreek22-Sep-05 17:15 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.