|
Author | Kate Gregory | Title | Microsoft Visual C++ .NET 2003 Kick Start | Publisher | Sams | Published | DEC 04, 2003 | ISBN | 0672326000 | Price | US$ 34.99 | Pages | 336 |
|
The .NET Base Class Libraries
In This Chapter
- Libraries Shared Across Languages
- Namespaces in C++
- The System Namespace
- Other Useful Namespaces
- In Brief
Libraries Shared Across Languages
When you write managed C++, you have access to all of the managed code
libraries that come with the .NET Framework: the Base Class Libraries, ADO.NET,
ASP.NET, and so on. These libraries are modern and powerful. They provide
services, such as XML processing, that weren't even thought of when older
libraries such as MFC and ATL were first written.
In sharp contrast to the historical capabilities of C++ and Visual Basic, on
the .NET Framework these two languages share the same class libraries. Every
library class and method that's available from Visual Basic is available from
managed C++ and from C#. Every library class and method that's available from C#
is available from Visual Basic and Managed C++.
The advantages of a shared class library are many. They include:
- Reduced learning time when moving from one .NET-supported language to
another
- A larger body of samples and documentation, because these do not need to be
language specific
- Better communication between programmers who work in different
.NET-supported languages
The C++ Advantage - C++ can use the same class libraries as C# and
VB.NET. Does it work the other way around? No. There are libraries of unmanaged
code available from managed C++ that cannot be called from Visual Basic or
C#—ATL and MFC are just two examples. However, it's unlikely that a Visual Basic
or C# programmer would want to use those libraries, because their functionality
is provided elsewhere; the capability to call them from managed C++ helps
simplify a port from unmanaged to managed C++.
Working in Multiple Languages - I have a small consulting firm, and
often our clients specify the programming language we are to use for a project.
When several projects are on the go at once, I might switch languages several
times in the course of a single day, as I help my associates with problems or
track down the last few bugs in systems that are almost finished.
Before I started using the .NET Framework, about once a month I'd suffer a
"brain freeze" and for a moment or two forget how to perform some really simple
task, like determining whether a string contains a particular character, in the
language of the moment. Usually at those times, my fingers would start typing
the method or operator in randomly chosen other languages or libraries: Perl,
Visual Basic, MFC, STL, Java—anything except the one I wanted. Now, with a
common set of libraries across languages, I don't have to "context switch"
nearly as often—and I avoid those "deer in the headlights" moments.
In the past, just because you knew how to perform a specific task, such as
writing some text to a file in Visual C++, didn't mean you knew how to do that
same task in Visual Basic. A great tutorial you found on database access in
Visual Basic wasn't much help if you wanted to write your database application
in Visual C++. Now, as long as you're working in managed C++, the walkthroughs,
tutorials, and samples you find for any managed language—and you'll find plenty
for Visual Basic and C#—are equally applicable to Visual C++. You'll have to
translate the actual language elements, of course, but an explanation of a
particular class or a method of that class is valid no matter which .NET
supported language you intend to use.
Namespaces in C++
Namespaces are a C++ feature designed to eliminate name conflicts,
such as having two classes, each in different libraries, called
String
. Before namespaces were added to the language, library
developers tried to make their names unique by adding letters to them: One
developer's string class might be called GCString
, whereas another
developer might call it TKString
, the string class in MFC is called
CString
, and so on. This approach is ugly and reduces, but doesn't
prevent, name conflicts.
With namespaces, classes can have simple names. Name conflicts are much less
likely, because in addition to a short or local name, classes have a fully
qualified name that includes their namespace. Here's a slightly artificial
example (normally namespaces are used in separate libraries, not jumbled
together in one piece of code like this) that illustrates how they work:
namespace One
{
class Common
{
private:
int x;
public:
Common(int a): x(a) {}
int getx() {return x;}
};
void Do()
{
Common c(3);
Console::WriteLine(__box(c.getx()));
}
}
namespace Two
{
class Common
{
private:
double d1, d2;
public:
Common(double param1) : d1(param1),d2(param1) {}
double getd1() {return d1;}
double getd2() {return d2;}
};
void Do()
{
Common c(3);
String* output = String::Concat(__box(c.getd1()), S" " ,
__box(c.getd2()));
Console::WriteLine(output);
}
}
int _tmain()
{
One::Common c1(3);
Two::Common c2(3);
One::Do();
Two::Do();
return 0;
}
This code defines two namespaces, named One
and
Two
. In each namespace, there is a class called Common
and a function called Do()
. Inside the namespace, there's no
problem referring to Common
just using its short or local name. The
two Do()
functions accomplish this without error; each is working
with the Common
class from its own namespace.
The main function, _tmain()
, cannot refer to Common
or to Do()
using a short name. (The two lines of code commented out
in _tmain()
cause compiler errors.) It has to use the fully
qualified name: the namespace name and the class name, separated by the
scope-resolution operator (::
).
A using
statement allows you to refer to a class with
only its short name. It does not cause the compiler or linker to include any
files that otherwise wouldn't have been included in your build; it's just a
convenience to reduce typing. The example main function can be rewritten as:
using namespace One;
int _tmain()
{
Common c1(3);
Two::Common c2(3);
Do();
Two::Do();
return 0;
}
Modern class libraries are each in their own namespace—for example, the
templates in the Standard Template Library are in the namespace
std
. The developers of the .NET Framework built on this concept,
dividing the class libraries into namespaces and sub-namespaces. This makes them
easier to learn and document.
To use a class in a namespace, you have two choices:
Punctuation: Using . or :: - In most other .NET languages, the
punctuation between the namespace name and the class name is a dot (.
). For example, in both Visual Basic and C#, a developer would
type System.Math.PI
. But in C++, you use a double colon (::
), called the scope-resolution operator. In the
documentation, if you see a reference to System.Something
, you just
need to change it to System::Something
in your code. Use
the scope-resolution operator between namespace and sub-namespace, namespace and
class, or sub-namespace and class.
As always, you use the dot between the name of an object and an ordinary
member function, and the scope-resolution operator between the name of the class
and a static member function or variable. In the previous examples,
PI
is a static member variable of the Math
class. In
other .NET languages, the punctuation between class or object name and function
is always a dot, even when the function is static. This can make the
documentation confusing. Most occurrences of .
in the documentation
should be changed to ::
.
IntelliSense, the feature that pops up lists for you to choose from as you
type, really helps with this confusion. If you type "System." into a file
of C++ code, no list appears and the status bar reads:
IntelliSense: 'Could not resolve type for
expression to the left of . or ->'
On the other hand, if you type "System::", a list of namespaces and
classes appears for you to choose from. Use the lack of feedback from
IntelliSense as an indicator that you have typed the wrong thing, and you'll
find working from the documentation a lot less confusing.
The second choice is a better approach when you're going to be typing class
names from the namespace a number of times, because it saves you typing the
namespace name repeatedly. I prefer the first choice when I'm only typing a
class name once, because the fuller name gives more clues to a maintainer about
what the code is doing. This is even more important when you're using classes
from more obscure namespaces—everyone uses classes from System
and
is familiar with many of them, but the System::Web::Security
namespace, for example, might not be so
obvious to you or to those who will maintain your code.
Whether you choose to add a using
statement to your
file or not, you must add a #using
directive to the top
of your source file. When you create a new .NET project, one of these directives
is added for you automatically:
#using <mscorlib.dll>
This gives you access to all the classes that are directly under the
System
namespace, such as the System::Math
class used in these examples. The documentation for the classes that are in
sub-namespaces of System
includes a line like this one, from System::Xml.Document
:
Assembly: System.XML.dll
This is a clue that you need to add this line to your file:
#using <System.XML.dll>
Don't worry about what seem to be extra dots in the filename, and don't
change dots to ::
here. If you will be using a particular
assembly in every file within a project, you can add a reference to the assembly
instead (right-click References in Solution View and choose Add Reference).
The System Namespace
You'll use the classes in the System
namespace in almost every
.NET application. All the data types are represented, for example. Two classes
in particular deserve special mention: System::Console
and System::String
.
The System::Console Class
System::Console
, called System.Console
in
the documentation, represents the screen and keyboard in a simple console
application. After you add a using
statement to your file
so you don't have to type System::
every time, the
Console
class is simple to use. To write a line of text to the
screen, you use the static function WriteLine()
:
Console::WriteLine("Calculations in Progress");
If you want to write text without a following line break, use the
Write()
function instead.
To read a line of text from the keyboard, first you should write out a line
to prompt the users, and then read the entire line into a System::String
object with the static ReadLine()
function:
Console::WriteLine("Enter a sentence:");
String* sentence = Console::ReadLine();
If you want to read in something more complicated than a single string, there
really isn't any support for it within the Console
class, nor the
classes in the System::IO
namespace covered later in this
chapter. You can read it into a string and then use string member functions to
separate it into the pieces you want.
If you want to write formatted output, there's an overload of
WriteLine
that's reminiscent of printf()
, except that
you don't have to tell the function the type of each parameter. For example:
Console::WriteLine("The time is {0} at this moment",
System::DateTime::Now.ToShortTimeString() );
You can write out a number of parameters at once. Use the placeholders, the
things in brace brackets in the format string, to call for the parameter you
want. As you can see, the count is zero-based. Here's an example:
Console::WriteLine("{0} lives at {1}", name, address);
The System::String and System::Stringbuilder Classes
The String
class represents a string, such as "Hello" or "Kate
Gregory". That's familiar ground for any programmer. But working with .NET
strings can be quite strange for an experienced C++ or MFC programmer. They are
certainly very far removed from the arrays of characters that you might be used
to working with.
If you've ever worked with the MFC class CString
, you've
probably written code like this:
CString message = "Value of x, ";
message += x;
message += "is over limit.";
You might guess that the .NET equivalent would be:
String* message = "Value of x, ";
message += x;
message += "is over limit.";
This just gets you a lot of strange compiler errors about illegal pointer
arithmetic. The message variable is a pointer to a String
instance,
so you can't use the +
operator. You can't do it in a single line
either, like this:
String* message = "Value of x, " + x + "is over limit.";
The bottom line is that you can't treat .NET strings like C++ or C strings.
So how do you build a string from several substrings, or from several pieces in
general? If you want to build it so you can write it out, forget building the
string, and use formatted output as described in the previous section. Or use
the Format()
method, which is reminiscent of
sprintf()
, and of the Format()
method of the old MFC
class CString
. But if you need to build a string in little bits and
pieces, your best choice is a companion class called StringBuilder
,
from the System::Text
namespace. You use a string builder
like this:
String* name = "Kate";
System::Text::StringBuilder* sb = new System::Text::StringBuilder("Hello ");
sb->Append(name);
Using a string builder is more efficient than modifying a string as you go,
because .NET strings actually can't be modified; instead, a whole new one is
created with your changes, and the old one is cleaned up later.
StringBuilder
has all sorts of useful methods like
Append()
, Insert()
, Remove()
, and
Replace()
that you can use to work on your string. When it's ready,
just pass the string builder object to anything that's expecting a string:
Console::WriteLine(sb);
The framework gets the built string from the string builder and passes it to
the function for you.
The String
class has its own useful methods too. Consider the
problem mentioned earlier—reading something other than a single string from the
keyboard. The easiest way for you to tackle the problem is to write code that
reads the line of input into a string, and then works with it. Here's a simple
example:
Console::WriteLine("Enter three integers:");
String* input = Console::ReadLine();
String* numbers[] = input->Split(0);
int a1 = Convert::ToInt32(numbers[0]);
int a2 = Convert::ToInt32(numbers[1]);
int a3 = Convert::ToInt32(numbers[2]);
This code uses the Split()
member function of the
String
class. It splits a String
into an array of
strings based on a separator character. If you pass in a null pointer (0), as in
this example, it splits the string based on whitespace such as spaces or tabs,
which is perfect for this situation. The ToInt32()
method of the
Convert
class converts a String
to an integer.
If you already know how to manipulate strings, you might appreciate a quick
"cheat sheet" for the String
class. Table 3.1 is just such a
summary.
Table 3.1 String Functions
C Runtime | MFC CString | System::String |
strcpy | operator= | operator= |
strcat | operator+= | Append |
strchr | Find | IndexOf |
strcmp | operator == or Compare | Compare |
strlen | GetLength() | Length |
strtok | n/a | Split |
[] | [] or GetAt() | Chars |
sprintf | Format | Format |
n/a | Left or Right or Mid | Substring |
If you've worked with strings in other languages, you'll appreciate System::String
functions such as PadLeft()
,
PadRight()
, Remove()
, and StartsWith()
.
If those names aren't familiar to you, check the Visual C++ documentation. You
might be able to do what you want with a single function call!
The System::DateTime Structure
The DateTime
structure represents a date or a time, or both. It
has a number of useful constructors to create instances using a numeric date and
time, or a number of ticks (useful when you're working with older C++ code).
Here are some examples:
DateTime defaultdate;
Console::WriteLine(defaultdate.ToLongDateString());
DateTime Sept28(2003,9,28,14,30,0,0);
Console::Write(Sept28.ToShortDateString());
Console::Write(S" ");
Console::WriteLine(Sept28.ToShortTimeString());
DateTime now = DateTime::Now;
Console::WriteLine(now.ToString());
It can be intimidating to remember the parameters to the seven-integer
constructor, but it's simple when you realize they go from largest to smallest:
year, month, day, hour, minute, second, and millisecond. The millisecond
parameter is optional and if you want, you can omit all the time parameters
completely.
On September 17, 2003, this code produces the following output:
Monday, January 01, 0001
9/28/2003 2:30 PM
9/17/2003 1:17:41 PM
It matters that DateTime
is a structure, not a class, because it
is managed data. Managed classes can only be allocated on the heap with new
; managed structures can only be allocated on the stack as
in these examples.
To get the individual parts of a date, use these properties:
Day
: The day of the month
Month
: 1 to 12
Year
: Always four digits
Hour
: 0 to 23
Minute
Second
DayOfWeek
: 0 means Sunday
Format
: Creates a string based on the time and date, using a
format string
The format string passed to Format()
is either a single
character representing one of a number of "canned" formats, or a custom format
string. The most useful canned formats include:
d
: A short date, such as 12/19/00
D
: A long date, such as Tuesday, December 19, 2000
f
: A full time and date, such as Tuesday, December 19, 2000
17:49
g
: A general time and date, such as 12/19/00 17:49
s
: A sortable time and date, such as 2000-12-19 17:49:03
t
: A short time, such as 17:49
T
: A long time, such as 17:49:03
If none of the canned formats has what you need, you can make your own by
passing in strings such as "MMMM d, yy" for "December 3, 02" or whatever else
you desire. You can find all the format strings in the help installed with
Visual Studio.
Other Useful Namespaces
There are plenty of other useful classes contained in sub-namespaces of the
System
namespace. Some are covered elsewhere in this book. Four are
covered here because almost every .NET developer is likely to use them: System::IO
, System::Text
, System::Collections
, and System::Threading
.
The System::IO Namespace
Getting information from users and providing it to them is the sort of task
that can be incredibly simple (like reading a string and echoing it back to the
users) or far more complex. The most basic operations are in the
Console
class in the System
namespace. More
complicated tasks are in the System::IO
namespace. This
namespace includes 27 classes, as well as some structures and other related
utilities. They handle tasks such as:
- Reading and writing to a file
- Binary reads and writes (bytes or blocks of bytes)
- Creating, deleting, renaming, or moving files
- Working with directories
This snippet uses the FileInfo
class to determine whether a file
exists, and then deletes it if it does:
System::IO::FileInfo* fi = new System::IO::FileInfo("c:\\test.txt");
if (fi->Exists)
fi->Delete();
This snippet writes a string to a file:
System::IO::StreamWriter* streamW =
new System::IO::StreamWriter("c:\\test.txt");
streamW->Write("Hi there" );
streamW->Close();
Be sure to close all files, readers, and writers when you have finished with
them. The garbage collector might not finalize the streamW
instance
for a long time, and the file stays open until you explicitly close it or until
the instance that opened it is finalized.
When Typing Strings with Backslashes - The backslash character (\) in
the filename must be "escaped" by placing another backslash before it. Otherwise
the combination \t will be read as a tab character. This is standard C++
behavior when typing strings with backslashes.
Check the documentation to learn more about IO classes that you can use in
console applications, Windows applications, and class libraries. Keep in mind
also that many classes can persist themselves to and from a file, or to and from
a stream of XML.
The System::Text Namespace
Just as Console
offers simple input and output abilities, the
simplest string work can be tackled with just the String
class from
the System
namespace. More complicated work involves the System::Text
namespace. You've already seen System::Text::StringBuilder
. Other classes in this namespace
handle conversions between different types of text, such as Unicode and
ASCII.
The System::Text::RegularExpressions
namespace lets
you use regular expressions in string manipulations and elsewhere. Here is a
function that determines whether a string passed to it is a valid US ZIP
code:
using namespace System;
using namespace System::Text::RegularExpressions;
String* Check(String* code)
{
String* error = S"OK";
Match* m;
switch (code->get_Length())
{
case 5:
Regex* fivenums;
fivenums = new Regex("\\d\\d\\d\\d\\d");
m = fivenums->Match(code);
if (!m->Success)
error = S"Non numeric characters in 5 digit code";
break;
case 10:
Regex* fivedashfour;
fivedashfour = new Regex("\\d\\d\\d\\d\\d-\\d\\d\\d\\d");
m = fivedashfour->Match(code);
if (!m->Success)
error = S"Not a valid zip+4 code";
break;
default:
error = S"invalid length";
}
return error;
}
The Regex
class represents a pattern, such as "five numbers" or
"three letters." The Match
class represents a possible match
between a particular string and a particular pattern. This code checks the
string against two patterns representing the two sets of rules for ZIP
codes.
The syntax for regular expressions in the .NET class libraries will be
familiar to developers who have used regular expression as MFC programmers, or
even as UNIX users. In addition to using regular expressions with classes from
the System::Text
namespace, you can use them in the Find
and Replace dialog boxes of the Visual Studio editor, and with ASP.NET
validation controls. It's worth learning how they work.
Regular Expression Syntax
A regular expression is some text combined with special characters that
represent things that can't be typed, such as "the end of a string" or "any
number" or "three capital letters."
When regular expressions are being used, some characters give up their usual
meaning and instead stand in for one or more other characters. Regular
expressions in Visual C++ are built from ordinary characters mixed in with these
special entries, shown in Table 3.2.
Here are some examples of regular expressions:
- ^test$ matches only test alone in a string.
- doc[1234] matches doc1, doc2, doc3, or doc4 but not doc5.
- doc[1-4] matches the same strings as doc[1234] but requires less typing.
- doc[^56] matches doca, doc1, and anything else that starts with doc, except
doc5 and doc6.n -H\~ello matches Hillo and Hxllo (and lots more) but not Hello.
H[^e]llo has the same effect.
- [xy]z matches xz and yz.
- New *York matches New York, NewYork, and New York (with several spaces
between the words).
- New +York matches New York and New York, but not NewYork.
- New.*k matches Newk, Newark, and New York, plus lots more.
- World$ matches World at the end of a string, but World\$ matches only World$
anywhere in a string.
Table 3.2 Regular Expression Entries
Entry | Matches |
^ | Start of the string. |
$ | End of the string. |
. | Any single character. |
[] | Any one of the characters within the brackets (use – for a range, ^ for
"except"). |
\~ | Anything except the character that follows. |
* | Zero or more of the next character. |
+ | One or more of the next character. |
\w | A single letter or number, or an underscore. (These are called word
characters and are the only characters that can be used in a variable
name.) |
\s | Whitespace (tabs or spaces). |
\d | A single numerical digit. |
\ | Removes the special meaning from the character that
follows. |
The System::Collections Namespace
Another incredibly common programming task is holding on to a collection of
objects. If you have just a few, you can use an array to read three integers in
one line of input. In fact, arrays in .NET are actually objects, instances of
the System::Array
class, which have some useful member
functions of their own, such as Copy()
. There are times when you
want specific types of collections, though, and the System::Collections
namespace has plenty of them. The provided
collections include:
Stack
. A collection that stores objects in order. The object
stored most recently is the first taken out.
Queue
. A collection that stores objects in order. The first
stored is the first taken out.
Hashtable
. A collection that can be searched far more quickly
than other types of collections, but takes up more space.
ArrayList
. An array that grows as elements are added to it.
SortedList
. A collection of two-part (key and value) items that
can be accessed by key or in numerical order.
BitArray
. A compact way to store an array of true/false flags.
One rather striking omission here is a linked list. You have to code your own
if you need a linked or double-linked list.
The System::Threading Namespace
Threading has been a difficult part of Windows programming from the very
beginning. It's quite a bit simpler in .NET. The vital classes for threading are
in the System::Threading
namespace. These include classes
such as Mutex
, Thread
, and ThreadPool
,
which developers with experience in threaded applications will recognize
instantly.
A thread is a path of execution through a program. In a multithreaded
program, each thread has its own stack and operates independently of other
threads running within the same program.
How Many Threads? - Any application always has at least one thread,
which is the program's primary or main thread. You can start and stop as many
additional threads as you need, but the main thread keeps running as long as the
application is active.
A thread is the smallest unit of execution, much smaller than a process.
Generally, each running application on your system is a process. If you start
the same application twice (for example, Notepad), there are two processes: one
for each instance. It is possible for several instances of an application to
share a single process: For example, if you choose File, New Window in Internet
Explorer, two applications appear on your taskbar, and they share a process. The
unfortunate consequence is that if one instance crashes, they all do.
Writing a multithreaded application is simple with classes from the System::Thread
namespace. First, you need to think of some work
for a new thread to do: some slow calculation that you want to run without
slowing the responsiveness of your application. This work will go into a
function, and the function will be executed on a new thread.
The Thread Proc - For a long time, Windows C++ programmers have called
the function the thread proc (short for procedure).
Your thread proc must be a member function of a garbage-collected class. For
example:
public __gc class Counter
{
public:
void Countdown()
{
String* threadName = Thread::CurrentThread->Name;
Console::Write(S"This is the current thread: ");
Console::WriteLine(threadName);
for (int counter = maxCount; counter >= 1; counter--)
{
Console::WriteLine(S"{0} is currently on {1}.",threadName,
__box(counter));
}
Console::WriteLine(S"{0} has finished counting down from {1}.",
threadName,
__box(maxCount));
}
};
The heart of this function is the for
loop that counts
down from maxCount
to zero. It uses the Name
property
of the thread that is executing the function, because the sample that uses this
class calls the function on two different threads. Normally, the function that
does the asynchronous work would not communicate with the user; it would be
quietly working in the background, doing some long slow work. It might, however,
update a progress bar, or an icon that indicates a background task is in
progress.
This code calls the thread proc on a new thread and also on the application's
main thread:
Console::Write("Enter a number to count down from: ");
maxCount = Int16::Parse(Console::ReadLine());
Thread::CurrentThread->Name = "Main Thread";
Counter* counter = new Counter();
ThreadStart* SecondStart = new ThreadStart(counter,Counter::Countdown);
Thread* secondThread = new Thread(SecondStart);
secondThread->Name = "Secondary Thread";
secondThread->Start();
counter->Countdown();
This code keeps the number entered by the user in a global variable called
maxCount
. Because ReadLine
returns a pointer to a
System::String
instance, the static Parse()
method of Int16
is used to convert a string to an integer so that
it can be stored in maxCount
.
To execute a particular function on a new thread, you create a
Thread
object and call its Start()
method. To create a
Thread
object, you need a ThreadStart
object, and the
constructor for ThreadStart
needs a reference to an instance of a
garbage-collected class (counter
in this example), and a function
pointer (just the name of the function without the parentheses) to a member
function of that garbage-collected class.
Because this code calls Countdown
on a new thread and also on
the main thread, the output shows the two threads taking turns, as in Figure
3.1.
Figure 3.1
A multithreaded application demonstrates how threads take turns doing their
work.
In Brief
- The libraries that come with the .NET Framework are large, and cover almost
every task programmers are likely to tackle. Using them can save programmers
hours or days of work.
- The same class libraries are used in Visual Basic, C#, and managed C++, but
managed C++ can also call some unmanaged libraries that Visual Basic and C#
cannot access.
- The
System
namespace holds classes for simple common tasks, and
a large number of sub-namespaces for slightly less common (but still important)
tasks, including string manipulation, IO, and threading.
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.