2013-06-18 61 views
1

我們有一個.NET(c#)封裝器,用於一個相當大的C API。在這個包裝器中,用戶可以提供從本機代碼重複調用的回調。將回調從.NET傳遞給C代碼,有效的代碼或不是?

回調看起來是這樣的:

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] 
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle); 

用戶可以輸入通過回調:

public void SetLoggingCallback(LogCallBack lcallback, 
           IntPtr  handle) 
{ 
    SlmReturn ret = (SlmReturn)Native.SlmSetLoggingCallbackW(ModelPtr, 
                  lcallback, 
                  handle); 

    if(ret != SlmReturn.SlmRetOk) 
    { 
    throw new SlmException(ret,ret.ToString()); 
    } 
} 

其最終調用:

[DllImport("sulum20.dll",CallingConvention = CallingConvention.StdCall , CharSet = CharSet.Unicode)] 
public static extern int SlmSetLoggingCallbackW(IntPtr  ModelPtr, 
               LogCallBack lcallback, 
               IntPtr  handle); 

一個用戶調用的回調例程以下列方式(簡化):

string temp; 
    SetLoggingCallback((str, wtsr, handle) => { temp = wtsr; Console.WriteLine(temp); }, IntPtr.Zero); 

這會導致應用程序在某些平臺上崩潰而在其他平臺上崩潰。

所以我的問題仍然存在,這是否有效?

從C/C++世界的到來,有一兩件事讓我爲難:

它是有效的代碼訪問的回調(即「字符串TEMP」)的範圍之外創建類的實例?我的意思是我的猜測是編組需要將它們作爲輸入/輸出參數來控制它。我考慮通過使用句柄參數來嘗試marshalling on my own,但不確定這是否過度殺傷。

更新1:

也許這正是我需要的GCHandle

更新2:

回調從本機代碼ALA稱爲:

 if(logcallback_ != NULL) 
     { 
     (logcallback_)(cstream_,chbuf_,logcallbackhandle_); 
     } 

更新3:

隨着

string temp; 
       LogCallBack logCallback = (str, wtsr, handle) => 
              { 
               temp = "Hello"; 
              }; 
       smodel.SetLoggingCallback(logCallback, IntPtr.Zero); 

它給出了同樣的崩潰。

UPDATE 4:

typedef void (ISLMCALL *SlmLogCallBackW)(enum SlmStream,const wchar_t*, void *handle); 

UPDATE 5

還試圖與失敗:

var logCallback = new LogCallBack(TargetMethod); 
       smodel.SetLoggingCallback(logCallback, IntPtr.Zero); 

    private string _test; 

    private void TargetMethod(SlmStream str, IntPtr wtsr, IntPtr handle) 
    { 
     _test = "Hello"; 
    } 

SOLUTION:

使用GCHandler保持委託活着,所以它不是垃圾收集。

回答

1

讓我們看看你的委託:

所有的
[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] 
public delegate void LogCallBack(SlmStream str, string wtsr, IntPtr handle); 

首先,我不知道是什麼SlmStream是,所以我不能它是如何整理髮表評論。這當然是一個可能的失敗向量。

IntPtr句柄沒有問題。大概在本地方是某種指針,或許是void*

代碼最明顯的問題就是字符串參數wtsr。編組人員假定您將傳遞一個指向以空字符結尾的寬字符數組的指針。用原生術語wchar_t*。然而,編組人員也承擔着破壞本土記憶的責任。它假定內存是從COM堆分配的,因此調用CoTaskMemFree

我認爲你的本地代碼可能不會在COM堆上分配空終止的字符數組。這肯定會解釋某些平臺上的崩潰,但不是其他平臺。

這裏是解決這一問題的幾種方法:

  1. 獲取本機代碼分配關閉的COM堆,讓託管代碼釋放它。
  2. 如果本機代碼分配和釋放,則在您的代理中將參數聲明爲IntPtr,並在委託中調用Marshal.PtrToStringUni將其轉換爲受管字符串。

您的意見告訴我,選擇2是正確的解決方案。你代表應該是:

[UnmanagedFunctionPointer(CallingConvention.StdCall, CharSet = CharSet.Unicode)] 
public delegate void LogCallBack(SlmStream str, IntPtr wtsr, IntPtr handle); 

並實施這樣的:

(str, wtsr, handle) => { Console.WriteLine(Marshal.PtrToStringUni(wtsr)); } 

,你顯然需要做的就是確保該委託更讓其他的事情,所以它仍然當本地代碼調用它時存在。

+0

謝謝。我不知道編組實際上也會釋放「string wtsr」,這當然不是我想要的。這是一組wchars,但分配在本地並在那裏釋放。 –

+0

順便說一句:SlmStream只是一個簡單的枚舉,沒有危險。 –

+0

好的,我更新了我認爲合適的解決方案 –

相關問題