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

在 Dev-Studio 宏中使用您自己的 ATL 对象

2002 年 2 月 11 日

3分钟阅读

viewsIcon

49492

downloadIcon

243

展示如何开发 ATL 对象来扩展 VB-Script。使用 ISharedPropertyGroupManager 在内存中保存数据。监控 MSDEV 关闭。

Sample Image - MakroExtensions.gif

引言

我是一个懒惰的(试图避免不必要的工作)开发者,我尝试尽可能地自动化 (使用宏或其他工具)。由于 MSDEV 中的某个错误,如果我运行一个尝试打开不存在文件的宏,我的 MSDEV 会崩溃。(Documents.Open 'SomeFile')。因为我找不到一种方法来确定文件是否存在,所以我写了这个小程序。这个项目只能在 Win2000 或 NT4.0 + option pack 下运行,因为我使用了 ISharedProperty (可以替换或删除)。

概述

这篇文章展示了这些技术的一些方面。

  • 在宏中运行所需的接口
  • 删除无模式 ATL 对话框
  • Hook 进入 Dev-Studio 以确定进程关闭
  • 使用 ISharedProperty 在动态加载/卸载的 DLL 中存储内存中的数据
  • 使用 CDialogResize 调整控件大小

实现

第一点可以很容易地解释,因为它没有什么特别之处。只需创建一个 ATL 对象并额外实现这些接口

  • IObjectSafety - 必须实现,否则无法创建对象。
  • ISupportErrorInfo - 有助于显示用户友好的错误消息。

就是这样。现在您可以添加自己的接口,但始终记住 VB-Script 只知道 VARIANT 数据类型。因此,输入和输出参数必须是 VARIANT 类型。函数

const VARIANT* GetBSTRvt(VARIANT& vt) const;

展示了如何解析一个 VARIANTVARIANT 可以包含一个值(例如 bstrVal, long 等),或者可以保存对另一个 VARIANT 的引用,或者(在我看来最糟糕的类型)可以简单地包含一个 IDispatch。在这种情况下,该函数的行为类似于 VB,并且简单地调用该对象的第一个可用方法。(希望这将返回预期的类型。)

对于上面列出的第二和第三种技术,我编写了一个模板类,可以在任何其他项目中使用。 它们在这里

// RELEASE_TRACE 
#ifndef RELEASE_TRACE
    #ifdef _USE_RELEASE_TRACE
        #define RELEASE_TRACE AtlTrace
    #else
        #define RELEASE_TRACE     1 ? (void)0 : AtlTrace
    #endif
#endif



// Template Class to derive from, for deleting modeless dialogs 
// Behaves like delete this in MFC-Dialogs in PostNcDestroy 
template <class T>
class CSelfDeleteAtlDlg
{
public:
    CSelfDeleteAtlDlg() {m_szDbgClassName = _T("Unknown");}
    // Call this method to destroy a modeless ATL-Dialog
    BOOL DestroyWindowAndDeleteThis()
    {
        static_cast<T*>(this)->m_thunk.Init( DestroyWindowProc, 
                                          static_cast<T*>(this));
        if (0 == WM_ATLDESTROY_MODELESS) 
        {
            WM_ATLDESTROY_MODELESS = 
                RegisterWindowMessage( _T("DestroyModelessAtlWindow"));
        }
        static_cast<T*>(this)->PostMessage( WM_ATLDESTROY_MODELESS);
        return TRUE;
    }
private:
    static LRESULT CALLBACK DestroyWindowProc( HWND hWnd, 
                   UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        LRESULT lResult = 0L;
        try
        {
            T* pThis = reinterpret_cast<T*>( hWnd);
            if (WM_ATLDESTROY_MODELESS == uMsg) 
            {
                ::DestroyWindow( pThis->m_hWnd);
                RELEASE_TRACE("Deleting Pointer(%s)  %08x", 
                            pThis->m_szDbgClassName, pThis);
                delete pThis;
            }
            else 
            {
                lResult = pThis->DialogProc( hWnd, uMsg, wParam, lParam);
            }
        }
        catch(...) 
        {
            ATLASSERT(false);    // Hopefully never happenes
        }
        return lResult;
    }
protected:
    LPCTSTR m_szDbgClassName;
};
__declspec(selectany) UINT  WM_ATLDESTROY_MODELESS = 0;

