Debugging with DirectShow


This article discusses debugging practices in Microsoft® DirectShow™ for the C and C++ languages. Many of these practices apply both to writing filters and to writing applications that use the DirectShow run time. The article provides some tips on writing code that can be easily debugged and tested. It also discusses the use of debug and performance builds, and the need to obtain symbols for use in debugging. It gives a warning about the difficulty of debugging real-time DirectShow code, and hints for debugging real-time code anyway. This article contains some general debugging topics and a section on debugging new filters. Finally, some hints are given on detecting leaks in the DirectShow environment, and dealing with memory corruption.

Contents of this article:

Writing Code You Can Test and Debug

Debugging code in the DirectShow environment can be easier if it's written to be testable and debuggable in the first place. Some techniques that DirectShow supports include the following:

This section contains the following topics:

Assertion Checking

Use assertion checking liberally. If you're not familiar with asserts, they're a popular way to isolate potential programming errors. Some C implementations have had assert macros for over a decade. The Microsoft® Foundation Classes (MFC) have an equivalent ASSERT macro. DirectShow provides a number of assertion macros and functions, including ASSERT. For example:


    ASSERT( First != NULL );

For more information about assertion, see Assert Macros and Functions.

Pass Debugging Names

Pass the debugging name to the constructors that support it. Tracking object creation and destruction is provided in debugging builds for the CBaseObject class and classes derived from it. The object register is the list of objects that have been created but not yet destroyed in those classes. The debugging name that is passed to the constructors of those classes is stored in the object register. For more information about debugging object registers and the DbgDumpObjectRegister function, see Object Register Debugging.

Debug Logging

Use DirectShow's extensive debug logging abilities, especially DbgLog, in your code. (DirectShow is perhaps unique in the fine detail at which logging can be enabled or disabled while the code is running.) Here's an example from the bouncing ball source filter:


    DbgLog(( LOG_TRACE, 1, TEXT("New time: %d, Proportion: %d"),
        m_iRepeatTime, q.Proportion));

If enabled this example logs the given data. This would only be enabled in a debug build, when the LOG_TRACE logging level for that module is less than or equal to 1. In other builds or at other times, that call would simply be ignored.

See the Debug Logging by Module Level for more information on the following topics:

IOStream Sample Code

The C and C++ helpers provided in the IOStream helper library, SampIOS.lib, provide text output of the IBaseFilter interface and other DirectShow objects. The output from these helpers might be useful during debugging, to help understand the details of a given pin or filter. You can use these helpers in your DirectShow filters and applications. For more information about this library, see IOStream Helper Library.

Critical Section Usage

To make deadlocks easier to track, insert assertions in the code that determine whether or not a critical section is owned by the calling code. The CritCheckIn and CritCheckOut functions indicate whether or not the calling thread owns the given critical sections, and are generally called in ASSERT macros. For more information about these functions, see CCritSec Debug Functions.

For debug logging of each lock and unlock of a given critical section, you might want to use the DirectShow DbgLockTrace function.

Note Logging can affect performance.

Pointer Validation

Consider using the pointer validation macros. For example, you can call ValidateReadPtr to ensure that the given pointer actually points to readable memory. Note the performance cost of each of these calls. Currently, the DirectShow ValidateXxxPtr macros are built on top of the Win32 IsBadXxxPtr functions. On some systems, the IsBadXxxPtr functions swap in every page in the range specified. For more information about validation macros, see Pointer Validation Macros.

DLL Base Address Conflicts

If you copy any sample makefile to create any new DLL, including filters and plug-in distributors (PIDs), ensure you change the base address to avoid collisions with other DLLs. A collision of DLL load address results in one of the DLLs having to be relocated during the time of loading. This increases the load time for that DLL.

In the sample makefiles, the DLL base address is set in DLL_BASE, which is used in ActiveX.mak. Do not let ActiveX.mak use the default value for DLL_BASE, because this will cause collisions.

Using Different Kinds of Builds

DirectShow can be built for three kinds of builds: retail, debug, and performance. See Reserved Identifiers for information on the kinds of builds. Debugging has varying degrees of difficulty for the three kinds of builds, depending on the situation. For instance, the debug build can provide much more information, but it can run so slowly as to make real-time debugging impossible.

The binaries you create must match the kind of build you're using. The makefiles provided for each sample use ActiveX.mak, which comes with the DirectShow SDK. Comments at the head of ActiveX.mak explain the various nmake command-line parameters to use to obtain binaries compatible with the different DirectShow builds. Some of these parameters define identifiers like DEBUG and PERF when compiling the C or C++ code.

If you must have build-dependent code, you can conditionally compile with the same identifiers that the DirectShow headers use for that purpose. See Reserved Identifiers for a list of the identifiers reserved for that purpose.

For instance, in C or C++, you can conditionally compile code like this:


    ...   /* normal processing */
#ifdef DEBUG
    ...   /* debug only code */
#endif
    ...   /* resume normal processing */

Generating and Installing Debugging Information

Any debugger will need access to the debug information for the code you're debugging. (Debug information might include symbols or source line numbers.) The means of requesting the debug information depends on your compiler and operating system. On some systems, the debug information is added to the object files themselves; other systems can have separate files. Refer to your compiler and debugger documentation for explanations of the location and format of the debug information.

