Tải bản đầy đủ - 0trang
Chapter 13. Tracing and Debugging in .NET
The TraceDemo Example
The TraceDemo example illustrates the use of the diagnostic functionality. If you run the
example, you will get the following output:
This was compiled with a DEBUG directive!
This was compiled with a TRACE directive!
Debug Boolean Switch disabled at startup.
Debug Boolean Switch enabled!
Trace Switch Startup Value = Warning
Output File Listener
Refer to this output in the ensuing discussion. You will also find a file called output.txt
on your computer in the directory where this program ran.
Enabling Debug and Trace Output
To use the Debug class, the DEBUG flag must be defined or else the methods of this
class will not be compiled into the executable or library. Similarly, to use the Trace
class the TRACE flag must be defined. This way you can have different diagnostics for
release and debug builds. These constants can be set in the Visual Studio.NET Project |
Properties | Configuration Properties | Build Window's conditional compilation constants
shown in Figure 13-1.
Figure 13-1. Visual Studio window for setting conditional compilation
You can also define the constants in your source files or supply the definition to the
compiler's command line.
Using the Debug and Trace Classes
The useful methods and properties are static. The overloaded WriteLine and Write are used
to write debug or trace output. The overloaded WriteLineIf and WriteIf write output if the
condition in their first argument is true.
Debug.WriteLine("This was compiled with a DEBUG
Trace.WriteLine("This was compiled with a TRACE
Boolean Switch enabled at startup.");
"Debug Boolean Switch disabled at startup.");
Output is indented with the Indent and Unindent methods. The indentation size is
controlled with the IndentSize property.
Trace.IndentSize = 10;
You can also set the indentation size in the application configuration file.
The Assert method can check an assertion. The AutoFlush property and the Flush method
control the flushing of the output buffer.
Using Switches to Enable Diagnostics
Switches give you finer grain control over the diagnostic output. You can use the
BooleanSwitch class to turn output on or off based on the value of its Enabled property.
The TraceSwitch class gives you five hierarchical levels of control for its Level property:
TraceError, TraceWarning, TraceInfo, TraceVerbose, and Off. These values are part of
the TraceLevelEnumeration. Setting a lower Trace level means that the higher ones are set
as well. For example, if the TraceWarning level is set, both the TraceError and
TraceWarning levels are enabled.
DebugBooleanSwitch.Enabled = true;
Boolean Switch enabled!");
The constructors for these switches take two parameters. The first is the name of the switch,
the second is a text description of the switch. Both BooleanSwitch and TraceSwitch classes
inherit from the abstract class Switch. You can write your own customized switch classes by
inheriting from the Switch class. Note that the Enabled property of the BooleanSwitch and
the Level and named level properties of the TraceSwitch are not part of the Switch class.
Enabling or Disabling Switches
You can use settings in your application configuration file to enable or disable a switch at
startup. This can also be done programmatically.
Configuration File Switch Settings
You can set the switch's initial setting in the application's configuration file.
If no values are found, the initial value of the Enabled property of the BooleanSwitch with
the name DebugSwitch is set to false and the TraceSwitch's Level property is set to
Programmatic Switch Settings
The Enabled property of the BooleanSwitch can be set to true or false. The Level property
of the TraceSwitch can be set to one of the options of the TraceLevel enumeration:
TraceOff, TraceError, TraceWarning, TraceInfo, TraceVerbose. You can get the level
of the TraceSwitch's setting by examining the TraceError, TraceWarning, TraceInfo,
Using Switches to Control Output
You can test the value of the switch before you write, debug, or trace output. You can do this
with an if statement, or as an argument to one of the Trace or Debug classes' methods.
Since you can set these values outside of your program's code, you can select the
circumstances under which you get a particular level of debug or trace output. For example,
you can turn on TraceVerbose output if you really need a high level of diagnostics, but turn
it off after you have found the problem.
Classes derived from the abstract class TraceListener represent destinations for
the diagnostic output. The TextWriterTraceListener is designed to direct output
to a TextWriter, Stream, or FileStream. Console.Out is an example of a
commonly used output stream. The EventLogTraceListener class allows you to
send output to an EventLog. You can create your own event logs with the
EventLog's static method CreateEventSource method. The
DefaultTraceListener sends output to the debugging output window. Default
Debug output can be viewed in Visual Studio.NET's Output window or with
utilities (such as DBMon, which is included with this project). You can customize
where output appears by implementing your own class derived from
Both the Debug and Trace classes have a static Listeners collection. This collection of
TraceListeners represents a list of TraceListener objects that want to receive the output
from the Debug or Trace class. Listeners are added to or removed from the collection just as
with any other .NET collection.
TextWriterTraceListener ConsoleOutput = new
Stream OutputFile = File.Create("output.txt");
TextWriterTraceListener OutputFileListener = new
"Output File Listener");
In this code extract, the OutputFileListener in the example will send the Trace output to a
file called output.txt. The DefaultTraceListener is added automatically to the Listener
collections. Any of the listeners, including the default listener, can be removed from the
collection by invoking the collection's Remove method. To list all listeners in the collection:
foreach(TraceListener tr in Trace.Listeners)
Console.WriteLine("\t" + tr.Name);
Instrumenting your application for degrees of debugging and diagnostic output is
a common program task. The diagnostic classes exemplify the way .NET
provides classes to handle standard programming tasks so you can concentrate on
developing the business logic of your programming, not on building
infrastructure. On the other hand, they also exemplify how the .NET classes are
partitioned so that you can customize the infrastructure using as much or as little
of the other classes as you require.
Chapter 14. Interoperability
Microsoft .NET is a powerful platform, and there are many advantages in writing
a new application within the .NET Framework. However, a typical application is
not a world unto itself, but is built from legacy components as well as new
components, and interoperability is very important. We discussed one kind of
interoperability in Chapter 11 in connection with Web Services. Using the SOAP
protocol it is possible for .NET applications to call Web Services on other
platforms, including Unix, mainframes, and mobile devices.
In this chapter we will look at another kind of interoperability, the interfacing of
managed and unmanaged code running under Windows. The dominant
programming model in modern Windows systems is the Component Object
Model, or COM. There exist a great many legacy COM components, and so it is
desirable for a .NET program, running as managed code, to be able to call
unmanaged COM components. The converse situation, in which a COM client
needs to call a .NET server, can also arise.  Apart from COM, we may also
have need for a .NET program to call any unmanaged code that is exposed as a
DLL, including the Win32 API. The .NET Framework supports all these
interoperability scenarios through COM Interoperability and the Platform
Invocation Services or PInvoke.
COM interop is the only mechanism provided for unmanaged code to
call managed code.
In this chapter we assume that you understand the concepts behind the legacy
Calling COM Components from Managed Code
The first interoperability scenario we will look at is managed code calling COM components.
The .NET Framework makes it easy to create a Runtime Callable Wrapper (RCW), which acts
as a bridge between managed and unmanaged code. The RCW is illustrated in Figure 14-1.
Figure 14-1. A Runtime Callable Wrapper between managed and unmanaged
You could implement an RCW assembly yourself, using the PInvoke facility (described in a
later section) to call into the necessary APIs, such as CoCreateInstance and the IUnknown
methods directly. But that is not necessary, because the Tlbimp.exe tool can read type library
information, and automatically generate the appropriate RCW for you. Visual Studio.NET
makes it even easier when you add a reference to a COM object in Solution Explorer. We will
examine both of these facilities, as we look at some examples of COM components and .NET
The Tlbimp.exe Utility
The Tlbimp.exe utility (Type Library to .NET Assembly Converter) program is provided in the
\Program Files\Microsoft.NET\FrameworkSDK\Bin directory. It is used to generate
managed classes that wrap unmanaged COM classes. The resulting RCW is a .NET component
(i.e., a managed DLL assembly) that managed client code can use to access the COM interface
methods that are implemented in the COM component. The Tlbimp tool is a command line
program that reads COM type library information, and generates a managed wrapper class
along with the associated metadata, and places the result into the RCW assembly. You can view
the resulting contents in this assembly using the Ildasm tool. The command line syntax for
Tlbimp is shown below.
Tlbimp TypeLibName [options]
Where options may contain the following:
Assembly file name
Assembly version number