tags:

views:

238

answers:

5

[ this is getting TLDR...sorry... ]

I work on a huge (mostly) C++/MFC application with hundreds of DLLs; it supports a dynamically-loaded "add in" mechanism via COM, thus add-ins can be developed in .NET by using COM interop. Some limited new functionality is developed in .NET w/o using this "add-in" mechanism (although still dynamically loaded); however, the end user can decide to not use this feature. Thus, .NET might not be loaded at startup.

But, when .NET is loaded, I need to do some .NET-specific initialzation (specifically setting CurrentUICulture to match the native/unmanaged UI).

One solution is to simply punt and do this .NET initialization when the code gets around to loading either the new .NET functionaliy or COM add-ins. Given the nature of this applicaton, it is probably a 95+% solution (most users will be using the new functionality).

But it's not foolproof. Somebody could "readily" add new .NET functionality at any time by building a module with the /clr flag (remember, this is a huge application).

One more robust (and obvious) solution is simply cause .NET to be loaded at startup via C++/CLI. But some of the die-hard C++ developers to whom every byte and clock cycle matter don't want to do this; somewhat understandable as there's no need to set CurrentUICulture unless/until .NET is loaded.

Another possibility I thought of is to hook LoadLibrary and look for mscorlib. Now I know .NET is about to be loaded for some reason, load it in the normal manner and do the initialization before the other code does anything. But hooking LoadLibrary (or anything else, for that matter) really isn't something I want to do.

So, is there an easy(ier)/better way to tell if .NET about to be loaded?

Edit: Reed's answer of LockClrVersion is pretty darn close. The only hiccup is that it won't work if you link in a mixed-mode DLL/assembly.

// ClrAboutToLoad.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"

#include <MSCorEE.h>

// http://community.bartdesmet.net/blogs/bart/archive/2005/07/22/2882.aspx
FLockClrVersionCallback begin_init, end_init;
STDAPI hostCallback()
{
    printf("hostCallback()\n");

    // we're in control; notify the shim to grant us the exclusive initialization right
    begin_init();

    ICLRRuntimeHost *pHost = NULL;
    HRESULT hr = CorBindToRuntimeEx(NULL, L"wks", STARTUP_SERVER_GC, CLSID_CLRRuntimeHost, IID_ICLRRuntimeHost, (PVOID*) &pHost);
    hr = pHost->Start();

    // mission completed; tell the shim we're ready
    end_init();

    return S_OK;
}

int _tmain(int argc, _TCHAR* argv[])
{
    LockClrVersion(&hostCallback, &begin_init, &end_init);

    //fnTheDLL();
    HMODULE hModule = LoadLibrary(L"TheDLL");
    FARPROC fp = GetProcAddress(hModule, "fnTheDLL");
    typedef void (*fnTheDLL_t)();
    fnTheDLL_t fnTheDLL = reinterpret_cast<fnTheDLL_t>(fp);
    fnTheDLL();
    FreeLibrary(hModule);

    return 0;
}
A: 

You should probably initialize the .NET framework at application startup. I worked on app with a similar background and it would occasionally deadlock because 2 dlls were trying initialize the .net framework at the sametime by indirectly accessing .net dlls. To fix that we made a call to CorBindToRuntimeEx in the first few lines of the entrypoint of the executable.

I don't think there's a way the OS will tell you if it's about to load .NET.

hjb417
No argument from me; but the byte counters see no point to initializing .NET if it's not going to be used. Remember, this is a (very) large project; there are not only engineering issues to work through, but political ones as well.
Dan
+1  A: 

You can run your own CLR host to load assemblies. Then you can make more specific decisions about which version of the runtime (1.1?2.0?4.0?) to load, and separate managed code from your code so when a plugin crash, your code won't go down with it.

Sheng Jiang 蒋晟
I thought about that...but the byte counters aren't exactly embracing .NET to begin with. Thanks to COM interop, they can remain (mostly) blissfully unaware that .NET even exists (the "new functionality" written in .NET I mentioned was forced upon them).
Dan
+1  A: 

There are two things that might help you: Performance Counters and WMI. The .NET CLR integrates with both, and you should be able to interact with either (WMI would probably be best) to watch for AppDomain startup. However, neither of these tools is particularly ideal, and in terms of overhead, doing what Sheng Jiang suggested, hosting the CLR on your own, would probably be more efficient (and thus more pleasing to your "byte counters".)

On a slightly different note...if you have any measure of influence and control over the development team, I would reign in the "byte counters" a bit. One of the biggest misconceptions about .NET is that it is less efficient than C++. That old fallacy needs to be buried, as .NET can be amazingly efficient, and when it is used correctly, more efficient than C++ at times. Beyond the baseline efficiency of either platform, you have to ask the question: how much efficiency will I really gain by spending unknown volumes of hours optimizing the finest grain, when statistically, its larger scale things that usually kill performance: cross-process calls (i.e. Calling COM objects from C++ vs. calling .NET objects from .NET), invoking remote processes (i.e. Web services, RPC, etc.), calling into a database, etc.

You can try to solve your .NET AppDomain startup problem to appease the byte-counters misconceptions, or you can implement a .NET system properly, and avoid the marshaling and inter-op hit that is going to be massively more draining than any amount of byte-level performance tuning could counteract.

jrista
No argument from me; but old misconceptions die hard.
Dan
Heh, too true. Sad, but definitely true.
jrista
A: 

Instead of trying to hook into when the assembly is loaded, can you hook into when the AppDomain is loaded? Look into System.AppDomainManager to do this.

After you loaded your subclassed System.AppDomainManager in the GAC, you just have to set some enviroment variables (APPDOMAIN_MANAGER_TYPE, APPDOMAIN_MANAGER_ASM) before you run your C++ program.

jyoung
I want to know when .NET (the CLR) is about to be loaded, not an assembly or AppDomain. In other words, I want to know when my unmanaged (OK, mixed) process is about ready to run managed code for the first time.
Dan
+5  A: 

I believe you can do this by having your process call the unmanaged LockClrVersion function prior to using any managed APIs. This function allows you to specify two FLockClrVersion callback methods.

The first method (pBeginHostSetup), is called prior to the CLR being initialized the first time for the hosting process. The second method (pEndHostSetup) is called when initialization of the CLR is complete.

This should allow you to specify unmanaged code that is run just prior and just after CLR initialization. In your case, you probably need to hook into the pEndHostSetup to call your managed API setup routines (you'll need to wait until the CLR is hosted successfully).

Reed Copsey
This looks promising!
Dan
It should do exactly what you are after - I haven't ever implemneted it, but I went through the entire CLR hosting API trying to track it down.
Reed Copsey
This is almost perfect; the only hitch is that you need to make sure nothing causes the CLR to get loaded before you call *LockClrVersion*. The "problem" is that linking in a mixed mode assembly loads the CLR, you have to dynamically load the assembly.
Dan