Problem (simplified to make things clearer):
问题(简化以使事情更清楚):
-
1. there is one statically-linked static.lib that has a function that increments:
extern int CallCount = 0;
int TheFunction()
{
void *p = &CallCount;
printf("Function called");
return CallCount++;
}
2. static.lib is linked into a managed C++/CLI managed.dll that wraps TheFunction method:
int Managed::CallLibFunc()
{
return TheFunction();
}
3. Test app has a reference to managed.dll and creates multiple domains that call C++/CLI wrapper:
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
}
Question:
Based on what I have read in Essential .NET Vol1 The CLR by Don Box, I would expect val2 to be zero since a brand new copy of managed.dll/static.lib is loaded when CreateInstanceAndUnwrap is called. Am I misunderstanding what is happening? The static library does not seem to be respecting the appdomain boundaries since it's unmanaged code. Is there a way to get around this issue other than by creating a brand new process for instantiating Managed?
根据我在Don Box的Essential .NET Vol1 The CLR中所读到的内容,我希望val2为零,因为在调用CreateInstanceAndUnwrap时会加载一个全新的managed.dll / static.lib副本。我误解了发生了什么事吗?静态库似乎不尊重appdomain边界,因为它是非托管代码。有没有办法解决这个问题,而不是创建一个全新的实例化托管流程?
Thank you very much everyone!
非常感谢大家!
6 个解决方案
#1
3
My hunch was that, as you suspected, unmanaged DLLs are loaded in the context of the process and not in the context of the AppDomain, so any static data in unmanaged code is shared among AppDomains.
我的预感是,正如您所怀疑的那样,非托管DLL在进程的上下文中而不是在AppDomain的上下文中加载,因此非托管代码中的任何静态数据都在AppDomains之间共享。
This link shows someone with the same problem you have, still not 100% verification of this, but probably this is the case.
此链接显示您遇到同样问题的人,仍未100%验证此问题,但可能就是这种情况。
This link is about creating a callback from unmanaged code into an AppDomain using a thunking trick. I'm not sure this can help you but maybe you'll find this useful to create some kind of a workaround.
此链接是关于使用thunking技巧创建从非托管代码到AppDomain的回调。我不确定这可以帮到你,但也许你会发现这对于创建某种解决方法很有用。
#2
1
After you call
打电话后
Managed c1 = new Managed();
Your managed.dll wrapper will be loaded into main app domain of you application. Till it will be there domain unmanaged stuff from static.lib will be shared with other domains. Instead of creating separate process you just need to be sure (before each call) that managed.dll is not loaded into any application domain.
您的managed.dll包装器将加载到您的应用程序的主应用程序域中。直到它将来自static.lib的域非托管内容将与其他域共享。您需要确保(在每次调用之前)managed.dll未加载到任何应用程序域中,而不是创建单独的进程。
Compare with that
与此相比
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)
}
}
`
IMPORTANT and : If you add just one line JIT compiler will load managed.dll and the magic disappears.
重要的是:如果只添加一行,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();
}
If you don't want to depend on such lines you can create another one wrapper ManagedIsolated.dll that will reference managed.dll and will make each call in separate domain with domain unload just after the call. Main application will depend only on ManagedIsolated.dll types and Managed.dll will not be loaded into main app domain.
如果您不想依赖这些行,您可以创建另一个将引用managed.dll的包装器ManagedIsolated.dll,并在调用后立即在具有域卸载的单独域中进行每个调用。主应用程序将仅依赖于ManagedIsolated.dll类型,Managed.dll将不会加载到主应用程序域中。
That looks like a trick but may be it will be usefull for somebody. `
这看起来像一个技巧但可能对某人有用。 `
#3
0
In short, maybe. AppDomains are purely a managed concept. When an AppDomain is instantiated it doesn't map in new copies of the underlying DLLs, it can reuse the code already in memory (for example, you wouldn't expect it to load up new copies of all the System.* assemblies, right?)
总之,也许吧。 AppDomains纯粹是一个托管概念。当AppDomain被实例化时,它不会映射底层DLL的新副本,它可以重用已经在内存中的代码(例如,你不会期望它加载所有System。*程序集的新副本,对吧?)
Within the managed world all static variables are scoped by AppDomain, but as you point out this doesn't apply in the unmanaged world.
在托管世界中,所有静态变量都由AppDomain限定,但正如您所指出的,这不适用于非托管世界。
You could do something complex that forces a load of a unique managed.dll for each app domain, which would result in a new version of the static lib being brought along for the ride. For example, maybe using Assembly.Load with a byte array would work, but I don't know how the CLR will attempt to deal with the collision in types if the same assembly is loaded twice.
你可以做一些复杂的事情,迫使每个应用程序域加载一个唯一的managed.dll,这将导致一个新版本的静态库被带来。例如,也许使用带有字节数组的Assembly.Load可以工作,但我不知道如果同一个程序集加载两次,CLR将如何尝试处理类型中的碰撞。
#4
0
I don't think we're getting to the actual issue here - see this DDJ article.
我不认为我们在这里遇到实际问题 - 请参阅这篇DDJ文章。
The default value of the loader optimization attribute is SingleDomain, which "causes the AppDomain to load a private copy of each necessary assembly's code". Even if it were one of the Multi domain values "every AppDomain always maintains a distinct copy of static fields".
加载程序优化属性的默认值是SingleDomain,它“使AppDomain加载每个必需程序集代码的私有副本”。即使它是多域值之一,“每个AppDomain始终维护静态字段的不同副本”。
'managed.dll' is (as its name implies) is a managed assembly. The code in static.lib has been statically compiled (as IL code) into 'managed.dll', so I would expect the same behaviour as Lenik expects....
'managed.dll'(顾名思义)是一个托管程序集。 static.lib中的代码已经静态编译(作为IL代码)到'managed.dll'中,所以我期望与Lenik期望的行为相同....
... unless static.lib is a static export library for an unmanaged DLL. Lenik says this is not the case, so I'm still unsure what's going on here.
...除非static.lib是非托管DLL的静态导出库。 Lenik说事实并非如此,所以我仍然不确定这里发生了什么。
#5
0
Have you tried running in separate processes? A static library should not share memory instances outside of it's own process.
你试过在不同的进程中运行吗?静态库不应该在它自己的进程之外共享内存实例。
This can be a pain to manage, I know. I'm not sure what your other options would be in this case though.
我知道,这可能是一种难以管理的事情。我不确定在这种情况下你的其他选择是什么。
Edit: After a little looking around I think you could do everything you needed to with the System.Diagnostics.Process class. You would have a lot of options at this point for communication but .NET Remoting or WCF would probably be good and easy choices.
编辑:稍微环顾四周后,我认为您可以使用System.Diagnostics.Process类完成所需的一切。此时您可以有很多选项进行通信,但.NET Remoting或WCF可能是很好的选择。
#6
0
These are the best two articles I found on the subject
这是我在这个主题上找到的最好的两篇文章
- http://blogs.msdn.com/cbrumme/archive/2003/04/15/51317.aspx
- http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx
The important part is:
重要的是:
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.
基于RVA的静态字段是流程全局的。这些仅限于标量和值类型,因为我们不希望允许对象在AppDomain边界之间渗透。这会导致各种问题,特别是在AppDomain卸载期间。 ILASM和MC ++等语言可以方便地定义基于RVA的静态字段。大多数语言没有。
Ok, so if you control the code in the .lib, I'd try
好的,所以如果你控制.lib中的代码,我会试试
class CallCountHolder {
public:
CallCountHolder(int i) : count(i) {}
int count;
};
static CallCountHolder cc(0);
int TheFunction()
{
printf("Function called");
return cc.count++;
}
Since he said that RVA-based static fields are limited to scalars and value types. An int array might also work.
因为他说基于RVA的静态字段仅限于标量和值类型。 int数组也可以工作。
#1
3
My hunch was that, as you suspected, unmanaged DLLs are loaded in the context of the process and not in the context of the AppDomain, so any static data in unmanaged code is shared among AppDomains.
我的预感是,正如您所怀疑的那样,非托管DLL在进程的上下文中而不是在AppDomain的上下文中加载,因此非托管代码中的任何静态数据都在AppDomains之间共享。
This link shows someone with the same problem you have, still not 100% verification of this, but probably this is the case.
此链接显示您遇到同样问题的人,仍未100%验证此问题,但可能就是这种情况。
This link is about creating a callback from unmanaged code into an AppDomain using a thunking trick. I'm not sure this can help you but maybe you'll find this useful to create some kind of a workaround.
此链接是关于使用thunking技巧创建从非托管代码到AppDomain的回调。我不确定这可以帮到你,但也许你会发现这对于创建某种解决方法很有用。
#2
1
After you call
打电话后
Managed c1 = new Managed();
Your managed.dll wrapper will be loaded into main app domain of you application. Till it will be there domain unmanaged stuff from static.lib will be shared with other domains. Instead of creating separate process you just need to be sure (before each call) that managed.dll is not loaded into any application domain.
您的managed.dll包装器将加载到您的应用程序的主应用程序域中。直到它将来自static.lib的域非托管内容将与其他域共享。您需要确保(在每次调用之前)managed.dll未加载到任何应用程序域中,而不是创建单独的进程。
Compare with that
与此相比
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)
}
}
`
IMPORTANT and : If you add just one line JIT compiler will load managed.dll and the magic disappears.
重要的是:如果只添加一行,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();
}
If you don't want to depend on such lines you can create another one wrapper ManagedIsolated.dll that will reference managed.dll and will make each call in separate domain with domain unload just after the call. Main application will depend only on ManagedIsolated.dll types and Managed.dll will not be loaded into main app domain.
如果您不想依赖这些行,您可以创建另一个将引用managed.dll的包装器ManagedIsolated.dll,并在调用后立即在具有域卸载的单独域中进行每个调用。主应用程序将仅依赖于ManagedIsolated.dll类型,Managed.dll将不会加载到主应用程序域中。
That looks like a trick but may be it will be usefull for somebody. `
这看起来像一个技巧但可能对某人有用。 `
#3
0
In short, maybe. AppDomains are purely a managed concept. When an AppDomain is instantiated it doesn't map in new copies of the underlying DLLs, it can reuse the code already in memory (for example, you wouldn't expect it to load up new copies of all the System.* assemblies, right?)
总之,也许吧。 AppDomains纯粹是一个托管概念。当AppDomain被实例化时,它不会映射底层DLL的新副本,它可以重用已经在内存中的代码(例如,你不会期望它加载所有System。*程序集的新副本,对吧?)
Within the managed world all static variables are scoped by AppDomain, but as you point out this doesn't apply in the unmanaged world.
在托管世界中,所有静态变量都由AppDomain限定,但正如您所指出的,这不适用于非托管世界。
You could do something complex that forces a load of a unique managed.dll for each app domain, which would result in a new version of the static lib being brought along for the ride. For example, maybe using Assembly.Load with a byte array would work, but I don't know how the CLR will attempt to deal with the collision in types if the same assembly is loaded twice.
你可以做一些复杂的事情,迫使每个应用程序域加载一个唯一的managed.dll,这将导致一个新版本的静态库被带来。例如,也许使用带有字节数组的Assembly.Load可以工作,但我不知道如果同一个程序集加载两次,CLR将如何尝试处理类型中的碰撞。
#4
0
I don't think we're getting to the actual issue here - see this DDJ article.
我不认为我们在这里遇到实际问题 - 请参阅这篇DDJ文章。
The default value of the loader optimization attribute is SingleDomain, which "causes the AppDomain to load a private copy of each necessary assembly's code". Even if it were one of the Multi domain values "every AppDomain always maintains a distinct copy of static fields".
加载程序优化属性的默认值是SingleDomain,它“使AppDomain加载每个必需程序集代码的私有副本”。即使它是多域值之一,“每个AppDomain始终维护静态字段的不同副本”。
'managed.dll' is (as its name implies) is a managed assembly. The code in static.lib has been statically compiled (as IL code) into 'managed.dll', so I would expect the same behaviour as Lenik expects....
'managed.dll'(顾名思义)是一个托管程序集。 static.lib中的代码已经静态编译(作为IL代码)到'managed.dll'中,所以我期望与Lenik期望的行为相同....
... unless static.lib is a static export library for an unmanaged DLL. Lenik says this is not the case, so I'm still unsure what's going on here.
...除非static.lib是非托管DLL的静态导出库。 Lenik说事实并非如此,所以我仍然不确定这里发生了什么。
#5
0
Have you tried running in separate processes? A static library should not share memory instances outside of it's own process.
你试过在不同的进程中运行吗?静态库不应该在它自己的进程之外共享内存实例。
This can be a pain to manage, I know. I'm not sure what your other options would be in this case though.
我知道,这可能是一种难以管理的事情。我不确定在这种情况下你的其他选择是什么。
Edit: After a little looking around I think you could do everything you needed to with the System.Diagnostics.Process class. You would have a lot of options at this point for communication but .NET Remoting or WCF would probably be good and easy choices.
编辑:稍微环顾四周后,我认为您可以使用System.Diagnostics.Process类完成所需的一切。此时您可以有很多选项进行通信,但.NET Remoting或WCF可能是很好的选择。
#6
0
These are the best two articles I found on the subject
这是我在这个主题上找到的最好的两篇文章
- http://blogs.msdn.com/cbrumme/archive/2003/04/15/51317.aspx
- http://blogs.msdn.com/cbrumme/archive/2003/06/01/51466.aspx
The important part is:
重要的是:
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.
基于RVA的静态字段是流程全局的。这些仅限于标量和值类型,因为我们不希望允许对象在AppDomain边界之间渗透。这会导致各种问题,特别是在AppDomain卸载期间。 ILASM和MC ++等语言可以方便地定义基于RVA的静态字段。大多数语言没有。
Ok, so if you control the code in the .lib, I'd try
好的,所以如果你控制.lib中的代码,我会试试
class CallCountHolder {
public:
CallCountHolder(int i) : count(i) {}
int count;
};
static CallCountHolder cc(0);
int TheFunction()
{
printf("Function called");
return cc.count++;
}
Since he said that RVA-based static fields are limited to scalars and value types. An int array might also work.
因为他说基于RVA的静态字段仅限于标量和值类型。 int数组也可以工作。