通过 P/Invoke 使用回调和堆对象的安全方法
Safe way to use callbacks and heap objects with P/Invoke
以下样本来自Microsoft's documentation:
public delegate bool CallBack(int handle, IntPtr param);
public class LibWrap
{
// passing managed object as LPARAM
// BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
[DllImport("user32.dll")]
public static extern bool EnumWindows(CallBack cb, IntPtr param);
}
public class App
{
public static void Main()
{
Run();
}
[SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)]
public static void Run()
{
TextWriter tw = System.Console.Out;
GCHandle gch = GCHandle.Alloc(tw);
CallBack cewp = new CallBack(CaptureEnumWindowsProc);
// platform invoke will prevent delegate to be garbage collected
// before call ends
LibWrap.EnumWindows(cewp, GCHandle.ToIntPtr(gch));
gch.Free();
}
private static bool CaptureEnumWindowsProc(int handle, IntPtr param)
{
GCHandle gch = GCHandle.FromIntPtr(param);
TextWriter tw = (TextWriter)gch.Target;
tw.WriteLine(handle);
return true;
}
}
有两件事让我很困惑。
首先,GCHandle.Alloc
的文档只谈到防止对象被垃圾回收。如果仅此而已,您就不需要 GCHandle.Alloc
:显然在示例中,在调用 EnumWindows
期间不会收集 tw
- 在功能范围。
这里的问题是需要确保它没有移动。但是 GCHandle.Alloc
的文档并没有谈到这一点。那么这是怎么回事?
其次,代表呢?这个示例中可能没有问题,但是如果委托绑定到一个对象(带有闭包的 lambda 或 class 的非静态方法)怎么办?在那种情况下,一个人也需要照顾代表,对吧?是另一个 GCHandle.Alloc(myDelegate)
还是有更多的事情要考虑?
搬家不是问题。 GCHandle.ToIntPtr
承诺为您提供一个整数值,您可以在以后将其传递给 GCHandle.FromIntPtr
以检索原始句柄。就这样。如果您需要停止对象在内存中的移动,则必须将其固定。但是您实际上不需要固定对象,您只需要停止收集它,并能够在您的回调中检索它。
委托的生命周期在这里不是问题,因为 p/invoke 框架将确保它不会在对 EnumWindows
的外部调用期间被收集,如评论中所述。如果您将委托传递给非托管代码,并且该非托管代码持有对委托的引用,那么您还有工作要做。您必须确保委托比对它的非托管引用更有效。
以下样本来自Microsoft's documentation:
public delegate bool CallBack(int handle, IntPtr param);
public class LibWrap
{
// passing managed object as LPARAM
// BOOL EnumWindows(WNDENUMPROC lpEnumFunc, LPARAM lParam);
[DllImport("user32.dll")]
public static extern bool EnumWindows(CallBack cb, IntPtr param);
}
public class App
{
public static void Main()
{
Run();
}
[SecurityPermission(SecurityAction.Demand, UnmanagedCode=true)]
public static void Run()
{
TextWriter tw = System.Console.Out;
GCHandle gch = GCHandle.Alloc(tw);
CallBack cewp = new CallBack(CaptureEnumWindowsProc);
// platform invoke will prevent delegate to be garbage collected
// before call ends
LibWrap.EnumWindows(cewp, GCHandle.ToIntPtr(gch));
gch.Free();
}
private static bool CaptureEnumWindowsProc(int handle, IntPtr param)
{
GCHandle gch = GCHandle.FromIntPtr(param);
TextWriter tw = (TextWriter)gch.Target;
tw.WriteLine(handle);
return true;
}
}
有两件事让我很困惑。
首先,GCHandle.Alloc
的文档只谈到防止对象被垃圾回收。如果仅此而已,您就不需要 GCHandle.Alloc
:显然在示例中,在调用 EnumWindows
期间不会收集 tw
- 在功能范围。
这里的问题是需要确保它没有移动。但是 GCHandle.Alloc
的文档并没有谈到这一点。那么这是怎么回事?
其次,代表呢?这个示例中可能没有问题,但是如果委托绑定到一个对象(带有闭包的 lambda 或 class 的非静态方法)怎么办?在那种情况下,一个人也需要照顾代表,对吧?是另一个 GCHandle.Alloc(myDelegate)
还是有更多的事情要考虑?
搬家不是问题。 GCHandle.ToIntPtr
承诺为您提供一个整数值,您可以在以后将其传递给 GCHandle.FromIntPtr
以检索原始句柄。就这样。如果您需要停止对象在内存中的移动,则必须将其固定。但是您实际上不需要固定对象,您只需要停止收集它,并能够在您的回调中检索它。
委托的生命周期在这里不是问题,因为 p/invoke 框架将确保它不会在对 EnumWindows
的外部调用期间被收集,如评论中所述。如果您将委托传递给非托管代码,并且该非托管代码持有对委托的引用,那么您还有工作要做。您必须确保委托比对它的非托管引用更有效。