4

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

    1. 有一个静态链接的 static.lib,它有一个递增的函数:
    
        extern int CallCount = 0;
        int TheFunction()
        {
            void *p = &CallCount;
            printf("Function called");
            return CallCount++;
        }
    
    2. static.lib 链接到一个托管的 C++/CLI managed.dll 中,它包装了 TheFunction 方法:
    
        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 
        }
    

问题:

根据我在 Don Box 的 Essential .NET Vol1 The CLR 中所读到的内容,我希望 val2 为零,因为在调用 CreateInstanceAndUnwrap 时会加载 managed.dll/static.lib 的全新副本。我误解了正在发生的事情吗?静态库似乎不尊重 appdomain 边界,因为它是非托管代码。除了创建一个用于实例化托管的全新流程之外,还有其他方法可以解决此问题吗?

非常感谢大家!

4

6 回答 6

3

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

此链接显示了与您遇到相同问题的人,仍然没有 100% 验证此问题,但可能是这种情况。

此链接是关于使用 thunking 技巧从非托管代码创建回调到 AppDomain。我不确定这对您有帮助,但也许您会发现这对创建某种解决方法很有用。

于 2008-09-16T14:41:35.253 回答
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(); 


}

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

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

于 2009-03-28T18:49:57.933 回答
0

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

在托管世界中,所有静态变量都由 AppDomain 限定,但正如您所指出的,这不适用于非托管世界。

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

于 2008-09-16T14:47:28.093 回答
0

我不认为我们在这里解决了实际问题 -请参阅这篇 DDJ 文章

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

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

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

于 2008-09-16T15:00:02.480 回答
0

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

我知道,这可能很难管理。不过,我不确定在这种情况下您的其他选择是什么。

编辑:环顾四周后,我认为您可以使用System.Diagnostics.Process类完成所需的一切。此时您将有很多用于通信的选项,但.NET Remoting或 WCF 可能是不错且简单的选择。

于 2008-09-18T14:38:32.420 回答
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 数组也可以工作。

于 2008-10-01T14:02:18.137 回答