Programming Microsoft Windows Ce Net 3Rd [Electronic resources] نسخه متنی

اینجــــا یک کتابخانه دیجیتالی است

با بیش از 100000 منبع الکترونیکی رایگان به زبان فارسی ، عربی و انگلیسی

Programming Microsoft Windows Ce Net 3Rd [Electronic resources] - نسخه متنی

| نمايش فراداده ، افزودن یک نقد و بررسی
افزودن به کتابخانه شخصی
ارسال به دوستان
جستجو در متن کتاب
بیشتر
تنظیمات قلم

فونت

اندازه قلم

+ - پیش فرض

حالت نمایش

روز نیمروز شب
جستجو در لغت نامه
بیشتر
لیست موضوعات
توضیحات
افزودن یادداشت جدید






A Brief Introduction to Managed Applications


As mentioned previously, managed applications are compiled to an intermediate byte code called the Common Intermediate Language. This language is simply a series of byte codes that define an opcode set for a virtual stack-based processor. A run-time engine, implemented in native CPU instructions, converts the byte codes to native instructions and then has the CPU execute the native instructions. This process, called just-in-time compilation, differs from conventional interpreters because the native instructions are cached. If the method is executed again, the cached native instructions are directly called instead of the original byte codes being reinterpreted as in an interpreter. One can argue the merits of waiting to convert virtual instructions to native instructions at run time; this text simply accepts that the .NET runtime uses this process.

Performance of a JIT runtime, although inferior to that of native compiled code, isn't as bad as you might think. The compilation takes place only on the code paths being executed in the module, so any code not executed isn't compiled. Also, because most routines execute many times, the average speed of a routine will eventually approach the speed of precompiled native code. As long as the system retains enough free memory, the cache will retain the code for the application, alleviating the need to recompile the methods.

In low-memory conditions, the execution engine tries to recover memory in a process called garbage collection. During garbage collection, the runtime finds all the discarded heap objects and recovers that memory for reuse. The remaining objects that are still in use are relocated to the start of the heap. Because this process involves moving in-use objects, all threads in the application are suspended during the garbage collection. If the garbage collection doesn't free enough memory, parts of the code cache can be purged in a process called code pitching. If code for a method is pitched and that method is executed again, the original byte codes are reloaded and recompiled.

Microsoft currently supports two high-level languages for generating CIL byte codes for execution on the Compact Framework runtime: Visual Basic .NET and Visual C#. Both tools come with components that generate Compact Framework applications and automatically deploy the application to devices or emulators. Visual Studio also has a remote debugger that debugs the code on the device remotely from a connected PC. Microsoft doesn't provide a tool for generating managed code for the Compact Framework with its C++ compiler.

Although many embedded developers are familiar with Visual Basic, if only to sneer at Visual Basic developers, C# (pronounced C sharp) is a new language created by the developers at Microsoft to be a better C++. C# retains most of C++'s syntax while providing improvements that tend to encourage safer code.

Regardless of opinions and snobbery of some developers, the runtime doesn't care about the high-level language used to create a Compact Framework application as long as the compiler produces the IL byte codes supported by the runtime. One of the nicer features of the .NET environment is that the full power of the runtime and its accompanying class library is available to all languages equally. As a result, Visual Basic .NET programmers can now create applications just as powerful, fast, and functional as can be created with Visual C#.


HelloCF


In the spirit of the Chapter 1 discussion introducing Windows CE applications, the following HelloCF example introduces a basic Compact Framework application. This first HelloCF example is written in C#, although I'll present a Visual Basic .NET example shortly. The first version of HelloCF is shown in Listing 23-1.

Listing 23-1: HelloCF source code






public class Hello
{
static void Main()
{
System.Console.WriteLine ("Hello Compact Framework");
}
}











A C# program is encapsulated in one or more classes; in the preceding program, the class is Hello. The only method of this class is the static method Main, which is the entry point to the program. In this example, Main takes no parameters and returns no values, although both are possible in C#.

The static keyword indicates that this method is associated with the class, not an instance of the class. This essentially means that the method is accessible by referencing the class Hello instead of having to create an instance of the class Hello and then calling the method. In the case of the entry point of the application, this distinction is important since an instance of the class Hello has not been created when the application is launched. In most cases, Main will create an instance of its encapsulating class, as we'll see in later examples.

The single line of code in Main is a call to the WriteLine method of the Console class. As you can guess, WriteLine displays the string on the console associated with the application. If a console driver is not present on the system, as with the Pocket PC, the line doesn't get displayed at all. After calling WriteLine, Main exits, terminating the program.

