我想你混淆指針(IntPtr
或void*
)與手柄(到Windows對象的引用)。不幸的是,句柄可以用IntPtr
類型來表示,這可能會令人困惑。
SafeHandle
是專門用於處理句柄的。一個句柄不是一個指針,而是系統提供的表中的一個索引(排序 - 它意味着不透明)。例如,CreateFile
函數返回HANDLE
,這將適用於SafeFileHandle
。 SafeHandle
類本身是一個Windows句柄的包裝,它將在完成SafeHandle
時釋放Windows句柄。因此,只要您想使用手柄,就必須確保提及SafeHandle
對象。
指針只是一個值。這是內存中對象的地址。 IntPtr
是struct
,並且struct
語義將使其通過值傳遞(即每次將IntPtr
傳遞給實際製作IntPtr
副本的函數)。除非裝箱,GC甚至不知道您的IntPtr
s。
的HandleRef
文檔的重要組成部分,是這樣的:
的HandleRef
構造函數有兩個參數:一個Object
代表的包裝,以及代表非託管手柄的IntPtr
。互操作封送拆收器僅將句柄傳遞給非託管代碼,並確保在調用期間封裝(作爲HandleRef的構造函數的第一個參數傳遞)保持活動狀態。
讓我們以MSDN example:
FileStream fs = new FileStream("HandleRef.txt", FileMode.Open);
HandleRef hr = new HandleRef(fs, fs.SafeFileHandle.DangerousGetHandle());
StringBuilder buffer = new StringBuilder(5);
int read = 0;
// platform invoke will hold reference to HandleRef until call ends
LibWrap.ReadFile(hr, buffer, 5, out read, 0);
Console.WriteLine("Read with struct parameter: {0}", buffer);
LibWrap.ReadFile2(hr, buffer, 5, out read, null);
Console.WriteLine("Read with class parameter: {0}", buffer);
這相當於:
FileStream fs = new FileStream("HandleRef.txt", FileMode.Open);
var hf = fs.SafeFileHandle.DangerousGetHandle();
StringBuilder buffer = new StringBuilder(5);
int read = 0;
LibWrap.ReadFile(hf, buffer, 5, out read, 0);
Console.WriteLine("Read with struct parameter: {0}", buffer);
LibWrap.ReadFile2(hf, buffer, 5, out read, null);
Console.WriteLine("Read with class parameter: {0}", buffer);
// Since we no more have a HandleRef, the following line is needed:
GC.KeepAlive(fs);
但在這種特定情況下更好的解決辦法是:
using(FileStream fs = new FileStream("HandleRef.txt", FileMode.Open))
{
StringBuilder buffer = new StringBuilder(5);
int read = 0;
LibWrap.ReadFile(fs.SafeFileHandle, buffer, 5, out read, 0);
Console.WriteLine("Read with struct parameter: {0}", buffer);
LibWrap.ReadFile2(fs.SafeFileHandle, buffer, 5, out read, null);
Console.WriteLine("Read with class parameter: {0}", buffer);
}
綜上所述up:
對於手柄,使用SafeHandle
並確保它的到達,直到你不需要它了,此時你要麼讓GC收集它,或者你處置它明確(通過調用Dispose()
方法)。
對於指針,可以確保指向的內存在本機代碼可以訪問的整個過程中被鎖定。您可以使用fixed
關鍵字或固定的GCHandle
來實現此目的。
IntPtr
是struct
,如上所述,所以它不被GC收集。
這不是收集到的IntPtr
,而是HWnd
這個對象,暴露了它在此時不再可達並且可被GC收集的對象。完成後,它將處理手柄。
從referenced answer的代碼是:
HWnd a = new HWnd();
IntPtr h = a.Handle;
// The GC can kick in at this point and collect HWnd,
// because it's not referenced after this line.
// If it does, HWnd's finalizer could run.
// If it runs, HWnd will dispose the handle.
// If the handle is disposed, h will hold a freed handle value,
// which is invalid. It still has the same numerical value, but
// Windows will already have freed the underlying object.
// Being a value type, h itself has nothing to do with the GC.
// It's not collectable. Think of it like it were an int.
B.SendMessage(h, ...);
// Adding GC.KeepAlive(a) here solves this issue.
爲對象的可達性規則,對象被視爲不再只要沒有對象更可達參考使用。在前面的示例中,在IntPtr h = a.Handle;
行之後,a
變量沒有其他用法,因此假定該對象不再使用並且可以隨時釋放。 GC.KeepAlive(a)
創建了這種用法,因此該對象保持活動狀態(由於使用跟蹤是由JIT完成的,所以實際情況會更復雜一些,但對於此解釋來說這已足夠好)。
的SafeHandle不不包括安全措施像HandleRef。正確?
好問題。我認爲P/Invoke封送拆收器會在調用期間保持句柄活動,但它的擁有對象(如HWnd
)仍然可以在調用期間明確地處理它,如果它已完成。這是HandleRef
提供的安全措施10,您不會單獨使用SafeHandle
。您需要確保手柄所有者(上例中的HWnd
)保持自己的狀態。
但HandleRef
的主要目標是包裝一個IntPtr
,這是存儲句柄值的舊方法。現在,無論如何,SafeHandle
優於IntPtr
用於手柄存儲。您只需確保句柄所有者不會在P/Invoke調用期間顯式處理句柄。
'IntPtr'在超出範圍之前不能被GC化,但是擁有實際資源的對象可以使指針失效。 – 2014-12-07 21:47:36
@AntonSavin編寫一個方法創建一百萬個IntPtr並調用它一千次 - 你不會有內存不足的例外。爲什麼?因爲他們是GC'd。沒有? – ispiro 2014-12-07 21:49:13
我在談論不同的事情 - 由IntPtr指向的對象可能不再存在,因爲它的所有者已被GC化,但IntPtr可能在此時仍然存在。 – 2014-12-07 21:52:50