具有托管代码问题的静态库
问题(简化以使情况更清楚):
1.有一个静态链接的static.lib,其功能递增:
extern int CallCount = 0; int TheFunction() { void *p = &CallCount; printf("Function called"); return CallCount++; }
- static.lib链接到包含TheFunction方法的托管C ++ / CLImanaged.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的边界。除了通过创建用于实例化Managed的全新流程之外,是否还有其他方法可以解决此问题?
非常感谢大家!
解决方案
回答
我的直觉是,正如我们所怀疑的那样,非托管DLL是在进程的上下文中而不是在AppDomain的上下文中加载的,因此非托管代码中的任何静态数据都在AppDomain之间共享。
此链接显示的人遇到的问题与我们相同,但仍不能100%验证,但这可能是这种情况。
该链接是关于使用重击技巧将非托管代码回调到AppDomain中。我不确定这是否可以,但是也许我们会发现这对于创建某种解决方法很有用。
回答
简而言之,也许。 AppDomains纯粹是一个托管的概念。实例化AppDomain时,它不会映射到基础DLL的新副本中,它可以重用内存中已有的代码(例如,我们不希望它加载所有System。*程序集的新副本。 ?)
在托管环境中,所有静态变量都由AppDomain限定范围,但是正如我们所指出的,这不适用于非托管环境。
我们可以做一些复杂的事情,迫使每个应用程序域都加载一个唯一的managed.dll,这将导致带来新版本的静态库。例如,也许使用带有字节数组的Assembly.Load可以工作,但是我不知道如果两次加载相同的程序集,CLR将如何尝试处理类型冲突。
回答
我不认为我们要解决实际问题,请参阅此DDJ文章。
加载程序优化属性的默认值为SingleDomain,它"使AppDomain加载每个必要程序集代码的私有副本"。即使它是多域值之一,"每个AppDomain始终维护静态字段的唯一副本"。
顾名思义," managed.dll"是一个托管程序集。 static.lib中的代码已被静态编译(作为IL代码)到" managed.dll"中,因此我期望与Lenik期望的行为相同。
...除非static.lib是非托管DLL的静态导出库。莱尼克说情况并非如此,所以我仍然不确定这里发生了什么。
回答
我们是否尝试过在单独的进程中运行?静态库不应在自身进程之外共享内存实例。
我知道这可能很难管理。我不确定在这种情况下我们会选择其他什么方法。
编辑:经过一番环顾后,我认为我们可以使用System.Diagnostics.Process类来完成所需的一切。此时,我们将有很多选择进行通讯,但是.NET Remoting或者WCF可能是不错的选择。
回答
这是我在该主题上找到的最好的两篇文章
- http://blogs.msdn.com/cbrumme/archive/2003/04/15/51317.aspx
- http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx
重要的部分是:
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.
好的,所以如果我们控制.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数组也可能起作用。
回答
致电后
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将不会加载到主应用程序域中。
这看起来像个把戏,但可能对某些人有用。
`