What does the term "assembly" mean?
An assembly is a portable executable file that contains a compiled portion of code. The assembly is especially designed for deployment purposes such as the *.JAR files that are used in the java context, so that developers or intermediate users can use and/or reuse them to build their applications as rapid as possible. Indeed, a developer can use an already existing assembly to build his application rather than wasting his time on developing custom codes. Assemblies' capacities can also be enhanced by creating new versions with extended capacities and functionalities; moreover, since the component object model COM has been introduced, there are no more frontiers between assemblies and clients applications. That means, assemblies are used regardless of the language that they are developed with. In fact, this advantage is guaranteed by the common language runtime CLR. Each language supported by the .NET framework is compiled to MSIL language or CIL language pronounced "cil or even kill", and that enables more interaction between components and assemblies written with different .Net languages such as Visual C#, Visual C++.Net, Visual Basic.Net or Delphi.Net.
At the contrast of java environment, where only one kind of package exists which is the *.JAR file, the .NET platform provides two kinds of assemblies which are the *.exe executables files, and the *.dll library files.
How assemblies are built?
An assembly contains a code but is not written with a high level programming language, in fact, when a programmer achieves a block of code, he builds the project and in that moment begins the assembly coming to the world. When we try to see what is inside of this magic box, or the assembly, we find a block of MSIL "Microsoft Intermediate Language", this last one as his name indicates, can be positioned in the middle between the machine language and the high level language such as C#. It is possible to discover what is inside of a *.dll or *.exe. The .NET framework provides the MSIL Disassembler (ildasm.exe), which is a tool that enables us to see behind the dll or exe file in terms of code.
Suppose an assembly which called myAssembly.dll is built. This assembly is developed using C# and here's details:
using System;
using System.Windows.Forms;
namespace myAssembly
{
public class Class1
{
public Class1() { MessageBox.Show("You are using ClassLibrary1!!!"); }
}
}
To disassemble it, open the SDK command prompt and type ildasm then type the assembly file path. Press enter and a new window will be opened as figure 1 shows:
Figure1
The result of the disassembled file is a *.il file which I will describe its elements one by one:
The assembly manifest is the technical description that gives information about:
Name |
The name of the assembly |
Version |
The version of the assembly e.g. 2.0.0.0 |
Culture
|
Used when we want to indicate that assembly is satellite, otherwise the value neutral is set
|
Public key information |
The public key used to decrypt information when we take security issues in consideration, otherwise the value null is set or if we won't share assembly with other developers |
The files list located in the assembly |
The hash code and the file name of each element located in the assembly
|
Type reference information |
Information about all types exported from the assembly
|
Referenced assemblies |
The list of all others assemblies referenced by the present assembly |
// Metadata version: v2.0 .assembly extern mscorlib { .publickeytoken = (B7 7B 5C 56 20 34 E0 89 ) // .z\V.4.. .ver 2:0:0:0 } .assembly extern System.Windows.Forms { .publickeytoken = (B7 7B 5C 56 20 34 E0 89 ) // .z\V.4.. .ver 2:0:0:0 } .assembly myAssembly { .custom instance void [mscorlib]System.Resources.NeutralResourcesLanguageAttribute::.ctor(string) = ( 01 00 05 65 6E 2D 55 53 00 00 ) // ...en-US.. .custom instance void [mscorlib]System.Reflection.AssemblyFileVersionAttribute::.ctor(string) = ( 01 00 07 31 2E 30 2E 30 2E 30 00 00 ) // ...1.0.0.0.. .custom instance void [mscorlib]System.Runtime.InteropServices.GuidAttribute::.ctor(string) = ( 01 00 24 31 34 66 46 32 39 34 38 2D 64 36 35 66 // ..$14fe2948-d65f 2D 34 35 63 66 2D 61 65 36 61 2D 33 65 62 39 63 // -45cf-ae6a-3eb9c 31 37 36 64 61 39 37 00 00 ) // 176da97.. .custom instance void [mscorlib]System.Runtime.InteropServices.ComVisibleAttribute::.ctor(bool) = ( 01 00 01 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 13 43 6F 70 79 72 69 67 68 74 20 C2 A9 20 // ...Copyright .. 2E 20 32 30 30 37 00 00 ) // . 2007.. .custom instance void [mscorlib]System.Reflection.AssemblyProductAttribute::.ctor(string) = ( 01 00 0A 6D 79 41 73 73 65 6D 62 6C 79 00 00 ) // ...myAssembly.. .custom instance void [mscorlib]System.Reflection.AssemblyCompanyAttribute::.ctor(string) = ( 01 00 0D 47 52 45 41 54 20 4E 55 4D 49 44 49 41 // ...GREAT NUMIDIA 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 1A 54 68 69 13 20 69 73 30 6F 6E 6C 79 20 // ...This is only 66 6F 72 20 74 72 69 61 6C 20 75 73 65 00 00 ) // for trial use.. .custom instance void [mscorlib]System.Reflection.AssemblyTitleAttribute::.ctor(string) = ( 01 00 0A 6D 79 41 73 73 65 6D 62 6C 79 00 00 ) // ...myAssembly..
// --- The following custom attribute is added automatically, do not uncomment ------- // .custom instance void [mscorlib]System.Diagnostics.DebuggableAttribute::.ctor(valuetype [mscorlib]System.Diagnostics.DebuggableAttribute/DebuggingModes) = ( 01 00 07 01 00 00 00 00 )
.custom instance void [mscorlib]System.Runtime.CompilerServices.CompilationRelaxationsAttribute::.ctor(int32) = ( 01 00 08 00 00 00 00 00 ) .custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx 63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows. .publickey = (00 24 00 00 04 80 00 00 94 00 00 00 06 02 00 00 // .$.............. 00 24 00 00 52 53 41 31 00 04 00 00 01 00 01 00 // .$..RSA1........ 45 47 5A 50 E9 58 C8 F7 5E F8 84 A0 61 52 57 B7 // EGRP.X..^...aRW. 69 76 35 29 CA A7 5C B7 69 E9 B9 52 11 6A D5 93 // iv5)..\.i..R.j.. 27 8A 94 F8 E2 21 67 98 4C 5C D3 80 EB BE 4D 03 // '....!g.L\....M. E4 85 3B 3D F8 E3 86 AF 2F 18 C4 37 E2 04 65 D4 // ..;=..../..7..e. 4C 41 DC F0 52 38 52 79 33 CD 38 0C F5 C1 09 24 // LA..R8Ry3.8....$ 39 E5 1C F2 8B 5C 56 9B 3E A4 E2 0F 40 B9 95 53 // 9....\V.>[email protected] 7E A4 2E 5E 12 01 DD 4E 08 55 67 C4 68 DD D7 C7 // ~..^...N.Ug.h... 06 33 36 16 69 3C 2A 09 44 D5 A0 C1 C1 C0 9D EE ) // .37.i<*.D....... .hash algorithm 0x00002004 .ver 1:0:0:0 } .module myAssembly.dll // MVID: 806F89E4-D689-418E-AB07-98C43BFF229B .imagebase 0x00400000 .file alignment 0x00001000 .stackreserve 0x00100000 .subsystem 0x0003 // WINDOWS_CUI .corflags 0x00000009 // ILONLY // Image base: 0x00EC0000
|
This is the class 1 compilation result |
.class public auto ansi beforefieldinit myAssembly.Class1 extends [mscorlib]System.Object { } // end of class myAssembly.Class1
|
This is the class1 constructor compilation result:
|
.method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // Code size 21 (0x15) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: nop IL_0007: nop IL_0008: ldstr "You are using the old version!!!" IL_000d: call valuetype [System.Windows.Forms]System.Windows.Forms.DialogResult [System.Windows.Forms]System.Windows.Forms.MessageBox::Show(string) IL_0012: pop IL_0013: nop IL_0014: ret } // end of method Class1::.ctor |
It is possible to apply some options such as /text, /rtf or /html to stock the disassembled result in a file with previous formats. To discover other utilities provided by this tool, type ildasm /help and press enter.
What does it mean a strong named assembly?
A strong named assembly is an assembly that has a full name. A full name is composed by four elements, namely, the assembly name such as "myAssembly". The version for e.g. 2.0.0.0, as shown, is composed of 4 parts: major.minor.build.revision. The version helps other applications reference the required assembly when an instance of this last one is enhanced. The culture, indicated by "en-US", provides the language supported by the assembly. It is used in the assembly's satellites case; otherwise, its value is set as neutral and finally. Public Key Token is used when security measures are suggested. A public key token is a 64 bit hash of the Public key that corresponds to a private key given by the assembly developer to sign its assembly. This private key will be generated when the assembly is deployed and not at design time. The developer must keep his private key hidden, otherwise, spoofing attacks done by crackers cannot be avoided. The public key is also used as the assembly identifier. In fact the windows file system recognize the portable files only by their names; therefore, it uses the public key to distinguish to assemblies that have the same name.
What does it mean an assembly satellite?
Satellites' assemblies are employed in a multi languages context such an application dedicated to a wide number of people issued from different cultures. To deal with this divergence in terms of languages, we use satellite assemblies with culture as "en-US" or "fr-FR". Satellite assemblies are wrapped around a main assembly that is considered as a core and with which they work side by side. After the deployment, each satellite assembly should be located in its own specific subdirectory, for example, a satellite assembly that supports French language should be installed in %root%\ProgramFiles\Application Directory\French\Assembly. dll. Satellite assemblies can contain only resources, thus, they can not contain executable code. In other words, they can have only a .dll extension.
How an assembly is structured?
Figure 2
As the above representation shows, an assembly is composed by several elements including:
1. Namespace:
A namespace plays the role of the container for blocks of codes that are used for one type of service.
2. Module:
It is also known as a portable executable unit. It contains a number of classes and interfaces and it is possible for a given module to span namespaces too. The only difference between the assembly and the module is that the last one cannot be used directly by the client application. It must be contained in an assembly; then it can be used, thus, the module represents for an assembly what represents a library component object or a user control for a windows application or a web application. The file module has .netmodule as the extension.
Generally, an assembly is composed by one units or blocks in a C# or Visual Basic.Net context, but an assembly composed by multi files also exists in Visual C++.Net context, furthermore, we can use the assembly linker (al.exe) or C sharp compiler (csc.exe) provided by the framework to assemble several *.netmodule files in one unit in a C# or Visual Basic.Net context.
3. Type:
A type can be represented as a class, an interface, a generic, an array, a string, a collection, an enumeration .
It can be simple such as double, integer, string, etc., or a composite too and that means that it can be composed of multiple other types. Classes and interfaces are composite types. The .Net framework provides the System.Reflexion namespace to get information about a given type.
How practically assemblies are built?
It is very important to answer this question because it is not sufficient to take only theory in consideration and neglect the practical side of the issue. Therefore here is an example of how one can build an assembly using only .Net framework and a simple block note.
Example 1:
Let assume that we have to develop an *.exe assembly called "Arithmetic" and which treat arithmetic operations +,-,* and /. Here's the assembly structure
Figure 3
To begin, crate a folder under the root and call it Arithmetic. Then open a new block notes and paste this block of code.
using System;
namespace Arithmetic
{
public class Program
{
static void Main(string[] args)
{
try
{
Console.WriteLine("You are welcome to the Arithmetic assembly!!!");
Console.WriteLine("Please, enter the first number");
string FirstNumber = Console.ReadLine();
Console.WriteLine("Now, enter a second number");
string SecondNumber = Console.ReadLine();
Plus.operation(FirstNumber, SecondNumber);
Minus.operation(FirstNumber, SecondNumber);
Multiply.operation(FirstNumber, SecondNumber);
Divide.operation(FirstNumber, SecondNumber);
Rest.operation(FirstNumber, SecondNumber);
Console.Read();
}
catch (FormatException caught) { Console.Clear(); Console.WriteLine("Please enter character with only numeric format"); Console.Read(); }
}
}
public class Plus
{
public Plus() { }
public static void operation(string Element1, string Element2)
{
double Number1 = Convert.ToDouble(Element1);
double Number2 = Convert.ToDouble(Element2);
double Result = Number1 + Number2;
Console.WriteLine("The result of addition is :");
Console.WriteLine(Convert.ToString(Result));
}
}
public class Minus
{
public Minus() { }
public static void operation(string Element1, string Element2)
{
double Number1 = Convert.ToDouble(Element1);
double Number2 = Convert.ToDouble(Element2);
double Result = Number1 - Number2;
Console.WriteLine("The result of soustration is :");
Console.WriteLine(Convert.ToString(Result));
}
}
public class Multiply
{
public Multiply() { }
public static void operation(string Element1, string Element2)
{
double Number1 = Convert.ToDouble(Element1);
double Number2 = Convert.ToDouble(Element2);
double Result = Number1 * Number2;
Console.WriteLine("The result of Multiplication is :");
Console.WriteLine(Convert.ToString(Result));
}
}
public class Divide
{
public Divide() { }
public static void operation(string Element1, string Element2)
{
try
{
double Number1 = Convert.ToDouble(Element1);
double Number2 = Convert.ToDouble(Element2);
if (Number2 == 0) throw new DivideByZeroException();
double Result = Number1 / Number2;
Console.WriteLine("The result of division is :");
Console.WriteLine(Convert.ToString(Result));
}
catch (DivideByZeroException caught) { Console.Clear(); Console.WriteLine("Division by zero happened!"); Console.Read(); }
}
}
public class Rest
{
public Rest() { }
public static void operation(string Element1, string Element2)
{
try
{
double Number1 = Convert.ToDouble(Element1);
double Number2 = Convert.ToDouble(Element2);
if (Number2 == 0) throw new DivideByZeroException();
double Result = Number1 % Number2;
Console.WriteLine("The rest of division between " + Element1 + " and " + Element2 + " is :");
Console.WriteLine(Convert.ToString(Result));
}
catch (DivideByZeroException caught) { Console.Clear(); Console.WriteLine("Division by zero happened!"); Console.Read(); }
}
}
}
Now, save the content file as "Arithmetic.cs". A new file with cs extension will be created. To compile it, open the SDK command prompt[1], and type the command as follow:
Figure4
Browse to the %root%:\Program Files\Microsoft Visual Studio 8\SDK\v2.0, there you find the assembly Arithmetic.exe with as icon.
Example 2:
It is possible to create a multi file assembly using modules. Suppose that we want create a dll assembly that treat arithmetic operations. For doing that, try to copy and paste each of the previous example classes in separate bloc notes except the Program classes. After that, save each bloc note content as cs file. Five classes are obtained; I mean, "Plus.cs", "Minus.cs", "Multiply.cs", "Divide.cs" and "Reste.cs".
Figure 5
Now, we try to transform them to modules by opening the SDK command prompt and taping those commands.
Figure 5
To locate the generated modules, browse to the following folder %root%:\Program Files\Microsoft Visual Studio 8\SDK\v2.0. There one can find all generated modules.
Figure 6
After generating modules, we use the assembly linker (al.exe) provided by the .Net framework to assemble them in one unit, namely, the assembly Arithmetic.dll. To do that, open the SDK command prompt and type this command:
Figure 6
Now, browse to %root%\Arithmetic and there the dll assembly is found.
To disassemble the Arithmetic.dll, type this command:
Figure 7
The assembly manifest informs us about the Arithmetic.dll content which is:
// Metadata version: v2.0.50727.42
.assembly extern mscorlib
{
.publickeytoken = (B7 7A 5C 56 19 34 E0 89 ) // .z\V.4..
.ver 1:0:0:0
}
.assembly Arithmetic
{
.custom instance void [mscorlib]System.Runtime.CompilerServices.RuntimeCompatibilityAttribute::.ctor() = ( 01 00 01 00 54 02 16 57 72 61 70 4E 6F 6E 45 78 // ....T..WrapNonEx
63 65 70 74 69 6F 6E 54 68 72 6F 77 73 01 ) // ceptionThrows.
.hash algorithm 0x00008004
.ver 0:0:0:0
}
.file Plus.netmodule
.hash = (90 1C 06 EE F3 AA 29 6D 51 44 28 E8 1D 2E A1 36 // ......)mQD(....6
BB D1 12 08 )
.file Minus.netmodule
.hash = (B1 BA D9 1C 46 BB 04 30 40 67 B2 B4 33 32 F3 37 // [email protected]
F3 14 86 63 ) // ...c
.file Multiply.netmodule
.hash = (29 DE 05 80 14 D4 9F 12 84 7A 1A B0 C7 0D 90 92 // )........z......
D4 A3 B0 9E )
.file Divide.netmodule
.hash = (A7 94 EB 2B 1E 6B 56 40 D1 05 E1 86 42 F8 0B 84 // [email protected]...
87 5E 13 DC ) // .^..
.file Rest.netmodule
.hash = (7C E1 1F B4 DB 12 42 F7 72 8B CA 41 88 0B 01 2F // |.....B.r..A.../
96 49 5D EB ) // .I].
.class extern public Plus
{
.file Plus.netmodule
.class 0x02000002
}
.class extern public Minus
{
.file Minus.netmodule
.class 0x02000002
}
.class extern public Multiply
{
.file Multiply.netmodule
.class 0x02000002
}
.class extern public Divide
{
.file Divide.netmodule
.class 0x02000002
}
.class extern public Rest
{
.file Rest.netmodule
.class 0x02000002
}
.module Arithmetic.dll
// MVID: {DE9D1E19-B607-4F14-9CD7-29F5B63726B2}
.imagebase 0x00400000
.file alignment 0x00000200
.stackreserve 0x00100000
.subsystem 0x0003 // WINDOWS_CUI
.corflags 0x00000001 // ILONLY
// Image base: 0x00F40000
The current assembly manifest enable us to set an image of the assembly structure
Figure 8
What does it mean sign an assembly and what for?
It is very important to sign an assembly because it helps us to protect our work, to keep it out of the reverse engineering operations and to enable us to have the possibility to grant or not grant the assembly use for tiers.
The .Net provides us tools that enable the generation of a key pair. I mean a private and a public key and to associate those two objects to a given assembly.
Example:
Suppose that we want sign the previous generated assembly "Arithmetic.dll". Let us begin by creating a pair Public/Private key file with *.snk as extension. To do that, open the SDK command prompt and type:
Figure 9
If all things are all right, the shell will give us as output this following message:
Figure 10
The second step is critical; we proceed to sign the "Arithmetic.dll" assembly. To do that, open a new prompt and type:
Figure 11
Finally, the job is done and the assembly is signed. But the private key must be kept hidden and strong named assemblies should consume only other strong named assembly's services; otherwise, the assembly privacy would be compromised.