// This class installs a message hook to filter the WM_QUIT
// message. A derived class will be informed to do some clean up stuff
class CProcessWatcherThread
{
public:
    CProcessWatcherThread()
    {
        m_hThread                        = NULL;
        m_WaitHandles.hWatchProcess        = NULL;
        m_WaitHandles.hEventStopWatch    = NULL;
        m_pProcessWatcherThread            = this;
    }
    ~CProcessWatcherThread()
    {
        StopWatch();
        m_pProcessWatcherThread = NULL;
    }
    // This method is called if the process is shutting down
    virtual void OnProcessShutdown() = 0;
    void StartWatch()
    {
        if (m_hThread)    // allready running
            return;

        // Hook into applications main message loop
        m_hOriginalHook = SetWindowsHookEx(WH_GETMESSAGE, 
            ProcessGetMessageHook, 0, GetCurrentThreadId());
        
        // Create two events, one that
        // indicates normal exit (hEventStopWatch)
        // and another which indicated
        // Process is going down (hWatchProcess)
        m_WaitHandles.hEventStopWatch  = 
            ::CreateEvent(NULL, FALSE, FALSE, NULL);
        m_WaitHandles.hWatchProcess  = 
            ::CreateEvent(NULL, FALSE, FALSE, NULL);

        // Create a thread which is supervising the events
        m_hThread = ::CreateThread(NULL, NULL, 
                StartThread, this, 0, &m_dwThreadID);
    }
    void StopWatch()
    {
        // Signal normal exit and unhook
        StopWatch(m_WaitHandles.hEventStopWatch);
    }
private:
    void StopWatch(HANDLE hStop, bool bUnhook = true)
    {
        if (!m_WaitHandles.hEventStopWatch) return;
        ::SetEvent(hStop);
        ::WaitForSingleObject(m_hThread, 2000);

        CloseHandle(m_WaitHandles.hEventStopWatch);
        CloseHandle(m_hThread);
        CloseHandle(m_WaitHandles.hWatchProcess);
        m_WaitHandles.hEventStopWatch    = NULL;
        m_WaitHandles.hWatchProcess        = NULL;
        m_hThread                        = NULL;

        if (bUnhook)
        {
            UnhookWindowsHookEx(m_hOriginalHook);
            m_hOriginalHook = NULL;
        }
    }
    static DWORD WINAPI StartThread(LPVOID lpParameter)
    {
        CProcessWatcherThread* m_pProcessWatcherThread = 
                        (CProcessWatcherThread*) lpParameter;
        m_pProcessWatcherThread->Watch();
        return 0;
    };
    void Watch()
    {
        DWORD  dwWait = 
          ::WaitForMultipleObjects( sizeof(m_WaitHandles) / sizeof(HANDLE), 
          &m_WaitHandles.hWatchProcess , FALSE, INFINITE);
        if (dwWait == WAIT_OBJECT_0) // Process is terminated !!
        {        
            RELEASE_TRACE("CProcessWatcherThread: Process " 
                             "terminated.. calling OnProcessShutdown");
            OnProcessShutdown();
        }
        else if (dwWait == WAIT_OBJECT_0 + 1) // thread is terminated !!
        {        
            RELEASE_TRACE("CProcessWatcherThread:  Normal exit");
        }
        else 
        {        
                RELEASE_TRACE("CProcessWatcherThread: oops");
        }
    }
    
    static LRESULT CALLBACK ProcessGetMessageHook
            (int code, WPARAM wParam, LPARAM lParam)
        {
            MSG* pMsg = (MSG*) (lParam);
            HHOOK hookCall  = m_hOriginalHook;
            if (pMsg->message == WM_QUIT)
            {
                m_pProcessWatcherThread->StopWatch
                  (m_pProcessWatcherThread->m_WaitHandles.hWatchProcess, 
                  false);

                LRESULT lres =  CallNextHookEx(hookCall, 
                                 code, wParam, lParam);
                UnhookWindowsHookEx(m_hOriginalHook);
                m_hOriginalHook = NULL;
                return lres;
            }
            return CallNextHookEx(hookCall, code, wParam, lParam);
        }    

