在 Dev-Studio 宏中使用您自己的 ATL 对象
2002 年 2 月 11 日
3分钟阅读

49492

243
展示如何开发 ATL 对象来扩展 VB-Script。使用 ISharedPropertyGroupManager 在内存中保存数据。监控 MSDEV 关闭。
引言
我是一个懒惰的(试图避免不必要的工作)开发者,我尝试尽可能地自动化 (使用宏或其他工具)。由于 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;
展示了如何解析一个 VARIANT
。VARIANT
可以包含一个值(例如 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 剪贴板。
TextClipboard
和 Append2TextClipboard
显示一个简单的纯文本剪贴板,用于在 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
也许这个框架可以帮助你开发更多的宏扩展。 更多信息,请查看源代码,也许我在那里添加了一些注释。