For effective debugging in the DirectShow environment, you should have access to the debug information for the following components when you're debugging problems.

Real-Time Debugging Caveat

After you get a Run command, DirectShow becomes a real-time system, and at that point any normal debug output is liable to upset things. You cannot step through the code and expect it to reproduce a bug.

If you run into this condition, you should consider these ways to work around it:

First-Time DirectShow Debugging

If you have problems the first time you link to or call DirectShow code from your code, you should check the following things:

Debugging New Filters

This section contains the following topics:

Avoid GUID Conflicts

DirectShow uses globally unique identifiers (GUIDs) to find each filter, pin, and property page. Avoid reusing any of the same GUIDs when copying from the DirectShow sample code. Multiple use of the same GUID for different things can cause trouble in the registry and at run time. Use Guidgen.exe (or Uuidgen.exe) and avoid this. The Guidgen.exe and Uuidgen.exe utilities generate unique GUIDs, which by definition are different from the ones given in the DirectShow samples.

On the other hand, media types and subtypes are also indicated by GUIDs. If you are supporting the same kind of media as any sample code, then using the same GUID for the media type and subtype is probably correct.

Test With Filter Graph Editor and Other Sample Filters

The DirectShow SDK includes a tool called the Filter Graph Editor (Graphedt.exe). Once you have your new filter's registry information set, you can use the Filter Graph Editor to interactively test your filter.

When the Filter Graph Editor is unable to create an instance of the filter, it displays the following error message: "The filter could not be created. Resources used by this filter may already be in use." Ensure that the class ID and path in the registry match those for your filter, and that you re-register your filter after modifying any GUID it uses.

If you are debugging an audio filter, there are two sample filters you might consider debugging with. For overviews on those code samples, see Audio Synthesizer Filter and Oscilloscope Filter.

After you have the Filter Graph Editor successfully loading your new filter, you can use the File Dump Filter (Dump.ax) as a useful debugging tool. For instance, it can be used to verify, bit by bit, the results of a transform filter. Build a graph manually using the Filter Graph Editor and hook the File Dump Filter onto the output of a transform or any other pin. You can also hook up a Sample Tee Filter (InfTee.ax), and put the File Dump Filter on one leg of the tee and the "normal" output on another to monitor what happens in the real-time case. For more information, see File Dump Filter.

Associate the Filter File Type (.ax) in MSDEV Debugger

If you're going to debug your filter with Microsoft Developer Studio (MSDEV), you must tell the debugger about the new file type (.ax). Here are the MSDEV steps you should follow to identify the filter file type (.ax) as being a debuggable DLL:

  1. Go to Build.Settings.Debug.
  2. Choose "additional DLLs".
  3. Add "whatever.ax" to the list, where "whatever" is the name of your filter.

Detecting Leaks

Leaks of any sort (no matter how small) can cause problems over the long run. Imagine a DirectShow-based application as a "video kiosk," running 24 hours a day. What if it has a bug that leaks a HANDLE value for each customer? Even if the first 10,000 customers are happy, the ones after that will be denied service. To avoid all those potential unhappy customers, it is important to be thorough in stamping out all leaks.

This section contains the following topics:

Use Memory Leak Tools

Unfortunately, C++ has lots of potential for leaking memory. Code written in C can also leak memory, although there are fewer opportunities for problems. In either language, detecting memory leaks "by hand" is not trivial. You should use tools to help detect memory leaks while you're debugging and testing the code. Visual C++ has an optional debug heap, which can be useful in tracking down memory leaks. (See the "Using the Debug Heap" section of the Visual C++ documentation for more information.) For example, the Visual C++ _CrtSetDbgFlag function enables you to turn on the memory-leak-checking flag bit.

Other providers of memory leak tools can be found in the Microsoft Enterprise Development Partners directory.

Find COM Object Reference Leaks

Another kind of leak is of COM object references. You can track down object reference leaks by performing the following steps.

  1. Put a break point on the NonDelegatingAddRef and NonDelegatingRelease methods of that object.
  2. Use MSDEV (or another debugger) and step through every reference count change, trying to pair them up.
  3. Look at the call stack for each change.

There are often a few hundred reference count changes. With luck you can spot the unpaired one.

Debugging Addressing and Memory Corruption Problems

Debugging memory addressing and corruption problems can be quite difficult. The DirectShow environment is not immune to those problems. Fortunately, there are tools that can help.

The debug heap manager supplied with Microsoft Visual C++ version 4.x can be quite useful when used to debug DirectShow code. Knowing the kinds of addresses that can be generated can help track down the source of a given problem. Using the Visual C++ debug heap manager can result in the following addresses being generated by buggy code.
Address Value
0xFDFDFDFD Illegal access before or after an allocated area.
0xDDDDDDDD Access to memory after it has been freed.
0xCDCDCDCD Uninitialized access within an allocated area.

Use the address values given in the preceding list to determine the class of bug to look for.

For more information, see the "Memory Management and the Debug Heap" section of the Visual C++ documentation.

Other debug heap managers can also be valuable for detecting memory corruption.

© 1997 Microsoft Corporation. All rights reserved. Terms of Use.