Tuesday, October 20, 2009

Dependency Checker

This is a simple light weight utility to find which all dll/exe/ocx in a folder uses a particular dll.
Source File(DependsChecker_Src.zip)

My development environment usually includes large number of binaries. And I was working in some core library module which is used by many other dlls. So whenever I change something in the header file of this code library, I have to rebuild all the dependent libraries. Rebuilding the entire environment was not practical all times. So I had to create such an utility so that I can find out who all uses this code library and can rebuild those modules only.

How to Use

  1. In the “Dll Name” edit box, enter the name of the dll for which we have to search for.
  2. In the “Path” edit box, enter the folder in which we have to search for dlls/exe/ocx that uses dll entered in the “Dll Name” edit box.
  3. Click “Start” button.

Upon clicking the Start button, the list control starts’ populating the Dll, OCX and EXE’s that uses the specified dll. Please note that this application will not list dynamic dependencies and so we cannot enter ocx or COM dll as “Dll Name”.

How it works

When the start button is clicked, the application loop through each Dll, OCX and EXE’s in the specified folder. It opens each binary, parse the PE (Portable Executable) file format.
For example consider that user32.dll is specified in the "Dll name" edit box and the path is "c:\windows\system32". When user clicks on the “start” button, the application took a loop to find EXEs, dll's and OCXs in the specified folder. Intitally it enumarets all the EXEs. Suppose we got "calc.exe" as file name. The next thing to do is to map the binary file to memory. This is performed with the help of CreateFileMapping() and MapViewOfFile() function.

CFile fl;
if( !fl.Open( csExeName, CFile::modeReadCFile::shareDenyNone, 0 ))
CString csMsg;
csMsg.Format( _T(" Failed to open file %s"), csExeName );
AfxMessageBox( csMsg );
HANDLE hSM = CreateFileMapping( (HANDLE)fl.m_hFile, 0,PAGE_READONLY,0,0, _T("some_sM"));
LPVOID pBinary = MapViewOfFile( hSM,FILE_MAP_READ,0,0, fl.GetLength());
if( CheckForDependecy( csDllName, pBinary ))
m_List.InsertItem( 0, fl.GetFileName() );
UnmapViewOfFile( pBinary );
CloseHandle( hSM );

As you can see in the above code, after the binary is loaded to the memory, the memory address is passed to the CheckForDependecy() function. The function cast the memory address to a IMAGE_DOS_HEADER pointer.
After this, it will try to get the IMAGE_NT_HEADERS from pDOSHeader. Usually the 16 bit binaries will not be having this header. So we will ignore such binaries at this point.

// Get the PE header.
PIMAGE_NT_HEADERS pNTHeader = MakePtr(PIMAGE_NT_HEADERS, pDOSHeader, pDOSHeader->e_lfanew);
if( IsBadReadPtr( pNTHeader, sizeof( IMAGE_NT_HEADERS)))
return false;

May be because of the property of data in the file, it may occur that the pNTHeader points to a valid memory. So we will add one more checking to confirm that we are dealing with PE file (The famous DRWATSON.EXE fails at this checking!!!ya it is not a PE file).

char* pSig = (char*)&pNTHeader->Signature;
if( pSig[0] != 'P' pSig[1] != 'E')
return false;

The next task is to find the IMAGE_IMPORT_DESCRIPTOR. The IMAGE_NT_HEADERS.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]. VirtualAddress holds the relative address of IMAGE_IMPORT_DESCRIPTOR from the starting of the memory. We can use the ImageRvaToVa() API to convert the relative adress to virtual address. There's one IMAGE_IMPORT_DESCRIPTOR for each imported executable. So we will loop through each item and check whether it is for importing the specified dll (“user32.dll” in our case )

PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR) ImageRvaToVa( pNTHeader, lpStartAddress, pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].
VirtualAddress, 0 );
while( pImportDesc && pImportDesc->Name )
// pImportDesc->Name holds the name of the import library
PVOID pName = ImageRvaToVa( pNTHeader, lpStartAddress, pImportDesc->Name , 0 );
PSTR szCurrMod = (PSTR)pName;
if( IsBadReadPtr( szCurrMod, 1 ))
CString cs = szCurrMod;
if( 0 == cs.CollateNoCase( csDllName ))
// everything pass. We have found one dependency
return true;
// Move to the next import library

And for the records, let me say that, there are 1111 files in my system32 folder that has dependency with the user32.dll( Vista SP1 ).

Saturday, April 18, 2009

Centralizing modeless child dialog

Normally when we call the DoModal() function of a dialog, the dialog will created and displayed at the center of the parent dialog.

But when a modless dialog is displayed, the child dialog will be displayed at the top left portion of the parent dialog.

I noticed this when a codeproject user asked how to centralize a modeless child dialog. Initially I thought that MFC might be doing some thing in the DoModal() function. So I stepped into the DoModal() function. But I couldn't find any difference in the dialog creation in CDialog::DoModal and CDialog::Create().
Another difference that I know between a modal and modeless dialog is that, in the case of the modal dialog, the parent window will be disabled. MFC does this inside the DoModal() function. To try my luck I disabled the parent window before calling the Create function of dialog. Surprisingly it worked! The modeless child dialog came at the center of parent dialog.

So if you want to centralize a modeless dialog, just disable the parent dialog before create and re-enable it after the creation.
void CDialogBased2Dlg::OnBnClickedButton1()
EnableWindow( FALSE );
m_ChildDlg.Create( ChildDialog::IDD, this );
EnableWindow( TRUE );
m_ChildDlg.ShowWindow( SW_SHOW );