我在使用Winform7.0開發海康相機應用的時候系統悄無聲息的退出

2023-06-29 18:00:50

一、簡介
    1、說明一下
        最近,我在開發一個玻璃幕牆檢測的專案,這個專案需要使用到海康的相機系統。業務是這樣的,相機按著指定的座標,掃描玻璃幕牆的每塊玻璃,通過演演算法檢視是否有損壞的,如果有就發出報警資訊,告訴客戶。這個專案是有一個同事寫好的,我後來重構了一下,但是執行起來,執行若干次,就退悄無聲息的退出軟體,什麼也不提示。通過這個現象,我知道肯定是和硬體互動的時候出了問題,而且丟擲任何異常。海康的紀錄檔檔案也沒有顯示是什麼有價值的東西。
        但是,通過努力的查詢,偵錯,排除,終於找到了問題的關鍵,我們有四個相機,就有四個回撥函數,用於處理影象,但是每個相機註冊了一個自己的回撥,應該是四個相機,設定四個回撥,但是回撥函數的範例是一個,否則就會出現程式毫無徵兆的崩潰。
    2、開發平臺
        開發工具:Visual Studio2022
        開發語言:C#
        開發平臺:Winform 7.0
        海康類庫:MvCamCtrl.NET

二、詳細步驟
    這個問題搞了我兩天才搞定,但是搞定了,心裡舒服了。今天就把思路寫下來,自己可以查詢,也可以幫助大家。
    知道問題的關鍵點了,解決也就方便了。這也叫難著不會,會的不難。

    1、一定要把 MyCamera.cbOutputExdelegate 宣告為類的成員。
        private MyCamera.cbOutputExdelegate _outputImageDelegate;

 1      #region 私有範例欄位
 2 
 3         private MyCamera[]? _myCameras;//相機範例的陣列。
 4         private MyCamera.MV_CC_DEVICE_INFO_LIST _deviceInformationList;//相機資訊的列表
 5        
 7         //圖片檔案的儲存路徑:根目錄/當前掃描時間(作為目錄)/相機編號
 8         private string? _saveImageBaseDirectory;   //影象檔案儲存的根路徑路徑。
 9         private string? _saveImageOnceTimeDirectory;   //單次掃描影象檔案的儲存路徑,目錄結構:根目錄+當前掃描時間
10         private string[]? _saveImageForCameraDirectory;//最終儲存圖片檔案的路徑,目錄結構:根目錄+當前掃描時間+相機序列號(有多少臺相機,就有多少個目錄)
11         private IntPtr[]? _imageDisplayHandles;//針對每臺相機影象進行顯示處理,應為有多臺,所以是資料型別是陣列。
14         private MyCamera.cbOutputExdelegate _outputImageDelegate;
17         #endregion


    2、一定要在建構函式裡初始化,當然,如果你可以保證一個範例,就可以放在其他地方,我是為了方便,沒有過多的設計。
        _outputImageDelegate = new MyCamera.cbOutputExdelegate(SaveImageCallBack);
      

 1     /// <summary>
 2         /// 初始化新範例。
 3         /// </summary>
 4         public frmMonitoring()
 5         {
 6             InitializeComponent();
 7             _outputImageDelegate = new MyCamera.cbOutputExdelegate(SaveImageCallBack);
 8             _patrolProcessor = new OrientalMotorPatrolProcessor();
 9           
10 
11             #region 全域性例外處理
12 
13             AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
14             Application.ThreadException += Application_ThreadException;
15 
16             #endregion
17         }

      這是附送的方法,例外處理。

 1     #region 例外處理
 2 
 3         /// <summary>
 4         /// 該方法處理用於處理 Windows 表單執行緒引發的異常。
 5         /// </summary>
 6         /// <param name="sender"></param>
 7         /// <param name="e"></param>
 8         /// <exception cref="NotImplementedException"></exception>
 9         private void Application_ThreadException(object sender, ThreadExceptionEventArgs e)
