Dependency Walker

The Dependency Walker (also called Depends for the name of the executable file) enables you to view the dependencies between various DLLs. For example, you might know that User32.DLL contains the MessageBoxEx() function, but may not realize that User32.DLL also relies on other DLLs to make the function work. The interdependencies between DLLs are the cause of a number of problems with older, unmanaged applications, which is why Microsoft is now promoting the .NET Framework. However, whenever you work with the Win32 API, you also need to know about these dependencies to avoid problems with your application.

Now that you know how the Dependency Viewer is used normally, it's helpful to know how you'll use it for Win32 API application development. The Dependency Walker also displays a list of inputs and outputs for a DLL. For example, it shows that the User32.DLL file exports the MessageBoxEx() function. However, the Dependency Walker shows more—it shows that that there are actually two versions of this function, one for Unicode character use and a second for plain American National Standards Institute (ANSI) use. When you work with the C header files, they automatically convert MessageBoxExA() (ANSI) or MessageBoxExW() (Unicode) to MessageBoxEx() for the desired platform. In some cases, you'll have to perform this task manually by specifying an entry point as shown here. (Visual Basic developers will need to perform this task more often than C# developers.)

[DllImport("user32.dll",

CharSet=CharSet.Auto, EntryPoint="MessageBoxExA")] public static extern int MessageBoxEx( IntPtr hWnd,

[MarshalAs(UnmanagedType.LPTStr)]String Message,

[MarshalAs(UnmanagedType.LPTStr)]String Header, UInt32 Type, UInt16 LanguageID);

Notice that the EntryPoint argument specifies which version of the MessageBoxEx() function to use—the ANSI version in this case. Note that you can also use a numeric entry point if desired, but the text version is usually more readable and less susceptible to changes in the DLL's organization. Unfortunately, Windows 2000 and Windows XP both rely on the Unicode version of the MessageBoxEx() function, so you get some odd results as shown in Figure 3.4 when using this form of the Dlllmport attribute.

■ T X

i—

Cancel

Try Again

Continue

Help

Figure 3.4: Choosing the wrong entry point for a DLL can have unanticipated results.

Figure 3.4: Choosing the wrong entry point for a DLL can have unanticipated results.

Changing the Dlllmport attribute to [DllImport("user32.dll", CharSet=CharSet.Auto, EntryPoint="MessageBoxExW")] returns the output to normal. This modification demonstrates that both Visual Basic and C# will choose an appropriate version of a function if the selection is clear. The only time you'll need to specify an EntryPoint value is when the entry point is unclear or if you're compiling an application for a platform other than the current platform. For example, you'll need to use the MessageBoxExA() entry point when compiling an application for the Windows 9x platform. You'll also need to provide a specific entry point if you set the ExactSpelling argument to true (the default is false). Setting ExactSpelling to true ensures that you access only the function you need for a particular application, rather than allow .NET to locate something "close" for you.

Tip To obtain help on any of the common Windows API functions listed in the Dependency

Walker, highlight the function in question and press Enter. Dependency Walker will open the help file currently associated with Visual Studio. Unfortunately, not all of the functions are documented in the help provided with Visual Studio .NET. To obtain full documentation, you'll need a copy of the Platform SDK or MSDN.

Viewing the Dependencies

