2015-09-07 48 views
2

我目前正在爲一些本地C++代碼編寫一組包裝器。在包裝器中,我將託管數組作爲輸入並打算使用數組的內容來調用本機C++構造函數。出於某種原因,我似乎需要釘住數組或從構造函數調用中分別提取值。這裏是我的意思的一些例子。訪問託管陣列並鎖定

爲原生型的構造具有這樣的類型簽名:

NativeType(const double &d) 

初步嘗試:

public ref class ExampleWrapper 
{ 
    ExampleWrapper(array<double>^in) 
    { 
     for(int i= 0; i< in->Length; ++i) 
     { 
      NativeType test(in[i]); 
     } 
    } 
} 

這回來了一個錯誤,將無法編譯。接下來我試過這個

public ref class ExampleWrapper 
{ 
    ExampleWrapper(array<double>^in) 
    { 
     for(int i= 0; i< in->Length; ++i) 
     { 
      double d = in[i]; 
      NativeType test(d); 
     } 
    } 
} 

這似乎工作正常。最後,我試圖釘住這樣的陣列:

public ref class ExampleWrapper 
{ 
    ExampleWrapper(array<double>^in) 
    { 
     pin_ptr<double> pin_in = &in[0]; 
     for(int i= 0; i< in->Length; ++i) 
     { 
      NativeType test(pin_in[i]); 
     } 
    } 
} 

這似乎也正常工作。

我想知道的是爲什麼第一個例子不起作用,而另外兩個似乎工作正常。此外,我想知道什麼是首選的方法使用。

回答

1

爲什麼第一個例子失敗?

它會失敗,因爲您要求編譯器獲取託管變量(double數組中的單個double)的地址並將其傳遞給非託管代碼,但這是非法的。

爲什麼第二個示例通過?

因爲d不在託管內存中,所以它是包裝代碼中的局部變量。您可以從託管內存數組初始化它,因爲您只取值(double),而不是地址。

爲什麼第三個例子通過?

由於您將數組固定在固定內存位置,現在可以安全地將其傳遞到非託管代碼中。 (P.S.,我想你的意思是pin_ptr<double> pin_in = &in[0];

+0

那麼,構造函數需要引用的問題呢?我注意到,我進一步下了類似的例子,其中構造函數具有類型簽名NativeType(int i),它似乎編譯得很好,與第一個例子相當。 – Taus

+0

是的,這將工作得很好,因爲你會傳遞一個原始值,而不是對象/內存地址。 – Amit

2

in數組是一個管理對象,它沒有一個穩定的地址。在任何可能的時刻,垃圾收集器可以在壓縮堆的同時踢入並移動對象。這種情況發生的可能性不大,畢竟你調用的是本地代碼,所以GC沒有理由觸發一個集合。然而,它不是零,程序中的其他線程可能同時分配。

當發生這種情況,然後災難發生。這是一個const double所以至少本機代碼不能破壞GC堆。然而,它讀取的實際雙倍值是隨機的。

C++/CLI編譯器可以檢測到這種可能的不幸和抱怨。您必須爲雙重&提供穩定的地址。將它複製到局部變量當然是最簡單的方法,它存儲在堆棧中,並且這些變量永遠不會移動。使用pin_ptr <>也是一個很好的解決方法,它是一種廉價的方式來固定託管對象。它僅在表中設置抖動產生的位,以幫助GC發現存儲在本地變量和CPU寄存器中的引用。只有在發生實際收集時,它纔會收取任何費用,CLR在執行堆棧步行以查找參考時發現它。


傳遞一個雙&是相當奇怪的,非常重要的,你看看本機代碼。期望你會發現它存儲引用而不是存儲該值。這是一個非常大的問題,您發現的解決方法僅適用於構造函數調用的生命週期。本機代碼將在稍後使用該引用時讀取垃圾信息。

然後,您必須創建保持有效超越了構造函數調用一個穩定的參考,使用本機運營商的包裝,並將其存儲在一個領域。在檢查本機代碼不能再引用它之後再次清理它在終結器中。這通常只會在您的終結器也銷燬本機類對象時纔會很好地結束。

+0

構造函數中的實際類型是模板類型類型。在這種情況下,它恰好決定加倍。 – Taus

+0

這不會以任何方式改變我的答案。 –