Namespaces


Notice that the text System.Console.WriteLine specifies the call to WriteLine. Going from right to left, WriteLine is the name of the method in the class Console. System is the namespace where the Console class is defined. The concept of namespaces is used extensively throughout .NET languages. Namespaces are constructs that organize the naming scheme of the application and the library of classes the application references. Namespaces prevent naming conflicts across independently created classes by enclosing those classes in unique namespace names.

For example, two class libraries developed independently can both contain classes named Bob. As long as the namespaces of the two libraries are unique, the application can reference each of the classes by prefixing the reference with the namespace containing the desired class. So if one namespace is named BigCorpClassLib and the other is named UpAndComingClassLib, the classes will be referenced as BigCorpClassLib.Bob and UpAndComingClassLib.Bob.

To avoid having to explicitly reference the namespace of each class, C# provides the using directive to declare that the compiler should check references in the file with the definitions in the namespace in the using directive. Multiple using directives can be declared in a file. Listing 23-2 shows HelloCF with a using statement.

Listing 23-2: HelloCF with a using statement






using System;
public class Hello
{
static void Main()
{
Console.WriteLine ("Hello Compact Framework");
}
}











Regardless of whether a using directive is declared, the application can still reference the class explicitly by specifying the namespace that encapsulates the class. This scheme allows an application to use a using directive for simplicity but still reference a class in another namespace that happens to have a name that conflicts with a class in the current namespace. For example, if HelloCF wanted to reference both the Console class in System, which was referenced in the using directive, and a class named Console in BigCorpClassLib, the namespace could be explicitly referenced, as in

Console.WriteLine ("Hello Compact Framework");
BigCorpClassLib.Console.WriteLine ("Hello Compact Framework");

Traditionally, all but the most trivial of "Hello World" applications also declare a namespace of their own. This leads us to the last of the HelloCF examples written in C#, shown in Listing 23-3.

Listing 23-3: HelloCF that defines its own namespace






using System;
namespace HelloCF
{
public class Hello
{
static void Main()
{
Console.WriteLine ("Hello Compact Framework");
}
}
}











The namespace can be any name, although the Visual C# code wizard will create a namespace that matches the name of the project. The namespace encloses all the structures, classes, and other definitions within the namespace. In addition, multiple files can specify the same namespace. The code resulting from a compilation of multiple files each specifying the same namespace will then be combined in the resulting module.

Visual Basic .NET


But what of our Visual Basic cousins? Although all of the examples in this book have been written in C, C++, or C# for valid reasons, the truth of the matter is that the audience of Basic programmers is significantly larger than that of C and C++ programmers. The same Hello World program written in Visual Basic .NET would look like the version shown in Listing 23-4.

Listing 23-4: HelloCF written in Visual Basic .NET






Imports System
Module Module1
Sub Main()
Console.WriteLine("Hello Compact Framework.")
End Sub
End Module











The difference between the Visual Basic example and the C# example is frankly not that great. Of course, there is the difference in the text used to mark subroutines vs. methods, and the use of the Imports keyword instead of the using directive. Still, the structure of the two programs is remarkably similar. The key similarity is that both examples use the same class library! This means that a Visual Basic .NET program can do anything that a C# program can do. This is a huge improvement over the old days, when languages weren't picked for their syntax but for their function. The remaining examples in this chapter will be written in C# for consistency, but everything presented in this chapter can be applied equally well to Visual Basic and C# applications.


Common Language Runtime Basics


The common language runtime (CLR) is the foundation of all .NET languages. The runtime, along with the Framework Class Library, defines everything from the base data types to methods to connect to the Internet. Such an ambitious foundation deserves a bit of coverage here.

Value vs. Reference Types


Data types in the CLR are divided into two categories, value types and reference types. Instances of value types contain the data for that instance. Value types are data types such as signed and unsigned integers, shorts, and bytes. Structures are also value types in the CLR. Assigning a value to a value type causes the data to be held by the instance of that type. The data contained by the instance of the type can be copied to another instance of the type with a simple assignment, as in

x = y;

Reference types are those wherein the instance of the type refers to the data for the type instead of containing it. From a C perspective, reference types are pointers to data, whereas value types are the data itself. In C#, any class is a reference type. When a reference type is assigned to another reference type, the reference is copied instead of the data.

Understanding the difference between value types and reference types is critical to .NET languages. Consider the following scenario with two instances (A and B) of some data type:

