65.9K
CodeProject 正在变化。 阅读更多。
Home

DDEListener组件(用Explorer打开您的文档文件)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (12投票s)

2002 年 10 月 22 日

2分钟阅读

viewsIcon

112448

downloadIcon

1301

处理DDE_EXECUTE消息。

引言

当您希望 Windows 资源管理器在您的应用程序中打开文档文件时,如果您的应用程序尚未打开,则不会出现问题。 资源管理器会打开您的应用程序并将文档文件作为参数传递,但是当您的应用程序已经打开时,资源管理器将打开您的应用程序的新实例! 例如,在 MDI 应用程序中,您希望文件仅在一个实例中打开。

如果遇到此问题,您必须使用 DDE 与您的应用程序实例进行通信。 DDE 使用 Windows 消息,是 Windows 3.1 的遗留产物

它是如何工作的?

该组件创建一个虚拟窗口,该窗口具有消息泵,并等待 WM_DDE_* 消息。 当收到 WM_DDE_EXECUTE 时,它会触发 DDEExecute 事件。

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);
    }
}

这是 NativeWindowWithMessages 类,它继承自 Windows.Forms.NativeWindow,并为消息处理提供了一个事件。 事件处理程序处理完整的 DDE 过程

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;
    }
}

原生 Win32 调用用于消息处理 (GlobalLock, GlobalUnlock, GlobalAddAtom, GlobalDeleteAtom, PackDDElParam) 以及来自 System.Runtime.InteropService 的字符串转换函数 (Marshal.PtrToStringAuto, Marshal.StringToHGlobalAnsi)

  • GlobalLock / GlobalUnlock - 锁定/解锁一个 Windows 句柄到全局内存
  • GlobalAddAtom / GlobalDeleteAtom - 唯一的名称句柄
  • PackDDElParam - 一个 DDE 函数,将数据打包到消息参数中
  • Marshal.PtrToStringAuto - 从指针读取字符串,自动处理 Unicode 或普通字符串
  • Marshal.StringToHGlobalAnsi - 返回指向 ANSI 字符串的指针

AppName 和 ActionName 属性

/// <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;}
}

启动 DDE 对话的客户端必须指定一个应用程序名称和一个操作名称,DDEListener 组件必须检查这两个名称。 这很重要,因为 Windows 资源管理器会将 WM_DDE_Initiate 消息发送到所有窗口,并等待答案。 因此,DDEListener 组件仅回复具有相等 AppNameActionName 的消息,否则它将阻止其他应用程序。

安装

当您使用此类或示例项目时,请记住您必须将文件类型注册到您的应用程序。

  • 转到资源管理器中的菜单“额外”打开“文件夹选项”,然后转到“文件类型”选项卡。
  • 添加或编辑文件扩展名,添加新操作并指定您的项目或示例项目的可执行文件。
  • 选中 DDE 复选框,并输入例如 [open("%1")] 作为命令
  • 您必须指定一个应用程序名称,您也在项目中使用该名称:CPDDETest
  • 您必须指定一个操作名称,您也在项目中使用该名称:System

应用并在资源管理器中打开文件类型。 示例项目可执行文件应该启动。 当您在资源管理器中打开一个文件并且示例项目可执行文件正在运行时,它应该显示一个消息框,显示命令文本。

© . All rights reserved.