PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 1/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. This document covers issues related to creating OWL DLLs (v3.1) namely, using OWL31.DLL with Windows v3.0, allocating Local memory for TModule objects and restoring the Window Procedure of an 'Aliased Window'. 1. OWL31.DLL may crash upon unloading when running under Windows 3.0. This situation is related to problems with the WEP function under Windows v3.0. Under Windows v3.0 the WEP() function cannot contain Windows API calls, particularly KERNEL functions. More specifically, one cannot ( should not ) call a KERNEL function like GlobalFree. The WEP function of OWL31.DLL, ( located in the source file MODULE.CPP ) contains a call to the delete operator, which translates into a call to the KERNEL function GlobalFree() . This may cause a crash under Windows 3.0. The work around is to comment out that line of code, and rebuild OWL31.DLL. After rebuilding OWL31.DLL, it will be placed in the OWL\LIB directory. Replace the old OWL31.DLL which is located in the BORLANDC\BIN directory. ( NOTE: For more information regarding the above mentioned limitation of the WEP function and Windows v3.0 see the Microsoft Document 'Windows Exit Procedure for a Dynamic-Link Library Q66360' available from the Microsoft Software Language ( MSL ) forum on CompuServe ). 2. The DLLHELLO example that ships with OWL in BC 2.0 and 3.0 is a good demonstration of creating the required TModule object an OWL DLL needs, and of using an Object Window alias. It works as a good model for many DLL's that use OWL, but falls short in a couple of specific situations. One of these situations occurs when the DLL acts as a server for several different EXE's. A second is when the window, that is being aliased, persists after the DLL has been unloaded. The first situation refers to a case where two or more EXE's link to the same DLL and will be running simultaneously. The traditional DLLHELLO example poses a problem for this scenario because of one fact about DLLs: they do not own the memory which they allocate. That is, if a DLL calls GlobalAlloc, the memory is owned by the EXE task which called the DLL. When the EXE is PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 2/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. terminated, the memory it allocated is freed; any subsequent references to the freed memory will cause a general protection fault (GP fault). So consider this scenario: EXE A and EXE B both link to dllhello. EXE A loads up. Along with it, DLLHELLO is loaded. The memory allocation it does in LibMain is considered, by Windows, to be owned by EXE A. It allocates a TModule in LibMain as follows: DLLHelloLib = new TModule("DLLHello", hInstance, lpCmdLine); The TModule constructor also does the following: lpCmdLine = _fstrdup(ACmdLine? ACmdLine: ""); So, _fstrdup will allocate the required memory to copy the command line string. To Windows, the allocated memory has been accessed on behalf of EXE A. Now suppose EXE B gets run. Since DLLHELLO is already loaded, LibMain does not get called. Then, EXE A terminates. The allocated memory for the TModule and the command line is freed. The next time EXE B uses the DLL's function CreateDLLWindow, there will be a GP fault since that function uses the pointer to the TModule, that was freed when EXE A terminated. There are two solutions to this problem. Memory for the DLL ( the TModule and the saved command line string ) should be allocated from the DLL's local heap. Or, global memory can be allocated as shared memory. Shared memory allocated in a DLL is not freed until the DLL is unloaded, as it is considered the owner. The example provided below implements the first solution mentioned above. This is done by overloading the new operator for a class derived from TModule. 3. Another shortcoming of DLLHELLO is related to aliasing a Window which persists after the DLL is unloaded. The key things PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 3/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. the CreateDllWindow function does are the following: AParentAlias = DLLHelloLib->GetParentObject(ParentHWnd); TheWindow = new TWindow( AParentAlias, "Hello from a DLL!", DLLHelloLib ); The GetParentObject returns a TWindow alias around ParentHWnd (which is an HWND). The function actually subclasses the Window. That is, it installs its own WndProc in ParentHWnd via the following code in the TWindow( HWND, PTModule ) constructor: DefaultProc = (WNDPROC) SetWindowLong(AnHWindow, GWL_WNDPROC, (DWORD)GetInstance()); This means that any messages which the parent window receives now goes through the code in DLLHELLO. If DLLHELLO is unloaded after a call to CreateDllWindow, and the window it was aliasing still exists, then there will most likely be a GP fault the next time a message comes for that window. This is because the message will be routed to the WndProc in DLLHELLO which no longer exists. The simplest way to work around this is to restore the old WndProc right after creating the Object Window Alias. We will do that in the following example. The disadvantage is that we cannot trap messages to the parent anymore. This situation does not affect the DLLHELLO example, but a different application might want to derive something from TWindow and use it as an alias for an existing window. If one needs to subclass an aliased window, and that window is going to exist after the aliasing window goes away (for example the window is the main window in Object Vision), then one must ensure that the old WndProc is restored. Keep in mind though, that at the point when you wish to restore the old 'WndProc', someone else may be subclassing you. That is messages for the window are being routed to some other module that is in turn calling you. When looking at ways to restore the old WndProc, you cannot just delete the object Window Alias, because our destructor does not restore the old WndProc. This is true up to and including BC++ 3.1. This may change in the future. PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 4/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. One could look at adding it to the destructor of a derived TWindow object, or the WEP code of the DLL. WEP is called at a sensitive point in Windows, and it adviseable to do as little as possible there. The following is a modified version of DLLHELLO.CPP which allocates the TModule object in shared memory and restores the WndProc of the Windows it aliases. If you define DEBUG when compiling it, it includes code to make MessageBeeps when the Dll is loaded and unloaded. It also includes an exported function to de-reference the command line. This code can be useful for testing purposes. #include #include #include #include #include #include "dllhello.h" _CLASSDEF( TDLLModule ) // class TDLLModule forces the memory for instances of the class // to be allocated in the Dll's local heap. // It provides an operator new to do a local alloc for instances // of the class, and a static function to do a local alloc for // the command line string it will save. class TDLLModule : public TModule { public: TDLLModule( LPSTR , HMODULE , LPSTR ); ~TDLLModule(); void *operator new ( unsigned ); static void * GetLocalMem( unsigned ); static void DeleteLocalMem( void *); void operator delete (void *); }; void * TDLLModule::GetLocalMem( unsigned n ) { PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 5/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. // when memory is allocated as fixed from LocalAlloc, the // handle returned may be used as a pointer return (void *)( void near * )LocalAlloc( LPTR , n ); } void *TDLLModule::operator new( unsigned n ) { return GetLocalMem( n ); } void TDLLModule::DeleteLocalMem( void * p ) { // p is presumably a far pointer, the offset is what we // want to free LocalFree( ( HLOCAL ) FP_OFF( p ) ); } void TDLLModule::operator delete( void * p ) { DeleteLocalMem( p ); } TDLLModule::TDLLModule( LPSTR n , HMODULE hm , LPSTR cm ) : TModule( n , hm , cm ) { // free the memory which the TModule constructor allocated // save the command line string. That memory is owned by // the EXE. farfree( lpCmdLine ); // now save the command line string in local memory. if ( cm ) { lpCmdLine = (LPSTR)GetLocalMem( strlen( cm ) + 1 ); if ( lpCmdLine ) { strcpy( lpCmdLine , cm ); } else Status = EM_INVALIDMODULE; PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 6/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. } else { lpCmdLine = (LPSTR)GetLocalMem( 1 ); if ( lpCmdLine ) { *lpCmdLine = 0; } else { Status = EM_INVALIDMODULE; } } } TDLLModule::~TDLLModule( ) { if ( HIWORD( lpCmdLine ) ) DeleteLocalMem( lpCmdLine ); // set lpCmdLine to NULL so that the ~TModule won't try to // free it. lpCmdLine = NULL; } PTDLLModule DLLHelloLib; #ifdef DEBUG // Test function to make sure that the command line string // can dereferenced. Run two instances of a program linking // to DLLHELLO, close the first and have the second call // this function. // Most often the command line string will be null. extern "C" void far _export ShowCommandLine() { MessageBox( GetFocus() , DLLHelloLib->lpCmdLine , "The Dll Command Line", MB_OK ); } #endif BOOL far _export CreateDLLWindow(HWND ParentHWnd) { PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 7/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. PTWindowsObject AParentAlias; PTWindow TheWindow; // save the old WndProc for this window WNDPROC wp = (WNDPROC)GetWindowLong( ParentHWnd , GWL_WNDPROC ); AParentAlias = DLLHelloLib->GetParentObject(ParentHWnd); TheWindow = new TWindow(AParentAlias, "Hello from a DLL!", DLLHelloLib); TheWindow->Attr.Style |= WS_POPUPWINDOW | WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX; TheWindow->Attr.X = 100; TheWindow->Attr.Y = 100; TheWindow->Attr.W = 300; TheWindow->Attr.H = 300; PTWindow MadeWindow =(PTWindow)DLLHelloLib->MakeWindow( TheWindow ); // restore the old WndProc for the main window SetWindowLong( ParentHWnd , GWL_WNDPROC , (DWORD) wp ); return ( MadeWindow == TheWindow ); } int FAR PASCAL LibMain(HINSTANCE hInstance, WORD /*wDataSeg*/, WORD /* cbHeapSize */, LPSTR lpCmdLine) { int TheStatus; #ifdef DEBUG // Make beeps so we know when the Dll is loaded, helps debug // loading and unloading problems for ( int i = 0; i < 10 ; i ++ ) MessageBeep( 0 ); #endif DLLHelloLib = new TDLLModule("DLLHello", hInstance, lpCmdLine); TheStatus = DLLHelloLib->Status; if ( TheStatus != 0 ) { delete DLLHelloLib; DLLHelloLib = NULL; PRODUCT : Borland C++ NUMBER : 1017 VERSION : 3.1 OS : WIN DATE : October 19, 1993 PAGE : 8/8 TITLE : Creating Windows DLLs using v3.1 of Object Windows. } return (TheStatus == 0); } extern "C" int FAR PASCAL WEP ( int /*bSystemExit*/ ) { #ifdef DEBUG // Make beeps so we know when we've been unloaded, helps to // debug WEP code not being called problems, and loading and // unloading problems for ( int i = 0; i < 10 ; i ++ ) MessageBeep( 0 ); #endif delete DLLHelloLib; return 1; } DISCLAIMER: You have the right to use this technical information subject to the terms of the No-Nonsense License Statement that you received with the Borland product to which this information pertains.