using System; using System.Runtime.InteropServices; using System.Text; namespace HikvisionAttendanceService.Interop; internal static class HikvisionTemplateInterop { // These wrappers allow template query/delete/sync with direct HCNetSDK.dll calls. [DllImport("HCNetSDK.dll", CallingConvention = CallingConvention.StdCall)] private static extern bool NET_DVR_SetDeviceConfig( int lUserID, uint dwCommand, int lChannel, IntPtr lpInBuffer, uint dwInBufferSize, IntPtr lpStatusList, IntPtr lpInParamBuffer, uint dwInParamBufferSize); [DllImport("HCNetSDK.dll", CallingConvention = CallingConvention.StdCall)] private static extern bool NET_DVR_GetDeviceConfig( int lUserID, uint dwCommand, int lChannel, IntPtr lpOutBuffer, uint dwOutBufferSize, ref uint lpBytesReturned, IntPtr lpStatusList, IntPtr lpInParamBuffer, uint dwInParamBufferSize); // Command IDs are the official ACS face/fingerprint config commands. private const uint NET_DVR_SET_FACE_PARAM_CFG = 2568; private const uint NET_DVR_GET_FACE_PARAM_CFG = 2569; private const uint NET_DVR_DEL_FACE_PARAM_CFG = 2570; private const uint NET_DVR_SET_FINGERPRINT_PARAM = 2573; private const uint NET_DVR_GET_FINGERPRINT_PARAM = 2574; private const uint NET_DVR_DEL_FINGERPRINT_PARAM = 2575; [StructLayout(LayoutKind.Sequential)] internal struct NET_DVR_FACE_PARAM_CFG { public uint dwSize; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] byCardNo; public uint dwFaceLen; public IntPtr pFaceBuffer; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 128)] public byte[] byRes; } [StructLayout(LayoutKind.Sequential)] internal struct NET_DVR_FINGERPRINT_PARAM { public uint dwSize; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] byCardNo; public byte byFingerPrintID; public byte byEnableCardReader; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] public byte[] byRes1; public uint dwFingerPrintLen; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2048)] public byte[] byFingerData; [MarshalAs(UnmanagedType.ByValArray, SizeConst = 64)] public byte[] byRes2; } internal static bool SetFaceParam(int userId, int channel, string cardNo, byte[] faceData, out string error) { error = ""; var cfg = new NET_DVR_FACE_PARAM_CFG { dwSize = (uint)Marshal.SizeOf(typeof(NET_DVR_FACE_PARAM_CFG)), byCardNo = new byte[32], byRes = new byte[128], dwFaceLen = (uint)(faceData?.Length ?? 0), pFaceBuffer = IntPtr.Zero }; CopyAscii(cardNo, cfg.byCardNo); IntPtr cfgPtr = IntPtr.Zero; IntPtr facePtr = IntPtr.Zero; try { if (faceData == null || faceData.Length == 0) { error = "faceData is empty"; return false; } facePtr = Marshal.AllocHGlobal(faceData.Length); Marshal.Copy(faceData, 0, facePtr, faceData.Length); cfg.pFaceBuffer = facePtr; cfgPtr = Marshal.AllocHGlobal((int)cfg.dwSize); Marshal.StructureToPtr(cfg, cfgPtr, false); return NET_DVR_SetDeviceConfig(userId, NET_DVR_SET_FACE_PARAM_CFG, channel, cfgPtr, cfg.dwSize, IntPtr.Zero, IntPtr.Zero, 0); } finally { if (cfgPtr != IntPtr.Zero) Marshal.FreeHGlobal(cfgPtr); if (facePtr != IntPtr.Zero) Marshal.FreeHGlobal(facePtr); } } internal static bool GetFaceParam(int userId, int channel, string cardNo, out byte[] faceData) { faceData = Array.Empty(); var cfg = new NET_DVR_FACE_PARAM_CFG { dwSize = (uint)Marshal.SizeOf(typeof(NET_DVR_FACE_PARAM_CFG)), byCardNo = new byte[32], byRes = new byte[128] }; CopyAscii(cardNo, cfg.byCardNo); uint returned = 0; IntPtr cfgPtr = IntPtr.Zero; try { cfgPtr = Marshal.AllocHGlobal((int)cfg.dwSize); Marshal.StructureToPtr(cfg, cfgPtr, false); if (!NET_DVR_GetDeviceConfig(userId, NET_DVR_GET_FACE_PARAM_CFG, channel, cfgPtr, cfg.dwSize, ref returned, IntPtr.Zero, IntPtr.Zero, 0)) return false; var outCfg = Marshal.PtrToStructure(cfgPtr); if (outCfg.dwFaceLen == 0 || outCfg.pFaceBuffer == IntPtr.Zero) return true; faceData = new byte[outCfg.dwFaceLen]; Marshal.Copy(outCfg.pFaceBuffer, faceData, 0, (int)outCfg.dwFaceLen); return true; } finally { if (cfgPtr != IntPtr.Zero) Marshal.FreeHGlobal(cfgPtr); } } internal static bool DeleteFaceParam(int userId, int channel, string cardNo) { var cfg = new NET_DVR_FACE_PARAM_CFG { dwSize = (uint)Marshal.SizeOf(typeof(NET_DVR_FACE_PARAM_CFG)), byCardNo = new byte[32], byRes = new byte[128] }; CopyAscii(cardNo, cfg.byCardNo); IntPtr cfgPtr = IntPtr.Zero; try { cfgPtr = Marshal.AllocHGlobal((int)cfg.dwSize); Marshal.StructureToPtr(cfg, cfgPtr, false); return NET_DVR_SetDeviceConfig(userId, NET_DVR_DEL_FACE_PARAM_CFG, channel, cfgPtr, cfg.dwSize, IntPtr.Zero, IntPtr.Zero, 0); } finally { if (cfgPtr != IntPtr.Zero) Marshal.FreeHGlobal(cfgPtr); } } internal static bool SetFingerprintParam(int userId, int channel, string cardNo, byte fingerId, byte[] fpData) { var cfg = new NET_DVR_FINGERPRINT_PARAM { dwSize = (uint)Marshal.SizeOf(typeof(NET_DVR_FINGERPRINT_PARAM)), byCardNo = new byte[32], byRes1 = new byte[2], byFingerData = new byte[2048], byRes2 = new byte[64], byFingerPrintID = fingerId, byEnableCardReader = 1, dwFingerPrintLen = (uint)Math.Min(fpData?.Length ?? 0, 2048) }; CopyAscii(cardNo, cfg.byCardNo); if (fpData != null && fpData.Length > 0) Buffer.BlockCopy(fpData, 0, cfg.byFingerData, 0, (int)cfg.dwFingerPrintLen); IntPtr ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal((int)cfg.dwSize); Marshal.StructureToPtr(cfg, ptr, false); return NET_DVR_SetDeviceConfig(userId, NET_DVR_SET_FINGERPRINT_PARAM, channel, ptr, cfg.dwSize, IntPtr.Zero, IntPtr.Zero, 0); } finally { if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr); } } internal static bool GetFingerprintParam(int userId, int channel, string cardNo, byte fingerId, out byte[] fpData) { fpData = Array.Empty(); var cfg = new NET_DVR_FINGERPRINT_PARAM { dwSize = (uint)Marshal.SizeOf(typeof(NET_DVR_FINGERPRINT_PARAM)), byCardNo = new byte[32], byRes1 = new byte[2], byFingerData = new byte[2048], byRes2 = new byte[64], byFingerPrintID = fingerId, byEnableCardReader = 1 }; CopyAscii(cardNo, cfg.byCardNo); uint returned = 0; IntPtr ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal((int)cfg.dwSize); Marshal.StructureToPtr(cfg, ptr, false); if (!NET_DVR_GetDeviceConfig(userId, NET_DVR_GET_FINGERPRINT_PARAM, channel, ptr, cfg.dwSize, ref returned, IntPtr.Zero, IntPtr.Zero, 0)) return false; var outCfg = Marshal.PtrToStructure(ptr); if (outCfg.dwFingerPrintLen == 0) return true; fpData = new byte[outCfg.dwFingerPrintLen]; Buffer.BlockCopy(outCfg.byFingerData, 0, fpData, 0, (int)outCfg.dwFingerPrintLen); return true; } finally { if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr); } } internal static bool DeleteFingerprintParam(int userId, int channel, string cardNo, byte fingerId) { var cfg = new NET_DVR_FINGERPRINT_PARAM { dwSize = (uint)Marshal.SizeOf(typeof(NET_DVR_FINGERPRINT_PARAM)), byCardNo = new byte[32], byRes1 = new byte[2], byFingerData = new byte[2048], byRes2 = new byte[64], byFingerPrintID = fingerId, byEnableCardReader = 1 }; CopyAscii(cardNo, cfg.byCardNo); IntPtr ptr = IntPtr.Zero; try { ptr = Marshal.AllocHGlobal((int)cfg.dwSize); Marshal.StructureToPtr(cfg, ptr, false); return NET_DVR_SetDeviceConfig(userId, NET_DVR_DEL_FINGERPRINT_PARAM, channel, ptr, cfg.dwSize, IntPtr.Zero, IntPtr.Zero, 0); } finally { if (ptr != IntPtr.Zero) Marshal.FreeHGlobal(ptr); } } private static void CopyAscii(string text, byte[] target) { if (target == null || target.Length == 0) return; if (string.IsNullOrWhiteSpace(text)) return; var bytes = Encoding.ASCII.GetBytes(text.Trim()); var len = Math.Min(bytes.Length, target.Length); Buffer.BlockCopy(bytes, 0, target, 0, len); } }