This blog is to clarify How-to work with reflection to load .Net Assemblies, to avoid some mistakes during programming.

  • The shared assemblies and strong  assemblies

Shared assemblies is a single assembly, which could only be used for private deployment (Saying, deploy to application base folder or its sub folder).

Strong assemblies is a kind of assemblies with signature generated by public/private key, which could be used for private deployment and also global deployment (Saying, deploy to well-known location, such as GAC, internet location, etc).

Both Shared assemblies and Strong assemblies had the same PE(portable exectuable) file format – PE32+ header, CLR header, MetaData header, Manifest and IL(all of .net compatible language will be converted into IL command, like MACRO).

And Strong assemblies could be generated and configured via SN.exe (.Net public&private key generator)toolkit, together with CSC.exe(C# assemblies compiler).

The following step shown for us, how the strong assemblies has been generate:

SN -k MYCompany.snk

SN -tp MyCompany.snk [display public key]

csc /keyfile:MyCompany.snk app.cs [compile into assembly]

  • CSC compiler will generate assemblies with references information [it depends on who, for instance, UIDesigner.exe depends on sapLSConnector.dll]

Take UI Designer assemblies as example, we can use ilasm.exe to get the following sections:

35(0x23): AssemblyRef          cRecs:   25(0x19), cbRec: 24(0x18), cbTable:   600(0x258) -> will be converted into depended assembly.
col  0:  MajorVersion oCol: 0, cbCol:2, USHORT
col  1:  MinorVersion oCol: 2, cbCol:2, USHORT
col  2:  BuildNumber  oCol: 4, cbCol:2, USHORT
col  3:  RevisionNumber oCol: 6, cbCol:2, USHORT
col  4:  Flags        oCol: 8, cbCol:4, ULONG
col  5:  PublicKeyOrToken oCol: c, cbCol:2, blob
col  6:  Name         oCol: e, cbCol:4, string
col  7:  Locale       oCol:12, cbCol:4, string
col  8:  HashValue    oCol:16, cbCol:2, blob

  • OK, back to our topic How-to use API for reflection to load assemblies and their differences.

For Backgournd mechnaism:

First of all, JIT(just in time compiler) will load assemblies into memory and execute IL command(convert IL command into native code).

Take care: JIT compiler will use AssemblyRef and TypeRef metaData to fetch  Assembly Name(without extsion name and path), Version, language, and public key token.

So if you use reflection API to load assemblies,

You will always works with identification token – Assembly Name(without extension name and path), Version, language, and public key token. And for shared assemblies , identification token will be just Assembly Name(without extsion name and path).

Yeah, welcome, the background story stopped, let’s go to API analysis.

For class Assembly, we have the following versions to load assemblies, what’s the difference:

  • Group 1 – Assembly.Load()

public static Assembly Assembly.Load(AssemblyName)

public static Assembly Assembly.Load(String)

1, How-to use API

AssemblyName is parameter without extension name and path : for loading UIDesigner.exe,

Code Snappit : Assembly _uidesigner = Assembly.Load(“UIDesigner”);

//in ILasm.exe, you will see UIDesigner.exe have Assembly name “UIDesigner”, please do check before you wrote code, as assembly name mayn’t be the same as dll name.

Or using string for assemblystring [with full information],

Code Snappit : Assembly _uidesigner = Assembly.Load(“UIDesigner, Version=25.0.555.1045, Culture=neutral, PublicKeyToken=null”);

Clearly see here that: UI Designer won’t own one public/private key pair.

2, Important thing – The searching path

  1. internally Load method will use Version rebinding strategy to load assembly in GAC (Global assembly cache), if not, go to 2;
  2. it will go to application base directory, private path subfolder and codebase position(you can define codebase in app.config file), if not go to3.
  3. throw out exception – FileNotFoundException.

Three points here:

  1. Such searching strategy keeps the same as Win32 LoadLibrary;
  2. for shared assemblies[some one called “weak assemblies”], .Net runtime won’t got for Version rebinding strategy AND GAC. directly go to step2.
  3. you can use AppDomain.appendPrivatePath() API or section of probing in app.config to configure private path subfolder.
  • UI Designer’s issue exists here:

In our PDI AppDomain, when creating UI Designer AppDomain, the application base path is \\….\CopernicusIsolatedShell.exe’s root folder. And when loading UI Designer AppDomain, I have already use AppDomain.appendPrivatePath()  to add “\Extension\Application” for UI Designer AppDomain.

So in UI Designer AppDomain, the API -> Assembly _uidesigner = Assembly.Load(“sapLSUICheckmateCoreWPF”); to load sapLSUICheckmateCoreWPF assemblies [instead of  Assembly _uidesigner = Assembly.Load(“sapLSUICheckmateCoreWPF.dll”) ].  And JIT search in Private Path folder, just found one assembly with Assembly name sapLSUICheckmateCoreWPF. [JIT just search AssemlyRef Sections for all assemblies under search path].

The method is similar to linux library searching strategy, add slib explictly into application search path.

The key points is that before load target assembly, you need to add your customized PrivatePath and after that please let it recovery to avoid additional performance impact. And one more question is that for Microsoft AppDomain design perspective, AppDomain.appendPrivatePath() has already been depercated, and alternative of AppDomainSetup is suggested to use during startup of new AppDomain.

And when you try to load assembly, Assembly.Load() will be always your first choice.

  • Group 2 – Assembly.LoadFrom

public static Assembly Assembly.LoadFrom(String)

public static Assembly Assembly.UnsafeLoadFrom(String)

1, For LoadFrom, you will also try to use FullName[including extension name] of assembly.

take sapLSUICheckmateCoreWPF as example, the correct usage will be

Code Snappit :  Assembly _currentAssembly = Assembly.LoadFrom(@”G:\project_summary\leanstack0_PDI_dev6_FP35_Dev2_Mashupconcept2_backendAPI\OberonLocalUI1_ConsoleAppDomain\bin\Debug\SubFolder\UIDesigner.exe”)

And it allow use URL as parameter:

Code Snappit :  Assembly _currentAssembly = Assembly.LoadFrom(@””)

For this case, you have to allow some internet security setting, but if you’re trying to ignore internet security setting [seems not so good].

you can use

Code Snappit :  Assembly _currentAssembly = Assembly.UnsafeLoadFrom(@””)

2,Question: How LoadFrom work? what’s relationship between Load()?

Answers: at the beginning, it will call System.Reflection.AssemblyName.GetAssemblyName() to fetch Assembly Name for JIT [just read target file, and search AssemblyRef metaData, return string then close File]. Then it will call Assembly.Load() method to do the same processing as I mentioned before.

  • Group 3

public static Assembly Assembly.ReflectionOnlyLoadFrom(String FilePath)

public static Assembly Assembly.ReflectionOnlyLoad(String)

1,  For searching path and Version checking-up

ReflectionOnlyLoadFrom method will just load assembly on FilePath, and also ignore GAC and other searching path.

ReflectionOnlyLoad method will search the same searching path as Assembly.Load() done, the only difference is that it won’t check up with Version.

2,  These methods relevant with instance, which are instantiated by these two methods, don’t allow to be executed!

Basically, when we get Assembly instance, we can use reflection to get Type instance, and use dynamically invoke to invoke method calling. But this’s not allowed for this two methods. If you want to have a trial, of course, you can :-). You will get exception – InvalidOperationException.

