具有托管代码问题的静态库

时间:2008-09-16 14:16:47

标签: c# c++ managed

问题(简​​化以使事情更清楚):

    1.有一个静态链接的static.lib,它有一个递增的函数:
    
        extern int CallCount = 0;
        int TheFunction()
        {
            void *p = &CallCount;
            printf("Function called");
            return CallCount++;
        }
    
    2. static.lib链接到托管TheFunction方法的托管C ++ / CLI managed.dll:
    
        int Managed::CallLibFunc()
        {
            return TheFunction();
        }
    
    3.测试应用程序有一个对managed.dll的引用,并创建多个调用C ++ / CLI包装器的域:
    
        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 
        }
    

问题:

基于我在Essential .NET Vol1中看到的Don Box的CLR,我希望val2为零,因为在调用CreateInstanceAndUnwrap时会加载一个全新的managed.dll / static.lib副本。我误解了发生了什么事吗?静态库似乎不尊重appdomain边界,因为它是非托管代码。有没有办法绕过这个问题,而不是创建一个全新的过程来实例化托管?

非常感谢大家!

6 个答案:

答案 0 :(得分:3)

我的预感是,正如您所怀疑的那样,非托管DLL在进程的上下文中而不是在AppDomain的上下文中加载,因此非托管代码中的任何静态数据都在AppDomains之间共享。

This link表示有同样问题的人,仍然没有100%对此进行验证,但可能就是这种情况。

This link是关于使用thunking技巧创建从非托管代码到AppDomain的回调。我不确定这可以帮到你,但也许你会发现这对于创建某种解决方法很有用。

答案 1 :(得分:1)

致电后

Managed c1 = new Managed(); 

您的managed.dll包装器将被加载到您应用程序的主应用程序域中。直到它将来自static.lib的域非托管内容将与其他域共享。 您无需创建单独的进程,而只需确保(在每次调用之前)managed.dll未加载到任何应用程序域中。

与之比较

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)
    }


}
`

重要和:如果只添加一行,JIT编译器将加载managed.dll并且魔法消失。

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(); 


}

如果您不想依赖这些行,您可以创建另一个将引用managed.dll的包装器ManagedIsolated.dll,并在调用后立即使用域卸载在每个单独的域中进行调用。主应用程序仅依赖于ManagedIsolated.dll类型,Managed.dll将不会加载到主应用程序域。

这看起来像一个技巧但可能对某人有用。     `

答案 2 :(得分:0)

简而言之,也许吧。 AppDomains纯粹是一个托管概念。当AppDomain被实例化时,它不会映射到底层DLL的新副本,它可以重用已经在内存中的代码(例如,你不会期望它加载所有System。*程序集的新副本,对吧?)

在托管环境中,所有静态变量都由AppDomain确定范围,但正如您所指出的,这不适用于非托管环境。

你可以做一些复杂的事情,迫使每个应用程序域加载一个唯一的managed.dll,这将导致一个新版本的静态库被带来。例如,也许使用带有字节数组的Assembly.Load可以工作,但我不知道如果同一个程序集被加载两次,CLR将如何尝试处理类型中的碰撞。

答案 3 :(得分:0)

我认为我们不会在这里找到实际问题 - see this DDJ article

加载程序优化属性的默认值是SingleDomain,它“使AppDomain加载每个必需程序集代码的私有副本”。即使它是多域值之一,“每个AppDomain始终维护静态字段的不同副本”。

'managed.dll'(顾名思义)是一个托管程序集。 static.lib中的代码已经静态编译(作为IL代码)到'managed.dll'中,所以我期望与Lenik期望的行为相同......

...除非static.lib是非托管DLL的静态导出库。 Lenik说事实并非如此,所以我仍然不确定这里发生了什么。

答案 4 :(得分:0)

您是否尝试过在单独的进程中运行?静态库不应该在它自己的进程之外共享内存实例。

我知道,这可能是一种难以管理的事情。在这种情况下,我不确定你的其他选择是什么。

编辑:稍微环顾四周后,我认为您可以使用System.Diagnostics.Process课程完成所需的一切。此时你会有很多选择进行沟通,但.NET Remoting或WCF可能是好的和容易的选择。

答案 5 :(得分:0)

这是我在这个主题上找到的最好的两篇文章

重要的部分是:

  

基于RVA的静态字段是流程全局的。这些仅限于标量和值类型,因为我们不希望允许对象在AppDomain边界之间渗透。这会导致各种问题,特别是在AppDomain卸载期间。 ILASM和MC ++等语言可以方便地定义基于RVA的静态字段。大多数语言都没有。

好的,所以如果你控制.lib中的代码,我会尝试

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

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

因为他说基于RVA的静态字段仅限于标量和值类型。一个int数组也可以工作。