在 WTL 中使用从动态加载的 DLL (SDI) 导出的视图(对话框)





3.00/5 (6投票s)
2003年10月7日
4分钟阅读

73730

1468
一篇关于如何在 WTL 下将导出视图(对话框)的 DLL 加载到 SDI 应用程序中的文章
引言
为了在我的几乎所有新的个人项目中使用 WTL,我决定能够像使用 MFC 一样从 DLL 加载对话框(参见 https://codeproject.org.cn/docview/SdiCViewDll/[^])。
DLL 的实现
使用 Visual Studio (7.1) 项目向导,我创建了一个 Win32 应用程序,并选择了 DLL 和导出符号选项。接下来,我决定需要一个抽象接口,供我的 FormView 类派生。抽象接口创建了一个通用框架,允许随意派生类并定义在抽象接口中公开的方法。如果您不熟悉抽象接口,请参阅参考部分。
这是我为抽象接口想出的方案
struct IDLLFormView { virtual ~IDLLFormView() { } virtual HWND Initialize(HWND hParentWnd) = 0; virtual void Destroy() = 0; vitrual TCHAR* Description() = 0; };
创建了我们的抽象接口后,我们离能够将对话框加载到我们的 WTL 应用程序中又近了一步。接下来我们需要做的是将注意力转向 GetPlugin()
函数,GetPlugin()
将是 DLL 中唯一的导出函数,这是声明
TESTWTLDLL_API void __stdcall GetPlugin( IDLLFormView** ppOutput );
以及实现
TESTWTLDLL_API __stdcall void GetPlugin( IDLLFormView** ppOutput ) { //Note that error checking has been removed for clarity *ppOutput = new CReplaceFormView; }
在这里,我们只是创建了 CReplaceFormView
的一个实例,并适当地设置了我们的指针指针。如果您熟悉 MFC,这与我们使用 CRuntimeClass
所做的完全相同。
“宿主”应用程序
我们可以用无数种方法来设置“宿主”应用程序来加载 DLL 并导入视图。在这个例子中,我不会做任何花哨的事情,因为我想让你了解如何完成这项任务。
使用 Visual Studio 向导创建一个 ATL/WTL 应用程序。在“应用程序类型”选项卡下,选择 SDI 以及您可能需要的任何其他选项,有些人喜欢在 .h 文件中编写 WTL 代码,而另一些人则更喜欢将接口与实现分离 - 这实际上是一种品味问题。
在“用户界面功能”的“视图类型”组合框中,选择“表单(基于对话框)”,然后单击“完成”。下一部分很棘手,如果有人有更好的方法来做到这一点,请告诉我。您必须做的是取消连接使用该应用程序创建的 FormView;当然,您可能希望此对话框可见;如果是这样,请保持连接状态。
接下来我们需要做的是创建我们的对话框资源,然后创建一个使用对话框资源作为其模板的类。使用 WTL 向导创建一个新的 ATL/WTL 应用程序(您可以在此处找到 VS7.1 的版本 here[^])。对于应用程序类型,选择SDI 应用程序单选按钮,并在用户界面功能下,为视图类型选择“表单(基于对话框)”。
您可以将 Generic 窗口用于视图类型,因为这样做会稍微困难一些。但现在只需选择“表单”视图类型即可。
这是我的类class CReplaceFormView : public IDLLFormView, public CDialogImpl { public: enum { IDD = IDD_TEST_REPLACEFORMVIEW_FORM }; HWND Initialize(HWND hParentWnd) { return this->Create(hParentWnd); } void Destroy() { } // nothing to destroy TCHAR* Description() { return(“Test loading Dialog from DLL”); } BOOL PreTranslateMessage(MSG* pMsg) { return IsDialogMessage(pMsg); } BEGIN_MSG_MAP(CReplaceFormView) COMMAND_HANDLER(IDC_BUTTON1, BN_CLICKED, OnBnClickedButton1) END_MSG_MAP() LRESULT OnBnClickedButton1(WORD /*wNotifyCode*/, WORD /*wID*/, HWND /*hWndCtl*/, BOOL& /*bHandled*/); };
下一步是创建一种从 DLL 加载对话框的方法。我选择在应用程序工具栏上放置一个按钮,单击该按钮时加载对话框。这是执行此操作的代码
void CMainFrame::LoadDLLView() { HINSTANCE hLibrary = (HINSTANCE)::LoadLibrary( "testWTLDLL.dll" ); typedef void (*GETPLUGIN)( IDLLFormView** ); GETPLUGIN pfnLoad = (GETPLUGIN)::GetProcAddress( hLibrary, "GetPlugin" ); if(pfnLoad == NULL) { DWORD dwLastError = ::GetLastError(); LPVOID lpMsgBuf; ::FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM| FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dwLastError, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL ); WTL::CString msg; msg.Format("GetProcAddress(\"GetPlugin\") Failed\r\nError " "Code: %ld\r\nReason:%s", dwLastError, lpMsgBuf); LocalFree(lpMsgBuf); ::MessageBox(NULL, msg, "Error", MB_OK ); return 0; } IDLLFormView* pOutput = 0; pfnLoad( &pOutput ); m_hWndClient = pOutput->Initialize(m_hWnd); CMainFrame::UpdateLayout(); }
对于任何使用过动态 DLL 的人来说,这都是相当常见的代码。如果您不熟悉这些概念,可以在本网站上找到解释这些概念的文章。本质上,在加载 DLL 后,我们对 DLL 中的唯一导出函数执行 GetProcAddress()
,然后调用该函数,传入我们的 IDLLFormView*
的地址。在使用这些方法之前检查此指针的内容可能是明智之举,但在我的示例中,我删除了此错误检查。
一旦我们调用了基于 DLL 的类的 Initialize()
方法,我们只需调用 CMainFrame::UpdateLayout()
即可刷新窗口显示。
结论
这项工作基于我和其他人在 MFC 下所做的工作。一旦我对 WTL 产生了兴趣,我就决定要在我的新项目中实现相同的功能。尽管我已在生产代码中使用此方法并取得了很大成功,但随着我(像你们大多数人一样)进入 .NET,它可能会进入已存档的死亡代码 CD 中。