A.x = 3;
B = A;
B.x = 4;

Given this sequence, what result does the property A.x end up with? Well, it depends: are A and B value types or reference types? If A and B are structures, the assignment of B to A simply copies the data from structure A to structure B because structures are value types. So the data in the structures A and B remains independent. When B.x is assigned 4, the x property of B is modified but the same property of A is unchanged. A.x remains 3 because B was a copy of A.

If A and B are classes, they are reference types. In this case, the statement B = A simply copies a reference of the class referenced by A to B. So the assignment of B.x to 4 sets the x property of the class referred to by B to 4. Since both A and B refer to the same class, B.x refers to the same property x of the class as A.x. The expression A.x in this case results in the data 4.

Another consideration close to the heart of any embedded programmer is the implementation details of value types vs. reference types. Value types are maintained on the virtual stack of the machine. They are not subjected to garbage collection because their data is contained on the self-managing stack. The data for reference types is maintained in the system heap, with the reference to the data maintained on the stack. Think of a pointer to data being kept on the stack while the data itself is in the heap.

Aside from defining structures instead of classes, the programmer has little control over using reference types vs. value types. The value types are limited to the standard integer types known to all programmers. Everything else is a reference type.

Passing value types to methods can be accomplished by passing either the value or a reference to the value. If the value is passed, a copy of the data is placed on the stack and passed to the called method. Any changes to the value made by the called method do not change the value in the calling routine. If a reference is passed, a reference to the data type is passed to the called method. In this case, the called method can modify the value in the calling routine because the reference refers to the original data. The following short code fragment demonstrates this:

private void CallingRoutine ()
{
int a, b, c;
a = 1;
b = 2;
c = 3;
test1 (a, ref b, out c);
// Will produce a:1 b:6: c:7
Console.WriteLine ("a:{0} b:{1} c:{2}", a, b, c);
return;
}
private void test1 (int a, ref int b, out int c)
{
a = 5;
b = 6;
c = 7;
return;
}

The function prototype of the called routine, test1, shows the C# syntax for passing parameters. The parameter a is passed as a value type, so any changes in the called routine won't affect its original value in the calling routine. The parameter b is passed as a reference type. This means that any changes to b in test1 are reflected in the original value in the calling routine. The ref keyword also causes the C# compiler to require that a type parameter be set before calling test1. The final parameter, c, is marked as an out parameter. Out parameters are reference parameters with a twist: they don't have to be initialized before they're passed to the called routine, but the called routine must set any out parameters before returning.

Events and Delegates


Another somewhat interesting aspect of .NET programming is the concept of delegates and events. Although the names are new, the concepts are not. A delegate is the managed version of a typed function pointer. C++ programmers are familiar with declaring a callback function of a specific type. In the following unmanaged code, the type POWERCBPROC is defined as a function prototype that can be used as a callback to the application in case of a power change.

// Callback function prototype
typedef BOOL (CALLBACK* POWERCBPROC)(DWORD);

In C#, the corresponding delegate would be defined as follows:

public delegate bool PowerEventHandler (uint data);

The public designation allows the delegate to be referenced outside the class where it was defined. The public designation is common because delegates are typically used by classes to define methods that will be called from other classes. The delegate keyword defines this line as a delegate. The remainder of the line is the prototype of all the methods that will be called when the event tied to this delegate is fired.

An event is an object that contains a list of methods matching a specific prototype. The methods can then be called by invoking the event. From a C++ perspective, think of an event as an array of function pointers. The process of raising an event results in the code calling each of the function pointers in the array. In managed code, each method in the event list must match the specific function prototype specified in an associated delegate. The following code declares an event named PowerEvent of type CbEventHandler, which was declared in the preceding delegate discussion.

public event PowerEventHandler PowerEvent;

The event declared here is PowerEvent. Its type is the delegate PowerEventHandler. As you can see, the event can't be declared without the corresponding delegate type being defined as well. Referring back to the C++ comparison, a list of function pointers shouldn't be defined without a corresponding function prototype defining the functions pointed to in the list.

Once a class defines both a delegate and an event, classes can register for the event by creating a method that matches the delegate and then adding a reference to that method to the event. The creation of the method is simple because all it has to do is match the parameter list and return a value defined by the delegate. The following code shows a skeleton of an event handler routine that matches the PowerEventHandler delegate:

bool MyPowerEventHandler (uint data)
{
// Do something here.
return true;
}

