Question

Problem (simplified to make things clearer):

    1. there is one statically-linked static.lib that has a function that increments:
    
        extern int CallCount = 0;
        int TheFunction()
        {
            void *p = &CallCount;
            printf("Function called");
            return CallCount++;
        }
    
    2. static.lib is linked into a managed C++/CLI managed.dll that wraps TheFunction method:
    
        int Managed::CallLibFunc()
        {
            return TheFunction();
        }
    
    3. Test app has a reference to managed.dll and creates multiple domains that call C++/CLI wrapper:
    
        static void Main(string[] args)
        {
            Managed c1 = new Managed();
            int val1 = c1.CallLibFunc();
            // value is zero
    
            AppDomain ad = AppDomain.CreateDomain("NewDomain");
            Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
            int val2 = c.CallLibFunc();
            // value is one 
        }
    

Question:

Based on what I have read in Essential .NET Vol1 The CLR by Don Box, I would expect val2 to be zero since a brand new copy of managed.dll/static.lib is loaded when CreateInstanceAndUnwrap is called. Am I misunderstanding what is happening? The static library does not seem to be respecting the appdomain boundaries since it's unmanaged code. Is there a way to get around this issue other than by creating a brand new process for instantiating Managed?

Thank you very much everyone!

Was it helpful?

Solution

My hunch was that, as you suspected, unmanaged DLLs are loaded in the context of the process and not in the context of the AppDomain, so any static data in unmanaged code is shared among AppDomains.

This link shows someone with the same problem you have, still not 100% verification of this, but probably this is the case.

This link is about creating a callback from unmanaged code into an AppDomain using a thunking trick. I'm not sure this can help you but maybe you'll find this useful to create some kind of a workaround.

OTHER TIPS

After you call

Managed c1 = new Managed(); 

Your managed.dll wrapper will be loaded into main app domain of you application. Till it will be there domain unmanaged stuff from static.lib will be shared with other domains. Instead of creating separate process you just need to be sure (before each call) that managed.dll is not loaded into any application domain.

Compare with that

static void Main(string[] args)
{

    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  Value is zero

               AppDomain.Unload(ad)
    }
    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  I think value is zero

               AppDomain.Unload(ad)
    }


}
`

IMPORTANT and : If you add just one line JIT compiler will load managed.dll and the magic disappears.

static void Main(string[] args)
{

    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  Value is zero 

               AppDomain.Unload(ad)
    }
    {       
                AppDomain ad = AppDomain.CreateDomain("NewDomain");
                Managed c = ad.CreateInstanceAndUnwrap(a.FullName, typeof(Managed).FullName) as Managed;
                int val2 = c.CallLibFunc();
                //  I think value is one

               AppDomain.Unload(ad)
    }
Managed c1 = new Managed(); 


}

If you don't want to depend on such lines you can create another one wrapper ManagedIsolated.dll that will reference managed.dll and will make each call in separate domain with domain unload just after the call. Main application will depend only on ManagedIsolated.dll types and Managed.dll will not be loaded into main app domain.

That looks like a trick but may be it will be usefull for somebody. `

In short, maybe. AppDomains are purely a managed concept. When an AppDomain is instantiated it doesn't map in new copies of the underlying DLLs, it can reuse the code already in memory (for example, you wouldn't expect it to load up new copies of all the System.* assemblies, right?)

Within the managed world all static variables are scoped by AppDomain, but as you point out this doesn't apply in the unmanaged world.

You could do something complex that forces a load of a unique managed.dll for each app domain, which would result in a new version of the static lib being brought along for the ride. For example, maybe using Assembly.Load with a byte array would work, but I don't know how the CLR will attempt to deal with the collision in types if the same assembly is loaded twice.

I don't think we're getting to the actual issue here - see this DDJ article.

The default value of the loader optimization attribute is SingleDomain, which "causes the AppDomain to load a private copy of each necessary assembly's code". Even if it were one of the Multi domain values "every AppDomain always maintains a distinct copy of static fields".

'managed.dll' is (as its name implies) is a managed assembly. The code in static.lib has been statically compiled (as IL code) into 'managed.dll', so I would expect the same behaviour as Lenik expects....

... unless static.lib is a static export library for an unmanaged DLL. Lenik says this is not the case, so I'm still unsure what's going on here.

Have you tried running in separate processes? A static library should not share memory instances outside of it's own process.

This can be a pain to manage, I know. I'm not sure what your other options would be in this case though.

Edit: After a little looking around I think you could do everything you needed to with the System.Diagnostics.Process class. You would have a lot of options at this point for communication but .NET Remoting or WCF would probably be good and easy choices.

These are the best two articles I found on the subject

The important part is:

RVA-based static fields are process-global. These are restricted to scalars and value types, because we do not want to allow objects to bleed across AppDomain boundaries. That would cause all sorts of problems, especially during AppDomain unloads. Some languages like ILASM and MC++ make it convenient to define RVA-based static fields. Most languages do not.

Ok, so if you control the code in the .lib, I'd try

class CallCountHolder {
   public:
     CallCountHolder(int i) : count(i) {}
     int count;
};

static CallCountHolder cc(0);
int TheFunction()
{
    printf("Function called");
    return cc.count++;
}

Since he said that RVA-based static fields are limited to scalars and value types. An int array might also work.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top