10         {
11             try
12             {
13                 ShowPromptMessage(e.Exception.Message);
14             }
15             catch (Exception)
16             {
17                 try
18                 {
19                     ShowPromptMessage("不可恢復的非 Windows 表單執行緒異常,應用程式將退出!");
20                 }
21                 finally
22                 {
23                     Application.Exit();
24                 }
25             }
26         }
27 
28         /// <summary>
29         /// 使用該方法處理非UI執行緒所發生的異常。
30         /// </summary>
31         /// <param name="sender"></param>
32         /// <param name="e"></param>
33         /// <exception cref="NotImplementedException"></exception>
34         private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
35         {
36             try
37             {
38                 var exceptionObject = e.ExceptionObject as Exception;
39                 if (exceptionObject != null)
40                 {
41                     ShowPromptMessage(exceptionObject.Message);
42                 }
43             }
44             catch (Exception)
45             {
46                 try
47                 {
48                     ShowPromptMessage("不可恢復的非 Windows 表單執行緒異常,應用程式將退出!");
49                 }
50                 finally
51                 {
52                     Application.Exit();
53                 }
54             }
55 
56         }
57 
58         #endregion 

      輔助方法。    

 1      /// <summary>
 2         /// 用於顯示系統的提示資訊,包括異常資訊、操作資訊等。
 3         /// </summary>
 4         /// <param name="message">需要顯示的資訊內容。</param>
 5         /// <param name="stateCode">具體操作的狀態碼值,該值可有可無,不是所有操作都有狀態值的,預設值:0,表示沒有狀態值。</param>
 6         private void ShowPromptMessage(string message, int stateCode = 0)
 7         {
 8             string resultMessage;
 9             if (stateCode == 0)
10             {
11                 resultMessage = $"{message}\r\n";
12             }
13             else
14             {
15                 resultMessage = $"{message}: Error ={string.Format("{0:X}", stateCode)}";
16             }
17 
18             switch (stateCode)
19             {
20                 case MyCamera.MV_E_HANDLE: resultMessage += ",錯誤或無效控制程式碼(Error or invalid handle)\r\n"; break;
21                 case MyCamera.MV_E_SUPPORT: resultMessage += ",不支援的功能(Not supported function)\r\n"; break;
22                 case MyCamera.MV_E_BUFOVER: resultMessage += ",快取已滿(Cache is full)\r\n"; break;
23                 case MyCamera.MV_E_CALLORDER: resultMessage += ",函數呼叫順序錯誤(Function calling order error)\r\n"; break;
24                 case MyCamera.MV_E_PARAMETER: resultMessage += ",不正確的引數(Incorrect parameter)\r\n"; break;
25                 case MyCamera.MV_E_RESOURCE: resultMessage += ",應用資源失敗(Applying resource failed)\r\n"; break;
26                 case MyCamera.MV_E_NODATA: resultMessage += ",沒有資料(No data )\r\n"; break;
27                 case MyCamera.MV_E_PRECONDITION: resultMessage += ",前提條件錯誤,或執行環境已更改(Precondition error, or running environment changed)\r\n"; break;
28                 case MyCamera.MV_E_VERSION: resultMessage += ",版本不匹配(Version mismatches)\r\n"; break;
29                 case MyCamera.MV_E_NOENOUGH_BUF: resultMessage += ",記憶體不足(Insufficient memory)\r\n"; break;
30                 case MyCamera.MV_E_UNKNOW: resultMessage += ",未知錯誤(Unknown error)\r\n"; break;
31                 case MyCamera.MV_E_GC_GENERIC: resultMessage += ",一般錯誤(General error)\r\n"; break;
32                 case MyCamera.MV_E_GC_ACCESS: resultMessage += ",節點存取條件錯誤(Node accessing condition error)\r\n"; break;
33                 case MyCamera.MV_E_ACCESS_DENIED: resultMessage += ",沒有許可權(No permission)\r\n"; break;
34                 case MyCamera.MV_E_BUSY: resultMessage += ",裝置正忙或網路斷開連線(Device is busy, or network disconnected)\r\n"; break;
35                 case MyCamera.MV_E_NETER: resultMessage += ",網路錯誤(Network error)\r\n"; break;
36             }
37 
38             SetAsyncControlText(txtExceptionMessage, $">> {resultMessage}", true);
39         }


    3、範例化相機,並初始化回撥函數。
        

 1          MyCamera.MV_CC_DEVICE_INFO[] _deviceInfoArray = new MyCamera.MV_CC_DEVICE_INFO[localDeviceCount];
 2                 object? localDeviceInfo = null;
 3                 MyCamera.MV_CC_DEVICE_INFO device;                
 4 
 5                 for (int i = 0; i < localDeviceCount; ++i)
 6                 {
 7                     if (_deviceInformationList.pDeviceInfo != null && _deviceInformationList.pDeviceInfo.Length > 0)
 8                     {
 9                         localDeviceInfo = Marshal.PtrToStructure(_deviceInformationList.pDeviceInfo[i], typeof(MyCamera.MV_CC_DEVICE_INFO));
10                         if (localDeviceInfo != null)
11                         {
12                             //獲取選擇的裝置資訊
13                             device = (MyCamera.MV_CC_DEVICE_INFO)localDeviceInfo;
14 
15                             //開啟裝置
16                             if (cameras[i] == null)
17                             {
18                                 cameras[i] = new MyCamera();
19                             }
20 
21                             stateCode = cameras[i].MV_CC_CreateDevice_NET(ref device);
22                             if (MyCamera.MV_OK != stateCode)
23                             {
24                                 return;
25                             }
26 
27                             stateCode = cameras[i].MV_CC_OpenDevice_NET();
28                             if (MyCamera.MV_OK != stateCode)
29                             {
30                                 return;
31                             }
32                             else
33                             {
34                                 _deviceInfoArray[i] = device;
35                                 // 探測網路最佳包大小(只對GigE相機有效),我們是USB相機
36                                 if (device.nTLayerType == MyCamera.MV_GIGE_DEVICE)
37                                 {
38                                     int nPacketSize = cameras[i].MV_CC_GetOptimalPacketSize_NET();
39                                     if (nPacketSize > 0)
40                                     {
41                                         stateCode = cameras[i].MV_CC_SetIntValue_NET("GevSCPSPacketSize", (uint)nPacketSize);
42                                         if (stateCode != MyCamera.MV_OK)
43                                         {
44                                             ShowPromptMessage("Warning: Set Packet Size failed {0:x8}", stateCode);
45                                         }
46                                     }
47                                     else
48                                     {
49                                         ShowPromptMessage("Warning: Get Packet Size failed {0:x8}", stateCode);
50                                     }
51                                 }
52 
53                                 //1、開啟設定觸發模式                                
54                                 SetCameraTriggerMode(cameras[i]);
55                                 //2、設定具體的觸發模式為軟觸發
56                                 SetCameraTriggerSource(cameras[i]);
57                                 cameras[i].MV_CC_RegisterImageCallBackEx_NET(_outputImageDelegate, i);
58                             }
59                         }
60                     }
61                 }

    4、回撥方法的實現。
        回撥的方法可以根據自己的需求編寫,這是我的需求,我的具體編寫方法,不能照抄。

 1      /// <summary>
 2         /// 獲取幀資料並儲存為影象的回撥函數。
 3         /// </summary>
 4         /// <param name="frameData">影象的幀資料。</param>
 5         /// <param name="frameInfo">影象的幀資訊。</param>
 6         /// <param name="cameraIndex">相機的索引,因為有多臺相機。</param>
 7         private void SaveImageCallBack(IntPtr frameData, ref MyCamera.MV_FRAME_OUT_INFO_EX frameInfo, IntPtr cameraIndex)
 8         {
 9             int _cameraIndex = (int)cameraIndex;
10 
11             if (_perCameraTotalFrames != null && _perCameraTotalFrames.Length > 0)
12             {
13                 //抓取的幀數
14                 ++_perCameraTotalFrames[_cameraIndex];
15 
16                 //第一個相機資料,每臺相機處理的資料是一樣的,隨機選擇第一個,作為結果輸出。
17                 if (_cameraIndex == 0)
18                 {
19                     SetAsyncControlText(lblAcquisitionCountValue, _perCameraTotalFrames[_cameraIndex].ToString());
20                 }
21             }
22 
23             //顯示影象 
24             //將相機影象顯示到對應的位置
25             if (_imageDisplayHandles != null && _imageDisplayHandles.Length > 0)
26             {
27                 MyCamera.MV_DISPLAY_FRAME_INFO displayFrameInfo = new MyCamera.MV_DISPLAY_FRAME_INFO();
28                 displayFrameInfo.hWnd = _imageDisplayHandles[_cameraIndex];
29                 displayFrameInfo.pData = frameData;
30                 displayFrameInfo.nDataLen = frameInfo.nFrameLen;
31                 displayFrameInfo.nWidth = frameInfo.nWidth;
32                 displayFrameInfo.nHeight = frameInfo.nHeight;
33                 displayFrameInfo.enPixelType = frameInfo.enPixelType;
34 
35                 if (_myCameras != null && _myCameras.Length > 0)
36                 {
37                     CameraDisplayOneFrame(_myCameras[_cameraIndex], ref displayFrameInfo);
38                 }
39             }
40 
41             //判斷當前相機是否允許儲存影象
42             if (_isPerCameraSaveImage != null && _isPerCameraSaveImage.Length > 0)
43             {
44                 if (_isPerCameraSaveImage[_cameraIndex] && _saveImageForCameraDirectory != null && _saveImageForCameraDirectory.Length > 0 && _perCameraSerialNumbers != null && _perCameraSerialNumbers.Length > 0)
45                 {
46                     MyCamera.MV_SAVE_IMG_TO_FILE_PARAM stSaveToFileParam = new MyCamera.MV_SAVE_IMG_TO_FILE_PARAM();
47 
48                     stSaveToFileParam.enPixelType = frameInfo.enPixelType;
49                     stSaveToFileParam.pData = frameData;
50                     stSaveToFileParam.nDataLen = frameInfo.nFrameLen;
51                     stSaveToFileParam.nWidth = frameInfo.nWidth;
52                     stSaveToFileParam.nHeight = frameInfo.nHeight;
53 
54                     stSaveToFileParam.enImageType = MyCamera.MV_SAVE_IAMGE_TYPE.MV_Image_Bmp;
55                     stSaveToFileParam.nQuality = 100;
56                     //影象檔名應包含採集時間、對應相機ID號、玻璃編號等資訊
57                     var saveImageFullPath = $"{_saveImageForCameraDirectory[_cameraIndex]}\\{DateTime.Now.ToString("yyyyMMddHHmmss")}_{_perCameraSerialNumbers[_cameraIndex]}_{_patrolFileGlassNumber}.bmp";
58                     stSaveToFileParam.pImagePath = saveImageFullPath;
59 
60                     if (_myCameras != null && _myCameras.Length > 0)
61                     {
62                         _myCameras[_cameraIndex].MV_CC_SaveImageToFile_NET(ref stSaveToFileParam);
63                         CameraSaveImageToFile(_myCameras[_cameraIndex], ref stSaveToFileParam);
64                     }
65                 }
66             }
67         }


三、總結
    我已經實驗過單相機回撥沒問題了,所以多相機就直接複製了多個回撥,因此相機回撥還沒傳到外面的範例,就已經崩了,肯定就是封裝的問題。看了海康的多相機demo,相機有4個範例,回撥函數只有一個範例,4個相機註冊了四次回撥,但都是同一個回撥範例。這就是我發生錯誤的根本原因。
    好了,問題解決了,又學了點技術,繼續努力,蒼天不負努力的人。