最近搞了個小程序需要用到FFT,可是找來找去都沒有一種C#下可靠地FFT代碼,而且速度也不能令人滿意。發現MIT做過一個很好的C++類庫(http://www.fftw.org/)可以比較好的解決FFT的問題而且運算速度號稱世界最快的(雖然未經驗證,不過的確很快)。於是研究起在C#中調用C++的dll。
1. 在 C#中調用C++首先要包裝,將C++中的方法包裝成C#下的方法,下面這個不是我寫的,但過程都是這個,"libfftw3-3.dll" 文件名,"fftw_malloc"c++函數名,其他參數可以參照VS的幫助選擇。記得 using System.Runtime.InteropServices ,包裝後的方法必須是靜態的,至於如何使用靜態函數就不用多說了吧。
1 /// <summary>
2 /// Contains the Basic Interface FFTW functions for double-precision (double) operations
3 /// </summary>
4 public class FFT
5 {
6 /// <summary>
7 /// Allocates FFTW-optimized unmanaged memory
8 /// </summary>
9 /// <param name="length">Amount to allocate, in bytes</param>
10 /// <returns>Pointer to allocated memory</returns>
11 [DllImport("libfftw3-3.dll",
12 EntryPoint = "fftw_malloc",
13 ExactSpelling = true,
14 CallingConvention = CallingConvention.Cdecl)]
15 public static extern IntPtr malloc(int length);
16 }
2,緊接著關鍵的問題來了,參數怎麼設置?C++大量使用指針而在C#中沒有指針。別急,看到上面個例子中的和函數返回值 IntPtr (using System)了嗎?其實這個就是C#中的地址數據類型。調用中簡單數據類型(如 int等)都可以直接使用的,少數(如string)要特殊處理都很容易,對於復雜的數據類型(類,數組...)就要用到這個 IntPtr 了。
對於C++出現任何類型的指針在C#中都用 IntPtr 代替,但是如何使用呢?這裡還涉及到一個代理 GCHandle (using System.Runtime.InteropServices)他可以幫我們在地址和對象之間進行轉換,我舉個例子。
1 IntPtr plan;
2
3 double[] fin = new double[nx * ny * 2];
4 GCHandle hin = GCHandle.Alloc(fin, GCHandleType.Pinned);
5
6 plan = FFT.dft_2d(nx, ny, hin.AddrOfPinnedObject(), hin.AddrOfPinnedObject(), fftw_direction.Forward, fftw_flags.Estimate);
dft_2d 原型:後面兩個參數是枚舉類型
1 public static extern IntPtr dft_2d(int nx, int ny, IntPtr input, IntPtr output, fftw_direction direction, fftw_flags flags);
FFT.dft_2d 就是封裝好的目標函數,我們就是要調用這個函數。fin是一個復雜對象,hin是我們的代理,Alloc操作就是將對象和內存綁定起來交給系統代理。hin.AddrOfPinnedObject()就是fin對應的地址(IntPtr類型)我們把它當做指針傳給C++,這樣C++會調用fin數據同時如果C++在函數內修改了hin代理的數據C#這邊的數據也會隨之變化,用C++的人很熟悉這種通過傳入指針獲得輸出結果的方式吧。GCHandle 還有一個重要的屬性Target,C#中可以直接通過這個屬性獲得代理的對象,這樣我們就可以把對象換指針也可以把指針換成對象。
簡單說就是1.通過 GCHandle hin = GCHandle.Alloc(fin, GCHandleType.Pinned)綁定對象後通過hin.AddrOfPinnedObject()獲得IntPtr型的地址給C++用
2.通過 hin.Target 獲得對象給C#用
注意:1.Alloc已經將對象綁定到內存了,如果之後再修改 fin 的對象 比如在第五行加入 fin = new double[2]對hin是無影響的,所以賦值操作一定要在Alloc之前執行。記得要為被綁定的對象賦初值,不然C++會報空指針的(new 一下不是很累吧!)。
3 是不是很簡單,不過我還是要在多說幾句。既然用了C++和代理那麼不要忘記釋放內存哦,這點C#程序員很容易搞忘了。C++類庫一般提供釋放方法,GCHandle有Free()函數,謹記之!!!
摘自 Ghost_zhao