.Net 框架程序设计-英文版

Applied Microsoft .NET Framework Programming Jeffrey Richter PUBLISHED BY Microsoft Press A Division of Microsoft Corporation One Microsoft Way Redmond, Washington 98052-6399 Copyright © 2002 by Jeffrey Richter All rights reserved. No part of the contents of this book may be reproduced or transmitted in any form or by any means without the written permission of the publisher. Library of Congress Cataloging-in-Publication Data Richter, Jeffrey. Applied Microsoft .NET Framework Programming / Jeffrey Richter. p. cm. Includes index. ISBN 0-7356-1422-9 1. Microsoft .NET Framework. 2. Internet programming. I. Title. QA76.625 .R53 2002 005.2’76—dc21 2001056250 Printed and bound in the United States of America. 3 4 5 6 7 8 9 QWT 7 6 5 4 3 2 Distributed in Canada by Penguin Books Canada Limited. A CIP catalogue record for this book is available from the British Library. Microsoft Press books are available through booksellers and distributors worldwide. For further information about international editions, contact your local Microsoft Corporation office or contact Microsoft Press International directly at fax (425) 936-7329. Visit our Web site at www.microsoft.com/mspress. Send comments to mspinput@microsoft.com. Active Directory, ActiveX, Authenticode, DirectX, IntelliSense, JScript, Microsoft, Microsoft Press, MSDN, the .NET logo, PowerPoint, Visual Basic, Visual C++, Visual Studio, Win32, Windows, and Windows NT are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries. Other product and company names mentioned herein may be the trademarks of their respective owners. The example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious. No association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred. Acquisitions Editor: Anne Hamilton Project Editor: Sally Stickney Body Part No. X08-22449 To Kristin I want to tell you how much you mean to me. Your energy and exuberance always lift me higher. Your smile brightens my every day. Your zest makes my heart sing. I love you. Jeffrey Richter Jeffrey Richter is a co-founder of Wintellect (http://www.Wintellect.com/), a training, design, and debugging company dedicated to helping companies produce better software faster. Jeff has written many books, including Programming Applications for Microsoft Windows (Microsoft Press, 1999) and Programming Server-Side Applications for Microsoft Windows 2000 (Microsoft Press, 2000). Jeff is also a contributing editor for MSDN Magazine, where he has written several feature articles and is the .NET columnist. Jeff also speaks at various trade conferences worldwide, including VSLive!, WinSummit, and Microsoft’s TechEd and PDC. Jeff has consulted for many companies, including AT&T, DreamWorks, General Electric, Hewlett-Packard, IBM, and Intel. Jeff’s code has shipped in many Microsoft products, among them Visual Studio, Microsoft Golf, Windows Sound System, and various versions of Windows, from Windows 95 to Windows XP and the Windows .NET Server Family. Since October 1999, Jeff has consulted with the .NET Framework team and has used the .NET Framework to produce the XML Web service front end to Microsoft’s very popular TerraServer Web property (http://www.TerraServer.net/). On the personal front, Jeff holds both airplane and helicopter pilot licenses, though he never gets to fly as often as he’d like. He is also a member of the International Brotherhood of Magicians and enjoys showing friends slight-of-hand card tricks from time to time. Jeff’s other hobbies include music, drumming, and model railroading. He also enjoys traveling and the theater. He lives near Bellevue, Washington, with his wife, Kristin, and their cat, Max. He doesn’t have any children yet, but he has the feeling that kids may be a part of his life soon. Acknowledgments I couldn’t have written this book without the help and technical assistance of many people. In particular, I’d like to thank the following people: § Members of the Microsoft Press editorial team: Sally Stickney, project editor and manuscript editor; Devon Musgrave, manuscript editor; Jim Fuchs, technical editing consultant; Carl Diltz and Katherine Erickson, compositors; Joel Panchot, artist; and Holly M. Viola, copy editor. § Members of the Microsoft .NET Framework team: Fred Aaron, Brad Abrams, Mark Anders, Chris Anderson, Dennis Angeline, Keith Ballinger, Sanjay Bhansali, Mark Boulter, Christopher Brown, Chris Brumme, Kathleen Carey, Ian Carmichael, Rajesh Chandrashekaran, Yann Christensen, Suzanne Cook, Krzysztof Cwalina, Shajan Dasan, Peter de Jong, Blair Dillaway, Patrick Dussud, Erick Ellis Bill Evans, Michael Fanning, Greg Fee, Kit George, Peter Golde, Will Greg, Bret Grinslade, Brian Grunkemeyer, Eric Gunnerson, Simon Hall, Jennifer Hamilton, Brian Harry, Michael Harsh, Jonathan Hawkins, Anders Hejlsberg, Jim Hogg, Paul Johns, Gopal Kakivaya, Sonja Keserovic, Abhi Khune, Loren Kornfelder, Nikhil Kothari, Tim Kurtzman, Brian LaMacchia, Sebastian Lange, Serge Lidin, Francois Liger, Yung-Shin “Bala” Lin, Mike Magruder, Rudi Martin, Erik Meijer, Gene Milener, Jim Miller, Anthony Moore, Vance Morrison, David Mortenson, Yuval Neeman, Lance Olson, Srivatsan Parthasarathy, Mahesh Prakriya, Steven Pratchner, Susan Radke-Sproul, Jayanth Rajan, Dmitry Robsman, Jay Roxe, Dario Russi, Craig Schertz, Alan Shi, Craig Sinclair, Greg Singleton, Ralph Squillace, Paul Stafford, Larry Sullivan, Dan Takacs, Ryley Taketa, David Treadwell, Sean Trowbridge, Nate Walker, Sara Williams, Jason Zander, and Eric Zinda. If I’ve forgotten anyone, please forgive me. § Reviewers: Keith Ballinger, Tom Barclay, Lars Bergstrom, Stephen Butler, Jeffrey Cooperstein, Robert Corstanje, Tarek Dawoud, Sylvain Dechatre, Ash Dhanesha, Shawn Elliott, Chris Falter; Lakshan Fernando, Manish Godse, Eric Gunnerson, Brian Harry, Chris Hockett, Dekel Israeli, Paul Johns, Jeanine Johnson, Jim Kieley, Alex Lerner, Richard Loba, Kerry Loynd, Rob Macdonald, Darrin Massena, John Noss, Piet Obermeyer, Peter Plamondon, Keith Pleas, Mahesh Prakriya, Doug Purdy, Kent Sharkey, Alan Shi, Dan Vallejo, Scott Wadsworth, Beth Wood, and Steven Wort. § Wintellectuals: Jim Bail, Francesco Balena, Doug Boling, Jason Clark, Paula Daniels, Dino Esposito, Lewis Frazer, John Lam, Jeff Prosise, John Robbins, Kenn Scribner, and Chris Shelby. Introduction Over the years, our computing lifestyles have changed. Today, everyone sees the value of the Internet, and our computing lifestyle is becoming more and more dependent on Web- based services. Personally, I love to shop, get traffic conditions, compare products, buy tickets, and read product reviews all via the Internet. However, I’m finding that there are still many things I’d like to do using the Internet that aren’t possible today. For example, I’d like to find restaurants in my area that serve a particular cuisine. Furthermore, I’d like to be able to ask if the restaurant has any seating for, say, 7:00 p.m. that night. Or if I had my own business, I might like to know which vendor has a particular item in stock. If multiple vendors can supply me with the item, I’d like to be able to find out which vendor offers the least expensive price for the item or maybe which vendor can deliver the item to me the fastest. Services like these don’t exist today for two main reasons. The first reason is that no standards are in place for integrating all this information. After all, vendors today each have their own way of describing what they sell. The emerging standard for describing all types of information is Extensible Markup Language (XML). The second reason these services don’t exist today is the complexity of developing the code necessary to integrate such services. Microsoft has a vision in which selling services is the way of the future—that is, companies will offer services and interested users can consume these services. Many services will be free; others will be available through a subscription plan, and still others will be charged per use. You can think of these services as the execution of some business logic. Here are some examples of services: § Validating a credit card purchase § Getting directions from point A to point B § Viewing a restaurant’s menu § Booking a flight on an airline, a hotel room, or a rental car § Updating photos in an online photo album § Merging your calendar and your children’s calendars to plan a family vacation § Paying a bill from a checking account § Tracking a package being shipped to you I could go on and on with ideas for services that any company could implement. Without a doubt, Microsoft will build some of these services and offer them in the near future. Other companies (like yours) will also produce services, some of which might compete with Microsoft in a free market. So how do we get from where we are today to a world in which all these services are easily available? And how do we produce applications—HTML-based or otherwise—that use and combine these services to produce rich features for the user? For example, if restaurants offered the service of retrieving their menu, an application could be written to query every restaurant’s menu, search for a specific cuisine or dish, and then present only those restaurants in the user’s own neighborhood in the application. Note To create rich applications like these, businesses must offer a programmatic interface to their business logic services. This programmatic interface must be callable remotely using a network, like the Internet. This is what the Microsoft .NET initiative is all about. Simply stated, the .NET initiative is all about connecting information, people, and devices. Let me explain it this way: Computers have peripherals—mouse, monitor, keyboard, digital cameras, and scanners—connected to them. An operating system, such as Microsoft Windows, provides a development platform that abstracts the application’s access to these peripherals. You can even think of these peripherals as services, in a way. In this new world, the services (or peripherals) are now connected to the Internet. Developers want an easy way to access these services. Part of the Microsoft .NET initiative is to provide this development platform. The following diagram shows an analogy. On the left, Windows is the development platform that abstracts the hardware peripheral differences from the application developer. On the right, the Microsoft .NET Framework is the development platform that abstracts the XML Web service communication from the application developer. Although a leader in the development and definition of the standards involved in making this new world possible, Microsoft doesn’t own any of the standards. Client machines describe a server request by creating specially formatted XML and then sending it (typically using HTTP) over an intranet or the Internet. Servers know how to parse the XML data, process the client’s request, and return the response as XML back to the client. Simple Object Access Protocol (SOAP) is the term used to describe the specially formatted XML when it is sent using HTTP. The following figure shows a bunch of XML Web services all communicating with one another using SOAP with its XML payload. The figure also shows clients running applications that can talk to Web services and even other clients via SOAP (XML). In addition, the figure shows a client getting its results via HTML from a Web server. Here the user probably filled out a Web form, which was sent back to the Web server. The Web server processed the user’s request (which involved communicating with some Web services), and the results are ultimately sent back to the user via a standard HTML page. In addition, the computers providing the services must be running an operating system that is listening for these SOAP requests. Microsoft hopes that this operating system will be Windows, but Windows isn’t a requirement. Any operating system that can listen on a TCP/IP socket port and read/write bytes to the port is good enough. In the not too distant future, mobile phones, pagers, automobiles, microwave ovens, refrigerators, watches, stereo equipment, game consoles, and all kinds of other devices will also be able to participate in this new world. On the client or application side, an operating system must be running that can read/write to a socket port to issue service requests. The client’s computer must also be capable of supporting whatever features the user’s application desires. If the user’s application wants to create a window or a menu, the operating system must provide this functionality or the application developer must implement it manually. Of course, Microsoft hopes that people will write applications that take advantage of the rich feature set in Windows, but again, Windows is a recommendation, not a necessity. What I’m trying to say is that this new world will happen whether Microsoft is a part of it or not. Microsoft’s .NET initiative is all about making it really easy for developers to create and access these services. Today, we could all go write our own operating system and create our own custom Web servers to listen and manually process SOAP requests if we wanted to, but it’s really hard and would take a long time. Microsoft has taken on all this hard work for us, and we can just leverage Microsoft’s efforts to greatly simplify our own development efforts. Now we, as application developers, can concentrate and focus on our business logic and services, leaving all the communication protocols and plumbing to Microsoft (who has a lot of developers that just love to do this nitty-gritty stuff). What Makes Up the Microsoft .NET Initiative I’ve been working with Microsoft and its technologies for many years now. Over the years, I’ve seen Microsoft introduce all kinds of new technologies and initiatives: MS-DOS, Windows, Windows CE, OLE, COM, ActiveX, COM+, Windows DNA, and so on. When I first started hearing about Microsoft’s .NET initiative, I was surprised at how solid Microsoft’s story seemed to be. It really seemed to me that they had a vision and a plan and that they had rallied the troops to implement the plan. I contrast Microsoft’s .NET platform to ActiveX, which was just a new name given to good old COM to make it seem more user friendly. ActiveX didn’t mean much (or so many developers thought), and the term, along with ActiveX controls, never really took off. I also contrast Microsoft’s .NET initiative to Windows DNA (Distributed InterNet Architecture), which was another marketing label that Microsoft tacked onto a bunch of already existing technologies. But I really believe in the Microsoft .NET initiative, and to prove it, I’ve written this book. So, what exactly constitutes the Microsoft .NET initiative? Well, there are several parts to it, and I’ll describe each one in the following sections. An Underlying Operating System: Windows Because these Web services and applications that use Web services run on computers and because computers have peripherals, we still need an operating system. Microsoft suggests that people use Windows. Specifically, Microsoft is adding XML Web serviceÐspecific features to its Windows line of operating systems, and Windows XP and the servers in the Windows .NET Server Family will be the versions best suited for this new service-driven world. Specifically, Windows XP and the Windows .NET Server Family products have integrated support for Microsoft .NET Passport XML Web service. Passport is a service that authenticates users. Many Web services will require user authentication to access information securely. When users log on to a computer running Windows XP or one of the servers from the Windows .NET Server Family, they are effectively logging on to every Web site and Web service that uses Passport for authentication. This means that users won’t have to enter usernames and passwords as they access different Internet sites. As you can imagine, Passport is a huge benefit to users: one identity and password for everything you do, and you have to enter it only once! In addition, Windows XP and the Windows .NET Server Family products have some built-in support for loading and executing applications implementing the .NET Framework. Finally, Windows XP and the Windows .NET Server Family operating systems have a new, extensible instant messaging notification application. This application allows third-party vendors (such as Expedia, the United States Postal Service, and many others) to communicate with users seamlessly. For example, users can receive automatic notifications when their flights are delayed (from Expedia) and when a package is ready to be delivered (from the U.S. Postal Service). I don’t know about you, but I’ve been hoping for services like these for years—I can’t wait! Helpful Products: The .NET Enterprise Servers As part of the .NET initiative, Microsoft is providing several products that companies can choose to use if their business logic (services) find them useful. Here are some of Microsoft’s enterprise server products: § Microsoft Application Center 2000 § Microsoft BizTalk Server 2000 § Microsoft Commerce Server 2000 § Microsoft Exchange 2000 § Microsoft Host Integration Server 2000 § Microsoft Internet Security and Acceleration (ISA) Server 2000 § Microsoft Mobile Information Server 2002 § Microsoft SQL Server 2000 It’s likely that each of these products will eventually have a “.NET” added to its name for marketing purposes. But I’m also sure that over time, these products will integrate more .NET features into them as Microsoft continues the initiative. Microsoft XML Web Services: .NET My Services Certainly, Microsoft wants to do more than just provide the underlying technologies that allow others to play in this new world. Microsoft wants to play too. So, Microsoft will be building its own set of XML Web services: some will be free, and others will require some usage fee. Microsoft initially plans to offer the following .NET My Services: § .NET Alerts § .NET ApplicationSettings § .NET Calendar § .NET Categories § .NET Contacts § .NET Devices § .NET Documents § .NET FavoriteWebSites § .NET Inbox § .NET Lists § .NET Locations § .NET Presence § .NET Profile § .NET Services § .NET Wallet These consumer-oriented XML Web services are known as Microsoft’s “.NET My Services.” You can find out more information about them at http://www.Microsoft.com/MyServices/. Over time, Microsoft will add many more consumer services and will also be creating business-oriented XML Web services. In addition to these public Web services, Microsoft will create internal services for sales data and billing. These internal services will be accessible to Microsoft employees only. I anticipate that companies will quickly embrace the idea of using Web services on their intranets to make internal company information available to employees. The implementation of publicly available Internet Web services and applications that consume them will probably proceed more slowly. The Development Platform: The .NET Framework Some of the Microsoft .NET My Services (like Passport) exist today. These services run on Windows and are built using technologies such as C/C++, ATL, Win32, COM, and so on. As time goes on, these services and new services will ultimately be implemented using newer technologies, such as C# (pronounced “C sharp”) and the .NET Framework. Important Even though this entire introduction has been geared toward building Internet applications and Web services, the .NET Framework is capable of a lot more. All in all, the .NET Framework development platform allows developers to build the following kinds of applications: XML Web services, Web Forms, Win32 GUI applications, Win32 CUI (console UI) applications, services (controlled by the Service Control Manager), utilities, and stand-alone components. The material presented in this book is applicable to any and all of these application presented in this book is applicable to any and all of these application types. The .NET Framework consists of two parts: the common language runtime (CLR) and the Framework Class Library (FCL). The .NET Framework is the part of the initiative that makes developing services and applications really easy. And, most important, this is what this book is all about: developing applications and XML Web services for the .NET Framework. Initially, Microsoft will make the CLR and FCL available in the various versions of Windows, including Windows 98, Windows 98 Second Edition, and Windows Me as well as Windows NT 4, Windows 2000, and both 32-bit and 64-bit versions of Windows XP and the Windows .NET Server Family. A “lite” version of the .NET Framework, called the.NET Compact Framework, is also available for PDAs (such as Windows CE and Palm) and appliances (small devices). On December 13, 2001, the European Computer Manufacturers Association (ECMA) accepted the C# programming language, portions of the CLR, and portions of the FCL as standards. It won’t be long before ECMA-compliant versions of these technologies appear on a wide variety of operating systems and CPUs. Note Windows XP (both Home Edition and Professional) doesn’t ship with the .NET Framework “in the box.” However, the Windows .NET Server Family (Windows .NET Web Server, Windows .NET Standard Server, Windows .NET Enterprise Server, and Windows .NET Datacenter Server) will include the .NET Framework. In fact, this is how the Windows .NET Server Family got its name. The next version of Windows (code-named “Longhorn”) will include the .NET Framework in all editions. For now, you’ll have to redistribute the .NET Framework with your application, and your setup program will have to install it. Microsoft does make a .NET Framework redistribution file that you’re allowed to freely distribute with your application:http://go.microsoft.com/fwlink/?LinkId=5584. Almost all programmers are familiar with runtimes and class libraries. I’m sure many of you have at least dabbled with the C-runtime library, the standard template library (STL), the Microsoft Foundation Class library (MFC), the Active Template Library (ATL), the Visual Basic runtime library, or the Java virtual machine. In fact, the Windows operating system itself can be thought of as a runtime engine and library. Runtime engines and libraries offer services to applications, and we programmers love them because they save us from reinventing the same algorithms over and over again. The Microsoft .NET Framework allows developers to leverage technologies more than any earlier Microsoft development platform did. Specifically, the .NET Framework really delivers on code reuse, code specialization, resource management, multilanguage development, security, deployment, and administration. While designing this new platform, Microsoft also felt it was necessary to improve on some of the deficiencies of the current Windows platform. The following list gives you just a small sampling of what the CLR and the FCL provide: § Consistent programming model Unlike today, where some operating system facilities are accessed via dynamic-link library (DLL) functions and other facilities are accessed via COM objects, all application services are offered via a common object- oriented programming model. § Simplified programming model The CLR seeks to greatly simplify the plumbing and arcane constructs required by Win32 and COM. Specifically, the CLR now frees the developer from having to understand any of the following concepts: the registry, globally unique identifiers (GUIDs), IUnknown, AddRef, Release , HRESULTs, and so on. The CLR doesn’t just abstract these concepts away from the developer; these concepts simply don’t exist, in any form, in the CLR. Of course, if you want to write a .NET Framework application that interoperates with existing, non-.NET code, you must still be aware of these concepts. § Run once, run always All Windows developers are familiar with “DLL hell” versioning problems. This situation occurs when components being installed for a new application overwrite components of an old application, causing the old application to exhibit strange behavior or stop functioning altogether. The architecture of the .NET Framework now isolates application components so that an application always loads the components that it was built and tested with. If the application runs after installation, then the application should always run. This slams shut the gates of “DLL hell.” § Simplified deployment Today, Windows applications are incredibly difficult to set up and deploy. Several files, registry settings, and shortcuts usually need to be created. In addition, completely uninstalling an application is nearly impossible. With Windows 2000, Microsoft introduced a new installation engine that helps with all these issues, but it’s still possible that a company authoring a Microsoft installer package might fail to do everything correctly. The .NET Framework seeks to banish these issues into history. The .NET Framework components (known simply as types) are not referenced by the registry. In fact, installing most .NET Framework applications requires no more than copying the files to a directory and adding a shortcut to the Start menu, desktop, or Quick Launch bar. Uninstalling the application is as simple as deleting the files. § Wide platform reach When compiling source code for the .NET Framework, the compilers produce common intermediate language (CIL) instead of the more traditional CPU instructions. At run time, the CLR translates the CIL into native CPU instructions. Because the translation to native CPU instructions is done at run time, the translation is done for the host CPU. This means that you can deploy your .NET Framework application on any machine that has an ECMA-compliant version of the CLR and FCL running on it. These machines can be x86, IA64, Alpha, PowerPC, and so on. Users will immediately appreciate the value of this broad execution if they ever change their computing hardware or operating system. § Programming language integration COM allows different programming languages to interoperate with one another. The .NET Framework allows languages to be integrated with one another so that you can use types of another language as if they are your own. For example, the CLR makes it possible to create a class in C++ that derives from a class implemented in Visual Basic. The CLR allows this because it defines and provides a Common Type System (CTS) that all programming languages that target the CLR must use. The Common Language Specification (CLS) describes what compiler implementers must do in order for their languages to integrate well with other languages. Microsoft is itself providing several compilers that produce code targeting the runtime: C++ with Managed Extensions, C#, Visual Basic .NET (which now subsumes Visual Basic Scripting Edition, or VBScript, and Visual Basic for Applications, or VBA), and JScript. In addition, companies other than Microsoft and academic institutions are producing compilers for other languages that also target the CLR. § Simplified code reuse Using the mechanisms described earlier, you can create your own classes that offer services to third-party applications. This makes it extremely simple to reuse code and also creates a large market for component (type) vendors. § Automatic memory and management (garbage collection) Programming requires great skill and discipline, especially when it comes to managing the use of resources such as files, memory, screen space, network connections, database resources, and so on. One of the most common bugs is neglecting to free one of these resources, ultimately causing the application to perform improperly at some unpredictable time. The CLR automatically tracks resource usage, guaranteeing that your application never leaks resources. In fact, there is no way to explicitly “free” memory. In Chapter 19, “Automatic Memory Management (Garbage Collection),” I explain exactly how garbage collection works. § Type-safe verification The CLR can verify that all your code is type-safe. Type safety ensures that allocated objects are always accessed in compatible ways. Hence, if a method input parameter is declared as accepting a 4-byte value, the CLR will detect and trap attempts to access the parameter as an 8-byte value. Similarly, if an object occupies 10 bytes in memory, the application can’t coerce the object into a form that will allow more than 10 bytes to be read. Type safety also means that execution flow will transfer only to well-known locations (that is, method entry points). There is no way to construct an arbitrary reference to a memory location and cause code at that location to start executing. Together, these measures ensure type safety eliminating many common programming errors and classic system attacks (for example, exploiting buffer overruns). § Rich debugging support Because the CLR is used for many programming languages, it is now much easier to implement portions of your application using the language best suited to a particular task. The CLR fully supports debugging applications that cross language boundaries. § Consistent method failure paradigm One of the most aggravating aspects of Windows programming is the inconsistent style that functions use to report failures. Some functions return Win32 status codes, some functions return HRESULTs, and some functions throw exceptions. In the CLR, all failures are reported via exceptions— period. Exceptions allow the developer to isolate the failure recovery code from the code required to get the work done. This separation greatly simplifies writing, reading, and maintaining code. In addition, exceptions work across module and programming language boundaries. And, unlike status codes and HRESULTs, exceptions can’t be ignored. The CLR also provides built-in stack-walking facilities, making it much easier to locate any bugs and failures. § Security Traditional operating system security provides isolation and access control based on user accounts. This model has proven useful, but at its core assumes that all code is equally trustworthy. This assumption was justified when all code was installed from physical media (for example, CD-ROM) or trusted corporate servers. But with the increasing reliance on mobile code such as Web scripts, applications downloaded over the Internet, and e-mail attachments, we need ways to control the behavior of applications in a more code-centric manner. Code access security provides a means to do this. § Interoperability Microsoft realizes that developers already have an enormous amount of existing code and components. Rewriting all this code to take full advantage of the .NET Framework platform would be a huge undertaking and would prevent the speedy adoption of this platform. So the .NET Framework fully supports the ability for the developer to access their existing COM components as well as call Win32 functions in existing DLLs. Users won’t directly appreciate the CLR and its capabilities, but they will certainly notice the quality and features of applications that utilize the CLR. In addition, users and your company’s bottom line will appreciate how the CLR allows applications to be developed and deployed more rapidly and with less administration than Windows has ever allowed in the past. The Development Environment: Visual Studio .NET The last part of the .NET initiative that I want to mention is Visual Studio .NET. Visual Studio .NET is Microsoft’s development environment. Microsoft has been working on it for many years and has incorporated a lot of .NET FrameworkÐspecific features into it. Visual Studio .NET runs on Windows NT 4, Windows 2000, Windows XP, and the Windows .NET Server Family servers, and it will run on future versions of Windows. Of course, the code produced by Visual Studio .NET will run on all these Windows platforms plus Windows 98, Windows 98 Second Edition, and Windows Me. Like any good development environment, Visual Studio .NET includes a project manager; a source code editor; UI designers; lots of wizards, compilers, linkers, tools, and utilities; documentation; and debuggers. It supports building applications for both the 32-bit and 64- bit Windows platforms as well as for the new .NET Framework platform. Another important improvement is that there is now just one integrated development environment for all programming languages. Microsoft also provides a .NET Framework SDK. This free SDK includes all the language compilers, a bunch of tools, and a lot of documentation. Using this SDK, you can develop applications for the .NET Framework without using Visual Studio .NET. You’ll just have to use your own editor and project management system. You also don’t get drag-and-drop Web Forms and Windows Forms building. I use Visual Studio .NET regularly and will refer to it throughout this book. However, this book is mostly about programming in general, so Visual Studio .NET isn’t required to learn, use, and understand the concepts I present in each chapter. Goal of This Book The purpose of this book is to explain how to develop applications for the .NET Framework. Specifically, this means that I intend to explain how the CLR works and the facilities it offers. I’ll also discuss various parts of the FCL. No book could fully explain the FCL—it contains literally thousands of types, and this number is growing at an alarming rate. So, here I’m concentrating on the core types that every developer needs to be aware of. And while this book isn’t specifically about Windows Forms, XML Web services, Web Forms, and so on, the technologies presented in the book are applicable to all these application types. With this book, I’m not attempting to teach you any particular programming language. I’m assuming that you’re familiar with a programming language such as C++, C#, Visual Basic, or Java. I also assume that you’re familiar with object-oriented programming concepts such as data abstraction, inheritance, and polymorphism. A good understanding of these concepts is critical because all .NET Framework features are offered via an object-oriented paradigm. If you’re not familiar with these concepts, I strongly suggest you first find a book that teaches these concepts. Although I don’t intend to teach basic programming, I will spend time on various programming topics that are specific to the .NET Framework. All .NET Framework developers must be aware of these topics, which I explain and use throughout this book. Finally, because this is a book about the .NET Framework’s common language runtime, it’s not about programming in any one specific programming language. However, I provide lots of code examples in the book to show how things really work. To remain programming language agnostic, the best language for me to use for these examples would be IL (intermediate language) assembly language. IL is the only programming language that the CLR understands. All language compilers compile source code to IL, which is later processed by the CLR. Using IL, you can access every feature offered by the CLR. However, using IL assembly language is a pretty low-level way to write programs and isn’t an ideal way to demonstrate programming concepts. So I decided to use C# as my programming language of choice throughout this entire book. I chose C# because it is the language Microsoft designed specifically for developing code for the .NET Framework. If you’ve decided not to use C# for your programming projects, that’s OK—I’ll just assume that you can read C# even if you’re not programming in it. System Requirements The .NET Framework will install on Windows 98, Windows 98 Second Edition, Windows Me, Windows NT 4 (all editions), Windows 2000 (all editions), Windows XP (all editions), and the Windows .NET Server Family servers. You can download it from http://go.microsoft.com/fwlink/?LinkId=5584. The .NET Framework SDK and Visual Studio .NET require Windows NT 4 (all editions), Windows 2000 (all editions), Windows XP (all editions), and the servers in the Windows .NET Server Family. You can download the .NET Framework SDK from http://go.microsoft.com/fwlink/?LinkId=77. You have to buy Visual Studio .NET, of course. You can download the code associated with this book from http://www.Wintellect.com. This Book Has No Mistakes This section’s title clearly states what I want to say. But we all know that it is a flat-out lie. My editors and I have worked hard to bring you the most accurate, up-to-date, in-depth, easy-to- read, painless-to-understand, bug-free information. Even with the fantastic team assembled, things inevitably slip through the cracks. If you find any mistakes in this book (especially bugs), I would greatly appreciate it if you would send the mistakes to me at http://www.Wintellect.com. Support Every effort has been made to ensure the accuracy of this book. Microsoft Press provides corrections for books through the World Wide Web at the following address: http://www.microsoft.com/mspress/support/ To connect directly to the Microsoft Press Knowledge Base and enter a query regarding a question or issue that you may have, go to: http://www.microsoft.com/mspress/support/search.asp If you have comments, questions, or ideas regarding this book, please send them to Microsoft Press using either of the following methods: Postal Mail: Microsoft Press Attn: Applied Microsoft .NET Framework Programming Editor One Microsoft Way Redmond, WA 98052-6399 E-Mail: MSPINPUT@MICROSOFT.COM Please note that product support is not offered through the above mail addresses. For support information regarding C#, Visual Studio, or the .NET Framework, visit the Microsoft Product Standard Support Web site at: http://support.microsoft.com Part I: Basics of the Microsoft .NET Framework Chapter List Chapter 1: The Architecture of the .NET framework Development Platform Chapter 2: Building, Packaging, Deploying, and Administering Applications and Types Chapter 3: Shared Assemblies Chapter 1: The Architecture of the .NET Framework Development Platform The Microsoft .NET Framework introduces many new concepts, technologies, and terms. My goal in this chapter is to give you an overview of how the .NET Framework is architected, introduce you to some of the new technologies the framework includes, and define many of the terms you’ll be seeing when you start using it. I’ll also take you through the process of building your source code into an application or a set of redistributable components (types) and then explain how these components execute. Compiling Source Code into Managed Modules OK, so you’ve decided to use the .NET Framework as your development platform. Great! Your first step is to determine what type of application or component you intend to build. Let’s just assume that you’ve completed this minor detail, everything is designed, the specifications are written, and you’re ready to start development. Now you must decide what programming language to use. This task is usually difficult because different languages offer different capabilities. For example, in unmanaged C/C++, you have pretty low-level control of the system. You can manage memory exactly the way you want to, create threads easily if you need to, and so on. Visual Basic 6, on the other hand, allows you to build UI applications very rapidly and makes it easy for you to control COM objects and databases. The common language runtime (CLR) is just what its name says it is: a runtime that is usable by different and varied programming languages. The features of the CLR are available to any and all programming languages that target it—period. If the runtime uses exceptions to report errors, then all languages get errors reported via exceptions. If the runtime allows you to create a thread, then any language can create a thread. In fact, at runtime, the CLR has no idea which programming language the developer used for the source code. This means that you should choose whatever programming language allows you to express your intentions most easily. You can develop your code in any programming language you desire as long as the compiler you use to compile your code targets the CLR. So, if what I say is true, what is the advantage of using one programming language over another? Well, I think of compilers as syntax checkers and “correct code” analyzers. They examine your source code, ensure that whatever you’ve written makes some sense, and then output code that describes your intention. Different programming languages allow you to develop using different syntax. Don’t underestimate the value of this choice. For mathematical or financial applications, expressing your intentions using APL syntax can save many days of development time when compared to expressing the same intention using Perl syntax, for example. Microsoft is creating several language compilers that target the runtime: C++ with managed extensions, C# (pronounced “C sharp”), Visual Basic, JScript, J# (a Java language compiler), and an intermediate language (IL) assembler. In addition to Microsoft, several other companies are creating compilers that produce code that targets the CLR. I’m aware of compilers for Alice, APL, COBOL, Component Pascal, Eiffel, Fortran, Haskell, Mercury, ML, Mondrian, Oberon, Perl, Python, RPG, Scheme, and Smalltalk. Figure 1-1 shows the process of compiling source code files. As the figure shows, you can create source code files using any programming language that supports the CLR. Then you use the corresponding compiler to check the syntax and analyze the source code. Regardless of which compiler you use, the result is a managed module. A managed module is a standard Windows portable executable (PE) file that requires the CLR to execute. In the future, other operating systems may use the PE file format as well. Figure 1-1 : Compiling source code into managed modules Table 1-1 describes the parts of a managed module. Table 1-1: Parts of a Managed Module Part Description PE header The standard Windows PE file header, which is similar to the Common Object File Format (COFF) header. This header indicates the type of file: GUI, CUI, or DLL, and it also has a timestamp indicating when the file was built. For modules that contain only IL code, the bulk of the information in the PE header is ignored. For modules that contain native CPU code, this header contains information about the native CPU code. CLR header Contains the information (interpreted by the CLR and utilities) that makes this a managed module. The header includes the version of the CLR required, some flags, the MethodDef metadata token of the managed module’s entry point method (Main method), and the location/size of the module’s metadata, resources, strong name, some flags, and other less interesting stuff. Metadata Every managed module contains metadata tables. There are two main types of tables: tables that describe the types and members defined in your source code and tables that describe the types and members referenced by your source code. Intermediate language (IL) code Code that the compiler produced as it compiled the source code. The CLR later compiles the IL into native CPU instructions. Most compilers of the past produced code targeted to a specific CPU architecture, such as x86, IA64, Alpha, or PowerPC. All CLR-compliant compilers produce IL code instead. (I’ll go into more detail about IL code later in this chapter.) IL code is sometimes referred to as managed code because the CLR manages its lifetime and execution. In addition to emitting IL, every compiler targeting the CLR is required to emit full metadata into every managed module. In brief, metadata is simply a set of data tables that describe what is defined in the module, such as types and their members. In addition, metadata also has tables indicating what the managed module references, such as imported types and their members. Metadata is a superset of older technologies such as type libraries and interface definition language (IDL) files. The important thing to note is that CLR metadata is far more complete. And, unlike type libraries and IDL, metadata is always associated with the file that contains the IL code. In fact, the metadata is always embedded in the same EXE/DLL as the code, making it impossible to separate the two. Because the compiler produces the metadata and the code at the same time and binds them into the resulting managed module, the metadata and the IL code it describes are never out of sync with one another. Metadata has many uses. Here are some of them: § Metadata removes the need for header and library files when compiling since all the information about the referenced types/members is contained in the file that has the IL that implements the type/members. Compilers can read metadata directly from managed modules. § Visual Studio .NET uses metadata to help you write code. Its IntelliSense feature parses metadata to tell you what methods a type offers and what parameters that method expects. § The CLR’s code verification process uses metadata to ensure that your code performs only “safe” operations. (I’ll discuss verification shortly.) § Metadata allows an object’s fields to be serialized into a memory block, remoted to another machine, and then deserialized, re-creating the object and its state on the remote machine. § Metadata allows the garbage collector to track the lifetime of objects. For any object, the garbage collector can determine the type of the object and, from the metadata, know which fields within that object refer to other objects. In Chapter 2, I’ll describe metadata in much more detail. Microsoft’s C#, Visual Basic, JScript, J#, and the IL Assembler always produce managed modules that require the CLR to execute. End-users must have the CLR installed on their machine in order to execute any managed modules, in the same way that they must have the Microsoft Foundation Class (MFC) library or Visual Basic DLLs installed to run MFC or Visual Basic 6 applications. By default, Microsoft’s C++ compiler builds unmanaged modules: the EXE or DLL files that we’re all familiar with. These modules don’t require the CLR in order to execute. However, by specifying a new command-line switch, the C++ compiler can produce managed modules that do require the CLR to execute. Of all the Microsoft compilers mentioned, C++ is unique in that it is the only language that allows the developer to write both managed and unmanaged code and have it emitted into a single module. This can be a great feature because it allows developers to write the bulk of their application in managed code (for type safety and component interoperability) but continue to access their existing unmanaged C++ code. Combining Managed Modules into Assemblies The CLR doesn’t actually work with modules; it works with assemblies. An assembly is an abstract concept that can be difficult to grasp initially. First, an assembly is a logical grouping of one or more managed modules or resource files. Second, an assembly is the smallest unit of reuse, security, and versioning. Depending on the choices you make with your compilers or tools, you can produce a single-file or a multifile assembly. In Chapter 2, I’ll go over assemblies in great detail, so I don’t want to spend a lot of time on them here. All I want to do now is make you aware that there is this extra conceptual notion that offers a way to treat a group of files as a single entity. Figure 1-2 should help explain what assemblies are about. In this figure, some managed modules and resource (or data) files are being processed by a tool. This tool produces a single PE file that represents the logical grouping of files. What happens is that this PE file contains a block of data called the manifest. The manifest is simply another set of metadata tables. These tables describe the files that make up the assembly, the publicly exported types implemented by the files in the assembly, and the resource or data files that are associated with the assembly. Figure 1-2 : Combining managed modules into assemblies By default, compilers actually do the work of turning the emitted managed module into an assembly; that is, the C# compiler emits a managed module that contains a manifest. The manifest indicates that the assembly consists of just the one file. So, for projects that have just one managed module and no resource (or data) files, the assembly will be the managed module and you don’t have any additional steps to perform during your build process. If you want to group a set of files into an assembly, you’ll have to be aware of more tools (such as the assembly linker, AL.exe) and their command-line options. I’ll explain these tools and options in Chapter 2. An assembly allows you to decouple the logical and physical notions of a reusable, deployable, versionable component. How you partition your code and resources into different files is completely up to you. For example, you could put rarely used types or resources in separate files that are part of an assembly. The separate files could be downloaded from the Web as needed. If the files are never needed, they’re never downloaded, saving disk space and reducing installation time. Assemblies allow you to break up the deployment of the files while still treating all the files as a single collection. An assembly’s modules also include information, including version numbers, about referenced assemblies. This information makes an assembly self-describing. In other words, the CLR knows everything about what an assembly needs in order to execute. No additional information is required in the registry or in Active Directory. Because no additional information is needed, deploying assemblies is much easier than deploying unmanaged components. Loading the Common Language Runtime Each assembly that you build can be either an executable application or a DLL containing a set of types (components) for use by an executable application. Of course, the CLR is responsible for managing the execution of code contained within these assemblies. This means that the .NET Framework must be installed on the host machine. Microsoft has created a redistribution package that you can freely ship to install the .NET Framework on your customers’ machines. Eventually, the .NET Framework will be packaged with future versions of Windows so that you won’t have to ship it with your assemblies. You can tell if the .NET Framework has been installed by looking for the MSCorEE.dll file in the %windir%\system32 directory. The existence of this file tells you that the .NET Framework is installed. However, several versions of the .NET Framework can be installed on a single machine simultaneously. If you want to determine exactly which versions of the .NET Framework are installed, examine the subkeys under the following registry key: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\policy When you build an EXE assembly, the compiler/linker emits some special information into the resulting assembly’s PE file header and the file’s .text section. When the EXE file is invoked, this special information causes the CLR to load and initialize. The CLR then locates the application’s entry point method and allows the application to start executing. Similarly, if an unmanaged application calls LoadLibrary to load a managed assembly, the DLL’s entry point function knows to load the CLR in order to process the code contained within the assembly. For the most part, you don’t need to know about or understand how the CLR gets loaded. For most programmers, this special information allows the application to just run, and there’s nothing more to think about. For the curious, however, I’ll spend the remainder of this section explaining how a managed EXE or DLL starts the CLR. If you’re not interested in this subject, feel free to skip to the next section. Also, if you’re interested in building an unmanaged application that hosts the CLR, see Chapter 20. Figure 1-3 summarizes how a managed EXE loads and initializes the CLR. Figure 1-3 : Loading and initializing the CLR When the compiler/linker creates an executable assembly, the following 6-byte x86 stub function is emitted into the PE file’s .text section: JMP _CorExeMain Because the _CorExeMain function is imported from Microsoft’s MSCorEE.dll dynamic-link library, MSCorEE.dll is referenced in the assembly file’s import (.idata) section. MSCorEE.dll stands for Microsoft Component Object Runtime Execution Engine. When the managed EXE file is invoked, Windows treats it just like any normal (unmanaged) EXE file: the Windows loader loads the file and examines the .idata section to see that MSCorEE.dll should be loaded into the process’s address space. Then the loader obtains the address of the _CorExeMain function inside MSCorEE.dll and fixes up the stub function’s JMP instruction in the managed EXE file. The process’s primary thread begins executing this x86 stub function, which immediately jumps to _CorExeMain in MSCorEE.dll. _CorExeMain initializes the CLR and then looks at the executable assembly’s CLR header to determine what managed entry point method should execute. The IL code for the method is then compiled into native CPU instructions, and the CLR jumps to the native code (using the process’s primary thread). At this point, the managed application’s code is running. The situation is similar for a managed DLL. When building a managed DLL, the compiler/linker emits a similar 6-byte x86 stub function in the PE file’s .text section for a DLL assembly: JMP _CorDllMain The _CorDllMain function is also imported from the MSCorEE.dll, causing the DLL’s .idata section to reference MSCorEE.dll. When Windows loads the DLL, it will automatically load MSCorEE.dll (if it isn’t already loaded), obtain the address of the _CorDllMain function, and fix up the 6-byte x86 JMP stub in the managed DLL. The thread that called LoadLibrary to load the managed DLL now jumps to the x86 stub in the managed DLL assembly, which immediately jumps to the _CorDllMain function in MSCorEE.dll. _CorDllMain initializes the CLR (if it hasn’t already been initialized for the process) and then returns so that the application can continue executing as normal. These 6-byte x86 stub functions are required to run managed assemblies on Windows 98, Windows 98 Standard Edition, Windows Me, Windows NT 4, and Windows 2000 because all these operating systems shipped long before the CLR became available. Note that the 6- byte stub function is specifically for x86 machines. This stub doesn’t work properly if the CLR is ported to run on other CPU architectures. Because Windows XP and the Windows .NET Server Family support both the x86 and the IA64 CPU architectures, Windows XP and the Windows .NET Server Family loader was modified to look specifically for managed assemblies. On Windows XP and the Windows .NET Server Family, when a managed assembly is invoked (typically via CreateProcess or LoadLibrary), the OS loader detects that the file contains managed code by examining directory entry 14 in the PE file header. (See IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR in WinNT.h.) If this directory entry exists and is not 0, the loader ignores the file’s import (.idata) section and automatically loads MSCorEE.dll into the process’s address space. Once loaded, the OS loader makes the process’s thread jump directly to the correct function in MSCorEE.dll. The 6-byte x86 stub functions are ignored on machines running Windows XP and the Windows .NET Server Family. One last note on managed PE files: they always use the 32 bit PE file format, not the 64-bit PE file format. On 64-bit Windows systems, the OS loader detects the managed 32-bit PE file and automatically knows to create a 64-bit address space. Executing Your Assembly’s Code As mentioned earlier, managed modules contain both metadata and intermediate language (IL). IL is a CPU-independent machine language created by Microsoft after consultation with several external commercial and academic language/compiler writers. IL is much higher level than most CPU machine languages. IL understands object types and has instructions that create and initialize objects, call virtual methods on objects, and manipulate array elements directly. It even has instructions that throw and catch exceptions for error handling. You can think of IL as an object-oriented machine language. Usually, developers will program in a high-level language, such as C# or Visual Basic. The compilers for these high-level languages produce IL. However, like any other machine language, IL can be written in assembly language, and Microsoft does provide an IL Assembler, ILAsm.exe. Microsoft also provides an IL Disassembler, ILDasm.exe. IL and Protecting Your Intellectual Property Some people are concerned that IL doesn’t offer enough intellectual property protection for their algorithms. In other words, they think you could build a managed module and someone else could use a tool, such as IL Disassembler, to easily reverse engineer exactly what your application’s code does. Yes, it’s true that IL code is higher level than most other assembly languages and that, in general, reverse engineering IL code is relatively simple. However, when implementing an XML Web service or a Web Forms application, your managed module resides on your server. Because no one outside your company can access the module, no one outside your company can use any tool to see the IL—your intellectual property is completely safe. If you’re concerned about any of the managed modules that you do distribute, you can obtain an obfuscator utility from a third-party vendor. These utilities “scramble” the names of all the private symbols in your managed module’s metadata. It will be difficult for someone to “unscramble” the names and understand the purpose of each method. Note that these obfuscators can only provide a little protection since the IL must be available at some point in order for the CLR to process it. If you don’t feel that an obfuscator offers the kind of intellectual property protection that you desire, you can consider implementing your more sensitive algorithms in some unmanaged module that will contain native CPU instructions instead of IL and metadata. Then you can use the CLR’s interoperability features to communicate between the managed and unmanaged portions of your application. Of course, this assumes that you’re not worried about people reverse engineering the native CPU instructions in your unmanaged code. Keep in mind that any high-level language will most likely expose only a subset of the facilities offered by the CLR. However, using IL assembly language allows a developer access to all the CLR’s facilities. So, should your programming language of choice hide a facility the CLR offers that you really want to take advantage of, you can choose to write that portion of your code in IL assembly or perhaps another programming language that exposes the CLR feature you seek. The only way for you to know what facilities the CLR offers is to read documentation specific to the CLR itself. In this book, I try to concentrate on CLR features and how they are exposed or not exposed by the C# language. I suspect that most other books and articles will present the CLR via a language perspective and that most developers will come to believe that the CLR offers only what the developer’s chosen language exposes. As long as your language allows you to accomplish what you’re trying to get done, this blurred perspective isn’t a bad thing. Important I think that this ability to switch programming languages easily with rich integration between languages is an awesome feature of the CLR. Unfortunately, I also believe that developers will often overlook this feature. Programming languages such as C# and Visual Basic are excellent languages for doing I/O operations. APL is a great language for doing advanced engineering or financial calculations. Through the CLR, you can write the I/O portions of your application using C# and then write the engineering calculations part using APL. The CLR offers a level of integration between these languages that is unprecedented and really makes mixed-language programming worthy of consideration for many development projects. Another important point to keep in mind about IL is that it isn’t tied to any specific CPU platform. This means that a managed module containing IL can run on any CPU platform as long as the operating system running on that CPU platform hosts a version of the CLR. Although the initial release of the CLR runs only on 32-bit Windows platforms, developing an application using managed IL sets up a developer to be more independent of the underlying CPU architecture. Standardizing the .NET Framework In October 2000, Microsoft (along with Intel and Hewlett-Packard as co-sponsors) proposed a large subset of the .NET Framework to the ECMA (the European Computer Manufacturer’s Association) for the purpose of standardization. The ECMA accepted this proposal and created a technical committee (TC39) to oversee the standardization process. The technical committee is charged with the following duties: §Technical Group 1 Develop a dynamic scripting language standard (ECMAScript). Microsoft’s implementation of ECMAScript is JScript. §Technical Group 2 Develop a standardized version of the C# programming language. §Technical Group 3 Develop a Common Language Infrastructure (CLI) based on a subset of the functionality offered by the .NET Framework’s CLR and class library. Specifically, the CLI will define a file format, a common type system, an extensible metadata system, an intermediate language (IL), and access to the underlying platform (P/Invoke). In addition, the CLI will define a factorable (to allow for small hardware devices) base class library designed for use by multiple programming languages. Once the standardization is complete, these standards will be contributed to ISO/IEC JTC 1 (Information Technology). At this time, the technical committee will also investigate further directions for CLI, C#, and ECMAScript as well as entertain proposals for any complementary or additional technology. For more information about ECMA, see http://www.ECMA.ch and http://MSDN.Microsoft.com/Net/ECMA. With the standardization of the CLI, C#, and ECMAScript, Microsoft won’t “own” any of these technologies. Microsoft will simply be one company of many (hopefully) that are producing implementations of these technologies. Certainly Microsoft hopes that their implementation will be the best in terms of performance and customer-demand-driven features. This is what will help sales of Windows, since the Microsoft “best of breed” implementation will run only on Windows. However, other companies may implement these standards, compete against Microsoft, and possibly win. Even though today’s CPUs can’t execute IL instructions directly, CPUs of the future might have this capability. To execute a method, its IL must first be converted to native CPU instructions. This is the job of the CLR’s JIT (just-in-time) compiler. Figure 1-4 shows what happens the first time a method is called. Figure 1-4 : Calling a method for the first time Just before the Main method executes, the CLR detects all the types that are referenced by Main’s code. This causes the CLR to allocate an internal data structure that is used to manage access to the referenced type. In Figure 1-4, the Main method refers to a single type, Console, causing the CLR to allocate a single internal structure. This internal data structure contains an entry for each method defined by the type. Each entry holds the address where the method’s implementation can be found. When initializing this structure, the CLR sets each entry to an internal, undocumented function contained inside the CLR itself. I call this function JITCompiler. When Main makes its first call to WriteLine, the JITCompiler function is called. The JITCompiler function is responsible for compiling a method’s IL code into native CPU instructions. Because the IL is being compiled "just in time," this component of the CLR is frequently referred to as a JITter or a JIT compiler. When called, the JITCompiler function knows what method is being called and what type defines this method. The JITCompiler function then searches the defining assembly’s metadata for the called method’s IL. JITCompiler next verifies and compiles the IL code into native CPU instructions. The native CPU instructions are saved in a dynamically allocated block of memory. Then, JITCompiler goes back to the type’s internal data structure and replaces the address of the called method with the address of the block of memory containing the native CPU instructions. Finally, JITCompiler jumps to the code in the memory block. This code is the implementation of the WriteLine method (the version that takes a String parameter). When this code returns, it returns to the code in Main, which continues execution as normal. Main now calls WriteLine a second time. This time, the code for WriteLine has already been verified and compiled. So the call goes directly to the block of memory, skipping the JITCompiler function entirely. After the WriteLine method executes, it returns to Main. Figure 1-5 shows what the situation looks like when WriteLine is called the second time. A performance hit is incurred only the first time a method is called. All subsequent calls to the method execute at the full speed of the native code: verification and compilation to native code are not performed again. The JIT compiler stores the native CPU instructions in dynamic memory. This means that the compiled code is discarded when the application terminates. So, if you run the application again in the future or if you run two instances of the application simultaneously (in two different operating system processes), the JIT compiler will have to compile the IL to native instructions again. For most applications, the performance hit incurred by JIT compiling isn’t significant. Most applications tend to call the same methods over and over again. These methods will take the performance hit only once while the application executes. It’s also likely that more time is spent inside the method than calling the method. Figure 1-5 : Calling a method for the second time You should also be aware that the CLR’s JIT compiler optimizes the native code just as the back-end of an unmanaged C++ compiler does. Again, it may take more time to produce the optimized code, but the code will execute with much better performance than if it hadn’t been optimized. For those developers coming from an unmanaged C or C++ background, you’re probably thinking about the performance ramifications of all this. After all, unmanaged code is compiled for a specific CPU platform and, when invoked, the code can simply execute. In this managed environment, compiling the code is accomplished in two phases. First, the compiler passes over the source code, doing as much work as possible in producing IL. But to execute the code, the IL itself must be compiled into native CPU instructions at run time, requiring more memory to be allocated and requiring additional CPU time to do the work. Believe me, since I approached the CLR from a C/C++ background myself, I was quite skeptical and concerned about this additional overhead. The truth is that this second compilation stage that occurs at run time does hurt performance and it does allocate dynamic memory. However, Microsoft has done a lot of performance work to keep this additional overhead to a minimum. If you too are skeptical, you should certainly build some applications and test the performance for yourself. In addition, you should run some nontrivial managed applications Microsoft or others have produced and measure their performance. I think you’ll be surprised at how good the performance actually is. In fact, you’ll probably find this hard to believe, but many people (including me) think that managed applications could actually outperform unmanaged applications. There are many reasons to believe this. For example, when the JIT compiler compiles the IL code into native code at run time, the compiler knows more about the execution environment than an unmanaged compiler would know. Here are some ways that managed code could outperform unmanaged code: § A JIT compiler could detect that the application is running on a Pentium 4 and produce native code that takes advantage of any special instructions offered by the Pentium 4. Usually, unmanaged applications are compiled for the lowest-common-denominator CPU and avoid using special instructions that would give the application a performance boost over newer CPUs. § A JIT compiler could detect that a certain test is always false on the machine that it is running on. For example, consider a method with code like this: §if (numberOfCPUs > 1) { § ? } This code could cause the JIT compiler not to generate any CPU instructions if the host machine has only one CPU. In this case, the native code has been fine-tuned for the host machine: the code is smaller and executes faster. § The CLR could profile the code’s execution and recompile the IL into native code while the application runs. The recompiled code could be reorganized to reduce incorrect branch predictions depending on the observed execution patterns. These are only a few of the reasons why you should expect future managed code to execute better than today’s unmanaged code. As I said, the performance is currently quite good for most applications, and it promises to improve as time goes on. If your experiments show that the CLR’s JIT compiler doesn’t offer your application the kind of performance it requires, you may want to take advantage of the NGen.exe tool that ships with the .NET Framework SDK. This tool compiles all an assembly’s IL code into native code and saves the resulting native code to a file on disk. At run time, when an assembly is loaded, the CLR automatically checks to see whether a precompiled version of the assembly also exists, and if it does, the CLR loads the precompiled code so that no compilation at run time is required. IL and Verification IL is stack-based, which means that all its instructions push operands onto an execution stack and pop results off the stack. Because IL offers no instructions to manipulate registers, compiler developers have an easy time producing IL code; they don’t have to think about managing registers, and fewer IL instructions are needed (since none exist for manipulating registers). IL instructions are also typeless. For example, IL offers an add instruction that adds the last two operands pushed on the stack; there are not separate 32-bit and 64-bit add instructions. When the add instruction executes, it determines the types of the operands on the stack and performs the appropriate operation. In my opinion, the biggest benefit of IL isn’t that it abstracts away the underlying CPU. The biggest benefit is application robustness. While compiling IL into native CPU instructions, the CLR performs a process called verification. Verification examines the high-level IL code and ensures that everything it does is “safe.” For example, verification checks that no memory is read from without having previously been written to, that every method is called with the correct number of parameters and that each parameter is of the correct type, that every method’s return value is used properly, that every method has a return statement, and so on. The managed module’s metadata includes all the method and type information used by the verification process. If the IL code is determined to be “unsafe,” then a System.Security.VerifierException exception is thrown, preventing the method from executing. Is Your Code Safe? By default, the Microsoft C# and Visual Basic compilers produce safe code. Safe code is code that is verifiably safe. However, using the unsafe keyword in C# or other languages (such as C++ with Managed Extensions or IL assembly language), it’s possible to produce code that can’t be verifiably safe. The code might, in fact, be safe, but verification is unable to prove this. To ensure that all your managed module’s methods contain verifiably safe IL, you can use the PEVerify utility (PEVerify.exe) that ships with the .NET Framework SDK. When Microsoft tests their C# and Visual Basic compilers, they run the resulting module through PEVerify to ensure that the compiler always produces verifiably safe code. If PEVerify detects unsafe code, Microsoft fixes the compiler. You may want to consider running PEVerify on your own modules before you package and ship them. If PEVerify detects a problem, then there is a bug in the compiler and you should report it to Microsoft (or whatever company produces the compiler you’re using). If PEVerify doesn’t detect any unverifiable code, you know that your code will run without throwing a VerifierException on the end-user’s machine. You should be aware that verification requires access to the metadata contained in any dependant assemblies. So, when you use PEVerify to check an assembly, it must be able to locate and load all referenced assemblies. Because PEVerify uses the CLR to locate the dependant assemblies, the assemblies are located using the same binding and probing rules that would normally be used when executing the assembly. (I’ll discuss these binding and probing rules in Chapters 2 and 3.) Note that an administrator can elect to turn off verification (using the Microsoft .NET Framework Configuration administrative tool). With verification off, the JIT compiler will compile unverifiable IL into native CPU instructions; however, the administrator is taking full responsibility for the code’s behavior. In Windows, each process has its own virtual address space. Separate address spaces are necessary because you can’t trust the application’s code. It is entirely possible (and unfortunately, all too common) that an application will read from or write to an invalid memory address. By placing each Windows process in a separate address space, you gain robustness: one process can’t adversely affect another process. By verifying the managed code, however, you know that the code doesn’t improperly access memory that it shouldn’t and you know that the code can’t adversely affect another application’s code. This means that you can run multiple managed applications in a single Windows virtual address space. Because Windows processes require a lot of operating system resources, having many of them can hurt performance and limit available resources. Reducing the number of processes by running multiple applications in a single OS process can improve performance, require fewer resources, and be just as robust. This is another benefit of managed code as compared to unmanaged code. The CLR does, in fact, offer the ability to execute multiple managed applications in a single OS process. Each managed application is called an AppDomain. By default, every managed EXE will run in its own, separate address space that has just the one AppDomain. However, a process hosting the CLR (such as Internet Information Services [IIS] or a future version of SQL Server) can decide to run AppDomains in a single OS process. I’ll devote part of Chapter 20 to a discussion of AppDomains. The .NET Framework Class Library Included with the .NET Framework is a set of .NET Framework Class Library (FCL) assemblies that contains several thousand type definitions, where each type exposes some functionality. All in all, the CLR and the FCL allow developers to build the following kinds of applications: § XML Web services Methods that can be accessed over the Internet very easily. XML Web services are, of course, the main thrust of Microsoft’s .NET initiative. § Web Forms HTML-based applications (Web sites). Typically, Web Forms applications will make database queries and Web service calls, combine and filter the returned information, and then present that information in a browser using a rich HTML-based user interface. Web Forms provides a Visual Basic 6 and Visual InterDev style development environment for Web applications written in any CLR language. § Windows Forms Rich Windows GUI applications. Instead of using a Web Forms page to create your application’s UI, you can use the more powerful, higher performance functionality offered by the Windows desktop. Windows Forms applications can take advantage of controls, menus, and mouse and keyboard events, and they can talk directly to the underlying operating system. Like Web Forms applications, Windows Forms applications also make database queries and call XML Web services. Windows Forms provides a Visual Basic 6Ðlike development environment for GUI applications written in any CLR language. § Windows console applications For applications with very simple UI demands, a console application provides a quick and easy way to build an application. Compilers, utilities, and tools are typically implemented as console applications. § Windows services Yes, it is possible to build service applications controllable vi a the Windows Service Control Manager (SCM) using the .NET Framework. § Component library The .NET Framework allows you to build stand-alone components (types) that can be easily incorporated into any of the previously mentioned application types. Because the FCL contains literally thousands of types, a set of related types is presented to the developer within a single namespace. For example, the System namespace (which you should become most familiar with) contains the Object base type, from which all other types ultimately derive. In addition, the System namespace contains types for integers, characters, strings, exception handling, and console I/O as well as a bunch of utility types that convert safely between data types, format data types, generate random numbers, and perform various math functions. All applications will use types from the System namespace. To access any of the platform’s features, you need to know which namespace contains the types that expose the facilities you’re after. If you want to customize any type’s behavior, you can simply derive your own type from the desired FCL type. The object-oriented nature of the platform is how the .NET Framework presents a consistent programming paradigm to software developers. Also, developers can easily create their own namespaces containing their own types. These namespaces and types merge seamlessly into the programming paradigm. Compared to Win32 programming paradigms, this new approach greatly simplifies software development. Most of the namespaces in the FCL present types that can be used for any kind of application. Table 1-2 lists some of the more general namespaces and briefly describes what the types in that namespace are used for. Table 1-2: Some General FCL Namespaces Namespace Description of Contents System All the basic types used by every application System.Collections Types for managing collections of objects; includes the popular collection types, such as stacks, queues, hash tables, and so on System.Diagnostics Types to help instrument and debug applications System.Drawing Types for manipulating 2-D graphics; typically used for Windows Forms applications and for creating images that are to appear in a Web Forms page System.EnterpriseServices Types for managing transactions, queued components, object pooling, JIT activation, security, and other features to make the use of managed code more efficient on the server System.Globalization Types for National Language Support (NLS), such as string compares, formatting, and calendars System.IO Types for doing stream I/O, walking directories and files Table 1-2: Some General FCL Namespaces Namespace Description of Contents System.Management Types used for managing other computers in the enterprise via Windows Management Instrumentation (WMI) System.Net Types that allow for network communications System.Reflection Types that allow the inspection of metadata and late binding to types and their members System.Resources Types for manipulating external data resources System.Runtime.InteropServices Types that allow managed code to access unmanaged OS platform facilities such as COM components and functions in Win32 DLLs System.Runtime.Remoting Types that allow for types to be accessed remotely System.Runtime.Serialization Types that allow for instances of objects to be persisted and regenerated from a stream System.Security Types used for protecting data and resources System.Text Types to work with text in different encodings, such as ASCII or Unicode System.Threading Types used for asynchronous operations and synchronizing access to resources Table 1-2: Some General FCL Namespaces Namespace Description of Contents System.Xml Types used for processing XML schemas and data This book is about the CLR and about the general types that interact closely with the CLR (which are most of the namespaces listed in Table 1-2). So the content of this book is applicable to all .NET Framework programmers, regardless of the type of application they’re building. In addition to the more general namespaces, the FCL also offers namespaces whose types are used for building specific application types. Table 1-3 lists some of the application- specific namespaces in the FCL. Table 1-3: Some Application-Specific FCL Namespaces Namespace Application Type System.Web.Services Types used to build XML Web services System.Web.UI Types used to build Web Forms System.Windows.Forms Types used to build Windows GUI applications System.ServiceProcess Types used to build a Windows service controllable by the SCM I expect many good books will be published that explain how to build specific application types (such as Windows services, Web Forms, and Windows Forms). These books will give you an excellent start at helping you build your application. I tend to think of these application-specific books as helping you learn from the top down because they concentrate on the application type and not on the development platform. In this book, I’ll offer information that will help you learn from the bottom up. After reading this book and an application-specific book, you should be able to easily and proficiently build any kind of .NET Framework application you desire. The Common Type System By now, it should be obvi ous to you that the CLR is all about types. Types expose functionality to your applications and components. Types are the mechanism by which code written in one programming language can talk to code written in a different programming language. Because types are at the root of the CLR, Microsoft created a formal specification—the Common Type System (CTS)—that describes how types are defined and how they behave. The CTS specification states that a type can contain zero or more members. In Part III, I’ll cover all these members in great detail. For now, I just want to give you a brief introduction to them: § Field A data variable that is part of the object’s state. Fields are identified by their name and type. § Method A function that performs an operation on the object, often changing the object’s state. Methods have a name, a signature, and modifiers. The signature specifies the calling convention, the number of parameters (and their sequence), the types of the parameters, and the type of value returned by the method. § Property To the caller, this member looks like a field. But to the type implementer, it looks like a method (or two). Properties allow an implementer to validate input parameters and object state before accessing the value and/or calculate a value only when necessary. They also allow a user of the type to have simplified syntax. Finally, properties allow you to create read-only or write-only “fields.” § Event An event allows a notification mechanism between an object and other interested objects. For example, a button could offer an event that notifies other objects when the button is clicked. The CTS also specifies the rules for type visibility and for access to the members of a type. For example, marking a type as public (called public) exports the type, making it visible and accessible to any assembly. On the other hand, marking a type as assembly (called internal in C#) makes the type visible and accessible to code within the same assembly only. Thus, the CTS establishes the rules by which assemblies form a boundary of visibility for a type, and the CLR enforces the visibility rules. Regardless of whether a type is visible to a caller, the type gets to control whether the caller has access to its members. The following list shows the valid options for controlling access to a method or a field: § Private The method is callable only by other methods in the same class type. § Family The method is callable by derived types, regardless of whether they are within the same assembly. Note that many languages (such as C++ and C#) refer to family as protected. § Family and assembly The method is callable by derived types, but only if the derived type is defined in the same assembly. Many languages (such as C# and Visual Basic) don’t offer this access control. Of course, IL Assembly language makes it available. § Assembly The method is callable by any code in the same assembly. Many languages refer to assembly as internal. § Family or assembly The method is callable by derived types in any assembly. The method is also callable by any types in the same assembly. C# refers to family or assembly as protected internal. § Public The method is callable by any code in any assembly. In addition, the CTS defines the rules governing type inheritance, virtual functions, object lifetime, and so on. These rules have been designed to accommodate the semantics expressible in modern-day programming languages. In fact, you won’t even need to learn the CTS rules per se since the language you choose will expose its own language syntax and type rules in the same way you’re familiar with today and will map the language-specific syntax into the “language” of the CLR when it emits the managed module. When I first started working with the CLR, I soon realized that it is best to think of the language and the behavior of your code as two separate and distinct things. Using C++, you can define your own types with their own members. Of course, you could have used C# or Visual Basic to define the same type with the same members. Sure, the syntax you use for defining this type is different depending on the language you choose, but the behavior of the type will be absolutely identical regardless of the language because the CLR’s CTS defines the behavior of the type. To help clarify this idea, let me give you an example. The CTS supports single inheritance only. So, while the C++ language supports types that inherit from multiple base types, the CTS can’t accept and operate on any such type. To help the developer, the Visual C++ compiler reports an error if it detects that you’re attempting to create managed code that includes a type inherited from multiple base types. Here’s another CTS rule. All types must (ultimately) inherit from a predefined type: System.Object. As you can see, Object is the name of a type defined in the System namespace. This Object is the root of all other types and therefore guarantees every type instance has a minimum set of behaviors. Specifically, the System.Object type allows you to do the following: § Compare two instances for equality § Obtain a hash code for the instance § Query the true type of an instance § Perform a shallow (bitwise) copy of the instance § Obtain a string representation of the instance’s object’s current state The Common Language Specification COM allows objects created in different languages to communicate with one another. On the other hand, the CLR now integrates all languages and allows objects created in one language to be treated as equal citizens by code written in a completely different language. This integration is possible because of the CLR’s standard set of types, self-describing type information (metadata), and common execution environment. While this language integration is a fantastic goal, the truth of the matter is that programming languages are very different from one another. For example, some languages don’t treat symbols with case-sensitivity or don’t offer unsigned integers, operator overloading, or methods that support a variable number of parameters. If you intend to create types that are easily accessible from other programming languages, you need to use only features of your programming language that are guaranteed to be available in all other languages. To help you with this, Microsoft has defined a Common Language Specification (CLS) that details for compiler vendors the minimum set of features that their compilers must support if these compilers are to target the CLR. The CLR/CTS supports a lot more features than the subset defined by the CLS, so if you don’t care about interlanguage operability, you can develop very rich types limited only by the language’s feature set. Specifically, the CTS defines rules that externally visible types and methods must adhere to if they are to be accessible from any CLR-compliant programming language. Note that the CLS rules don’t apply to code that is accessible only within the defining assembly. Figure 1-6 summarizes the ideas expressed in this paragraph. Figure 1-6 : Languages offer a subset of the CLR/CTS and a superset of the CLS (but not necessarily the same superset) As Figure 1-6 shows, the CLR/CTS offers a set of features. Some languages expose a large subset of the CLR/CTS. A programmer willing to write in IL assembly language, for example, is able to use all the features the CLR/CTS offers. Most other languages, such as C#, Visual Basic, and Fortran, expose a subset of the CLR/CTS features to the programmer. The CLS defines the minimum set of features that all languages must support. If you’re designing a type in one language and you expect that type to be used by another language, you shouldn’t take advantage of any features that are outside the CLS. Doing so would mean that your type’s members might not be accessible by programmers writing code in other programming languages. In the following code, a CLS-compliant type is being defined in C#. However, the type has a few non-CLS-compliant constructs causing the C# compiler to complain about the code. using System; // Tell compiler to check for CLS compliance [assembly:CLSCompliant(true)] // Errors appear because the class is public public class App { // Error: Return type of ÔApp.Abc()’ is not CLS-compliant public UInt32 Abc() { return 0; } // Error: Identifier ÔApp.abc()’ differing // only in case is not CLS-compliant public void abc() { } // No error: Method is private private UInt32 ABC() { return 0; } } In this code, the [assembly:CLSCompliant(true)] attribute is applied to the assembly. This attribute tells the compiler to ensure that any publicly exposed type doesn’t have any construct that would prevent the type from being accessed from any other programming language. When this code is compiled, the C# compiler emits two errors. The first error is reported because the method Abc returns an unsigned integer; Visual Basic and some other languages can’t manipulate unsigned integer values. The second error is because this type exposes two public methods that differ only by case: Abc and abc. Visual Basic and some other languages can’t call both these methods. Interestingly, if you were to delete public from in front of Ôclass App’ and recompile, both errors would go away. The reason is that the App type would default to internal and would therefore no longer be exposed outside the assembly. For a complete list of CLS rules, refer to the "Cross-Language Interoperability" section in the .NET Framework SDK documentation. Let me distill the CLS rules to something very simple. In the CLR, every member of a type is either a field (data) or a method (behavior). This means that every programming language must be able to access fields and call methods. Certain fields and certain methods are used in special and common ways. To ease programming, languages typically offer additional abstractions to make coding these common programming patterns easier. For example, languages expose concepts such as enums, arrays, properties, indexers, delegates, events, constructors, destructors, operator overloads, conversion operators, and so on. When a compiler comes across any of these things in your source code, it must translate these constructs into fields and methods so that the CLR and any other programming language can access the construct. Consider the following type definition, which contains a constructor, a destructor, some overloaded operators, a property, an indexer, and an event. Note that the code shown is there just to make the code compile; it doesn’t show the correct way to implement a type. using System; class Test { // Constructor public Test() {} // Destructor ~Test() {} // Operator overload public static Boolean operator == (Test t1, Test t2) { return true; } public static Boolean operator != (Test t1, Test t2) { return false; } // An operator overload public static Test operator + (Test t1, Test t2) { return null; } // A property public String AProperty { get { return null; } set { } } // An indexer public String this[Int32 x] { get { return null; } set { } } // An event event EventHandler AnEvent; } When the compiler compiles this code, the result is a type that has a number of fields and methods defined in it. You can easily see this using the IL Disassembler tool (ILDasm.exe) provided with the .NET Framework SDK to examine the resulting managed module, which is shown in Figure 1-7. Figure 1-7 : ILDasm showing Test type’s fields and methods (obtained from metadata) Table 1-4 shows how the programming language constructs got mapped to the equivalent CLR fields and methods. Table 1-4: Test Type’s Fields and Methods (obtained from metadata) Type Member Member Type Equivalent Programming Language Construct AnEvent Field Event; the name of the field is AnEvent and its type is System.EventHandler .ctor Method Constructor Finalize Method Destructor add_AnEvent Method Event add accessor method get_AProperty Method Property get accessor method get_Item Method Indexer get accessor method Table 1-4: Test Type’s Fields and Methods (obtained from metadata) Type Member Member Type Equivalent Programming Language Construct op_Addition Method + operator op_Equality Method == operator op_Inequality Method != operator remove_AnEvent Method Event remove accessor method set_AProperty Method Property set accessor method set_Item Method Indexer set accessor method The additional nodes under the Test type that aren’t mentioned in Table 1-4—.class, .custom, AnEvent, AProperty, and Item—identify additional metadata about the type. These nodes don’t map to fields or methods; they just offer some additional information about the type that the CLR, programming languages, or tools can get access to. For example, a tool can see that the Test type offers an event, called AnEvent, which is exposed via the two methods (add_AnEvent and remove_AnEvent). Interoperability with Unmanaged Code The .NET Framework offers a ton of advantages over other development platforms. However, very few companies can afford to redesign and reimplement all of their existing code. Microsoft realizes this and has constructed the CLR so that it offers mechanisms that allow an application to consist of both managed and unmanaged parts. Specifically, the CLR supports three interoperability scenarios: § Managed code can call an unmanaged function in a DLL Managed code can easily call functions contained in DLLs using a mechanism called P/Invoke (for Platform Invoke). After all, many of the types defined in the FCL internally call functions exported from Kernel32.dll, User32.dll, and so on. Many programming languages will expose a mechanism that makes it easy for managed code to call out to unmanaged functions contained in DLLs. For example, a C# or Visual Basic application can call the CreateSemaphore function exported from Kernel32.dll. § Managed code can use an existing COM component (server) Many companies have already implemented a number of unmanaged COM components. Using the type library from these components, a managed assembly can be created that describes the COM component. Managed code can access the type in the managed assembly just like any other managed type. See the TlbImp.exe tool that ships with the .NET Framework SDK for more information. At times, you might not have a type library or you might want to have more control over what TlbImp.exe produces. In these cases, you can manually build a type in source code that the CLR can use to achieve the proper interoperability. For example, you could use DirectX COM components from a C# or Visual Basic application. § Unmanaged code can use a managed type (server) A lot of existing unmanaged code requires that you supply a COM component for the code to work correctly. It’s much easier to implement these components using managed code so that you can avoid all the code having to do with reference counting and interfaces. For example, you could create an ActiveX control or a shell extension in C# or Visual Basic. See the TlbExp.exe and RegAsm.exe tools that ship with the .NET Framework SDK for more information. In addition to these three scenarios, Microsoft’s Visual C++ compiler (version 13) supports a new /clr command-line switch. This switch tells the compiler to emit IL code instead of native x86 instructions. If you have a large amount of existing C++ code, you can recompile the code using this new compiler switch. The new code will require the CLR to execute, and you can now modify the code over time to take advantage of the CLR-specific features. The /clr switch can’t compile to IL any methods that contain inline assembly language (via the __asm keyword), accept a variable number of arguments, call setjmp, or contain intrinsic routines (such as __enable, __disable, _ReturnAddress, and _AddressOfReturnAddress). For a complete list of the constructs that the C++ compiler can’t compile into IL, refer to the documentation for the Visual C++ compiler. When the compiler can’t compile the method into IL, it compiles the method into x86 so that the application still runs. Keep in mind that although the IL code produced is managed, the data is not; that is, data objects are not allocated from the managed heap and they are not garbage collected. In fact, the data types don’t have metadata produced for them, and the types’ method names are mangled. The following C code calls the standard C runtime library’s printf function and also calls the System.Console WriteLine method. The System.Console type is defined in the FCL. So, C/C++ code can use libraries available to C/C++ as well as managed types. #include // For printf #using // For managed types defined in this assembly using namespace System; // Easily access System namespace types // Implement a normal C/C++ main function void main() { // Call the C runtime library’s printf function. printf("Displayed by printf.\r\n"); // Call the FCL’s System.Console’s WriteLine method. Console::WriteLine("Displayed by Console::WriteLine."); } Compiling this code couldn’t be easier. If this code were in a MgdCApp.cpp file, you’d compile it by executing the following line at the command prompt: cl /clr MgdCApp.cpp The result is a MgdCApp.exe assembly file. If you run MgdCApp.exe, you’ll see the following output: C:\>MgdCApp Displayed by printf. Displayed by Console::WriteLine. If you use ILDasm.exe to examine this file, you’ll see the output shown in Figure 1-8. Figure 1-8 : ILDasm showing MgdCApp.exe assembly’s metadata In Figure 1-8, you see that ILDasm shows all the global functions and global fields defined within the assembly. Obviously, the compiler has generated a lot of stuff automatically. If you double-click the Main method, ILDasm will show you the IL code: .method public static int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) main() cil managed { .vtentry 1 : 1 // Code size 28 (0x1c) .maxstack 1 IL_0000: ldsflda valuetype $ArrayType$0x0faed885 Ô?A0x44d29f64.unnamed-global-0’ IL_0005: call vararg int32 modopt([mscorlib]System.Runtime.CompilerServices.CallConvCdecl) printf(int8 modopt([Microsoft.VisualC]Microsoft.VisualC.NoSignSpecifiedModifier) modopt([Microsoft.VisualC]Microsoft.VisualC.IsConstModifier)*) IL_000a: pop IL_000b: ldsflda valuetype $ArrayType$0x0e6cb2b2 Ô?A0x44d29f64.unnamed-global-1’ IL_0010: newobj instance void [mscorlib]System.String::.ctor(int8*) IL_0015: call void [mscorlib]System.Console::WriteLine(string) IL_001a: ldc.i4.0 IL_001b: ret } // end of method ÔGlobal Functions’::main What we see here isn’t pretty because the compiler generates a lot of special code to make all this work. However, from this IL code, you can see that printf and the Console.WriteLine method are both called. Chapter 2: Building, Packaging, Deploying, and Administering Applications and Types Overview Before we get into the chapters that explain how to develop programs for the Microsoft .NET Framework, let’s discuss the steps required to build, package, and deploy your applications and their types. In this chapter, I’ll focus on the basics of how to build components that are for your application’s sole use. In Chapter 3, I’ll cover the more advanced concepts you’ll need to understand, including how to build and use assemblies containing components that are to be shared by multiple applications. In both chapters, I’ll also talk about the ways an administrator can use information to affect the execution of an application and its types. Today, applications consist of several types. (In the .NET Framework, a type is called a component, but in this book, I’ll avoid the term component and use type instead.) Applications typically consist of types created by you and Microsoft as well as several other organizations. If these types are developed using any language that targets the common language runtime (CLR), they can all work together seamlessly; a type can even use another type as its base class, regardless of what languages the types are developed in. In this chapter, I’ll also explain how these types are built and packaged into files for deployment. In the process, I’ll take you on a brief historical tour of some of the problems that the .NET Framework is solving. .NET Framework Deployment Goals Over the years, Windows has gotten a reputation for being unstable and complicated. This reputation, whether deserved or not, is the result of many different factors. First, all applications use dynamic-link libraries (DLLs) from Microsoft or other vendors. Because an application executes code from various vendors, the developer of any one piece of code can’t be 100 percent sure how someone else is going to use it. This situation can potentially cause all kinds of trouble, but in practice, problems don’t typically arise from this kind of interaction because applications are tested and debugged before they are deployed. Users, however, frequently run into problems when one company decides to update its code and ships new files to them. These new files are supposed to be “backward compatible” with the previous files, but who knows for sure? In fact, when one vendor updates its code, retesting and debugging all the already shipped applications to ensure that the changes have had no undesirable effect is usually impossible. I’m sure that everyone reading this book has experienced some variation of this problem: when installing a new application, you discover that it has somehow corrupted an already installed application. This predicament is known as “DLL hell.” This type of instability puts fear into the hearts and minds of the typical computer user. The end result is that users have to carefully consider whether to install new software on their machines. Personally, I’ve decided not to try out certain applications for fear of some application I really rely on being adversely affected. The second reason that has contributed to the reputation of Windows is installation complexities. Today, when most applications are installed, they affect all parts of the system. For example, installing an application causes files to be copied to various directories, updates registry settings, and installs shortcuts links on your desktop, Start menu, and Quick Launch toolbar. The problem with this is that the application isn’t isolated as a single entity. You can’t easily back up the application since you must copy the application’s files and also the relevant parts of the registry. In addition, you can’t easily move the application from one machine to another; you must run the installation program again so that all files and registry settings are set properly. Finally, you can’t easily uninstall or remove the application without having this nasty feeling that some part of the application is still lurking on your machine. The third reason has to do with security. When applications are installed, they come with all kinds of files, many of them written by different companies. In addition, “Web applications” frequently have code that is downloaded over the wire in such a way that users don’t even realize that code is being installed on their machine. Today, this code can perform any operation, including deleting files or sending e-mail. Users are right to be terrified of installing new applications because of the potential damage they can cause. To make users comfortable, security must be built into the system so that users can explicitly allow or disallow code developed by various companies to access the system resources. The .NET Framework addresses the “DLL hell” issue in a big way, as you’ll see as you read this chapter and Chapter 3. It also goes a long way toward fixing the problem of having an application’s state scattered all over a user’s hard disk. For example, unlike COM, components no longer require settings in the registry. Unfortunately, applications still require shortcut links, but future versions of Windows may solve this problem. As for security, the .NET Framework includes a new security model called code access security. Whereas Windows security is based around a user’s identity, code access security is based around an assembly’s identity. So a user could decide to trust all assemblies published by Microsoft or not to trust any assemblies downloaded from the Internet. As you’ll see, the .NET Framework enables users to control their machines, what gets installed, and what runs more than Windows ever did. Building Types into a Module In this section, I’ll show you how to turn your source file, containing various types, into a file that can be deployed. Let’s start by examining the following simple application: public class App { static public void Main(System.String[] args) { System.Console.WriteLine("Hi"); } } This application defines a type, called App. This type has a single static, public method called Main. Inside Main is a reference to another type called System.Console. System.Console is a type implemented by Microsoft, and the IL code that implements this type’s methods are in the MSCorLib.dll file. So, our application defines a type and also uses another company’s type. To build this sample application, put the preceding code into a source code file, say App.cs, and then execute the following command line: csc.exe /out:App.exe /t:exe /r:MSCorLib.dll App.cs This command line tells the C# compiler to emit an executable file called App.exe (/out:App.exe). The type of file produced is a Win32 console application (/t[arget]:exe). When the C# compiler processes the source file, it sees that the code references the System.Console type’s WriteLine method. At this point, the compiler wants to ensure that this type exists somewhere, that it has a WriteLine method, and that it checks that the types of the arguments that WriteLine expects match up with what the program is supplying. To make the C# compiler happy, you must give it a set of assemblies that it can use to resolve references to external types. (I’ll define assemblies shortly, but for now you can think of an assembly as a set of one or more DLL files.) In the command line above, I’ve included the /r[eference]:MSCorLib.dll switch telling the compiler to look for external types in the assembly identified by the MSCorLib.dll file. MSCorLib.dll is a special file in that it contains all the core types, such as bytes, integers, characters, strings, and so on. In fact, these types are so frequently used that the C# compiler automatically references this assembly. In other words, the following command line (with the /r switch omitted) gives the same results as the line shown earlier: csc.exe /out:App.exe /t:exe App.cs Furthermore, because the /out:App.exe and the /t:exe command-line switches also match what the C# compiler would choose as defaults, the following command line gives the same results too: csc.exe App.cs If, for some reason, you really don’t want the C# compiler to reference the MSCorLib.dll assembly, you can use the /nostdlib switch. For example, the following command line will generate an error when compiling the App.cs file since the System.Console type is defined in MSCorLib.dll: csc.exe /out:App.exe /t:exe /nostdlib App.cs Now, let’s take a closer look at the App.exe file produced by the C# compiler. What exactly is this file? Well, for starters, it is a standard PE (portable executable) file. This means that a machine running 32-bit or 64-bit Windows should be able to load this file and do something with it. Windows supports two types of applications, those with a console user interface (CUI) and those with graphical user interface (GUI). Because I specified the /t:exe switch, the C# compiler produced a CUI application. You’d use the /t:winexe switch to have the C# compiler produce a GUI application. Now we know what kind of PE file we’ve created. But what exactly is in the App.exe file? A managed PE file has four main parts: the PE header, the CLR header, the metadata, and the intermediate language (IL). The PE header is the standard information that Windows expects. The CLR header is a small block of information that is specific to modules that require the CLR (managed modules). The header includes the major and minor version number of the metadata that the module was built with, some flags, a MethodDef token (described later) indicating the module’s entry point method if this module is a CUI or GUI executable, and an optional strong name digital signature (discussed in Chapter 3). Finally, the header contains the size and offsets of certain metadata tables contained within the module. You can see the exact format of the CLR header by examining the IMAGE_COR20_HEADER defined in the CorHdr.h header file. The metadata is a block of binary data that consists of several tables. There are three categories of tables: definition tables, reference tables, and manifest tables. Table 2-1 describes some of the more common definition tables that exist in a module’s metadata block. Table 2-1: Common Definition Metadata Tables Metadata Definition Table Name Description ModuleDef Always contains one entry that identifies the module. The entry includes the module’s filename and extension (without path) and a Table 2-1: Common Definition Metadata Tables Metadata Definition Table Name Description module version ID (in the form of a GUID created by the compiler). This allows the file to be renamed while keeping a record of its original name. However, renaming a file is strongly discouraged and can prevent the CLR from locating an assembly at runtime—don’t do this. TypeDef Contains one entry for each type defined in the module. Each entry includes the type’s name, base type, flags (i.e., public, private, etc.) and points to the methods it owns in the MethodDef table, the fields it owns in the FieldDef table, the properties it owns in the PropertyDef table, and the event it owns in the EventDef table. MethodDef Contains one entry for each method defined in the module. Each entry includes the method’s name, flags (private, public, virtual, abstract, static, final, etc), signature, and offset within the module where IL code can be found. Each entry can also refer to a ParamDef table entry where more information about the method’s parameters can be found. FieldDef Contains one entry for every field defined in the module. Each entry includes a name, flags (i.e., private, public, etc.), and type. ParamDef Contains one entry for each parameter defined in the module. Each entry includes a name and flags (in, out, retval, etc). PropertyDef Contains one entry for each property defined in the module. Each entry includes a name, flags, type, and backing field (which can be null). EventDef Contains one entry for each event defined in the module. Each entry includes a name and flags. As a compiler compiles your source code, everything that your code defines causes an entry to be created in one of the tables described in Table 2-1. As the compiler compiles the source code, it also detects the types, fields, methods, properties, and events that the source code references. The metadata includes a set of reference tables that keep a record of this stuff. Table 2-2 shows some of the more common reference metadata tables. Table 2-2: Common Reference Metadata Tables Metadata Reference Table Name Description AssemblyRef Contains one entry for each assembly referenced by the module. Each entry includes the information necessary to bind to the assembly: the assembly’s name (without path and extension), version number, culture, and public key token (normally a small hash value, generated from the publisher’s public key, identifying the referenced assembly’s publisher). Each entry also contains some flags and a hash value. This hash value was intended to be a checksum of the referenced assembly’s bits. The CLR completely ignores this hash value and will probably Table 2-2: Common Reference Metadata Tables Metadata Reference Table Name Description continue to do so in the future. ModuleRef Contains one entry for each PE module that implements types referenced by this module. Each entry includes the module’s filename and extension (without path). This table is used to bind to types that are implemented in different modules of the calling assembly’s module. TypeRef Contains one entry for each type referenced by the module. Each entry includes the type’s name and a reference to where the type can be found. If the type is implemented within another type, then the reference indicates a TypeRef entry. If the type is implemented in the same module, then the reference indicates a ModuleDef entry. If the type is implemented in another module within the calling assembly, then the reference indicates a ModuleRef entry. If the type is implemented in a different assembly, then the reference indicates an AssemblyRef entry. MemberRef Contains one entry for each member (fields and methods, as well as property and event methods) referenced by the module. Each entry includes the member’s name and signature, and points to the TypeRef entry for the type that defines the member. There are many more tables than what I list in Tables 2-1 and 2-2, but I just wanted to give you a sense of the kind of information that the compiler emits to produce the metadata information. Earlier I mentioned that there is also a set of manifest metadata tables; I’ll discuss these a little later in the chapter. Various tools allow you to examine the metadata within a managed PE file. My personal favorite is ILDasm.exe, the IL disassembler. To see the metadata tables, execute the following command line: ILDasm /Adv App.exe This causes ILDasm.exe to run, loading the App.exe assembly. The /Adv switch tells ILDasm to make some "advanced" menu items available. These advanced menu items can be found on the View menu. To see the metadata in a nice, human-readable form, select the View.MetaInfo.Show! menu item (or press Ctrl+M). This causes the following information to appear: ScopeName : App.exe MVID : {ED543DFC-44DD-4D14-9849-F7EC1B840BD0} ========================================================= == Global functions ——————————————————————————— Global fields ——————————————————————————— Global MemberRefs ——————————————————————————— TypeDef #1 ——————————————————————————— TypDefName: App (02000002) Flags : [Public] [AutoLayout] [Class] [AnsiClass] (00100001) Extends : 01000001 [TypeRef] System.Object Method #1 [ENTRYPOINT] ——————————————————————————— MethodName: Main (06000001) Flags : [Public] [Static] [HideBySig] [ReuseSlot] (00000096) RVA : 0x00002050 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: SZArray String 1 Parameters (1) ParamToken : (08000001) Name : args flags: [none] (00000000) Method #2 ——————————————————————————— MethodName: .ctor (06000002) Flags : [Public] [HideBySig] [ReuseSlot] [SpecialName] [RTSpecialName] [.ctor] (00001886) RVA : 0x00002068 ImplFlags : [IL] [Managed] (00000000) CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #1 (01000001) ——————————————————————————— Token: 0x01000001 ResolutionScope: 0x23000001 TypeRefName: System.Object MemberRef #1 ——————————————————————————— Member: (0a000003) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void No arguments. TypeRef #2 (01000002) ——————————————————————————— Token: 0x01000002 ResolutionScope: 0x23000001 TypeRefName: System.Diagnostics.DebuggableAttribute MemberRef #1 ——————————————————————————— Member: (0a000001) .ctor: CallCnvntn: [DEFAULT] hasThis ReturnType: Void 2 Arguments Argument #1: Boolean Argument #2: Boolean TypeRef #3 (01000003) ——————————————————————————— Token: 0x01000003 ResolutionScope: 0x23000001 TypeRefName: System.Console MemberRef #1 ——————————————————————————— Member: (0a000002) WriteLine: CallCnvntn: [DEFAULT] ReturnType: Void 1 Arguments Argument #1: String Assembly ——————————————————————————— Token: 0x20000001 Name : App Public Key : Hash Algorithm : 0x00008004 Major Version: 0x00000000 Minor Version: 0x00000000 Build Number: 0x00000000 Revision Number: 0x00000000 Locale: Flags : [SideBySideCompatible] (00000000) CustomAttribute #1 (0c000001) ——————————————————————————— CustomAttribute Type: 0a000001 CustomAttributeName: System.Diagnostics.DebuggableAttribute :: instance void .ctor(bool,bool) Length: 6 Value : 01 00 00 01 00 00 > < ctor args: ( ) AssemblyRef #1 ——————————————————————————— Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: 0x00000c1e Revision Number: 0x00000000 Locale: HashValue Blob: Flags: [none] (00000000) User Strings ——————————————————————————— 70000001 : ( 2) L"Hi" Fortunately, ILDasm processes the metadata tables and combines information where appropriate so that you don’t have to parse the raw table information. For example, in the dump above, you see that when ILDasm shows a TypeDef entry, the corresponding member definition information is shown with it before the first TypeRef entry is displayed. You don’t need to fully understand everything you see here. The important thing to remember is that App.exe contains a TypeDef whose name is App. This type identifies a public class that is derived from System.Object (a type referenced from another assembly). The App type also defines two methods: Main and .ctor (a constructor). Main is a static, public method whose code is IL (vs. native CPU code, such as x86). Main has a void return type and takes a single argument, args, which is an array of String. The constructor method (always shown with a name of .ctor) is public and its code is also IL. The constructor has a void return type and has no arguments but has a this pointer, which refers to the object’s memory that is to be constructed when the method is called. I strongly encourage you to experiment using ILDasm. It can show you a wealth of information, and the more you understand what you’re seeing, the better you’ll understand the common language runtime and its capabilities. As you’ll see, I use ILDasm quite a bit more in this book. Just for fun, let’s look at some statistics about the App.exe assembly. When you select ILDasm’s View.Statistics menu item, the following information is displayed: File size : 3072 PE header size : 512 (496 used) (16.67%) PE additional info : 923 (30.05%) Num.of PE sections : 3 CLR header size : 72 ( 2.34%) CLR meta-data size : 520 (16.93%) CLR additional info : 0 ( 0.00%) CLR method headers : 24 ( 0.78%) Managed code : 18 ( 0.59%) Data : 828 (26.95%) Unaccounted : 175 ( 5.70%) Num.of PE sections : 3 .text - 1024 .rsrc - 1024 .reloc - 512 CLR meta-data size : 520 Module - 1 (10 bytes) TypeDef - 2 (28 bytes) 0 interfaces, 0 explicit layout TypeRef - 3 (18 bytes) MethodDef - 2 (28 bytes) 0 abstract, 0 native, 2 bodies MemberRef - 3 (18 bytes) ParamDef - 1 (6 bytes) CustomAttribute- 1 (6 bytes) Assembly - 1 (22 bytes) AssemblyRef - 1 (20 bytes) Strings - 128 bytes Blobs - 40 bytes UserStrings - 8 bytes Guids - 16 bytes Uncategorized - 172 bytes CLR method headers : 24 Num.of method bodies - 2 Num.of fat headers - 2 Num.of tiny headers - 0 Managed code : 18 Ave method size - 9 Here you can see the size (in bytes) of the file and the size (in bytes and percentages) of the various parts that make up the file. For this very small App.cs application, the PE header and the metadata occupy the bulk of the file’s size. In fact, the IL code occupies just 18 bytes. Of course, as an application grows, it will reuse most of its types and references to other types and assemblies, causing the metadata and header information to shrink considerably as compared to the overall size of the file. Combining Modules to Form an Assembly The App.exe file discussed in the previous section is more than just a PE file with metadata; it is also an assembly. An assembly is a collection of one or more files containing type definitions and resource files. One of the assembly’s files is chosen to hold a manifest. The manifest is another set of metadata tables that basically contain the name of the files that are part of the assembly. They also describe the assembly’s version, culture, publisher, publicly exported types, and all the files that comprise the assembly. The CLR operates on assemblies; that is, the CLR always loads the file that contains the manifest metadata tables first and then uses the manifest to get the names of the other files that are in the assembly. Here are some characteristics of assemblies that you should remember: § An assembly defines the reusable types. § An assembly is marked with a version number. § An assembly can have security information associated with it. An assembly’s individual files don’t have these attributes—except for the file that contains the manifest metadata tables. To package, version, secure, and use types, you must place them in modules that are part of an assembly. In most cases, an assembly consists of a single file, as the preceding App.exe example does. However, an assembly can also consist of multiple files: some PE files with metadata and some resource files such as .gif or .jpg files. It might help you to think of an assembly as a logical EXE or a DLL. I’m sure that many of you reading this are wondering why Microsoft has introduced this new assembly concept. The reason is that an assembly allows you to decouple the logical and physical notions of reusable types. For example, an assembly can consist of several types. You could put the frequently used types in one file and the less frequently used types in another file. If your assembly is deployed by downloading it via the Internet, the file with the infrequently used types might not ever have to be downloaded to the client if the client never accesses the types. For example, an ISV specializing in UI controls might choose to implement Active Accessibility types in a separate module (to satisfy Microsoft’s Logo requirements). Only users who require the additional accessibility features would require that this module be downloaded. You configure an application to download assembly files by specifying a codeBase element (discussed in Chapter 3) in the application’s configuration file. The codeBase element identifies a URL where all of an assembly’s files can be found. When attempting to load an assembly’s file, the CLR obtains the codeBase element’s URL and checks the machine’s download cache to see if the file is present. If it is, the file is loaded. If the file isn’t in the cache, the CLR downloads the file from the URL into the cache. If the file can’t be found, the CLR throws a FileNotFoundException exception at runtime. I’ve identified three reasons to use multifile assemblies: § You can partition your types among separate files, allowing for files to be incrementally downloaded as described in the Internet download scenario. Partitioning the types into separate files also allows for partial or piecemeal packaging and deployment for “shrink- wrapped” scenarios. § You can add resource or data files to your assembly. For example, you could have a type that calculates some insurance information. This type might require access to some actuarial tables to make its computations. Instead of embedding the actuarial tables in your source code, you could use a tool (such as the assembly linker (AL.exe), discussed later) so that the data file is considered to be part of the assembly. By the way, this data file can be in any format: a text file, a Microsoft Excel spreadsheet, a Microsoft Word table, or whatever you like—as long as your application knows how to parse the file’s contents. § You can create assemblies consisting of types implemented in different programming languages. When you compile C# source code, the compiler produces a module. When you compile Visual Basic source code, the compiler produces a separate module. You can implement some types in C#, some types in Visual Basic, and other types in other languages. You can then use a tool to combine all these modules into a single assembly. To developers using the assembly, the assembly just contains a bunch of types; developers won’t even know that different programming languages were used. By the way, if you prefer, you can run ILDasm.exe on each of the modules to obtain an IL source code file. Then you can run ILAsm.exe and pass it all the IL source code files. ILAsm.exe will produce a single file containing all the types. This technique requires that your source code compiler produces IL-only code, so you can’t use this technique with Visual C++, for example. Important To summarize, an assembly is a unit of reuse, versioning, and security. It allows you to partition your types and resources into separate files so that you and consumers of your assembly get to determine which files to package together and deploy. Once the CLR loads the file containing the manifest, it can determine which of the assembly’s other files contain the types and resources that the application is referencing. Anyone consuming the assembly is required to know only the name of the file containing the manifest; the file partitioning is then abstracted away from the consumer and may change in the future without breaking the application’s behavior. To build an assembly, you must select one of your PE files to be the keeper of the manifest. Or you can create a separate PE file that contains nothing but the manifest. Table 2-3 shows the manifest metadata tables that turn a managed module into an assembly. Table 2-3: Manifest Metadata Tables Manifest Metadata Table Name Description AssemblyDef Contains a single entry if this module identifies an assembly. The entry includes the assembly’s name (without path and extension), version (major, minor, build, and revision), culture, flags, hash algorithm, and the publisher’s public key (which can be null). FileDef Contains one entry for each PE and resource file that is part of the assembly. The entry includes the file’s name and extension (without path), hash value, and flags. If this assembly consists only of its own file, the FileDef table has no entries. ManifestResourceDef Contains one entry for each resource that is part of the assembly. The entry includes the resource’s name, flags (public, private), and an index into the FileDef table indicating the file that contains the resource file or stream. If the resource isn’t a stand-alone file (such as .jpeg or a .gif), the resource is a stream contained within a PE file. For an embedded resource, the entry also includes an offset indicating the Table 2-3: Manifest Metadata Tables Manifest Metadata Table Name Description start of the resource stream within the PE file. ExportedTypesDef Contains one entry for each public type exported from all the assembly’s PE modules. The entry includes the type’s name, an index into the FileDef table (indicating which of this assembly’s files implements the type), and an index into the TypeDef table. Note: To save file space, types exported from the file containing the manifest are not repeated in this table because the type information is available using the metadata’s TypeDef table. The existence of a manifest provides a level of indirection between consumers of the assembly and the partitioning details of the assembly and makes assemblies self-describing. Also, note that the file containing the manifest knows which files are part of the assembly, but the individual files themselves aren’t aware that they are part of an assembly. Note The assembly file that contains the manifest also has an AssemblyRef table in it. This table contains an entry for all the assemblies referenced by all the assembly’s files. This allows tools to open an assembly’s manifest and see its set of referenced assemblies without having to open the assembly’s other files. Again, the entries in the AssemblyRef table exist to make an assembly self-describing. The C# compiler produces an assembly when you specify any of the following command-line switches: /t[arget]:exe, /t[arget]:winexe, or /t[arget]:library. All these switches cause the compiler to generate a single PE file that contains the manifest metadata tables. The resulting file is either a CUI executable, a GUI executable, or a DLL, respectively. In addition to these switches, the C# compiler supports the /t[arget]:module switch. This switch tells the compiler to produce a PE file that doesn’t contain the manifest metadata tables. The PE file produced is always a DLL PE file, and this file must be added to an assembly before the types within it can be accessed. When you use the /t:module switch, the C# compiler, by default, names the output file with an extension of .netmodule. Important Unfortunately, the Visual Studio .NET integrated development environment (IDE) doesn’t natively support the ability for you to create multifile assemblies. If you want to create multifile assemblies, you must resort to using command-line tools. There are many ways to add a module to an assembly. If you’re using the C# compiler to build a PE file with a manifest, you can use the /addmodule switch. To understand how to build a multifile assembly, let’s assume that we have two source code files: § RUT.cs, which contains rarely used types § FUT.cs, which contains frequently used types Let’s compile the rarely used types into their own module so that users of the assembly won’t need to deploy this module if they never access the rarely used types: csc /t:module RUT.cs This line causes the C# compiler to create a RUT.netmodule file. This file is a standard DLL PE file, but, by itself, the CLR can’t load it. Next let’s compile the frequently used types into their own module. We’ll make this module the keeper of the assembly’s manifest because the types are used so often. In fact, because this module will now represent the entire assembly, I’ll change the name of the output file to JeffTypes.dll instead of calling it FUT.dll: csc /out:JeffTypes.dll /t:library /addmodule:RUT.netmodule FUT.cs This line tells the C# compiler to compile the FUT.cs file to produce the JeffTypes.dll file. Because /t:library is specified, a DLL PE file containing the manifest metadata tables is emitted into the JeffTypes.dll file. The /addmodule:RUT.netmodule switch tells the compiler that RUT.netmodule is a file that should be considered part of the assembly. Specifically, the /addmodule switch tells the compiler to add the file to the FileDef manifest metadata table and to add RUT.netmodule’s publicly exported types to the ExportedTypesDef manifest metadata table. Once the compiler has finished all its processing, the two files shown in Figure 2-1 are created. The module on the right contains the manifest. Figure 2-1 : A multifile assembly consisting of two managed modules, one with a manifest The RUT.netmodule file contains the IL code generated by compiling RUT.cs. This file also contains metadata tables that describe the types, methods, fields, properties, events, and so on that are defined by RUT.cs. The metadata tables also describe the types, methods, and so on that are referenced by RUT.cs. The JeffTypes.dll is a separate file. Like RUT.netmodule, this file includes the IL code generated by compiling FUT.cs and also includes similar definition and reference metadata tables. However, JeffTypes.dll contains the additional manifest metadata tables, making JeffTypes.dll an assembly. The additional manifest metadata tables describe all the files that make up the assembly (the JeffTypes.dll file itself and the RUT.netmodule file). The manifest metadata tables also include all the public types exported from JeffTypes.dll and RUT.netmodule. Note In reality, the manifest metadata tables don’t actually include the types that are exported from the PE file that contains the manifest. The purpose of this optimization is to reduce the number of bytes required by the manifest information in the PE file. So statements like, “The manifest metadata tables also include all the public types exported from JeffTypes.dll and RUT.netmodule” aren’t 100 percent accurate. However, this statement does accurately reflect what the manifest is logically exposing. Once the JeffTypes.dll assembly is built, you can use ILDasm.exe to examine the metadata’s manifest tables to verify that the assembly file does in fact have references to the RUT.netmodule file’s types. If you build this project and then use ILDasm.exe to examine the metadata, you’ll see the FileDef and ExportedTypesDef tables included in the output. Here’s what those tables look like: File #1 ——————————————————————————— Token: 0x26000001 Name : RUT.netmodule HashValue Blob : 03 d4 09 ef 2d ac d3 4b 64 75 d7 81 cc 8e 88 7d 51 67 e2 5b Flags : [ContainsMetaData] (00000000) ExportedType #1 ——————————————————————————— Token: 0x27000001 Name: ARarelyUsedType Implementation token: 0x26000001 TypeDef token: 0x02000002 Flags : [Public] [AutoLayout] [Class] [AnsiClass] (00100001) From this, you can see that RUT.netmodule is a file considered to be part of the assembly. From the ExportedType table, you can see that there is a publicly exported type, ARarelyUsedType. The implementation token for this type is 0x26000001, which indicates that the type’s IL code is contained in the RUT.netmodule file. Note For the curious, metadata tokens are 4-byte values. The high byte indicates the type of token (0x01=TypeRef, 0x02=TypeDef, 0x26=FileRef, 0x27=ExportedType). For the complete list, see the CorTokenType enumerated type in the CorHdr.h file included with the .NET Framework SDK. The low three bytes of the token simply identify the row in the corresponding metadata table. For example, the implementation token 0x26000001 refers to the first row of the FileRef table. (Rows are numbered starting with 1, not 0.) Any client code that consumes the JeffTypes.dll assembly’s types must be built using the /r[eference]:JeffTypes.dll compiler switch. This switch tells the compiler to load the JeffTypes.dll assembly and all the files listed in its FileDef table. The compiler requires that all the assembly’s files are installed and accessible. If you were to delete the RUT.netmodule file, the C# compiler would produce the following error: "fatal error CS0009: Metadata file ‘C:\JeffTypes.dll’ could not be opened—’Error importing module ‘rut.netmodule’ of assembly ‘C:\JeffTypes.dll’—The system cannot find the file specified." This means that to build a new assembly, all the files from a referenced assembly must be present. As the client code executes, it calls methods. When a method is called for the first time, the CLR detects what types the method references. The CLR then attempts to load the referenced assembly’s file that contains the manifest. If the type being accessed is in this file, the CLR performs its internal bookkeeping, allowing the type to be used. If the manifest indicates that the referenced type is in a different file, the CLR attempts to load the necessary file, performs its internal bookkeeping, and allows the type to be accessed. The CLR loads assembly files only when a method referencing a type is called. This means that to run an application, all the files from a referenced assembly do not need to be present. Adding Assemblies to a Project Using the Visual Studio .NET IDE If you’re using the Visual Studio .NET IDE to build your project, you’ll have to add any assemblies you want to reference to your project. To do so, open the Solution Explorer window, right-click on the project you want to add a reference to, and select the Add Reference menu item. This causes the Add Reference dialog box, shown in Figure 2-2, to appear. Figure 2-2 : Add Reference dialog box in Visual Studio .NET To have your project reference a managed assembly, select the desired assembly from the list. If the assembly you want isn’t in the list, select the Browse button to navigate to the desired assembly (file containing a manifest) to add the assembly reference. The COM tab on the Add Reference dialog box allows an unmanaged COM server to be accessed from within managed source code. The Projects tab allows the current project to reference an assembly that is created by another project in the same solution. To make your own assemblies appear in the .NET tab’s list, add the following subkey to the registry: HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\.NETFramework\ AssemblyFolders\MyLibName MyLibName is a unique name that you create—Visual Studio doesn’t display this name. After creating the subkey, change its default string value so that it refers to a directory path (such as "C:\Program Files\MyLibPath") containing your assembly’s files. Using the Assembly Linker Instead of using the C# compiler, you might want to create assemblies using the Assembly Linker utility, AL.exe. The Assembly Linker is useful if you want to create an assembly consisting of modules built from different compilers (if your compiler doesn’t support the equivalent of C#’s /addmodule switch) or perhaps if you just don’t know your assembly packaging requirements at build time. You can also use AL.exe to build resource-only assemblies (called satellite assemblies, which I’ll talk about again later in the chapter), which are typically used for localization purposes. The AL.exe utility can produce an EXE or a DLL PE file that contains nothing but a manifest describing the types in other modules. To understand how AL.exe works, let’s change the way the JeffTypes.dll assembly is built: csc /t:module RUT.cs csc /t:module FUT.cs al /out:JeffTypes.dll /t:library FUT.netmodule RUT.netmodule Figure 2-3 shows the files that result from executing these statements. Figure 2-3 : A multifile assembly consisting of three managed modules, one with a manifest In this example, two separate modules, RUT.netmodule and FUT.netmodule, are created that are not themselves assemblies (because they don’t contain manifest metadata tables). Then a third file is produced: JeffTypes.dll, which is a small DLL PE file (because of the /t[arget]:library switch) that contains no IL code but has manifest metadata tables indicating that RUT.netmodule and FUT.netmodule are part of the assembly. The resulting assembly consists of three files: JeffTypes.dll, RUT.netmodule, and FUT.netmodule. The Assembly Linker has no way to combine multiple files into a single file. The AL.exe utility can also produce CUI and GUI PE files (using the /t[arget]:exe or /t[arget]:winexe command-line switch), but this is very unusual since it would mean that you’d have an EXE PE file with just enough IL code in it to call a method in another module. The Assembly Linker generates this IL code when you call AL.exe using the /main command-line switch. csc /t:module /r:JeffTypes.dll App.cs al /out:App.exe /t:exe /main:App.Main app.netmodule Here the first line builds the App.cs file into a module. The second line produces a small App.exe PE file that contains the manifest metadata tables. In addition, there is a small global function emitted by AL.exe because of the /main:App.Main switch. This function, __EntryPoint, contains the following IL code: .method privatescope static void __EntryPoint() il managed { .entrypoint // Code size 8 (0x8) .maxstack 8 IL_0000: tail. IL_0002: call void [.module ’App.mod’]App::Main() IL_0007: ret } // end of method ’Global Functions::__EntryPoint’ As you can see, this code simply calls the Main method contained in the App type defined in the App.netmodule file. The /main switch in AL.exe isn’t that useful because it’s unlikely that you’d ever create an assembly for an application where the application’s entry point isn’t in the PE file that contains the manifest metadata tables. I mention the switch here only to make you aware of its existence. Including Resource Files in the Assembly When using AL.exe to create an assembly, you can add resource files (non-PE files) to the assembly by using the /embed[resource] switch. This switch takes a file (any file) and embeds the file’s contents into the resulting PE file.The manifest’s ManifestResourceDef table is updated to reflect the existence of the resources. AL.exe also supports a /link[resource] switch, which also takes a file containing resources. However, the /link[resource] switch updates the manifest’s ManifestResourceDef and FileDef tables, indicating that the resource exists and identifying which of the assembly’s files contain it. The resource file is not embedded into the assembly PE file; it remains separate and must be packaged and deployed with the other assembly files. Like AL.exe, CSC.exe also allows you to combine resources into an assembly produced by the C# compiler. The C# compiler’s /resource switch embeds the specified resource file into the resulting assembly PE file, updating the ManifestResourceDef table. The compiler’s /linkresource switch adds an entry to the ManifestResourceDef and the FileDef manifest tables to refer to a stand-alone resource file. One last note about resources: it’s possible to embed standard Win32 resources into an assembly. You can do this easily by specifying the pathname of a .res file with the /win32res switch when using either AL.exe or CSC.exe. In addition, you can quickly and easily embed a standard Win32 icon resource into an assembly file by specifying the pathname of an .ico file with the /win32icon switch when using either AL.exe or CSC.exe. The typical reason that an icon is embedded is so that Explorer can show an icon for a managed executable file. Assembly Version Resource Information When AL.exe or CSC.exe produces a PE file assembly, it also embeds into the PE file a standard Win32 Version resource. Users can examine this resource by viewing the file’s properties. Figure 2-4 shows the Version page of the JeffTypes.dll Properties dialog box. Figure 2-4 : Version tab of the JeffTypes.dll Properties dialog box In addition, you can use the resource editor in Visual Studio .NET, shown in Figure 2-5, to view/modify the version resource fields. Figure 2-5 : Resource editor in Visual Studio .NET When building an assembly, you should set the version resource fields using custom attributes that you apply at the assembly level in your source code. Here’s what the code that produced the version information in Figure 2-5 looks like: using System.Reflection; // Set the version CompanyName, LegalCopyright & LegalTrademarks fields [assembly:AssemblyCompany("The Jeffrey Richter Company")] [assembly:AssemblyCopyright("Copyright (c) 2002 Jeffrey Richter")] [assembly:AssemblyTrademark( "JeffTypes is a registered trademark of the Richter Company")] // Set the version ProductName & ProductVersion fields [assembly:AssemblyProduct("Jeffrey Richter Type Library")] [assembly:AssemblyInformationalVersion("")] // Set the version FileVersion, AssemblyVersion, // FileDescription, and Comments fields [assembly:AssemblyFileVersion("")] [assembly:AssemblyVersion("")] [assembly:AssemblyTitle("Jeff’s type assembly")] [assembly:AssemblyDescription("This assembly contains Jeff’s types")] // Set the culture (discussed later in the "Culture" section) [assembly:AssemblyCulture("")] Table 2-4 shows the version resource fields and the custom attributes that correspond to them. If you’re using AL.exe to build your assembly, you can use command-line switches to set this information instead of using the custom attributes. The second column in Table 2-4 shows the AL.exe command-line switch that corresponds to each version resource field. Note that the C# compiler doesn’t offer these command-line switches and that, in general, using custom attributes is the preferred way to set this information. Table 2-4: Version Resource Fields and Their Corresponding AL.exe Switches and Custom Attributes Version Resource AL.exe Switch Custom Attribute/Comment FILEVERSION /fileversion System.Reflection.AssemblyFileVersionAttribute FileVersionAttribute PRODUCTVERSION /productversion System.Reflection.AssemblyInformationalVersionAttribute FILEFLAGSMASK (none) Always set to VS_FFI_FILEFLAGSMASK (defined in WinVer.h as 0x0000003F) FILEFLAGS (none) Always 0 FILEOS (none) Currently always VOS__WINDOWS32 FILETYPE /target Set to VFT_APP if /target:exe or /target:winexe is specified; set to VFT_DLL if /target:library is specified FILESUBTYPE (none) Always set to VFT2_UNKNOWN (This field has no meaning for VFT_APP and VFT_DLL.) AssemblyVersion /version System.Reflection.Assembly-VersionAttribute Comments /description System.Reflection.Assembly-DescriptionAttribute CompanyName /company System.Reflection.AssemblyCompanyAttribute FileDescription /title System.Reflection.AssemblyTitleAttribute FileVersion /version System.Reflection.AssemblyVersionAttribute InternalName /out Set to the name of the output file specified (without the extension) LegalCopyright /copyright System.Reflection.AssemblyCopyrightAttribute LegalTrademarks /trademark System.Reflection.AssemblyTrademarkAttribute OriginalFilename /out Set to the name of the output file (without a path) PrivateBuild (none) Always blank ProductName /product System.Reflection.AssemblyProductAttribute ProductVersion /productversion System.Reflection.AssemblyInformationalVersionAttribute SpecialBuild (none) Always blank Important When you create a new C# project in Visual Studio .NET, an AssemblyInfo.cs file is automatically created for you. This file contains all the assembly attributes described in this section plus a few additional attributes that I’ll cover in Chapter 3. You can simply open the AssemblyInfo.cs file and modify your assembly-specific information. The file that Visual Studio .NET creates for you has some problems that I’ll go over later in this chapter. In a real production project, you must modify the contents of this file. project, you must modify the contents of this file. Version Numbers In the previous section, you saw that several version numbers can be applied to an assembly. All these version numbers have the same format: each consists of four period- separated parts, as shown in Table 2-5. Table 2-5: Format of Version Numbers Part Major Number Minor Number Build Number Revision Number Example: 2 5 719 2 Table 2-5 shows an example of a version number: 2.5.719.2. The first two numbers make up the “public perception” of the version. The public will think of this example as version 2.5 of the assembly. The third number, 719, indicates the build of the assembly. If your company builds its assembly every day, you should increment the build number each day as well. The last number, 2, indicates the revision of the build. If for some reason your company has to build an assembly twice in one day, maybe to resolve a hot bug that is halting other work, then the revision number should be incremented. Microsoft uses this version-numbering scheme, and it’s simply a recommendation; you’re welcome to devise your own number-versioning scheme if you prefer. The only assumption the CLR makes is that bigger numbers indicate later versions. You’ll notice that an assembly has three version numbers associated with it. This is very unfortunate and leads to a lot of confusion. Let me explain each version number’s purpose and how it is expected to be used: § AssemblyFileVersion This version number is stored in the Win32 version resource. This number is informational only; the CLR doesn’t examine or care about this version number in any way. Typically, you set the major and minor parts to represent the version you want the public to see. Then you increment the build and revision parts each time a build is performed. Ideally, Microsoft’s tool (such as CSC.exe or AL.exe) would automatically update the build and revision numbers for you (based on the data/time when the build was performed), but unfortunately they don’t. This version number can be seen when using Windows Explorer and is used to determine exactly when an assembly file was built. § AssemblyInformationalVersionAttribute This version number is also stored in the Win32 version resource, and again, this number is informational only; the CLR doesn’t examine or care about it in any way. This version number exists to indicate the version of the product that includes this assembly. For example, Version 2.0 of MyProduct might contain several assemblies; one of these assemblies is marked as Version 1.0 since it’s a new assembly that didn’t ship in Version 1.0 of MyProduct. Typically, you set the major and minor parts of this version number to represent the public version of your product. Then you increment the build and revision parts each time you package a complete product with all its assemblies. § AssemblyVersion This version number is stored in the AssemblyDef manifest metadata table. The CLR uses this version number when binding to strongly named assemblies (discussed in Chapter 3). This number is extremely important and is used to uniquely identify an assembly. When starting to develop an assembly, you should set the major, minor, build, and revision numbers and shouldn’t change them until you’re ready to begin work on the next deployable version of your assembly. When you build an assembly, this version number in the referenced assembly is embedded in the AssemblyRef table’s entry. This means that an assembly is tightly bound to a specific version of a reference assembly. Important The CSC.exe and AL.exe tools support the ability to automatically increment the assembly version number with each build. This feature is a bug and shouldn’t be used because changing the assembly version number will break any assemblies that reference this assembly. The AssemblyInfo.cs file that Visual Studio .NET automatically creates for you when you create a new project is in error: it sets the AssemblyVersion attribute so that its major and minor parts are 1.0 and that the build and revision parts are automatically updated by the compiler. You should definitely modify this file and hard-code all four parts of the assembly version number. Culture Like version numbers, assemblies also have a culture as part of their identity. For example, I could have an assembly that is strictly for German, another assembly for Swiss German, another assembly for U.S. English, and so on. Cultures are identified via a string that contains a primary and a secondary tag (as described in RFC1766). Table 2-6 shows some examples. Table 2-6: Examples of Assembly Culture Tags Primary Tag Secondary Tag Culture de (none) German de AT Austrian German de CH Swiss German en (none) English en GB British English en US U.S. English In general, if you create an assembly that contains code, you don’t assign a culture to it. This is because code doesn’t usually have any culture-specific assumptions built into it. An assembly that isn’t assigned a culture is referred to as being culture neutral. If you’re designing an application that has some culture-specific resources to it, Microsoft highly recommends that you create one assembly that contains your code and your application’s default (or fallback) resources. When building this assembly, don’t specify a specific culture. This is the assembly that other assemblies will reference to create and manipulate types. Now you can create one or more separate assemblies that contain only culture-specific resources—no code at all. Assemblies that are marked with a culture are called satellite assemblies. For these satellite assemblies, assign a culture that accurately reflects the culture of the resources placed in the assembly. You should create one satellite assembly for each culture you intend to support. You’ll usually use the AL.exe tool to build a satellite assembly. You won’t use a compiler because the satellite assembly should have no code contained within it. When using AL.exe, you specify the desired culture using the /c[ulture]:text switch, where text is a string such as "en-US" representing U.S. English. When you deploy a satellite assembly, you should place it in a subdirectory whose name matches the culture text. For example, if the application’s base directory is C:\MyApp, then the U.S. English satellite assembly should be placed in the C:\MyApp\en-US subdirectory. At runtime, you access a satellite assembly’s resources using the System.Resources.ResourceManager class. Note Although discouraged, it is possible to create a satellite assembly that contains code. If you prefer, you can specify the culture using the System.Reflection.AssemblyCultureAttribute custom attribute instead of using AL.exe’s /culture switch, for example, as shown here: // Set assembly’s culture to Swiss German [assembly:AssemblyCulture("de-CH")] Normally, you shouldn’t build an assembly that references a satellite assembly. In other words, an assembly’s AssemblyRef entries should all refer to culture-neutral assemblies. If you want to access types or members contained in a satellite assembly, you should use reflection techniques as discussed in Chapter 20. Simple Application Deployment (Privately Deployed Assemblies) Throughout this chapter, I’ve explained how you build modules and how you combine those modules into an assembly. At this point, I’m ready to package and deploy all the assemblies so that users can run the application. Assemblies don’t dictate or require any special means of packaging. The easiest way to package a set of assemblies is simply to copy all the files directly. For example, you could put all the assembly files on a CD-ROM disk and ship it to the user with a batch file setup program that just copies the files from the CD to a directory on the user’s hard drive. Because the assemblies include all the dependant assembly references and types, the user can just run the application and the runtime will look for referenced assemblies in the application’s directory. No modifications to the registry or to Active Directory are necessary for the application to run. To uninstall the application, just delete all the files—that’s it! Of course, you can package and install the assembly files using other mechanisms, such as .cab files (typically used for Internet download scenarios to compress files and reduce download times). You can also package the assembly files into an MSI file for use by the Windows Installer service (MSIExec.exe). Using MSI allows assemblies to be installed on demand the first time the CLR attempts to load the assembly. This feature isn’t new to MSI; it can perform the same demand-load functionality for unmanaged EXE and DLL files as well. Note Using a batch file or some other simple “installation software” will get an application onto the user’s machine; however, you’ll need more sophisticated installation software to create shortcut links on the user’s desktop, Start menu, and Quick Launch toolbar. Also, you can easily back up and restore the application or move it from one machine to another, but the various shortcut links will require special handling. Future versions of Windows may improve this story. Assemblies that are deployed to the same directory as the application are called privately deployed assemblies because the assembly files aren’t shared with any other application (unless it’s also deployed to the same directory). Privately deployed assemblies are a big win for developers, end-users, and administrators because they can simply be copied to an application’s base directory and the CLR will load them and execute the code in them. In addition, an application can be uninstalled by simply deleting the assemblies in its directory. This allows simple backup and restore to work as well. This simple install/move/uninstall story is possible because each assembly has metadata indicating which referenced assembly should be loaded; no registry settings or Active Directory settings are required. In addition, the referencing assembly scopes every type. This means that an application always binds to the exact type that it was built and tested with; the CLR can’t load a different assembly that just happens to provide a type with the same name. This is different from COM, where types are recorded in the registry, making them available to any application running on the machine. In Chapter 3, I’ll discuss how to deploy shared assemblies that are accessible by multiple applications. Simple Administrative Control (Configuration) The user or the administrator can best determine some aspects of an application’s execution. For example, an administrator might decide to move an assembly’s files on the user’s hard disk or to override information contained in the assembly’s manifest. Other scenarios also exist related to versioning and remoting; I’ll talk about some of these in Chapter 3. To allow administrative control over an application, a configuration file can be placed in the application’s directory. An application’s publisher can create and package this file. The setup program would then install this configuration file in the application’s base directory. In addition, the machine’s administrator or an end-user could create or modify this file. The CLR interprets the content of this file to alter its policies for locating and loading assembly files. These configuration files contain XML and can be associated with an application or with the machine. Using a separate file (vs. registry settings) allows the file to be easily backed up and also allows the administrator to copy the application to another machine: just copy the necessary files and the administrative policy is copied too. In Chapter 3, we’ll explore this configuration file in more detail. But I want to give you a taste of it now. Let’s say that the publisher of an application wants its application deployed with the JeffTypes assembly files in a different directory than the application’s assembly file. The desired directory structure looks like this: AppDir directory (contains the application’s assembly files) App.exe App.exe.config (discussed below) AuxFiles subdirectory (contains JeffTypes’ assembly files) JeffTypes.dll FUT.netmodule RUT.netmodule Since the JeffTypes files are no longer in the application’s base directory, the CLR won’t be able to locate and load these files; running the application will cause a System.IO.FileNotFoundException exception to be thrown. To fix this, the publisher creates an XML configuration file and deploys it to the application’s base directory. The name of this file must be the name of the application’s main assembly file with a .config extension: App.exe.config, for this example. This configuration file should look like this: Whenever the CLR attempts to locate an assembly file, it always looks in the application’s directory first, and if it can’t find the file there, it looks in the AuxFiles subdirectory. You can specify multiple semicolon-delimited paths for the probing element’s privatePath attribute. Each path is considered relative to the application’s base directory. You can’t specify an absolute or a relative path identifying a directory that is outside the application’s base directory. The idea is that an application can control its directory and its subdirectories but has no control over other directories. By the way, you can write code that opens and parses the information contained in a configuration file. This allows your application to define settings that an administrator or a user can create and persist in the same file as all the application’s other settings. You use the classes defined in the System.Configuration namespace to manipulate a configuration file at runtime. The name and location of this XML configuration is different depending on the application type. Probing for Assembly Files When the CLR needs to locate an assembly, it scans several subdirectories. Here is the order in which directories are probed for a culture-neutral assembly: AppBase\AsmName.dll AppBase\AsmName\AsmName.dll AppBase\privatePath1\AsmName.dll AppBase\privatePath1\AsmName\AsmName.dll AppBase\privatePath2\AsmName.dll AppBase\privatePath2\AsmName\AsmName.dll ? In the example above, no configuration file would be needed if the JeffTypes assembly files were deployed to a subdirectory called JeffTypes since the CLR would automatically scan for a subdirectory whose name matches the name of the assembly being searched for. If the assembly can’t be found in any of the preceding subdirectories, the CLR starts all over, using an .exe extension instead of a .dll extension. If the assembly still can’t be found, a FileNotFoundException is thrown. For satellite assemblies, the same rules are followed except that the assembly is expected to be in a subdirectory of the application base directory whose name matches the culture. For example, if AsmName.dll has a culture of “en-US” applied to it, the following directories are probed: AppBase\en-US\AsmName.dll AppBase\en-US\AsmName\AsmName.dll AppBase\en-US\privatePath1\AsmName.dll AppBase\en-US\privatePath1\AsmName\AsmName.dll AppBase\en-US\privatePath2\AsmName.dll AppBase\en-US\privatePath2\AsmName\AsmName.dll ? Again, if the assembly can’t be found in any of the subdirectories listed here, the CLR checks the same set of assemblies looking for an .exe file instead of a .dll file. § For executable applications (EXEs), the configuration file must be in the application’s base directory and it must be the name of the EXE file with “.config” appended to it. § For ASP.NET Web Forms and XML Web service applications, the file must be in the Web application’s virtual root directory and is always named Web.config. In addition, subdirectories can also contain their own Web.config file and the configuration settings are inherited. For example, a Web application located at http://www.Wintellect.com/Training would use the settings in the Web.config files contained in the virtual root directory and in its Training subdirectory. § For assemblies containing client-side controls hosted by Microsoft Internet Explorer, the HTML page must contain a link tag whose rel attribute is set to "Configuration" and whose href attribute is set to the URL of the configuration file, which can be given any name. Here’s an example: For more information see the .NET Framework documentation. As mentioned at the beginning of this section, configuration settings apply to a particular application and to the machine. When you install the .NET Framework, it creates a Machine.config file. There is one Machine.config file per version of the CLR you have installed on the machine. In the future, it will be possible to have multiple versions of the .NET Framework installed on a single machine simultaneously. The Machine.config file is located in the following directory: C:\WINDOWS\Microsoft.NET\Framework\version\CONFIG Of course, C:\WINDOWS identifies your Windows directory, and version is a version number identifying a specific version of the .NET Framework. Settings in the Machine.config file override settings in an application-specific configuration file. An administrator can create a machine-wide policy by modifying a single file. Normally, administrators and users should avoid modifying the Machine.config file because this file has many settings related to various things, making it much more difficult to navigate. Plus, you want the application’s settings to be backed up and restored, and keeping an application’s settings in the application-specific configuration file enables this. Because editing an XML configuration file is a little unwieldy, Microsoft’s .NET Framework team produced a GUI tool to help. The GUI tool is implemented as a Microsoft Management Console (MMC) snap-in, which means that it isn’t available when running on a Windows 98, Windows 98 Standard Edition, or Windows Me machine. You can find the tool by opening Control Panel, selecting Administrative Tools, and then selecting the Microsoft .NET Framework Configuration tool. In the window that appears, you can traverse the tree’s nodes until you get to the Applications node, as shown in Figure 2-6. Figure 2-6 : Applications node of the Microsoft .NET Framework Configuration tool From the Applications node, you can select the Add An Application To Configure link that appears in the right-hand pane. This will invoke a wizard that will prompt you for the pathname of the executable file you want to create an XML configuration file for. After you’ve added an application, you can also use this to alter its configuration file. Figure 2-7 shows the tasks that you can perform to an application. Figure 2-7 : Configuring an application using the Microsoft .NET Framework Configuration tool I’ll discuss configuration files a bit more in Chapter 3. Chapter 3: Shared Assemblies Overview In Chapter 2, I talked about the steps required to build, package, and deploy an assembly. I focused on what’s called private deployment, where assemblies are placed in the application’s base directory (or a subdirectory thereof) for the application’s sole use. Deploying assemblies privately gives a company a large degree of control over the naming, versioning, and behavior of the assembly. In this chapter, I’ll concentrate on creating assemblies that multiple applications can access. The assemblies that ship with the Microsoft .NET Framework are an excellent example of globally deployed assemblies because almost all managed applications use types defined by Microsoft in the .NET Framework Class Library (FCL). As I mentioned in Chapter 2, Windows has a reputation for being unstable. The main reason for this reputation is the fact that applications are built and tested using code implemented by someone else. After all, when you write an application for Windows, your application is calling into code written by Microsoft developers. Also, a large number of companies out there make controls that application developers can incorporate into their own applications. In fact, the .NET Framework encourages this, and lot more control vendors will likely pop up over time. As time marches on, Microsoft developers and control developers modify their code: they fix bugs, add features, and so on. Eventually, the new code makes its way onto the user’s hard disk. The user’s applications that were previously installed and working fine are no longer using the same code that the applications were built and tested with. As a result, the applications’ behavior is no longer predictable, which contributes to the instability of Windows. File versioning is a very difficult problem to solve. In fact, I assert that if you take a file and change just one bit in the file from a 0 to a 1 or from a 1 to a 0, there’s absolutely no way to guarantee that code that used the original file will now work just as well if it uses the new version of the file. One of the reasons why this statement is true is that a lot of applications exploit bugs—either knowingly or unknowingly. If a later version of a file fixes a bug, the application no longer runs as expected. So here’s the problem: how do you fix bugs and add features to a file and also guarantee that you don’t break some application? I’ve given this question a lot of thought and have come to one conclusion: it’s just not possible. But, obviously, this answer isn’t good enough. Files will ship with bugs, and developers will always want to add new features. There must be a way to distribute new files with the hope that the applications will work just fine. And if the application doesn’t work fine, there has to be an easy way to restore the application to its “last-known good state.” In this chapter, I’ll explain the infrastructure that the .NET Framework has in place to deal with versioning problems. Let me warn you: what I’m about to describe is complicated. I’m going to talk about a lot of algorithms, rules, and policies that are built into the common language runtime (CLR). I’m also going to mention a lot of tools and utilities that the application developer must use. This stuff is complicated because, as I’ve mentioned, the versioning problem is difficult to address and to solve. Two Kinds of Assemblies, Two Kinds of Deployment The .NET Framework supports two kinds of assemblies: weakly named assemblies and strongly named assemblies. Important By the way, you won’t find the term weakly named assembly in any of the .NET Framework documentation. Why? Because I made it up. In fact, the documentation has no term to identify a weakly named assembly. I decided to coin the term so that I can talk about assemblies without any ambiguity as to what kind of assembly I’m referring to. Weakly named assemblies and strongly named assemblies are structurally identical—that is, they use the same portable executable (PE) file format, PE header, CLR header, metadata, and manifest tables that we examined in Chapter 2. And you use the same tools, such as the C# compiler and AL.exe, to build both kinds of assemblies. The real difference between weakly named and strongly named assemblies is that a strongly named assembly is signed with a publisher’s public/private key pair that uniquely identifies the assembly’s publisher. This key pair allows the assembly to be uniquely identified, secured, and versioned, and it allows the assembly to be deployed anywhere on the user’s hard disk or even on the Internet. This ability to uniquely identify an assembly allows the CLR to enforce certain “known to be safe” policies when an application tries to bind to a strongly named assembly. This chapter is dedicated to explaining what strongly named assemblies are and what policies the CLR applies to them. An assembly can be deployed in two ways: privately or globally. A privately deployed assembly is an assembly that is deployed in the application’s base directory or one of its subdirectories. A weakly named assembly can be deployed only privately. I talked about privately deployed assemblies in Chapter 2. A globally deployed assembly is an assembly that is deployed into some well-known location that the CLR knows to look in when it’s searching for an assembly. A strongly named assembly can be deployed privately or globally. I’ll explain how to create and deploy strongly named assemblies in this chapter. Table 3-1 summarizes the kinds of assemblies and the ways that they can be deployed. Table 3-1: How Weakly and Strongly Named Assemblies Can Be Deployed Kind of Assembly Can Be Privately Deployed? Can Be Globally Deployed? Weakly named Yes No Strongly named Yes Yes Giving an Assembly a Strong Name If multiple applications are going to access an assembly, the assembly must be placed in a well-known directory and the CLR must know to look in this directory automatically when a reference to the assembly is detected. However, we have a problem: two (or more) companies could produce assemblies that have the same filename. Then, if both of these assemblies get copied into the same well-known directory, the last one installed wins and all the applications that were using the old assembly no longer function as desired. (This is exactly why DLL hell exists today in Windows.) Obviously, differentiating assemblies simply by using a filename isn’t good enough. The CLR needs to support some mechanism that allows assemblies to be uniquely identified. This is what the term strongly named assembly refers to. A strongly named assembly consists of four attributes that uniquely identify the assembly: a filename (without an extension), a version number, a culture identity, and a public key token (a value derived from a public key). The following strings identify four completely different assembly files: "MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b77a5c5619 34e089" "MyTypes, Version=1.0.8123.0, Culture="en- US", PublicKeyToken=b77a5c561934e089" "MyTypes, Version=2.0.1234.0, Culture=neutral, PublicKeyToken=b77a5c5619 34e089" "MyTypes, Version=1.0.8123.0, Culture=neutral, PublicKeyToken=b03f5f7f11d 50a3a" The first string identifies an assembly file called MyTypes.dll. The company producing the assembly is creating version 1.0.8123.0 of this assembly, and nothing in the assembly is sensitive to any one culture because Culture is set to neutral. Of course, any company could produce a MyTypes.dll assembly that is marked with a version number of 1.0.8123.0 and a neutral culture. There must be a way to distinguish this company’s assembly from another company’s assembly that happens to have the same attributes. For several reasons, Microsoft chose to use standard public/private key cryptographic technologies instead of any other unique identification technique, such as GUIDs, URLs, or URNs. Specifically, cryptographic techniques provide a way to check the integrity of the assembly’s bits as they are installed on a hard drive, and they also allow permissions to be granted on a per-publisher basis. I’ll discuss these techniques more later in this chapter. So, a company that wants to uniquely mark its assemblies must acquire a public/private key pair. Then the public key can be associated with the assembly. No two companies should have the same public/private key pair, and this distinction is what allows two companies to create assemblies that have the same name, version, and culture without causing any conflict. Note The System.Reflection.AssemblyName class is a helper class that makes it easy for you to build an assembly name and to obtain the various parts of an assembly’s name. The class offers several public instance properties, such as CultureInfo, FullName, KeyPair, Name, and Version. The class also offers a few public instance methods, such as GetPublicKey, GetPublicKeyToken, SetPublicKey, and SetPublicKeyToken. In Chapter 2, I showed you how to name an assembly file and how to apply an assembly version number and a culture. A weakly named assembly can have assembly version and culture attributes embedded in the manifest metadata; however, the CLR always ignores the version number and uses only the culture information when it’s probing subdirectories looking for the satellite assembly. Because weakly named assemblies are always privately deployed, the CLR simply uses the name of the assembly (tacking on a .dll or an .exe extension) when searching for the assembly’s file in the application’s base directory or any of the subdirectories specified in the XML configuration file’s probing element’s privatePath attribute. A strongly named assembly has a filename, an assembly version, and a culture. In addition, a strongly named assembly is signed with the publisher’s private key. The first step in creating a strongly named assembly is to obtain a key by using the Strong Name Utility, SN.exe, that ships with the .NET Framework SDK and Visual Studio .NET. This utility offers a whole slew of features depending on the command-line switch you specify. Note that all SN.exe’s command-line switches are case-sensitive. To generate a public/private key pair, you run SN.exe as follows: SN Ðk MyCompany.keys This line tells SN.exe to create a file called MyCompany.keys. This file will contain the public and private key numbers persisted in a binary format. Public key numbers are very big. If you want to see them, you can execute this command line: SN Ðtp MyCompany.keys When I execute this line, I get the following output: Microsoft (R) .NET Framework Strong Name Utility Version 1.0.3210.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Public key is 070200000024000052534132000400000100010031f38d3b2e55454ed52c5d2469 11011be59543 878d99e7da35c8bca8b714a96010572ca8ad63b9a1ea20f62036d79f250c86bbb3b 85eb52785a8 7b543a068d9563c9b6db5bbc33898248d8a8cd7476a006b1977ce0c41ba502147d 53e51ce06104 836dd392b85aac991d36884e20409de4aa362de46bd00ff043e012b57878c981647 e3deec439c5 087d60e978d972663e7c7b28ab7236aab2ae686bfc7c1eda062d4027bdfed92ef5cc 93d1138047 20c91abbe5a88ccca87f8b6751cafecee8b17657cdaef038568a9bf59ccefd056d971 f9e839564 3849384688ebeb6b6b4fda9e8dc95606af700244923c822bafcee7dfe6606580bb12 5277fff941 4e8add01daacc5189209437cf2df24f5a3b8b463d37f059aa1dca6183460103912f2 5bc5304f01 4bcecff1bf1f50ca24c57f42eb885ed18834be32317357f33e8809abd1cd820847d3 65b7bf62c6 f1799fd1f3fa726e355a7eccf111f0f7a64a3d2e8cd83375a523d5fb99eb55c4abf59e c5ce571c c6d4eb0eafa9891e19a94b3a264b64f83fa8dd3dbb3ffbfa2798c0f07c76d624a0d31 f2ac0e536 80b021356f575ae4bf6f2ed794805ef29723261dcd5faace2f42f821f5b1fb6fad133 1d30c621e 01187fce0b3067f409239f8b40fca884793b47bade292c1509c1169bb09c96803f27 0bdad9c8a8 ff8b9a6cf10025b53509b615623accd7a5f90641dd234b6537f7bb6215236639d81 16569755817 308efaf043a627060191d0072a1eacadcb646ca23c13bef498cff88b3c0f49298446a caaabe62e 8b95326fea73ef1783b073 Public key token is 4da24326b8a214c7 The size of public keys makes them difficult to work with. To make things easier for the developer (and for end-users too), public key tokens were created. A public key token is a 64-bit hash of the public key. SN.exe’s Ðtp switch shows the public key token that corresponds to the complete public key at the end of its output. Now that you know how to create a public/private key pair, creating a strongly named assembly is simple. You just apply an instance of the System.Reflection.AssemblyKeyFileAttribute attribute to your source code: [assembly:AssemblyKeyFile("MyCompany.keys")] When a compiler sees this attribute in your source code, the compiler opens the specified file (MyCompany.Keys), signs the assembly with the private key, and embeds the public key in the manifest. Note that you sign only the assembly file that contains the manifest; the assembly’s other files can’t be signed explicitly. Here’s what it means to sign a file: When you build a strongly named assembly, the assembly’s FileDef manifest metadata table includes the list of all the files that make up the assembly. As each file’s name is added to the manifest, the file’s contents are hashed and this hash value is stored along with the file’s name in the FileDef table. You can override the default hash algorithm used with AL.exe’s /algid switch or the assembly level System.Reflection.Assembly-AlgIDAttribute custom attribute. By default, a SHA- 1 algorithm is used, and this should be sufficient for almost all applications. After the PE file containing the manifest is built, the PE file’s entire contents are hashed, as shown in Figure 3-1. The hash algorithm used here is always SHA-1 and can’t be overridden. This hash value—typically around 100 or 200 bytes in size—is signed with the publisher’s private key, and the resulting RSA digital signature is stored in a reserved section (not included in the hash) within the PE file. The CLR header of the PE file is updated to reflect where the digital signature is embedded within the file. Figure 3-1 : Signing an assembly The publisher’s public key is also embedded into the AssemblyDef manifest metadata table in this PE file. The combination of the filename, the assembly version, the culture, and the public key gives this assembly a strong name, which is guaranteed to be unique. There is no way that two companies could produce a “Calculus” assembly with the same public key (assuming that the companies don’t share this key pair with each other). At this point, the assembly and all its files are ready to be packaged and distributed. As described in Chapter 2, when you compile your source code, the compiler detects the types and members that your code references. You must specify the referenced assemblies to the compiler. For the C# compiler, you use the /reference command-line switch. Part of the compiler’s job is to emit an AssemblyRef metadata table inside the resulting managed module. Each entry in the AssemblyRef metadata table indicates the referenced assembly’s name (without path and extension), version number, culture, and public key information. Important Because public keys are such large numbers and a single assembly might reference many assemblies, a large percentage of the resulting file’s total size would be occupied with public key information. To conserve storage space, Microsoft hashes the public key and takes the last 8 bytes of the hashed value. This reduced value has been determined to be statistically unique and is therefore safe to pass around the system. These reduced public key values—known as public key tokens—are what are actually stored in an AssemblyRef table. In general, developers and end-users will see public key token values much more frequently than full public key values. Following is the AssemblyRef metadata information for the JeffTypes.dll file that I discussed in Chapter 2: AssemblyRef #1 ——————————————————————————— Token: 0x23000001 Public Key or Token: b7 7a 5c 56 19 34 e0 89 Name: mscorlib Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: 0x00000c1e Revision Number: 0x00000000 Locale: HashValue Blob: 3e 10 f3 95 e3 73 0b 33 1a 4a 84 a7 81 76 eb 32 4b 36 4d a5 Flags: [none] (00000000) From this, you can see that JeffTypes.dll references a type that is contained in an assembly matching the following attributes: "MSCorLib, Version=1.0.3300.0, Culture=neutral, PublicKeyToken=b77a5c561 934e089" Unfortunately, ILDasm.exe uses the term Locale when it really should be using Culture instead. Microsoft says that they’ll fix the term in a future version of the tool. If you look at JeffTypes.dll’s AssemblyDef metadata table, you see the following: Assembly ——————————————————————————— Token: 0x20000001 Name : JeffTypes Public Key : Hash Algorithm : 0x00008004 Major Version: 0x00000001 Minor Version: 0x00000000 Build Number: 0x00000253 Revision Number: 0x00005361 Locale: Flags : [SideBySideCompatible] (00000000) This is equivalent to the following: "JeffTypes, Version=1.0.595.21345, Culture=neutral, PublicKeyToken=null" In this line, no public key token is specified because in Chapter 2 the JeffTypes.dll assembly wasn’t signed with a public key, making it a weakly named assembly. If I had used SN.exe to create a key file, added the AssemblyKeyFile-Attribute to the source code, and then recompiled, the resulting assembly would be signed. If you’re using AL.exe to build the assembly, you specify its /keyfile switch instead of using the AssemblyKeyFileAttribute. If I had used ILDasm.exe to explore the new assembly’s metadata, the AssemblyDef entry would have bytes appearing after the Public Key field and the assembly would be strongly named. By the way, the AssemblyDef entry always stores the full public key, not the public key token. The full public key is necessary to ensure that the file hasn’t been tampered with. I’ll explain the tamper resistance of strongly named assemblies later in this chapter. The Global Assembly Cache Now that you know how to create a strongly named assembly, it’s time to learn how to deploy this assembly and how the CLR uses the information to locate and load the assembly. If an assembly is to be accessed by multiple applications, the assembly must be placed into a well-known directory and the CLR must know to look in this directory automatically when a reference to the assembly is detected. This well-known location is called the global assembly cache (GAC), which can usually be found in the following directory: C:\Windows\Assembly\GAC The GAC directory is structured: it contains many subdirectories, and an algorithm is used to generate the names of these subdirectories. You should never manually copy assembly files into the GAC; instead, you should use tools to accomplish this task. These tools know the GAC’s internal structure and how to generate the proper subdirectory names. While developing and testing, the most common tool for installing a strongly named assembly into the GAC is GACUtil.exe. Running this tool without any command-line arguments yields the following usage: Microsoft (R) .NET Global Assembly Cache Utility. Version 1.0.3415.0 Copyright (C) Microsoft Corporation 1998-2001. All rights reserved. Usage: Gacutil