Inherent within the Microsoft .NET Framework are many design goals that are practical yet extremely ambitious. In this section, we discuss the main design goals of the Microsoft .NET Framework, including better support for components, language integration, application interoperation across cyberspace, simple development and deployment, better reliability, and greater security.
Prior to the existence of COM technology, Microsoft developers had no simple way to integrate binary libraries without referring to or altering their source code. With the advent of COM, programmers were able to integrate binary components into their applications, similar to the way we plug-and-play hardware components into our desktop PCs. Although COM was great, the grungy details of COM gave developers and administrators many headaches.
While COM permits you to integrate binary components developed using any language, it does require you to obey the COM identity, lifetime, and binary layout rules. You must also write the plumbing code that is required to create a COM component, such as DllGetClassObject, CoRegisterClassObject, and others.
Realizing that all of these requirements result in frequent rewrites of similar code, .NET sets out to remove all of them. In the .NET world, all classes are ready to be reused at the binary level. You don’t have to write extra plumbing code to support componentization in the .NET Framework. You simply write a .NET class, which then becomes a part of an assembly (to be discussed in Chapter 2), and it will support plug-and-play.[1]
In addition to providing such a framework to make development easier, .NET also removes the pain of developing COM components. Specifically, .NET removes the use of the registry for component registration and eliminates the requirements for extraneous plumbing code found in all COM components, including code to support IUnknown, class factories, component lifetime, registration, dynamic binding, and others.
COM supports language independence, which means that you can develop a COM component in any language you want. As long as your component meets all the rules spelled out in the COM specification, it can be instantiated and used by your applications. While this supports binary reuse, it doesn’t support language integration . In other words, you can’t reuse the code in the COM components written by someone else; you can’t extend a class hosted in the COM component; you can’t catch exceptions thrown by code in the COM component; and so forth.
Microsoft .NET supports not only language independence, but also language integration. This means that you can inherit from classes, catch exceptions, and take advantage of polymorphism across different languages. The .NET Framework makes this possible with a specification called the Common Type System (CTS), which all .NET components must support. For example, everything in .NET is an object of a specific class that derives from the root class called System.Object. The CTS supports the general concepts of classes, interfaces, delegates (which support callbacks), reference types, and value types. The .NET base classes provide most of the base system types, such as ones that support integer, string, and file manipulation. Because every language compiler must meet a minimum set of rules stipulated by the Common Language Specification (CLS) and generate code to conform to the CTS, different .NET languages can intermingle with one another. We will examine the CTS and CLS in Chapter 2.
COM supports distributed computing through its Distributed COM (DCOM) wire protocol. A problem with DCOM is that it embeds the host TCP/IP address inside the Network Data Representation (NDR) buffer, such that it will not work through firewalls and Network Address Translation (NAT) software. In addition, the DCOM dynamic activation, protocol negotiation, and garbage-collection facilities are proprietary, complex, and expensive. The solution is an open, simple, and lightweight protocol for distributed computing. The .NET Framework uses the new industry-supported SOAP protocol, which is based on the widely accepted XML and HTTP standards.
If you have developed software for the Windows platforms since their appearance, you have seen everything from the Windows APIs to the Microsoft Foundation Classes (MFC), the Active Template Library (ATL), the system COM interfaces, and the countless other environments, such as Visual Interdev, Visual Basic, JScript, and other scripting languages. Each time you set out to develop something in a different compiler, you had to learn a new API or a class library, because there is no consistency or commonality among these different libraries or interfaces.
The .NET solution provides a set of framework classes and lets every language use it. Such a framework removes the need for learning a new API each time you switch languages. Put differently, it’s certainly easier to go through ten methods of a particular class than to go through a thousand API functions.
Imagine this scenario: your Windows application, which uses three shared DLLs, works just fine for months, but stops working one day after you’ve installed another software package that overwrites the first DLL, does nothing to the second DLL, and adds an additional copy of the third DLL into a different directory. If you have ever encountered such a brutal—yet entirely possible—problem, you have entered DLL Hell. And if you ask a group of seasoned developers whether they have experienced DLL Hell, they will grimace at you in disgust, not because of the question you’ve posed, but because they have indeed experienced the pain and suffering.
To avoid DLL Hell on Windows 2000 (at least for system DLLs), Windows 2000 stores system DLLs in a cache. If you install an application that overwrites system DLLs, Windows 2000 will overwrite the added system DLLs with the original versions from the cache.
Microsoft .NET further diminishes DLL Hell. In the .NET environment,
your executable will use the shared DLL with which it was built. This
is guaranteed, because a shared DLL must be registered against
something similar to the Windows 2000 cache, called the
Global Assembly Cache (GAC).
In addition to this requirement, a shared DLL must have a unique hash
value, public key, locale, and version number. Once you’ve met
these requirements and registered your shared DLL in the GAC, its
physical filename is no longer important. In other words, if you have
two versions of a DLL that are both called
MyDll.dll
,
both of them can live and execute on the same system without causing
DLL Hell. Again, this is possible because the executable that uses
one of these DLLs is tightly bound to the DLL during compilation.
In addition to eradicating DLL Hell, .NET also removes the need for component-related registry settings. A COM developer will tell you that half the challenge of learning COM is understanding the COM-specific registry entries for which the developer is responsible. Microsoft .NET stores all references and dependencies of .NET assemblies within a special section called a manifest (see Chapter 2). In addition, assemblies can be either private or shared. Private assemblies are found using logical paths or XML-based application configuration files, and public assemblies are registered in the GAC; in both cases the system will find your dependencies at runtime. If they are missing, you get an exception telling you exactly what happened.
Finally, .NET brings back the concept of zero-impact installation and removal. This concept is the opposite of what you have to deal with in the world of COM. To set up a COM application, you have to register all your components after you have copied them over to your machine. If you fail to perform this step correctly, nothing will work and you’ll pull your hair out. Likewise, to uninstall the application, you should unregister your components (to remove the registry entries) prior to deleting your files. Again, if you fail to perform this step correctly, you will leave remnants in the registry that will be forever extant.
Unlike COM, but like DOS, to set up an application in .NET, you
simply xcopy
your files from one directory on a CD
to another directory on your machine, and the application will run
automatically.[2] Similarly, you can just delete the
directory to uninstall the application from your machine.
There are many programming languages and platforms in the commercial software industry, but few of them attempt to provide both a reliable language and a robust runtime or infrastructure. The most successful language that we have seen in the commercial software industry is the Java™ language and the Java Virtual Machine™, which have brought the software-development community much satisfaction. Microsoft is positioning .NET as the next big thing.
Microsoft .NET requires type safety. Unlike C++, every class in .NET is derived from the mother of all classes, Object, which supports runtime type-identification features, content-dumping features, and so on. The CLR must recognize and verify types before they can be loaded and executed. This decreases the chances for rudimentary programming errors and prevents buffer overruns, which can be a security weakness.
Traditional programming languages don’t provide a common error- handling mechanism. C++ and Java support exception handling, but many others leave you in the dust, forcing to invent your own error-handling facilities. Microsoft .NET supports exceptions in the CLR, providing a consistent error-handling mechanism. Put another way: exceptions work across all .NET-compatible languages.
When you program in C++, you must deallocate all heap-based objects that you have previously allocated. If you fail to do this, the allocated resources on your system will never be reclaimed even though they are no longer needed. And if this is a server application, it won’t be robust because the accumulation of unused resources in memory will eventually bring down the system. Similar to Java, the .NET runtime tracks and garbage-collects all allocated objects that are no longer needed.
When developing applications in the old days of DOS, Microsoft developers cared little about security because their applications ran on a single desktop with a single thread of execution. As soon as developers started developing client and server applications, things got a bit complicated: multiple users might then have accessed the servers, and sensitive data might be exchanged between the client and the server. The problem became even more complex in the web environment, since you could unknowingly download and execute malicious applets on your machine.
To mitigate these problems, .NET provides a number of security features. Windows NT and Windows 2000 protect resources using access-control lists and security identities, but don’t provide a security infrastructure to verify access to parts of an executable’s code. Unlike traditional security support whereby only access to the executable is protected, .NET goes further to protect access to specific parts of the executable code. For example, to take advantage of declarative security checks, you can prefix your method implementations with security attributes without having to write any code. To take advantage of imperative security checks, you write the code in your method to explicitly cause a security check. There are many other security facilities that .NET provides in an attempt to make it harder to penetrate your applications and system.
[1] COM still plays a role in the .NET Framework. In fact, if you
use dumpbin.exe
to dump a Portable Executable
(PE) file created by the compilers available in the prerelease or
Beta l version of the .NET SDK, you will see some COM residues,
specifically a mention of something called the
COM+Header
. See Section 2.2.4 for
more information.
Get .Net Framework Essentials now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.