C++通過Callback向C#傳遞數據

来源:https://www.cnblogs.com/tewuapple/archive/2018/01/05/8205893.html
-Advertisement-
Play Games

現在比較流行C#與C++融合:C#做GUI,開發效率高,C++做運算,運行效率高,二者兼得。 但是C++與C#必然存在數據交互,C#與C++dll的數據交互從來都是一個讓人頭疼的問題。 從調用方式看也有兩種情況: 1、C#調用C++函數 這種情況用的比較多,數據流向可以是C#流向C++,通過參數將數 ...


現在比較流行C#與C++融合:C#做GUI,開發效率高,C++做運算,運行效率高,二者兼得。

但是C++與C#必然存在數據交互,C#與C++dll的數據交互從來都是一個讓人頭疼的問題。

從調用方式看也有兩種情況:

1、C#調用C++函數

這種情況用的比較多,數據流向可以是C#流向C++,通過參數將數據傳遞給C++(如:SetData(double[] data));也可以是C++流向C#(如:GetData(double[] data))。

2、C++ Callback

這種情況是C++中通過Callback的方式調用C#代碼,類似於C++做過一些處理後向C#發送事件,事件可以攜帶數據(如處理後的數據)。則C++中定義函數指針的方式是:

typedef  void(*Render)(double* data, BOOL* color);

 

C#作為委托,定義的函數被C++ callback:

public delegate void RenderCallback([MarshalAs(UnmanagedType.LPArray, SizeConst =23)]double[] data, [MarshalAs(UnmanagedType.LPArray, SizeConst = 23)]int[] colors);

千萬註意,delegate中的double[]數組一定要加上MarshalAs標記,標記為傳遞數組,而且必須指定傳遞的數量,如果不標記數量,則每次只傳遞一個數值,這個問題折磨我很久才搞定!

其他註意事項:

1、如何在C#中保持C++的函數指針

回調函數的另一個註意事項是向C++ dll傳遞迴調函數指針的問題

假設有個函數向C++dll傳遞指針:

public delegate void EKFRenderCallback(string data, string colors);

