【问题标题】:Static libraries with managed code issue具有托管代码问题的静态库
【发布时间】:2008-09-16 14:16:47
【问题描述】:

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

    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 边界,因为它是非托管代码。除了创建一个用于实例化 Managed 的​​全新流程之外,还有其他方法可以解决此问题吗?

非常感谢大家!

【问题讨论】:

  • 几年前我们在这里尝试过同样的事情,结果相同。我们只得到一个非托管代码的实例。

标签: c# c++ managed


【解决方案1】:

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

This link 显示与您有相同问题的人,仍然不是 100% 验证,但可能是这种情况。

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

【讨论】:

    【解决方案2】:

    打电话后

    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 不会加载到主应用程序域中。

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

        【解决方案4】:

        我认为我们在这里没有解决实际问题 - see this DDJ article

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

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

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

        【讨论】:

        • LoaderOptimizationAttribute 的 MSDN 文档指出“此属性只是对加载程序的提示,不会影响程序行为。”所以无论它做什么,它都应该能够改变观察到的行为,所以不能在这里发挥作用。
        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

          重要的部分是:

          基于 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 数组也可以工作。

          【讨论】:

            猜你喜欢
            • 2011-12-23
            • 2011-08-31
            • 1970-01-01
            • 1970-01-01
            • 2014-02-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多