    DWORD                            m_dwThreadID;
    HANDLE                            m_hThread;
    static    HHOOK                    m_hOriginalHook;
    static    CProcessWatcherThread*    m_pProcessWatcherThread;

#pragma pack( push, 1 )
    struct
    {
        HANDLE    hWatchProcess;
        HANDLE    hEventStopWatch;
    } m_WaitHandles;
#pragma pack( pop )
};

__declspec(selectany) HHOOK CProcessWatcherThread::m_hOriginalHook = NULL;
__declspec(selectany) CProcessWatcherThread* 
  CProcessWatcherThread::m_pProcessWatcherThread = NULL;

RELEASE_TRACE 可用于调试应用程序。 使用来自 SysInternals 的 "DbgView" 之类的工具,您可以读取发送到调试端口的字符串,而无需启动调试器。 调试运行 COM 对象的宏的唯一方法(我知道)是将新的调试器附加到正在运行的 MSDEV.exe 进程。(您可以使用任务管理器执行此操作)。 这会花费大量时间,停止调试会导致 MSDEV.exe 关闭。

CSelfDeleteAtlDlg:从 CSelfDeleteAtlDlg 派生您的 ATL 对话框,并在 OnOk()OnCancel() 或任何其他应该关闭和删除对话框的方法中调用 DestroyWindowAndDeleteThis()

CProcessWatcherThread:从 CProcessWatcherThread 派生您的类,并从属于要监控的进程的线程中调用 StartWatch()。 如果进程正在关闭(必须是具有 GUI 的进程),则会调用方法 OnProcessShutdown()。 您可以在此处执行清理工作。 如果您调用 StopWatch(),则不再监控该进程。

示例代码包含以下宏扩展

[id(1)         ] HRESULT IsFilePresent([in] VARIANT bsFileName, 
                            [out, retval] VARIANT* pIsPresent);
[propget, id(2)] HRESULT TextClipboard([out, retval] VARIANT *pVal);
[propput, id(2)] HRESULT TextClipboard([in] VARIANT newVal);
[id(3)         ] HRESULT Append2TextClipboard([in] VARIANT vtAppend, 
                            [optional] VARIANT vtInsertCRLF);
[id(4)         ] HRESULT Copy2Clipboard([in] VARIANT vtCopyText);

如果文件存在,IsFilePresent 返回 VARIANT_TRUE,否则返回 VARIANT_FALSE

Copy2Clipboard 只是将文本复制到 Windows 剪贴板。

TextClipboardAppend2TextClipboard 显示一个简单的纯文本剪贴板,用于在 dev-studio 实例中使用。 可以将文本复制或追加到此“cliptext”。 使用 ISharedPropertyGroupManager 存储存储在“cliptext”中的文本和到 cliptext 窗口的窗口句柄。 我们的 DLL 可能会被重新加载多次,因此我们无法使用简单的指针存储数据。 ISharedPropertyGroupManager 与我们的进程一样长,因此方便使用它来存储“命名”数据。

我输入了一些宏,并将 shift+ctrl+C 之类的击键分配给 CopyEx 宏。 下面你可以看到如何调用函数

Sub CopyEx()
    Set ExtFuncs = CreateObject("MakroExtensions.ExtFuncs")
    ExtFuncs.TextClipboard = ActiveDocument.Selection

End Sub
Sub PasteEx()
    Set ExtFuncs = CreateObject("MakroExtensions.ExtFuncs")
    ActiveDocument.Selection = ExtFuncs.TextClipboard
End Sub
Sub AppendCopy()
    Set ExtFuncs = CreateObject("MakroExtensions.ExtFuncs")
    ExtFuncs.Append2TextClipboard ActiveDocument.Selection
End Sub
Sub CopyFullFileName()
    Set ExtFuncs = CreateObject("MakroExtensions.ExtFuncs")
    ExtFuncs.Copy2Clipboard ActiveDocument.FullName
End Sub

也许这个框架可以帮助你开发更多的宏扩展。 更多信息,请查看源代码,也许我在那里添加了一些注释。

© . All rights reserved.