问题(简化以使事情更清楚):
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边界,因为它是非托管代码。有没有办法绕过这个问题,而不是创建一个全新的过程来实例化托管?
非常感谢大家!
答案 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数组也可以工作。