The reason is that Microsoft design this two methods for different CPU architecture, x64, x86, etc. This method is cross-CPU metaData time [Orlando made this work out, :-)]. But JIT needed one specific CPU architecture for runtime convestion [mentioned before, during runtime, IL command will be converted into Native code.]

Two addtional Questions:

  • If CLR couldn’t find depended DLL,  do we have one second choice to point out for them during runtime?

Answers: Yes

In you AppDomain, you can use the following code to let your AppDomain know that I’ll find it for you.

Code snippet:

AppDomain.CurrentDomain.AssemblyResolve += (sender, argvs) =>
using (var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(argvs.Name))
byte[] assemblyData = new byte[stream.Length];
stream.Read(assemblyData, 0, assemblyData.Length);
return Assembly.Load(assemblyData);

  • AppDomain also have Assembly assembly = AppDomain.Load(string), Can I use it?

Answer: Yes, you can, but take care the following situation [Orlando ever be suffering a lot for this]

In principle, AppDomain.Load() is used for “managed-unmanage” [Orlando made the name, that means also it’s managed code, but it call unmanaged CLR internal implementation finally]operation  to insert assembly into target AppDomain.

And if target AppDomain has its own searching path strategy, [in UI Designer appDomain, we already do such things], you need to take care.

In this case, the searching strategy will fit for the caller of AppDomain.Load(), not your target.

caller : AppDomain.Load() -> get Assembly reference from target AppDomain

As Assembly isn’t MarshalbyRefObject type, so this reference will be passed by value to caller AppDomain. But currently, the searching strategy has been replaced with caller AppDomain.

In target AppDomain and caller AppDomain doesn’t have the same searching strategy. My Godness, hope you good luck. 🙂

  • How about Assembly.LoadFile() ?

Assembly.LoadFile() will be always relevant with FullPath of Assembly. For instance:

Code Snippet:

Assembly _currentAssembly = Assembly.LoadFile(@”G:\project_summary\leanstack0_PDI_dev6_FP35_Dev2_Mashupconcept2_backendAPI\OberonLocalUI1_ConsoleAppDomain\bin\Debug\SubFolder\UIDesigner.exe”);