Dependency Walker (or Depends, as it's listed on the Microsoft Visual Studio 6.0 Tools menu) helps you prevent the problem of the missing file. It lists every file that an application, DLL, or other executable file depends on to execute. You can use the output of this application to create a list of required files for your application or to discover the inner workings of DLLs. Both of these functions are actually important when you use the Win32 API in your application, because you now need to consider the problems of providing the user with the correct version of any DLL that you use.

Loading a file for examination is as easy as using the File 0 Open command to open the executable file that you want to examine. Figure 3.5 shows an example of the output generated for the User32.DLL file. As you can see, User32.DLL contains a wealth of functions. Notice that the figure shows the MessageBoxExA() function highlighted—the Unicode version, MessageBoxExW() function appears directly below this function. The figure also shows that each function entry includes both a function name and an ordinal number—either will work as an entry point.

Note DLLs created with Visual C++ often have what's termed decoration in the function names. The decoration makes the function names almost unreadable to the average human. To undecorate the function names, right-click within the appropriate function-listing pane, and then choose Undecorate C++ Functions from the context menu.

Figure 3.5: Dependency Walker can help you determine what external files your component needs to operate.

As you can see, this DLL provides you with a lot of information about the dependencies of your file. In the upper-left corner is a hierarchical view of dependencies, starting with the executable file that you want to check. The hierarchy shows the files that each preceding file requires to run. So, while the User32.DLL file itself relies on NTDLL.DLL, KERNEL32.DLL, and GDI32.DLL (along with other files), each of the support DLLs rely on other DLLs, such as the RPCRT4.DLL used by the ADVAPI32.DLL.

To the right of the hierarchical view are two lists. The upper list tells you the functions the parent executable imports from the current file. The lower list tells you the functions the highlighted executable exports for other executables to use. You'll typically see a blank export list for applications. Most DLLs export functions, but some don't import anything. The presentation will vary depending on the type of file you view.

At the bottom, you'll see an alphabetical list of all of the files along with pertinent information, such as the executable file's version number and whether the DLL or other files relies on a debug version of that file. This list comes in handy when debugging an application. It helps you to check for problems that might occur when using an older version of the DLL or to detect potential corruption in a support file. You'll also find it handy when you want to check that final release build before you release it for public use. Many applications have tested poorly because they still had "hidden" debug code in them.

Tip It's interesting to note that Dependency Walker doesn't include any kind of print functionality.

Fortunately, you can highlight a list of items you want to print, and click Copy (or press Ctrl-C) to copy them to the clipboard. Use the Paste function in your favorite word processor to create a document you can print for future reference.

Newer versions of the Dependency Walker (including the version that ships with Visual Studio .NET) have a final window shown at the bottom of Figure 3.5. This window contains any messages that affect the display of the opened file. For example, User32.DLL or one of the imported DLLs in the hierarchy relies on a delay-loaded module (some executable file). The Dependency Walker might not be able to display this module if the associated executable doesn't document it properly.

Special Viewing Considerations for Managed Applications

As far as a managed application is concerned, the world revolves around MSCOREE.DLL—there are no other DLLs. Even if you import a DLL using the [DllImport] attribute, the application only sees MSCOREE.DLL—at least at the outset (see the details on using profiling in the "Using Special Dependency Viewer Features" section). To demonstrate this principle, open the ShowMessage.EXE application from Chapter 2. Figure 3.6 shows the C# version of this application, but the Visual Basic version behaves in a similar manner.

igure 3.6: Managed applications only see the MSCOREE.DLL file and rely on it for everything.

The really odd part of the display is that you won't see any imported or exported functions for the application. The managed environment doesn't expose its requirements for outside sources the same way as the unmanaged environment does. If you want to see which DLLs a managed application requires to work, you'll need to use ILDASM to view the application manifest. Figure 3.7 shows the example from the ShowMessage application. Notice that the imported function appears as any other function, which means that you might spend a considerable amount of time looking for imported functions in a complex application.

Figure 3.7: To see the imported functions for a managed application, you must view the application in ILDASM.

Double-clicking a function is the only way to make sure it's actually imported from an external DLL. Figure 3.8 shows the code for the MessageBoxEx() function. Notice that this function relies on PInvoke—a sure sign that the function appears in another DLL. In fact, the code tells you which DLL is used and all of the implementation details for the function. However, having to dig for this information does make things inconvenient for the developer.

' llMJMlt I <-■!«.. lOfMllwr «i->t(ai4 NIK)

Mtlkad pMi: l.k labyai y itkti: panwafca»^! < * nam 72 111* wlacW naift)

in« yj lUw* jrfi ft 1-» in« htfr.1

IU)M 1««H ItMIMS

•«»a« UIIU1( Iptatv) »to» it

mtjynmi inOi trp*

