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

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

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (6投票s)

2003年10月7日

4分钟阅读

viewsIcon

73730

downloadIcon

1468

一篇关于如何在 WTL 下将导出视图(对话框)的 DLL 加载到 SDI 应用程序中的文章

Sample Image - SDIViewWTLDLL.jpg

引言

为了在我的几乎所有新的个人项目中使用 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 中。

© . All rights reserved.