Introduction

Are you interested in what makes the .NET runtime tick or in need to change the behavior of the .NET runtime to your needs? Then this article is for you. This is part 1, where we go through the basics and create a simple AppDomainManager. In part 2, we will implement AppDomainManagers with functionality for sandboxing, exception handling, and alternative assembly loading.

Background

This article is a result of an educational endeavor from my side. I have a very strong interest in debugging and testing so most of my articles
are related to that, either directly as how to write debugger extensions or indirectly such as increasing logging capabilities or exploring APIs.
My last article was about Building a mixed mode sampling profiler
which was something I needed. When I started exploring the API related to this article, I didn't have a special need
for it, I did it out of curiosity.
At this point, I can see several interesting points.

How does a .NET application start?

It is too soon to talk about this API. Let's step back a little.

How does Windows know that a binary is a .NET application? Actually the answer varies depending on which version of
Windows you run.

Normally .exe files are executed by Windows by looking at the PE-Header. This PE-Header says how it should be loaded into memory, what dependencies it has, and where the entry point is.

Where is the entry-point of a .NET application? Well, your application is in some IL-code. Executing that directly will clearly lead to a crash.
It is not the IL-code that should start executing, but the .NET runtime, which eventually should load the IL-code and execute it.

In newer versions of Windows, .NET comes preinstalled, and Windows has built-in support
for recognizing a .NET application. This can be done by simply looking in the PE-Header present in all executables and DLLs.
In older versions of Windows, execution is passed to an entry point where boot-strapper code is located.
The boot-strapper, which is native code, uses an unmanaged CLR Hosting API, to start the .NET runtime inside the current process and launch the real program which is the IL-code.

The CLR Hosting API

Hosting the CLR in an unmanaged app

When you start the .NET runtime inside a native process, that native application becomes a host for the runtime.
This lets you add .NET capabilities to your native applications.

But for good reasons this interface has been deprecated. The old API had huge parts in unmanaged code, which proved to be a great disadvantage.
Retrieving values or manipulating objects from an AppDomain in unmanaged code resulted in a lot of Marshaling (a.k.a. Serialization), which
severely affects performance.
The marshaling was also implicit, so it was not always obvious where it took place. Sometimes even the whole AppDomain was marshaled.
So we will not use this interface, but instead use the new one and keep all our custom code running within an AppDomain.

New API

The new version of the CLR Hosting interfaces has been reworked. Much of the API has been moved from unmanaged code to managed code.
In order to obtain an AppDomain instance, one has to register an AppDomainManager implementation. The good part is that it is much easier and faster to develop in C#.
The code also gets cleaner, because you don't have to write as much boilerplate code.

To be able to register a new AppDomainManager, we will need an interface called ICLRControl. This interface contains a method
SetAppDomainManagerType,
which loads your managed implementation of the AppDomainManager.

That is what you need to do to override it. You just need an implementation to go with it. I have made a basic one in managed code, called CustomAppDomainManager.
Below is the source listing of the implementation of my CustomAppDomainManager (SampleAppDomainManager.dll).

What we really wanted was to execute the Main method of the assembly directly, via the
ExecuteAssembly call like we did on the AppDomain. There is a minor problem.
There is no ExecuteAssembly method, but there is an ExecuteApplication method we can try instead.

Unfortunately, I didn't get it to work. The documentation says something about manifests and
Click-Once deployment.
The only error I get is E_UNEXPECTED as the HRESULT error.
This is a minor problem, since we can work around it easily when we create our CustomAppDomainManager implementation,
simply by adding a method, which calls ExecuteAssembly either on the default AppDomain or a newly created one like this:

Modifying the SampleApp to use this Run method gives us the following output:

It runs the Main method of an Assembly, exactly as we want it to. We are not there just yet, although very close. I deliberately jumped a step just to show you the end result.
What is missing is a way to obtain the pointer to our CustomAppDomainManager. If you remember, it is not created by us, but by the CLR framework.
We will have to implement another interface called IHostControl.

IHostControl

This is a class that the CLR will query for implementation of alternative Managers, we should of course instantiate our customized versions if we have any and return them.

Examples of handlers or managers that can be overridden with a user implementation can be seen below:

IID_IHostTaskManager

IID_IHostThreadpoolManager

IID_IHostSyncManager

IID_IHostAssemblyManager

IID_IHostGCManager

IID_IHostPolicyManager

In the AppDomainManager case, the CLR will actually call IHostControl::SetAppDomainManager with a pointer to the instance of the class we
told it to create. If you remember, we called a method with a similar name ICLRRuntimeHost::SetAppDomainType.

Implementing IHostControl

Below is a listing of a minimal implementation of the IHostControl interface.
For brevity I have removed the boiler plate code, such as constructors, destructors,
AddRef, and Release required by COM.

Conclusion

