用海康HCNetSDK:CLN參數(shù)映射與抓圖預(yù)覽避坑指南)
簡(jiǎn)介這套HCNetSDK V5.3.6.30庫(kù)文件專為L(zhǎng)abVIEW調(diào)用DLL而設(shè)計(jì)面向需要在LabVIEW中集成??低晹z像頭功能的開(kāi)發(fā)者。適用于監(jiān)控系統(tǒng)搭建、工業(yè)視覺(jué)項(xiàng)目等場(chǎng)景可借助該SDK快速實(shí)現(xiàn)設(shè)備連接、實(shí)時(shí)預(yù)覽、抓圖、錄像與云臺(tái)控制等核心功能省去從底層協(xié)議寫(xiě)起的繁瑣過(guò)程。壓縮包共45個(gè)文件大小20.37MB主體為34個(gè)DLL與7個(gè)LIB文件同時(shí)附帶屬性配置文件、可執(zhí)行演示程序與說(shuō)明文檔DLL封裝了可供調(diào)用的函數(shù)接口LIB用于編譯鏈接演示程序則有助于核對(duì)調(diào)用方式。目前已有657人瀏覽學(xué)習(xí)特別適合具備一定LabVIEW基礎(chǔ)、希望對(duì)接硬件設(shè)備的工程師參考。資料內(nèi)提供完整庫(kù)文件與配套示例將DLL導(dǎo)入項(xiàng)目并參考演示封裝成VI即可投入功能開(kāi)發(fā)文檔對(duì)關(guān)鍵參數(shù)和兼容性注意事項(xiàng)的說(shuō)明也能明顯減少環(huán)境配置與調(diào)用排錯(cuò)的時(shí)間。1. ??低旽CNetSDK庫(kù)文件與LabVIEW調(diào)用DLL庫(kù)先把CLN參數(shù)表映射對(duì)再談抓圖和預(yù)覽有次現(xiàn)場(chǎng)聯(lián)調(diào)一臺(tái)工控機(jī)裝的是LabVIEW 2021項(xiàng)目目錄里明明放著HCNetSDK.dll代碼里拖一個(gè)CLN節(jié)點(diǎn)去調(diào)NET_DVR_Login_V40運(yùn)行起來(lái)不是返回錯(cuò)誤碼就是直接崩潰。折騰到半夜才發(fā)現(xiàn)問(wèn)題不在DLL本身而在結(jié)構(gòu)體對(duì)齊、調(diào)用規(guī)范和依賴文件這三件事上。這份海康威視HCNetSDK V5.3.6.30庫(kù)文件解決的正是“LabVIEW調(diào)用DLL庫(kù)”時(shí)最磨人的一段鏈路加載、登錄、抓圖、預(yù)覽。它適合做設(shè)備上位機(jī)、視覺(jué)工裝、流水線質(zhì)檢站的工程師也適合被回調(diào)函數(shù)搞得頭皮發(fā)麻的老手。下面按實(shí)際聯(lián)調(diào)的順序把參數(shù)怎么配、坑在哪、怎么繞過(guò)一層層拆開(kāi)。2. 準(zhǔn)備工程環(huán)境HCNetSDK目錄結(jié)構(gòu)與32/64位調(diào)用邊界2.1 開(kāi)發(fā)目錄里的文件清單哪些DLL必須跟著發(fā)布包走拿到??礖CNetSDK V5.3.6.30庫(kù)文件后第一件事不是急著寫(xiě)VI而是先看完整目錄。解壓出來(lái)后bin目錄下不是只有一個(gè)HCNetSDK.dll常見(jiàn)的還有HCCore.dll、HCAlarm.dll、libxml2.dll以及hlog、hpr等運(yùn)行時(shí)組件。很多翻車案例是把HCNetSDK.dll單獨(dú)拷出來(lái)放進(jìn)LabVIEW項(xiàng)目目錄一運(yùn)行就報(bào)“加載DLL錯(cuò)誤找不到指定的模塊”。這不是主DLL缺是依賴沒(méi)跟上。我一般會(huì)做一個(gè)最小發(fā)布目錄把bin里與release相關(guān)的文件整包拷過(guò)去而不是只挑主DLL。文件清單可以參考下表文件作用發(fā)布時(shí)必須HCNetSDK.dll主導(dǎo)出庫(kù)登錄、預(yù)覽、報(bào)警、抓圖接口必須HCNetSDK.lib / .h靜態(tài)鏈接庫(kù)和C頭文件僅C二次封裝時(shí)需要HCCore.dll核心算法與設(shè)備通道管理必須HCAlarm.dll報(bào)警功能相關(guān)用了報(bào)警就必須libxml2.dllXML解析IP配置相關(guān)必須hlog / hpr 目錄日志與加密組件必須CLN配置里填寫(xiě)“庫(kù)名或路徑”時(shí)別只填HCNetSDK.dll。Windows加載DLL時(shí)會(huì)先到該DLL所在目錄找它的依賴模塊。所以把整個(gè)bin目錄放到LabVIEW工程目錄下CLN里寫(xiě)絕對(duì)路徑指向bin\HCNetSDK.dll依賴文件放在同一目錄這樣加載鏈路最干凈。發(fā)布到現(xiàn)場(chǎng)機(jī)器時(shí)這個(gè)目錄結(jié)構(gòu)原樣拷貝不要自作聰明把依賴合并到system32后期升級(jí)會(huì)很難受。2.2 32位還是64位先確認(rèn)LabVIEW和DLL的位寬再動(dòng)手海康HCNetSDK的bin目錄通常分x86和x64兩套。LabVIEW 32位只能加載32位DLL64位版本同理。如果裝了64位LabVIEW卻加載32位DLL加載階段就會(huì)失敗報(bào)錯(cuò)信息卻五花八門(mén)有時(shí)是“找不到指定的模塊”有時(shí)是“內(nèi)存位置訪問(wèn)無(wú)效”。這類問(wèn)題最隱蔽因?yàn)榇a本身沒(méi)寫(xiě)錯(cuò)??焖俅_認(rèn)DLL位寬的命令用Visual Studio自帶的dumpbindumpbin /headers HCNetSDK.dll | findstr machine輸出里如果顯示x86說(shuō)明這只能被32位進(jìn)程加載x64則必須配合64位進(jìn)程。同樣要確認(rèn)LabVIEW的位數(shù)在“幫助—關(guān)于”里能看到32位/64位標(biāo)識(shí)。我習(xí)慣把這個(gè)命令寫(xiě)成一個(gè).bat腳本拿到任何DLL先跑一遍省得在加載報(bào)錯(cuò)上浪費(fèi)時(shí)間。還有一個(gè)配套問(wèn)題VC運(yùn)行時(shí)。HCNetSDK依賴VC運(yùn)行庫(kù)目標(biāo)機(jī)器上如果沒(méi)有對(duì)應(yīng)版本的vcredistDLL加載會(huì)報(bào)0xC0000135之類的異常。發(fā)布包里帶上vcredist安裝包現(xiàn)場(chǎng)部署時(shí)先裝一次能省下大半天排查時(shí)間。2.3 調(diào)用約定為什么CLN默認(rèn)配置會(huì)導(dǎo)致參數(shù)錯(cuò)亂海康HCNetSDK的導(dǎo)出接口絕大多數(shù)遵循__stdcall調(diào)用約定也就是Windows API標(biāo)準(zhǔn)。而LabVIEW的CLN節(jié)點(diǎn)在新建時(shí)默認(rèn)的“調(diào)用規(guī)范”是__cdecl。如果沒(méi)改參數(shù)壓棧和回退方式不匹配函數(shù)返回的句柄和錯(cuò)誤碼全是亂的表現(xiàn)為返回了一個(gè)看似真實(shí)的錯(cuò)誤碼但不崩潰。這種問(wèn)題極具迷惑性因?yàn)殄e(cuò)誤碼查文檔也能對(duì)上實(shí)際上根本原因是調(diào)用約定錯(cuò)位。排查方法不復(fù)雜在CLN節(jié)點(diǎn)上右鍵進(jìn)入配置界面把調(diào)用規(guī)范改成“stdcallWindows標(biāo)準(zhǔn)調(diào)用”。同時(shí)檢查函數(shù)原型里返回類型的配置BOOL對(duì)應(yīng)int32句柄對(duì)應(yīng)uint32指針類型單獨(dú)處理。調(diào)用約定已經(jīng)成為我調(diào)C庫(kù)的第一個(gè)入口檢查項(xiàng)比參數(shù)類型更優(yōu)先確認(rèn)。改完之后函數(shù)名的大小寫(xiě)也要和頭文件完全一致??档膶?dǎo)出函數(shù)名通常是全大寫(xiě)加下劃線抄錯(cuò)一個(gè)字符加載階段就會(huì)報(bào)“無(wú)法找到入口點(diǎn)”。3. 讓LabVIEW加載并調(diào)用HCNetSDK登錄、抓圖與預(yù)覽參數(shù)配置實(shí)操3.1 從C頭文件到LabVIEW Cluster結(jié)構(gòu)體映射的對(duì)應(yīng)關(guān)系HCNetSDK所有接口在HCNetSDK.h里有明確原型。以NET_DVR_Login_V40為例它的登錄信息結(jié)構(gòu)體長(zhǎng)這樣// 用戶登錄信息結(jié)構(gòu)體注意內(nèi)部對(duì)齊 typedef struct tagNET_DVR_USER_LOGIN_INFO { char sDeviceAddress[129]; // 設(shè)備IP地址 BYTE byUseTransport; // 0不使用按傳輸協(xié)議1使用 WORD wPort; // 設(shè)備端口默認(rèn)8000 char sUserName[64]; // 用戶名 char sPassword[64]; // 密碼 // 后續(xù)還有幾個(gè)保留字段映射時(shí)要一起補(bǔ)上 } NET_DVR_USER_LOGIN_INFO;這段結(jié)構(gòu)體在LabVIEW里不能直接復(fù)制要手動(dòng)構(gòu)建。我的習(xí)慣是先建一個(gè)固定大小的字節(jié)數(shù)組作為原始緩沖區(qū)比如512字節(jié)然后按偏移量寫(xiě)入IP、端口、用戶名和密碼。這樣能避開(kāi)Cluster對(duì)齊問(wèn)題特別是結(jié)構(gòu)體里有枚舉、位域、聯(lián)合體時(shí)Cluster方式極易錯(cuò)位。新手我更推薦用“字節(jié)數(shù)組偏移量寫(xiě)入”的方式配合頭文件逐字段對(duì)照字段順序不能亂。注意字節(jié)序CLN按主機(jī)字節(jié)序傳參x86平臺(tái)就是小端數(shù)值寫(xiě)進(jìn)緩沖區(qū)不需要手動(dòng)翻轉(zhuǎn)。還有填充字節(jié)C結(jié)構(gòu)體為了對(duì)齊會(huì)自動(dòng)在字段間插入padding如果只填有效字段、不填paddingSDK讀到的結(jié)構(gòu)體是歪的。最穩(wěn)妥的做法是先用C語(yǔ)言寫(xiě)一個(gè)小工具把結(jié)構(gòu)體大小和每個(gè)字段的偏移量打印出來(lái)然后在LabVIEW側(cè)按偏移量填字節(jié)數(shù)組。3.2 加載與登錄第一段能在LabVIEW里跑起來(lái)的鏈路初始化登錄流程涉及三個(gè)核心接口先用C原型看它們的簽名BOOL NET_DVR_Init(void); BOOL NET_DVR_SetConnectTime(DWORD dwWaitTime, DWORD dwTryTimes); LONG NET_DVR_Login_V40(LPNET_DVR_USER_LOGIN_INFO pLoginInfo, LPNET_DVR_DEVICEINFO_V40 lpDeviceInfo, LPNET_DVR_DEVICEINFO_V30 lpDeviceInfoV30);在LabVIEW里按下面順序搭鏈路新建VI放一個(gè)CLN節(jié)點(diǎn)庫(kù)文件路徑指向bin\HCNetSDK.dll函數(shù)名填NET_DVR_Init返回類型選int32。運(yùn)行一次返回1說(shuō)明主DLL加載正常。放第二個(gè)CLN調(diào)NET_DVR_SetConnectTime兩個(gè)參數(shù)都用uint32連接超時(shí)3000ms重試次數(shù)2次。這一步不加也能登錄但設(shè)備離線時(shí)LabVIEW界面會(huì)卡死很久。放第三個(gè)CLN調(diào)NET_DVR_Login_V40。輸入?yún)?shù)按上一節(jié)的結(jié)構(gòu)體映射填好輸出參數(shù)里第一個(gè)是設(shè)備句柄LONG第二個(gè)是結(jié)構(gòu)體返回區(qū)。登錄返回值如果是-1說(shuō)明登錄失敗緊接著調(diào)NET_DVR_GetLastError拿具體錯(cuò)誤碼對(duì)照頭文件里的宏定義判斷是密碼錯(cuò)誤、設(shè)備離線還是通道號(hào)越界。為什么先調(diào)InitHCNetSDK內(nèi)部維護(hù)一套網(wǎng)絡(luò)緩沖池和日志系統(tǒng)不Init直接Login部分版本會(huì)返回互斥鎖錯(cuò)誤。Init和Cleanup要成對(duì)調(diào)用程序退出時(shí)調(diào)NET_DVR_Cleanup釋放資源否則下次啟動(dòng)SDK內(nèi)部狀態(tài)是臟的。登錄成功后設(shè)備信息結(jié)構(gòu)體里能讀到通道數(shù)量、設(shè)備序列號(hào)、能力集這些值后面配置預(yù)覽和抓圖時(shí)都要用。我會(huì)把設(shè)備句柄設(shè)成VI的全局變量供所有CLN調(diào)用復(fù)用避免每次傳Participant參數(shù)時(shí)復(fù)制出另一個(gè)句柄值。3.3 抓圖JPEG抓圖與“每次調(diào)用字節(jié)數(shù)”的設(shè)置抓圖接口用NET_DVR_CaptureJPEGPicture原型里最關(guān)鍵的是輸出緩沖區(qū)指針和緩沖區(qū)長(zhǎng)度BOOL NET_DVR_CaptureJPEGPicture(LONG lUserID, LONG lChannel, LPNET_DVR_JPEGPARA lpJpegPara, char* sJpegPicBuffer, DWORD dwPicSize);CLN配置時(shí)sJpegPicBuffer要按“字節(jié)數(shù)組的引用方式”傳遞數(shù)組容量預(yù)先分配足夠大。第一次跑通這個(gè)接口時(shí)我犯了一個(gè)經(jīng)典錯(cuò)誤緩沖區(qū)分配了256KB但CLN屬性里沒(méi)把數(shù)組大小同步設(shè)為262144結(jié)果接口返回成功圖片解碼出來(lái)卻是花的。問(wèn)題就出在“每次調(diào)用字節(jié)數(shù)”這一項(xiàng)配置上?!懊看握{(diào)用字節(jié)數(shù)”在LabVIEW CLN里表示這一次調(diào)用希望SDK寫(xiě)入該緩沖區(qū)的字節(jié)數(shù)。如果設(shè)為0SDK不知道有效尺寸內(nèi)部可能截?cái)嗷蛟浇?。我一般給分配512KB緩沖區(qū)CLN里把數(shù)組大小固定為524288。這個(gè)數(shù)值不用改抓圖返回后從輸出數(shù)組里取實(shí)際數(shù)據(jù)即可。抓圖返回的字節(jié)數(shù)組里就是完整JPEG編碼數(shù)據(jù)LabVIEW端可以直接用圖片控件顯示不需要做格式轉(zhuǎn)換。注意多路同時(shí)抓圖時(shí)每一路必須獨(dú)立分配緩沖區(qū)不能多個(gè)CLN共用同一個(gè)數(shù)組否則并發(fā)寫(xiě)入會(huì)互相覆蓋圖像錯(cuò)亂。3.4 實(shí)時(shí)預(yù)覽的邊界CLN不能直接用函數(shù)指針很多人搜“LabVIEW實(shí)時(shí)顯示??狄曨l”卡在NET_DVR_RealPlay_V40的REALDATACALLBACK回調(diào)參數(shù)上。CLN節(jié)點(diǎn)不支持函數(shù)指針即使把參數(shù)類型改成數(shù)值也無(wú)法真正傳遞一個(gè)LabVIEW側(cè)的回調(diào)地址。SDK在數(shù)據(jù)到達(dá)時(shí)會(huì)去調(diào)用一個(gè)無(wú)效地址表現(xiàn)就是句柄返回成功了但畫(huà)面永遠(yuǎn)出不來(lái)嚴(yán)重時(shí)直接崩潰。常見(jiàn)做法有兩條路。第一條是降低實(shí)時(shí)性放棄RealPlay改用定時(shí)抓圖輪詢2到3幀每秒用于監(jiān)控輔助畫(huà)面足夠。第二條是寫(xiě)一個(gè)C包裝DLL在包裝層注冊(cè)回調(diào)把幀數(shù)據(jù)放到共享內(nèi)存或通過(guò)本地TCP轉(zhuǎn)發(fā)給LabVIEW。要做25幀并發(fā)顯示必須走第二條路這個(gè)架構(gòu)決策要在項(xiàng)目一開(kāi)始就定好不然做一半換方案成本很高。輪詢抓圖的實(shí)現(xiàn)簡(jiǎn)單、穩(wěn)定性好缺點(diǎn)是有延遲且CPU占用偏高。包裝DLL方案初期工作量大但后續(xù)做多路預(yù)覽、錄像回放都順。對(duì)大多數(shù)LabVIEW聯(lián)調(diào)場(chǎng)景先把輪詢抓圖跑通再把實(shí)時(shí)預(yù)覽作為二期升級(jí)是比較務(wù)實(shí)的選擇。4. 調(diào)用HCNetSDK避坑清單崩潰、錯(cuò)位與回調(diào)不觸發(fā)排查4.1 加載失敗報(bào)錯(cuò)“找不到指定的模塊”現(xiàn)象CLN節(jié)點(diǎn)庫(kù)路徑正確工程目錄里也看得到HCNetSDK.dll但運(yùn)行時(shí)提示“找不到指定的模塊”。原因HCNetSDK.dll依賴的HCCore.dll、libxml2.dll不在同目錄或者VC運(yùn)行庫(kù)缺失。Windows加載DLL時(shí)會(huì)先到當(dāng)前DLL所在目錄找依賴文件只拷主DLL必然載入失敗。解決把bin目錄整個(gè)拷入工程目錄CLN里寫(xiě)絕對(duì)路徑在目標(biāo)機(jī)器上安裝對(duì)應(yīng)版本的VC運(yùn)行庫(kù)。排錯(cuò)時(shí)用依賴查看工具打開(kāi)HCNetSDK.dll能看到缺失的依賴項(xiàng)名稱按缺失項(xiàng)逐個(gè)補(bǔ)齊比瞎試快得多。4.2 返回值異常登錄返回看似真實(shí)的錯(cuò)誤碼但不成功現(xiàn)象初始化返回1登錄返回一個(gè)查文檔能查到的錯(cuò)誤碼但換設(shè)備、換密碼都一樣錯(cuò)誤碼還不固定。原因調(diào)用規(guī)范配置錯(cuò)誤。CLN默認(rèn)__cdeclHCNetSDK實(shí)際是__stdcall參數(shù)?;赝隋e(cuò)亂錯(cuò)誤碼看似真實(shí)實(shí)際是錯(cuò)位后的垃圾值。解決CLN右鍵配置里把調(diào)用規(guī)范切成“stdcallWindows標(biāo)準(zhǔn)調(diào)用”。改完再看返回值錯(cuò)誤碼會(huì)立刻變成有意義的值。這個(gè)檢查點(diǎn)應(yīng)該在寫(xiě)第一個(gè)CLN時(shí)就完成。4.3 結(jié)構(gòu)體錯(cuò)位登錄成功后通道數(shù)讀取為0現(xiàn)象CLN返回登錄成功但設(shè)備信息結(jié)構(gòu)體里的通道數(shù)量、能力集字段全是0。原因Cluster成員順序與C結(jié)構(gòu)體不一致或者對(duì)齊方式不對(duì)。SDK頭文件里通常有#pragma pack(push, 4)而LabVIEW Cluster默認(rèn)按8字節(jié)對(duì)齊偏移量一錯(cuò)后面字段全亂。解決逐字段比對(duì)偏移量在字段之間插入U(xiǎn)8占位數(shù)組補(bǔ)齊padding。核對(duì)方法打印C結(jié)構(gòu)體每個(gè)字段的offsetof值在LabVIEW側(cè)用相同偏移量填數(shù)組保證一致性。4.4 回調(diào)不觸發(fā)RealPlay返回成功但不出圖現(xiàn)象NET_DVR_RealPlay_V40返回了預(yù)覽句柄但回調(diào)一次也沒(méi)被調(diào)用畫(huà)面永遠(yuǎn)是黑屏。原因CLN沒(méi)有真正的函數(shù)指針地址傳入的是無(wú)效值。SDK在視頻流到達(dá)時(shí)回調(diào)到無(wú)效地址調(diào)用被系統(tǒng)忽略或直接崩潰。解決改用C包裝DLL承載回調(diào)或者降級(jí)為定時(shí)抓圖輪詢。多路預(yù)覽不要在LabVIEW循環(huán)里創(chuàng)建多個(gè)RealPlay實(shí)例CLN不具備多線程回調(diào)能力。4.5 圖像花屏或長(zhǎng)度截?cái)郕PEG數(shù)據(jù)緩沖越界現(xiàn)象抓圖接口返回成功但圖片解碼后一半正常一半灰色或者長(zhǎng)度固定為某個(gè)奇怪值。原因緩沖區(qū)分配太小JPEG數(shù)據(jù)被截?cái)嗷蛘逤LN里配置的“每次調(diào)用字節(jié)數(shù)”與數(shù)組實(shí)際大小不一致SDK在寫(xiě)入時(shí)越界。解決緩沖區(qū)至少分配512KBCLN屬性里的數(shù)組大小和實(shí)際分配值保持相同的524288。多路抓圖時(shí)每路獨(dú)立分配緩沖區(qū)不能共用同一數(shù)組。5. 進(jìn)階用包裝DLL把實(shí)時(shí)視頻流交給LabVIEW顯示5.1 數(shù)據(jù)鏈路回調(diào)線程到LabVIEW隊(duì)列包裝DLL里SDK網(wǎng)絡(luò)線程會(huì)觸發(fā)REALDATACALLBACK回調(diào)里不能直接操作UI也不能跨語(yǔ)言直接調(diào)用LabVIEW內(nèi)存。常見(jiàn)做法是回調(diào)函數(shù)里把幀數(shù)據(jù)拷貝到環(huán)形緩沖區(qū)再通過(guò)事件通知LabVIEW端讀取。這里必須用雙緩沖一塊緩沖區(qū)在寫(xiě)入時(shí)另一塊已經(jīng)就緒供讀取避免讀寫(xiě)指針互相踩踏。1080P裸流一幀約2MB環(huán)形開(kāi)到8幀內(nèi)存開(kāi)銷約16MB完全可接受。LabVIEW端通過(guò)循環(huán)讀取共享緩沖區(qū)結(jié)合生產(chǎn)者-消費(fèi)者模式把顯示刷新和采集分離。隊(duì)列長(zhǎng)度建議固定不要無(wú)界增長(zhǎng)否則網(wǎng)絡(luò)抖動(dòng)時(shí)內(nèi)存會(huì)一路漲上去。5.2 斷線重連與超時(shí)參數(shù)穩(wěn)定化把NET_DVR_SetConnectTime設(shè)置為3000ms重試2次設(shè)備離線和重新上線時(shí)登錄失敗不會(huì)卡死界面。斷線后不要立即重連等10秒以上再重新調(diào)用Init和Login否則設(shè)備端連接數(shù)會(huì)被打滿新連接會(huì)被拒絕。多路預(yù)覽時(shí)每路實(shí)例獨(dú)立句柄回調(diào)參數(shù)里用通道號(hào)區(qū)分?jǐn)?shù)據(jù)通路。我最早把預(yù)覽句柄當(dāng)成普通整數(shù)在循環(huán)里反復(fù)傳畫(huà)面定格又恢復(fù)折騰到半夜才發(fā)現(xiàn)是回調(diào)緩沖沒(méi)清干凈。從那以后每次初始化SDK都會(huì)把返回碼、句柄、緩沖區(qū)三項(xiàng)做一次核對(duì)再繼續(xù)下一步。這套流程也讓你少走點(diǎn)彎路希望幫到你。本文還有配套的精品資源點(diǎn)擊獲取