Introduction to the Assembly Concept

What does the term "assembly" mean?

An assembly is a portable executable file that contains a compiled portion of code. The assembly is specially 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 rapidly 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 client 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.

In contrast to the 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 at 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 called myAssembly.dll is built. This assembly is developed using C# and here are the 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.

SDK command prompt
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 into consideration, otherwise the value null is set or if we won't share the 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 other assemblies referenced by the present assembly

This is the class 1 compilation result.

// Metadata version: v2.0
.assembly extern mscorlib {
    .publickeytoken = (B77B5C562034E089) // .z\V.4..
    .ver 2:0:0:0
}

.assembly extern System.Windows.Forms {
    .publickeytoken = (B77B5C562034E089) // .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 73 20 69 73 20 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 = (0024000004800000940000000602000000240000525341310004000001000000 // $..............
        45067A50E958C8F75EF884A0615257B7 // EGZP.X..^...aRW.
        69763529CAA705CB769E9B952116AD593 // iv5)..\.i..R.j..
        278A94F8E22167984C5CD380EBBE4D03 // '....!g.L\....M.
        E4853B3DF8E386AF2F18C437E20465D4 // ..;=..../..7..e.
        4C41DCF05238527933CD380CF5C10924 // LA..R8Ry3.8....$
        39E51CF28B5C569B3EA4E20F40B99553 // 9....\V.>[email protected]
        7EA42E5E1201DD4E085567C468DDD7C7 // ~..^...N.Ug.h...
        06333616693C02A0944D5A0C1C1C09D // .336.i<*.D.....
        EE) // ..
    .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
.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 of four elements, namely, the assembly name such as "assembly". 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 satellite case; otherwise, its value is set as neutral and finally. A 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 an assembly identifier. In fact, the Windows file system recognizes portable files only by their names; therefore, it uses the public key to distinguish assemblies that have the same name.

What does it mean an assembly satellite?

Satellites' assemblies are employed in a multi-language context such as an application dedicated to a wide number of people 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 the main assembly that is considered 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 the 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?

Assembly structured
Figure 2

As the above representation shows, an assembly is composed of several elements including.

  • Namespace: A namespace plays the role of the container for blocks of codes that are used for one type of service.
  • 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 of one unit or blocks in a C# or Visual Basic.Net context, but an assembly composed of 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.
  • Type: A type can be represented as a class, an interface, a generic, an array, a string, a collection, or 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 into consideration and neglect the practical side of the issue. Therefore here is an example of how one can build an assembly using only the .Net framework and a simple block note.

Example 1

Let's assume that we have to develop a *.exe assembly called "Arithmetic" which treats arithmetic operations +,-,*, and /. Here's the assembly structure

Arithmetic
Figure 3

To begin, create a folder under the root and call it Arithmetic. Then open a new block note 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 a cs extension will be created. To compile it, open the SDK command prompt[1], and type the command as follow.

Command
Figure 4

Browse to the %root%:\Program Files\Microsoft Visual Studio 8\SDK\v2.0, there you find the assembly Arithmetic.exe with an icon.

Example 2

It is possible to create a multi-file assembly using modules. Suppose that we want to create a DLL assembly that treats arithmetic operations. To do 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 a cs file. Five classes are obtained; mean, "Plus. cs", "Minus. cs", "Multiply. cs", "Divide. cs" and "Reste. cs".

Bloc-notes
Figure 5

Now, we try to transform them into modules by opening the SDK command prompt and taping those commands.

Modules
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.

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.

.Net framework
Figure 6

Now, browse to %root%\Arithmetic and there the dll assembly is found.

To disassemble the Arithmetic.dll, type this command.

Microsoft
Figure 7

The assembly manifest informs us about the Arithmetic.dll content which is,

// Metadata version: v2.0.50727.42
.assembly extern mscorlib {
    .publickeytoken = (B77A5C561934E089) // .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 09 F1 2E 84 07 A1 AB 0C 70 D1 05 E1 86 42 F8 0B 84 // )............p....B...
        87 5E 13 DC) // .^..
.file Divide.netmodule
    .hash = (A7 94 EB 02 B1 0E 06 B5 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 08 BC 0A 41 88 0B 01 20 F7 // |.....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 enables us to set an image of the assembly structure/

Manifest
Figure 8

What does it mean to 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 used for tiers.

The .Net provides us with 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 won't sign the previously generated assembly "Arithmetic.dll". Let us begin by creating a pair of Public/Private key files with *.snk as an extension. To do that, open the SDK command prompt and type.

SDK
Figure 9

If all things are all right, the shell will give us as output the following message.

Shell
Figure 10

The second step is critical; we proceed to sign the "Arithmetic.dll" assembly. To do that, open a new prompt and type.

Second step
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.


Similar Articles