COM runtime offers no intrinsic support to enforce that the correct vesion of a COM binary is loaded for the calling client. All tasks related to versioning are left to the programmer.
In contrast, .NET allows multiple versions of the same binary to be installed on the same machine, If client A demands MyServer.dll version 1.0 while client B demands MyServer.dll version 2.0, the correct version from the same binary is loaded for the respective client immediately.
The relation between the binary file and the correct registry entries is very loose. If the file was moved or renamed, the entire system breaks. Also, if a DLL is installed on a remote server machine and that machine is accessed by 100 clients, then 101 machines must be configured correctly.
In contrast, .NET makes deployment very easy given the fact that .NET binaries are not registered in the system registry at all. Deploying a .NET application is as simple as copying the files that compose the application to some location on the machine and running the application.
A .NET application is a collection of assemblies. An assembly is a versioned, self-describing binary (DLL or EXE) containing collection of types (classes, structures, etc) and optional resources (images, string tables, etc.). .NET DLLs only require one function export, DllMain().
From Basics section , recall that assemblies are .NET binaries that contain IL, metadata, and manifest data. The following points summarize assemblies
/* C++: #using is similar to #import
but it does more. It does the work of the following lines for an equivalent
unmanaged C++ application:
#include <windows.h>
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "kernel.lib")
In other words, the using line imports the description of types from an assembly
so that the compiler can perform compile-time checking. In addition, it instructs
the compiler to add tables to the final exe that identify the libraries the code
will use (equivalent to the linker instructions passed through the #pragma
comment directive) */
#using <mscorlib.dll>
/* this tells C++ that when you use the types in the
System namespace, you do not want to full qualify the type name with its
namespace */
using namespace System;
Recall that a single file assembly contains all the necessary IL, metadata, and associated manifest in a single well-define package, whereas multi-file assemblies are composed of numerous .NET binaries, each of which is termed a module:
A multi-file assembly is an assembly composed of multiple files. A multi-file assembly encourages efficient code download. Assume a multi file assembly is composed of 2 DLLs and a remote client references only one of those DLLs.. The .NET runtime will only download the required DLL (imagine if each DLL was 2 MB in size)
Recall that A multi-file assembly contains more than one DLL that are logically related by information contained in the corresponding manifest. From the above figure, a multi file assemble contain a single manifest that may be placed in a stand-alone file but is more typically bundled into one of the related modules. The following figure also shows a sample schematic of a multifile assembly:
An assembly called one is made up of four separate files - one.dll, two.netmodule, three.netmodule, and four.jpg. The main file is one.dll which contains the manifest for the entire assembly. The short name of the assembly is the name of this file without the extension, so this is assembly one. Note that the assembly manifest indicates that the assembly one is made up of three other files beside one.dll. In addition, the manifest has references to two other assemblies, five.dll and mscorlib.dll. Finally, the assembly one uses localize resources in satellite assemblies. These are resource only assemblies, one of which which contains the right locale, is loaded at runtime by an object called ResourceManager.
Note that if assembly one is downloaded over the network, the physical file one.dll will be downloaded, but not necessarily the other modules. Only if your code refers to one of the other modules in assembly one or uses resources, will these files be downloaded.
.NET provides several way to determine where code is located, and it even
allows you to change the location through configuration files. A module cannot
be loaded by itself, it needs to be loaded only as part of an assembly. To
create a module in C# use /targert:module switch
or if in C++ pass the /NOASSEMBLY switch to the
linker. To create an assembly, you need to link all the modules together. In C#
you can use /target:library or /target:exe
or /target:winexe. In C++, it is similar
except you have to inform the linker with the /DLL
switch to create a library assembly or you can use /SUBSYSTEM
with CONSOLE or WINDOWS.
Multimodule assemblies are a great way of streamlining how your code will be
distributed. Unfortunately, Visual Studi.NET does not support multimodule
assemblies. You will have to write your own make files.
Conceptually, an assembly can be have two views. Physically, an assembly is a collection of files. Logically, as assembly is a collection of types.
For example, System.Drawing.dll can be viewed logically as a binary DLL. Viewed logically, it is a collection of related types. ILDasm.exe is the ideal tool to discover the logical layout of an assembly
The compiler for C# or linker for C++ allows you to determine the main entry point of an assembly. The following table shows the types of assemblies you can create:
Type | C++ | C# |
---|---|---|
GUI | Define WinMain() and use System.Windows.Forms classes | Use /t:winexe and define a class with a Main() method and use System.Windows.Forms classes |
Console | Define main() | Use /t:exe and define a class with a main() method |
NT Services | Define WinMain() and use System.ServiceProcess.ServiceBase classes | Use /t:winexe and System.ServiceProcess.ServiceBase classes |
ASP.NET | Apply the /LD switch and use System.Web.UI classes | Use /t:library and use System.Web.UI classes |
Web Services | Apply the /LD switch and use System.Web.Services classes | Use /t:library and use System.Web.WebServices classes |
Library | Apply the /LD switch | Use /t:library |
Whatever you can do in C# you can do in C++. The difference it too support. C# is accepted by Windows Forms and Web Forms designers. C++ is not. In other words, you do not get rapid application development with C++. Also note that the entry point in C# is always main() regardless of whether the application is GUI or console based
Much like the good old DLL, an assembly contains code that can shared and reused by multiple applications. However, unlike the old DLLs, assemblies can be configured as private (the default behavior), which means that an assembly is intended to be used by a single application on a single machine. This will greatly simplify the versioning and deployment of applications.
With .NET assemblies, cross-language inheritance is possible. In other words, it is possible to reuse and extend types between languages.
Assemblies are used to define a boundary for the types they contain. In .NET the identity of a given type is defined in part by the assembly in which it resides. Therefore, if two assemblies both define a class named MyClass, they are considered independent identities in the .NET universe.
In the world of COM, the programmer in in charge of correctly versioning a binary. The main problem with this is that the programmer-defined techniques are not enforced by the runtime. Another major headache is that a given COM server does not list the set of the binaries that must be present for it to function correctly (I overcome this by using the Depends utility that comes with the Win32 SDK)
Under .NET, an assembly's manifest is the entity responsible for listing all internal and external dependencies. Each assembly also has a version identifier that applies to all types and resources contained within each module of the assembly. Using this version identifier, the runtime is able to load the correct assembly on behalf of the calling client. An assembly's version identifier is composed of two pieces: A friendly text named the informational version, and a numeric identifier termed the compatibility version. It takes the numerical form of nMajor.nMinor.nBuildNumber.nRevision.
In the .NET universe, security measures are scoped at the assembly level. The security constraints defined by an assembly are listed in the assembly's manifest. For example, if AssemblyA wanted to use a class in AssemblyB, it is AssemblyB that determines whether to allow access or not.
One of the biggest advantages of .NET assemblies is the fact that multiple versions of the same assembly can be loaded and understood by the runtime. It is also possible to configure which version should be loaded using application configuration files.
<Code here for CSharp_SingleFileAssembly>
It is possible to create a type in a .NET-aware language, say C#, and extend it (i.e., derive from it) in another .NET aware language, say VB.NET (VB.NET supports the Inherits keyword) This allows an application to be broken down into discrete binary building blocks
The following is the manifest of the Library module from Single File Assembly - Test sections
/* The following two blocks of .assembly
extern list the external assemblies required by this assembly (Librrry). The
Library assembly therefore requires the mscorlib.dll and
System.Windows.Forms.dll assemblies. The .publickeytoken means that the
corresponding assembly has been configured as a shared assembly and is used to
reference the strong name of the assembly. The .ver attribute lists the
required version number. */
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:2411:0
}
.assembly extern System.Windows.Forms
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:2411:0
}
/* The .assembly tag marks the friendly name of
this assembly. The .ver tag defines the compatibility
version number of this assembly. Note that Library assembly does not define a .publickeytoken
tag, given that this assembly has not been configured as a shared
assembly. */
.assembly Library
{
.custom instance void [mscorlib]System.Reflection.AssemblyKeyNameAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyKeyFileAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyDelaySignAttribute::.ctor(bool) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyTrademarkAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCopyrightAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyConfigurationAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyDescriptionAttribute::.ctor(string) = ( 01 00 00 00 00 )
.custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 00 00 00 )
// --- The following custom attribute is added automatically, do not uncomment -------
// .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(bool,
// bool) = ( 01 00 01 01 00 00 )
.hash algorithm 0x00008004
.ver 1:0:714:37942
}
/* The one and only .module tag means that this is
a single file assembly. */
.module Library.dll
// MVID: {9BB55578-9580-4FCD-A7DA-2BCF02BEA8B2}
.imagebase 0x11000000
.subsystem 0x00000003
.file alignment 512
.corflags 0x00000001
// Image base: 0x038c0000
Make sure the following points are clear:
Formally speaking, an assembly can be either private or shared. Each variation provides the same underlying structure and the same kind of services. The difference between private and shared assemblies boils down to naming conventions, versioning policies, and deployment issues. Here are the main points concerning private assemblies:
The .NET runtime resolves the location of a private assembly using a technique called probing. Probing is the process of mapping an external assembly reference to the correct corresponding binary file, and once an assembly is located, it is bound to the assembly that uses it
/* When the runtime reads the following
line from an application manifest, a search is made in the application directory
for a file named CarLibrary.DLL. If a DLL binary cannot be found, an attempt is
make to locate an EXE version. If neither attempt is successful, an attempt is
made to search for a shared assembly */
.assembly extern CarLibrary
{
...
}
The identity of a private assembly consists of a friendly string name and numerical version, both of which are recorded in the assembly's manifest. Be aware that it is possible for a single machine to have multiple copies of the same private assemblies in various application directories.
When ,NET is instructed to bind to an assembly, the first step is to determine the presence of an application configuration file. These optional XML files contain XML tags that control the binding behavior of the launching application. Configuration files must have the same name as the application, have an extension of *.config, and be placed in the application directory with launching application. Configuration files can be used to specify an optional directories to be searched during the binding process.
.NET configuration files all have the same general format:
<configuration>
<configSections>
<!--
Description of each section -->
...
</configSections>
<!-- Sections -->
</configuration>
A .NET application can be deployed simply by placing all assemblies in the
application directory. Configuration files are used to when you want to deploy
an application with a subdirectory tree, \bin, \help,
\tools, \documentation,
etc.. Configuration files instruct the runtime where it should probe while it is
attempting to locate the set of private assemblies used by the launching application.
<configuration>
<runtime>
<assemblyBinding
xmlns='urn:schemas-microsoft-com:asm.v1'?
<probing
privatePath = 'Foo\Bar'/>
</assemblyBinding>
</runtime>
</configuration>
Assemblies do not have to be located on the hard disk. They can be located on a remote server. In this case, the URI for the assembly can be specified in a <codeBase> element in the assemblies collection in the configuration file. The <codeBase> element can also be used to load assemblies from a directory other than the application's directory and subdirectories, as long as the assembly has a strong name.
Unlike private assemblies, a shared assembly can be used by several clients on a single machine. Shared assemblies are not typically placed in an application's directory, but are rather installed into a machine-wide Global Assembly Cache - GAC. The GAC is located under <dirve>:\WinNT\Assembly subdirectory. In COM shared DLL can reside anywhere as long as they are properly registered. In .NET, all shared assemblies must be in the GAC.
Unlike private assemblies, a shared assembly requires additional version information beyond the friendly text name. .NET runtime does enforce version checking on shared assemblies before loading on behalf of a calling application. In addition, a shared assembly must be assigned a shared name knows as the strong name.
When creating a shared assembly, the first step is to create a unique shared name for the assembly. A shared name consists of the following information:
Assume a client has referenced a shared assembly, foo.dll. When the compiler generates the client' sbinary, the assembly's public key token is recorded in the client's manifest. At runtime, .NET runtime ensures that both client and shared assembly are using the same key pair. If these keys are identical, the client is assured that it is using the right assembly:
A strong name is generated using the sn.exe utility with the -k argument. A strong name is created in two steps:
You must have administrative rights on the PC in order to install to the GAC. Simply drag and drop the shared assembly into the GAC directory. Alternatively you could also use gacutil.exe utility.
Recall that .NET runtime does not bother to perform version checks for private assemblies. However, for a shared assembly, the version is of prime importance.
A version number is composed of four parts:
The rules are as follows:
Where do you specify version information? Recall that each C# project contains a file names AssemblyInfo.cs. In this file, there is an attribute names AssemblyVersion set to "1.0"
[assembly:
AssemblyVersion( "1.0.*" ) ]
// IDE automatically updates Revision and Build numbers
[assembly: AssemblyVersion(
"1.0.23.67" ) ] // Revision and Build numbers
updates by the programmer
It is your responsibility to update the version information. Be aware that the IDE automatically increments the Revision and Build numbers (as marked by the * tag).
The .NET default version policy behaves as follows: If a client references a shared assembly, the major and minor versions must be identical for the bind to succeed. The .NET runtime binds to a given assembly if the assembly reference differs by the Revision or Build numbers.
For example, if a client's manifest explicitly requests version 1.0.0.0, but the GAC has newer version by specifying a QFE (say 1.0.0.3), the client automatically receives the newer version.
If you want to customize how an application binds to an assembly (such as disabling QFEs), you need to author an application configuration file. Because .NET framework allows you to place multiple versions of the same shared assembly in the GAC, you can easily configure custom version policies as you see fie.
For example, you can specify a specific assembly version to be loaded, regardless of what may be listed in the manifest. This can be achieved using the <dependentAssembly> and <bindingRedirect> attributes:
<configuration>
<runtime>
<assemblyBinding
xmlns='urn:schemas-microsoft-com:asm.v1'?
<dependentAssembly>
<assemblyIdentity name="sharedAssembly" publicKeyToken="..."
culture=""/>
<bindingRedirect oldVersion="1.0.0.0" newVersion="2.0.0.0"
/>
</dependentAssembly>
</assemblyBinding>
</runtime>
</configuration>
The .NET framework also supports an administration configuration file. Each .NET-aware machine has a file named machine.config that contains listings used to override any application-specific configuration files. Reading this file is an excellent way to learn about *.config centric tags.
The process of building multithreaded applications in .NET has been simplified. Using types defined in the System.Threading namespace allows to spawn threads with relative ease. Likewise, there are types the provide the same synchronization primitives as in Win32 (mutexes, semaphores, etc.)
Recall that a .NET application is a collection of related assembly. A .NET process can host any number of application domains (AppDomains), each of which is fully and completely isolated from the other AppDomains. A .NET application (as opposed to a .NET process) runs in an AppDomain. Therefore, applications that run in different AppDomains are unable to share any information (global variables or static fields), unless they make use of the .NET remoting protocol. AppDomains are programmatically represented by the System.AppDomain type. System.AppDomain members provide numerous process-like behaviors.
AppDomain Test Project <TO DO: Add a link to the test project>
Again, each host .NET process has at least one domain that contains threads executing IL code. You can create additional domains in the process but each one is isolated from the other. All IL is verified by the type system before it is executed to ensure that the correct code is called and that the code does not perform invalid memory operations. This verification is performed by the runtime, in addition to the memory protection applied at the process level by the OS. Thus the verification is applied at a sub process level within the domain.
Similar to IPC, communication between domains requires marshaling of data and object references through proxies. However, when the two domains that are communicating are within the same process, the mechanism is much faster than IPC.
The analogy of domains being sub-processes within a process is useful. Application settings in .NET are applied at the domain level, so that if you have multiple domains in one process, they can be configured independently - each in a configuration file or by code.
When you load an assembly into a domain, the assembly is specific to that domain. It cannot directly access objects in any other domain, and thus it cannot access the memory used by other domains. If more than one domain loads the same assembly, a new copy of the assembly is loaded unless the assembly is loaded as domain-neutral. For example, mscorlib.dll assembly is domain-neutral. Domain-neutral assemblies can be private or shared assemblies. The point is that just one copy is loaded in each domain in a process.
Multithreaded programming is accomplished with the System.Threading namespace. In addition to providing types that represent a specific thread, the namespace also defines methods used to manage a collection of threads, a simple timer, and numerous types to provide synchronized access to data types.
Threading Project <TO DO: Add a link to the test project>
Note also that concurrency can be controlled with C# lock keyword, the System.Threading.Monitor class type, and the System.Threading.Interlocked type.