( >

Figure 3.8: ILDASM will tell you which DLL the application uses, as well as any implementation details.

The Visual Basic version of the code looks similar to the code in Figure 3.8. The differences are minor but notable. For example, because we can't use a UInt in Visual Basic, you'll see the Type and Language arguments listed as Int32 and marshaled as UInt32. The Visual Basic implementation also relies upon the EntryPoint argument for [Dlllmport], so the PInvoke information looks different from the C# implementation, but both act the same. In short, even though there are small differences between languages, the functionality of the PInvoke call is the same.

Using Special Dependency Viewer Features

Newer versions of the Dependency Walker include some special features that you might find helpful when creating an application that relies on one or more unmanaged DLLs. One of the more interesting features is the ability to profile your application. In this case, profiling doesn't have anything to do with performance; we're talking about tracing every call that your application makes.

The profiling feature is exceptionally helpful to developers who use Win32 API calls because it exposes the use of imported DLLs in most cases. In addition, you can track how the managed application uses the Win32 API call and compare it to an unmanaged application's use of the same call. This comparison provides you with clues when a managed application refuses to use a Win32 API call correctly and often leads to a solution to the problem. To start the profiling process, choose the Profile 0 Start Profiling command. You'll see a Profile Module dialog box like the one shown in Figure 3.9.

Profils Modul«

©H

Piogur» «pjntrte

1 OK 1

$1 tí trig dr*c<c*y

Caned |

|0 V011G Souc* CoSe\CK*rfn (B\VB\Sho»*«*tt«0ft>£«i\

B<cmw

r Oe« tt* bg «náx

V Log OM«n cjftt k* c*oeeti aflach tta p»oc«i d«Uc*i ««»»je» r Log ejftt kit ti o*mi n*»*g*t nei¿ng títomi «fetch and W Hoc*, tf* pocen 'o gathai my* AtaM depeoderxy ntanafccr.

p Log LoadL£r«y tirctwn c-afti log GetPtocAddro kntfart cA r logtoMdrtonMcn

f~ Log Vi» cKarce e*c«f»n! ^ log detug outoui rnettagvs r UwhiM^t leggng rts n<rw r log*i*wsunp«dtiMchlr»diog

AiicrMhcilf OpK *nj pcéia cHd pWWlttt

Figure 3.9: The Profile Module dialog box configures the profiling feature of the Dependency Walker.

There are actually two sections to this dialog box. The first section provides a command-line argument for the application and changes the application's starting path. In most cases, you won't need to change either entry. You can also choose whether Depends clears the Log window before it begins the profiling process. The Simulate ShellExecute option determines how the application is started. Normally, you'll keep this checked to ensure that the application path information is provided to the application when it starts. The only exception is when you're troubleshooting problems related to the application path. If you uncheck this option, then Dependency Walker will start the application using the CreateProcess() API call rather than using ShellExecute().

The second section contains a list of items that you will want to monitor. For example, you might only be interested in profiling the libraries that your application loads and when it loads them. In this case, you'd select the Log LoadLibrary function calls option. The number of entries in the Log window can build very quickly, so it helps to decide what you really need to monitor at the outset, rather than wading through a lot of useless information that you don't really want. Figure 3.9 shows the default information that Depends will collect about your application. This setup is useful in determining how an application uses the various libraries that it requires to operate. It's interesting to note that you can even use Depends to monitor debug output messages that you've placed within an application, making it a handy tool for monitoring application activity outside of a programming language's IDE.

Once you've decided how to start the application and what you want to monitor, click OK. Depends will load the application and start displaying profile information. In many cases, you'll need to clear the log entries shown in the bottom pane of the window before you proceed to test Win32 API calls; otherwise, there's simply too much material to check. Figure 3.10 shows the Log window entries for the ShowMessage.EXE application we looked at earlier. Notice the trapped call to the MessageBoxExW() function. Also notice that User32.DLL is now listed as one of the DLLs used by the application.

IvmVtei

es

M V». C|MM INKI

«A

. 0 m

| y f£ <\ £ o*

a m

«»EC»

- D mtfOMU«

I »mm * »«*rt

rr.

1 W»r-W» 1

It • II11!

o

m IMW

1

[ib'.tMSM i|

K

I >#« » w rwitr

I -

o

;<xo:cei > ij.ciX'i MM^MM

o-wcorvi 1 i*<»:ICO».| w

s

91».

1 w1

.«• '/i.w 1

KM

urn»*

45*

«f; «owr-.xcu

cacw»i reaJ

3MMM U Î9»| 1Ï»ÎV *c

«•CCOKK? 1

[owKi |.»

«

>

j**~<xa*>mmu. i«o.uj are* u -kl\ 'wtfw^ uêt» "w Ms^jtmaxx' m n>»u je mute* me msrrni m. iww^c

^«»♦»nrt'-woifirrv sul *,im Ji «*«»"•- ?u'«» ^rt.am' v4>«t*rw4

.a a*-*w>'i. ' iiil'r<»< i: -iix t«* « .yt«* ».n mu<

vj.i -woe*»« or« «un. y^rne« n .» a .«■*<>'«c-»;; CKi'm i*rw wt un******* UJÉHIH- «*»rw> -iiatuM «•r*awp«>an. 5U.1 » »><>. ) KtUMi

»ftwwn.c'JWCTcq 4* «ccirsKO ' IkUuJj CtXk.' :«xj. rf Ch**l*ltZ> ».".tiu*-dc AAymij.. n »LUI Uf.K JUL ~ AiNiiMm'l V» LU' « **r*a i> M*CM1> ml «rmc »./'lî*

p—

«Wl'

h

>

r- gjn

Figure 3.10: Depends will help you monitor the startup activity for any application you create.

Figure 3.10: Depends will help you monitor the startup activity for any application you create.

Even though you can't see it in the book, the Log window shows a problem with a call made by the application after I clicked Test. This call is highlighted in red in the Log window. In addition, the affected modules are highlighted in red in both the module list and the hierarchical display. What this means to you, as a developer, is that Depends has gone from being a simple analysis aid to an application that can help you diagnose application problems.

In this case, the errant call is caused by the method that .NET uses to locate a function accessed by the [Dlllmport] attribute. Looking at the C# version of the ShowMessage application, you'll notice that MSCORWKS.DLL first makes a call to User32.DLL for the MessageBoxEx() function. When that fails, it makes a call to the MessageBoxExW() function and succeeds. Now if you look at the Visual Basic version of the ShowMessage application, you'll notice that MSCORWKS.DLL begins by making a request for the MessageBoxExW() function, which succeeds. It then makes a request for the MessageBoxExWW() function, which fails. Observing these behaviors can tell you a lot about how .NET works with Win32 API calls.

Depends returns control of the application to you as soon as the application finishes loading. You can work with the application just as you normally would and monitor the results in the Log window. When you finish working with an application, you can stop the logging process using the Depends Profile 0 Stop Profiling command.

There are quite a few other new features provided with Depends, but the ability to profile your application is probably the highlight of the list. One of the new capabilities allows you to save a Dependency Walker Image

(DWI) file. This option creates a file on disk that allows you to restore your setup as needed. Depends provides so many new features when it comes to configuring the application environment—the previous versions didn't require this useful feature.

The View menu contains three options that you really need to know about. The first is a System Information command that displays a dialog similar to the one shown in Figure 3.11. This short summary provides a quick view of your current system configuration, which could be important if you want to stress the application under a set of specific conditions like low memory. There are also options to display the full paths for all files and to undecorate those really weird function names that you'll normally find within C++ generated DLLs.

Systrm Information (I ocdlj

Numliri of PiKfiimi umm nohi LocjJ Djle locj tom: OS LangMg« unmr imd Phrttc.J Hani I Htinidl Uicd

PftfiKdJ MMHHI TIM P«go rfr Mrroocy I dial I'ntj* f ir Mpkniiv Uf*d i':><)(■ FA* Mraw»y FIM

V«u*l Una) lul*l Vrtual H«x>r U ltd V«tud r.m t'nQr Stfe

Alwulmn Gimibrtr U.-i «n< Addim Han App Addieii

Heioscil Wnlom XP PtlMnnnil IJ2WI 5 01 2600

fm*> S Model 5 Siwxi 2. GenmlrM "450MHi 2M.il 0»00000009 MAIN Mil

Tluidw 11 Ap* 2002

10 08 «AM Dnitfi Tne l&HT-OS 001

536 38; 584(512«! 269.MO 528 266.797 056 774 656 000 17S.361.472 598,294 528 2147.352576 64 241.664 2083110.912 00000100014.096)

CkOOOl 00001655361 foOOOl aooo 165 5361 OhTFFEFFFF (2147 418.1111

Figure 3.11: The System Information dialog box gives you a quick overview of your system.

One final feature that improves the usability of Depends is the ability to search for specific information. For example, you can highlight a module of interest and use View menu options to search for other occurrences of the same module within the hierarchical view. This allows you to better see where specific modules are used and by whom. Another search feature, this one found on the Edit menu, allows you to search the Log window for words, just as you would with a text editor. You could use this feature to help find errors (the logs do get very long very fast) or to find instances where a specific module is used for tasks like application initialization.

Was this article helpful?

0 0

Post a comment