Introduction
In my effort to use WTL in almost all of my new personal projects I decided that I wanted to be able to load a dialog from a DLL, just like I did with MFC (see http://www.codeproject.com/docview/SdiCViewDll/[^]).
The DLL Implementation
Using Visual Studio (7.1) project wizard, I created a Win32 Application and selected the DLL and Export Symbols options. I next decided that I needed an abstract interface for my FormView classes to derive from. The abstract interface creates a generic framework that allows classes to be derived from at will and define the methods exposed in the abstract interface. If you are not familiar with abstract interfaces see the references section.
Here�s what I came up with for my abstract interface:
struct IDLLFormView
{
virtual ~IDLLFormView() { }
virtual HWND Initialize(HWND hParentWnd) = 0;
virtual void Destroy() = 0;
vitrual TCHAR* Description() = 0;
};
With our abstract interface created we are a step closer to being able to load up dialogs into our WTL applications. The next thing that we need to do is to turn our attention to the GetPlugin()
function, GetPlugin()
will be the only exposed function in the DLL, here�s the declaration:
TESTWTLDLL_API void __stdcall GetPlugin( IDLLFormView** ppOutput );
and the implementation:
TESTWTLDLL_API __stdcall void GetPlugin( IDLLFormView** ppOutput )
{
*ppOutput = new CReplaceFormView;
}
Here we are simply creating an instance of our CReplaceFormView
and setting our pointer pointer appropriately. If you are familiar with MFC this is exactly the same as we would have done using the CRuntimeClass
.
The 'Host' Application
There are endless ways that we can set up the �host� application to load up the DLLs and import the views. In this example I�m not going to do anything fancy, as I want you to understand how to accomplish the task.
Create a ALT/WTL application using the Visual Studio wizard. Under that Application Type tab select SDI and any other options that you may desire, some people like to code WTL in the .h file and others prefer the separation of interface from implementation - it�s really a mater of taste.
Under the User Interface Features in the View Type combo select Form (Dialog Base) and then click Finish. The next part is tricky and if anyone has a better way to do this let me know. What you have to do is unwire the FormView that is created with the application; of course you may want this dialog to be visible; if so leave it wired up.
The next thing that we need to do is create our dialog resource and then create a class that uses the dialog resource as it's template. Create a new ATL/WTL application using the WTL Wizard (you can find a version for VS7.1 here[^]). For the Application Type choose the SDI Application radio and under the User Interface Features choose Form(Dialog based) for the View type.
You can use a Generic window for the View type as there is just a tad bit more difficulty in doing so. But for now just choose the Form view type.
Here�s my class:
class CReplaceFormView : public IDLLFormView, public CDialogImpl
{
public:
enum { IDD = IDD_TEST_REPLACEFORMVIEW_FORM };
HWND Initialize(HWND hParentWnd)
{
return this->Create(hParentWnd);
}
void 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 ,
WORD , HWND , BOOL& );
};
The next step is to create a way to load up the dialog from the DLL. I chose to place a button on the applications toolbar and when clicked load up the dialog. Here�s the code to do just that:
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();
}
This is all fairly common code to anyone that has used DLLs dynamically. If you are unfamiliar with the concepts you can find articles that explain these concepts on this site. Essentially, after we have loaded the DLL we GetProcAddress()
for the only exported function in our DLL and then call that function passing in the address of our IDLLFormView*
. It's probably wise to check the contents of this pointer before using the methods but in my example I have removed this error check.
Once we have called the Initialize()
method of our DLL based class we simply call CMainFrame::UpdateLayout()
to refresh the windows display.
Conclusion
This work is based on the work that myself and others have done under MFC. Once I got hooked on WTL I decided that I wanted to implement the same functionality in my new projects. Although I have used this method in production code with much success, it will probably find its way onto an archived CD of dead code as I (as most of you) make my way to .NET.