2014-12-07 32 views
4

在閱讀了關於這兩個,包括在這個網站上的高票表決答案後,我仍然覺得有點不清楚。SafeHandle和HandleRef

因爲我的問題的理解可能是錯誤的,我將首先發布的是什麼,我知道這樣我就可以,如果我錯了,糾正大綱,然後發佈我的具體問題:編碼時

有時託管代碼,我們必須將地址傳遞給非託管代碼。這是一個IntPtr的用途。但是,我們試圖確保兩個相反的事情:a)將GC的指針(到一個地址)保留。 b)在不需要時釋放它(即使我們忘記明確這麼做)。

HandleRef做第一個,SafeHandle做第二個。 (我實際上在這裏指的是SafeHandle的列表here)。

我的問題:

  1. 很明顯,我想確認兩者。那麼我該如何獲得 的功能? (這是最主要的問題。)
  2. here和從MSDN(「呼叫管理對象」),它看起來像只someObject.Handle可能 被GC'd,而獨立IntPtr不會。但IntPtr本身 管理!
  3. IntPtr在超出範圍之前如何GC'd(如提到的 here)?
+0

'IntPtr'在超出範圍之前不能被GC化,但是擁有實際資源的對象可以使指針失效。 – 2014-12-07 21:47:36

+0

@AntonSavin編寫一個方法創建一百萬個IntPtr並調用它一千次 - 你不會有內存不足的例外。爲什麼?因爲他們是GC'd。沒有? – ispiro 2014-12-07 21:49:13

+0

我在談論不同的事情 - 由IntPtr指向的對象可能不再存在,因爲它的所有者已被GC化,但IntPtr可能在此時仍然存在。 – 2014-12-07 21:52:50

回答

4

我想你混淆指針(IntPtrvoid*)與手柄(到Windows對象的引用)。不幸的是,句柄可以用IntPtr類型來表示,這可能會令人困惑。

SafeHandle是專門用於處理句柄的。一個句柄不是一個指針,而是系統提供的表中的一個索引(排序 - 它意味着不透明)。例如,CreateFile函數返回HANDLE,這將適用於SafeFileHandleSafeHandle類本身是一個Windows句柄的包裝,它將在完成SafeHandle時釋放Windows句柄。因此,只要您想使用手柄,就必須確保提及SafeHandle對象。

指針只是一個值。這是內存中對象的地址。 IntPtrstruct,並且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:

  1. 對於手柄,使用SafeHandle並確保它的到達,直到你不需要它了,此時你要麼讓GC收集它,或者你處置它明確(通過調用Dispose()方法)。

    對於指針,可以確保指向的內存在本機代碼可以訪問的整個過程中被鎖定。您可以使用fixed關鍵字或固定的GCHandle來實現此目的。

  2. IntPtrstruct,如上所述,所以它不被GC收集。

  3. 這不是收集到的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調用期間顯式處理句柄。

+0

謝謝。我仍然試圖理解答案。但是,爲什麼你寫「h將會有一個無效的價值」 - 你不只是說它是按價值傳遞的?現在收集'a'後,沒有什麼會改變。它應該仍然有效。 – ispiro 2014-12-07 22:17:50

+0

我是否正確理解對3的回答是,即使GC沒有「正式」超出範圍,GC看到它們再也不會被訪問時,它們可以GC_d了? – ispiro 2014-12-07 22:19:50

+0

'所以你必須確保只要你想使用句柄就保持對SafeHandle對象的引用。 - 所以你說我正確理解SafeHandle並不包含像HandleRef這樣的安全措施。正確? – ispiro 2014-12-07 22:22:33

相關問題