public class EKFLib
{
    [DllImport("EKFLib.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
    public static extern void SetRenderCallback(EKFRenderCallback render);

  

C#中如下傳遞被回調的函數:

public void RenderCallback(string data, string color)
{
    // rendering
}

private void Window_Loaded(object sender, RoutedEventArgs e)
{
    EKFLib.SetRenderCallback(RenderCallback);
    EKFLib.Init();
}

  

這雖然沒什麼問題,但是通過SetRenderCallback()傳入到C++的指針不受托管代碼管理,在C#中認為此指針對象未被任何代碼引用,GC做垃圾回收時,將會把C#本地的空指針回收,導致C++無法執行回調,出現“CallbackOnCollectedDelegate”錯誤:

對“MotionCapture!MotionCapture.EKFRenderCallback::Invoke”類型的已垃圾回收委托進行了回調。這可能會導致應用程式崩潰、損壞和數據丟失。向非托管代碼傳遞委托時,托管應用程式必須讓這些委托保持活動狀態,直到確信不會再次調用它們。

微軟官網的例子是控制GC回收機制,這是個比較笨拙的方法,更加理所當然的方法是把委托定義成一個屬性,指向一個new出來的callback,然後再把這個callback傳遞進C++dll中,這樣,在C#端有對象引用,保證了GC不會回收此callback:

public void RenderCallback(string data, string color)
{
    // rendering
}

private EKFRenderCallback render;
private void Window_Loaded(object sender, RoutedEventArgs e)
{
    render = new EKFRenderCallback(RenderCallback);
    EKFLib.SetRenderCallback(render);
    EKFLib.Init();
}

2、__stdcall與_cdecl傳遞數據

最近一個項目是通過C++ 的 dll做高速運算,然後把結果數據通過Callback的方式回調給C#(界面部分),結果總是在C#中接到回調事件後就直接掛掉(程式直接在毫無提示的情況下退出,沒有任何調試信息或者提示)。

導致問題的原因是,預設情況下,C++中如下定義的函數指針,預設是以_cdecl方式調用的:

typedef  void(*Render)(double* data, BOOL* color);

這種情況下,參數堆棧是由調用者(C++一側)維護的,在C++調用此回調函數後,會把參數彈出堆棧而釋放,導致C#讀取數據時出現莫名其妙的錯誤。

以上是回調函數傳遞數組可能出現的情況,而如下所示,只傳遞一個參數的情況,甚至會在C#方莫名其妙的卡死:

typedef void (*CalibrationProgressCallback)(double percent);
改為__stdcall的方式即可解決問題,申明如下:

typedef  void(__stdcall *Render)(double* data, BOOL* color);

以下來自網路的一段_cdecl和__stdcall的解釋,必須牢記:

1. __cdecl

即所謂的C調用規則,按從右至左的順序壓參數入棧,由調用者把參數彈出棧。切記:對於傳送參數的記憶體棧是由調用者來維護的。返回值在EAX中。因此,對於象printf這樣變參數的函數必須用這種規則。編譯器在編譯的時候對這種調用規則的函數生成修飾名的餓時候,僅在輸出函數名前加上一個下劃線首碼,格式為_functionname。
2. __stdcall

按從右至左的順序壓參數入棧,由被調用者把參數彈出棧。_stdcall是Pascal程式的預設調用方式,通常用於Win32 Api中,切記:函數自己在退出時清空堆棧,返回值在EAX中。  __stdcall調用約定在輸出函數名前加上一個下劃線首碼,後面加上一個“@”符號和其參數的位元組數,格式為_functionname@number。如函數int func(int a, double b)的修飾名是_func@12

所以,從C++ dll中回調函數給C#傳遞數據,必須由C#函數在使用完數據後(退出函數時)自己清空堆棧!所C++中的回調函數指針應該如下定義:

typedef void (_stdcall *CalibrationProgressCallback)(double percent);

總結:

C++通過callback向C#傳遞數據必須註意以下幾點:

1、C++中的回調函數必須用_stdcall標記,使用stdcall方式回調;

2、如果是數組,必須用 [MarshalAs(UnmanagedType.LPArray, SizeConst = 23)]標記參數,指定為數組且標記數組長度;

3、C#方必須申明一個變數,用來指向C++的回調指針函數,避免被C#回收掉。

from:http://www.roboby.com/


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 相關介紹:  在java中,整數是有最大上限的。所謂大數是指超過整數最大上限的數,例如18 452 543 389 943 209 789 324 233和8 123 534 323 432 323 432 123 212 443就是兩個大數,在java中這是無法用整型int變數或長整型l ...
  • 1.數組的定義: 第一種: public class ArrayDemo{ public static void main(String[] args){ //定義數組 int [] arr = new int[3]; //數組中的元素預設值為0 System.out.println(arr[0]) ...
  • 恢復內容開始 1.python2.x與python3.x的區別 (1) 2.x的預設編碼是ASSIC碼,不支持中文 (2) 3.x的預設編碼是UNICODE,支持中文 (3) 2.x版本與3.x版本是互不相容的 (4) 3.x的語法更劍明,易學 2.32bits系統and64bits系統 支持最大的 ...
  • 一、DBUtils DBUtils是Python的一個用於實現資料庫連接池的模塊。 連接池的三種模式: 第一種模式: 它的缺點:每一次請求反覆創建資料庫的鏈接,鏈接的次數太多 from flask import Flask from db import POOL import pymysql app ...
  • 一、內置函數 1,數據類型:int,bool .......... 2,數據結構:dict,list,tuple,set,str 3,reversed--保留原列表,返回一個反序的迭代器 4,slice切片 l =(1,2,23,213,5612,234,43) sli =slice(1,5,2) ...
  • LindDotNetCore相關介紹 相關模塊 1. 全局都是依賴DI 1. 消息隊列 1. NoSql 1. Caching 1. 倉儲 1. 服務匯流排 1. Solr 1. 調度 1. 日誌 1. Asspect攔截組件 1. UAA授權 1. 各種組件環境的搭建 1. 各模塊單元測試編寫 DI ...
  • using System.Xml;using System.IO;using System; namespace Framework.Common{ /// /// 用於獲取或設置Web.config/*.exe.config中節點數據的輔助類 /// public sealed class App... ...
  • 就算懂正則的朋友,在遇到需要用正則校驗數據時,也往往是在網上去找很久,結果找來的還是不很符合要求。 所以我最近把開發中常用的一些正則表達式整理了一下,在這裡分享一下。給自己留個底,也給朋友們做個參考。 一、校驗數字的表達式 二、校驗字元的表達式 三、特殊需求表達式 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...