Registering with the event is syntactically different, depending on the language. For C#, registering with the event is accomplished with the += operator, as in

PowerEvent += new PowerEventHandler (MyPowerEventHandler);

The new keyword is needed because the code needs to create a new event that points to the MyPowerEventHandler method. The new event instance is then added to the event chain with the += operator. It's important not to use the = operator because the direct assignment would remove any other event handlers from the event list.

In Visual Basic .NET, the syntax for attaching to an event is as follows:

AddHandler PowerEvent AddressOf MyPowerEventHandler;

Regardless of the language, understanding events and delegates is critical to understanding how .NET applications work. When a Visual Studio designer tool is used, the part of the tool that provides forms-based code generation typically inserts the proper references and automatically creates the skeletons of the event handlers, so all that's necessary is to fill in the code. Although the designer is quite helpful, using it doesn't take the place of understanding what's really going on in the code.

Strings


The subtleties of managed strings are important to understand. The standard string class maintains what is called an immutable string, one that can't be changed. This means that anytime you modify a string, the Framework creates a new string class with the new characters and, unless the application keeps the original string in scope, discards the old string to be garbage collected sometime in the future. For example, consider the following:

string str = "abcdef";
str = str + "g";
str = str + "h";
str = str + "i";
str = str + "j";
Console.WriteLine (str);

In this code, five different string objects are created before the string is finally displayed with the WriteLine method. Each time the string is modified by appending a character, the original string instance is discarded and a new one created. Clearly, poorly designed managed applications can produce huge amounts of discarded strings that will eventually require a garbage collection to recover.

To avoid excessively generating discarded strings, the Framework provides a StringBuilder class. This stringlike class maintains a mutable string, one that can be modified in place. An instance of a StringBuilder class is analogous to a self-managing character buffer. When an instance is created, either a size of the buffer is specified by the application or a default size is used. Once the instance is created, characters can be added, trimmed, or changed without the overhead of discarding the current instance of the class. Individual characters in the string can be referenced by its index into the array of characters. For example

// Create a string builder with the string abcdef and a max capacity of 256.
StringBuilder sb = new StringBuilder ("abcdef", 256);
char c = sb[4];
Console.WriteLine (c.ToString()); // This displays the character 'e'.
sb[3] = 'z';
Console.WriteLine (sb.ToString()); // This displays the string "abczef".

The StringBuilder class is great for string manipulation, but it's not a string. So after the string is constructed, the characters in StringBuilder are typically converted to a string for use. Although both C# and Visual Basic .NET make it simple to use the standard string class, any involved string formatting routines should use the StringBuilder class to avoid excessive load on the heap.

The string class has one method that deserves special attention. The static Format method provides a sprintf-style function that formats parameters into a string. The prototype of string.Format looks like the following:

public static string Format(string, object, object, object);

As with sprintf, the first parameter is the formatting string. The remaining parameters in the variable-length parameter list are the objects whose string representations will be used in the formatting placeholders in the format string.

The formatting characters used in the Framework are different from the standard C format characters such as %d and %x. Instead, the parameter placeholders in the string are enclosed in curly brackets and reference the individual parameters explicitly. For example, to reference the first parameter in the parameter list, the placeholder is {0}. The placeholder {1} is used to reference the second parameter, {2} references the third parameter, and so on. The objects in the parameter list all must be able to be converted to a string. For standard numbers and strings, this conversion happens automatically, but for complex types, the ToString method, available in all objects in the Framework, must be explicitly used to create a string representation of the object.

Individual placeholders can also specify how the object is presented. For example, a number can be presented in decimal, hexadecimal, or any number of other formats. The formatting characters are specified in each of the placeholders by adding a colon followed by the formatting string. For example, the following string represents the number 100 in decimal, hexadecimal, and scientific formats:

string str = string.Format ("a:{0:d}, b:{1:x}, c:{1:e}", 100, 100, 100);
// Results in the output
a:100, b:64, c:1.000000e+002

In addition, significant digits both before and after the decimal point can be specified in the formatting string. Notice how the Format method is referenced by specifying the string type, not an instance of a string. This is because Format is a static method of the string class.

String formatting characters can also be used in the ToString methods of many of the objects in the Framework. For example, the string created previously with the Format method can also be created with the following statement:

string str= "a:" + 100.ToString() + "a:" + 100.ToString("x")
+ "a:" + 100.ToString("e");

The Framework supports a number of other formatting characters. A complete list of the capabilities of formatting strings can be found in the .NET Compact Framework documentation.

/ 169