Why did we go through all this trouble just to execute a managed app? A managed app is already executable by clicking on it or launching it from a Cmd prompt.

Well, this is just the first step. We have not yet implemented anything of use, but there is a small difference. We executed the managed app in a new AppDomain, not in the default one.
The advantage of this is that you can create a supervisor launcher. The next step would be to implement and replace the default Managers,
that the CLR queries the IHostControl about. If we are uncertain about the origin of an application, we can with this type of hosting
actually strengthen the security of the application, and sandbox it the way we want. It is of course a double edged sword. It can also be used to remove security from an application.

I have a strong interest in debugging and testing. Customizing the runtime will let me do more
sophisticated loggers, without having to modify any code.
It will just work, and the app will be unaware of the change.

Continuation - Part 2

There is a follow up article, where we will implement AppDomainManagers with functionality for sandboxing, exception handling, and alternative assembly loading.

Share

About the Author

Mattias works at Visma, a leading Nordic ERP solution provider. He has good knowledge in C++/.Net development, test tool development, and debugging. His great passion is memory dump analysis. He likes giving talks and courses.

Comments and Discussions

Now i transform the clrhost into dll named clrhost.dll,then I inject it into a .Net program a.exe.
When clrhost.dll is injected into a.exe, it will create the appDomainManager to launch method C in another dll named 'b.dll' like you do in your demo. When the method C is done, I would like to unload and delete b.dll, appDomainManager.dll,clrhost.dll without terminating the process a.exe. Please help me that if it exists any way to make it and how. Thank you.

Thank you.
I am glad that you appreciated the article, I am also thankful that you take the time to comment.
Some of the things I write about take my a lot of trial and error to find out.
Especially when the API is poorly documented and give insufficient information when you use the API in a wrong way.

Nice to see that people read the article and like them.
I know that debugging and obscure CLR COM interfaces are a little bit hard to digest for most people.
There were times I asked myself why I was even touching these APIs myself.
But i am glad I did, I learned a lot of how the CLR runtime works, and some debugging tricks on the road.

hello. the answer is both yes and no. If you use a splashscreen or some login before your app starts. You can make that appear instantly if you implement it in unmanaged code, in the background you can start up the .net runtime. This way the user will not experience the normal delayed start. I think it should also be possible to load assemblies in the order they are used. for example. you can load the gui assemblies first, so the app can start. Then you load everything else, meanwhile you could for example show a progress bar. The general answer is that it isnt faster, but you can get more control over the order of the assembly loading and when the loading shall take place.

Hello,
I appreciate your fast reply.
I.m currently implementing just that. Creating a small CPP with the splashscreen that will load the C# app.
The problem is that the user will see the splashscreen for a long time until he see's his desktop.
Basically, it takes about 5-7 seconds to load the C# app (see the splashscreen) and additional 1-2 seconds for the C# app to complete it's run.
I think it's due to the time of loading the Framework(?) but I still "fighting" with the loading time.
Again, thanks for the answer.

i understand your problem better now. Are you using wpf? I have noticed significant load time compared to winforms. Regarding assembly loading the clr looks in many different folders, always in the gac first. Thereafter several different. This is repeated for all assemblies. By using a .config file or clr hosting you might be able to narrow this search to just the current dir. Another tip might be Ngen, which jit compiles an assembly to native code, so this step can be skipped at runtime. yet another tip might be il-merge, which merges several assemblies into one assembly. running ngen on that big assembly should speed up load time, but i have heard that it doesnt work with wpf (rumours by colleagues).

My application is a winform and not WPF. and is set to Framework 2.0.
How can I set the .config file to narrow the search?
I've tried ngen but the application crashed all the time.
Can I use il-merge to merge the framework files? My application does not have any additional .exe's or .dll's.
I appreciate your time. Thanks.

I think you are only supposed to ILMerge your own assemblies, and perhaps 3rd party assemblies. Never, system assemblies.

Anyway. Up to 9 seconds in load time (5-7 + 1-2) sounds pretty much,
It might be latency from other sources too.

Chatty interfaces can be a bottleneck. If you are fetching data from another machine,
many smaller requests give a significant latency if they are run serially.
Do you use a database? How many queries do you do at application startup?
Maybe for the startup you can create a special variant that fetches more data at the time, to remove the chattyness. Do you use authentication with remote servers?
Do you consume web-services?

If this isn't the cause. I would manually stub out assemblies.
If you start with less and less functionality, it must get faster.
Then I would start enabling the code again, this time adding high performance counters to get better precision, see http://www.codeproject.com/Articles/2635/High-Performance-Timer-in-C

Then there is of course .Net profilers.
JetBrains DotTrace is free to try for 10 days.

If you have 10 things that take exactly 1 second each.
You have to ultimately, see if it is possible to skip it at startup.
Async and background workers, might also help to get a more responsive UI.