682 lines
17 KiB
C++
682 lines
17 KiB
C++
#include "IKapDevice.h"
|
||
#include <cstring>
|
||
#include <iostream>
|
||
|
||
// 静态工厂方法实现
|
||
int IIKapDevice::CreateObject(IIKapDevice** ppDevice)
|
||
{
|
||
if (ppDevice == nullptr)
|
||
return -1;
|
||
|
||
*ppDevice = new CIKapDevice();
|
||
return (*ppDevice) ? 0 : -1;
|
||
}
|
||
|
||
CIKapDevice::CIKapDevice()
|
||
: m_hDevice(nullptr)
|
||
, m_hStream(nullptr)
|
||
, m_bIsGrabbing(false)
|
||
, m_bLibInitialized(false)
|
||
, m_imageCallback(nullptr)
|
||
, m_pUserData(nullptr)
|
||
, m_nDeviceIndex(0)
|
||
, m_bDeviceOpened(false)
|
||
{
|
||
}
|
||
|
||
CIKapDevice::~CIKapDevice()
|
||
{
|
||
// 确保资源被释放
|
||
if (m_bIsGrabbing.load())
|
||
{
|
||
StopGrabbing();
|
||
}
|
||
|
||
if (m_bDeviceOpened)
|
||
{
|
||
CloseDevice();
|
||
}
|
||
|
||
if (m_bLibInitialized.load())
|
||
{
|
||
TerminateLibrary();
|
||
}
|
||
}
|
||
|
||
int CIKapDevice::InitLibrary()
|
||
{
|
||
if (m_bLibInitialized.load())
|
||
return 0;
|
||
|
||
ITKSTATUS status = ItkManInitialize();
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkManInitialize failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
m_bLibInitialized.store(true);
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::TerminateLibrary()
|
||
{
|
||
if (!m_bLibInitialized.load())
|
||
return 0;
|
||
|
||
ITKSTATUS status = ItkManTerminate();
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkManTerminate failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
m_bLibInitialized.store(false);
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::EnumerateDevices(unsigned int& nCount)
|
||
{
|
||
if (!m_bLibInitialized.load())
|
||
{
|
||
std::cerr << "Library not initialized" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status = ItkManGetDeviceCount(&nCount);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkManGetDeviceCount failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GetDeviceInfo(unsigned int nIndex, IKapDeviceInfo& deviceInfo)
|
||
{
|
||
if (!m_bLibInitialized.load())
|
||
{
|
||
std::cerr << "Library not initialized" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKDEV_INFO devInfo;
|
||
memset(&devInfo, 0, sizeof(devInfo));
|
||
|
||
ITKSTATUS status = ItkManGetDeviceInfo(nIndex, &devInfo);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkManGetDeviceInfo failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 复制设备信息
|
||
strncpy(deviceInfo.vendorName, devInfo.VendorName, sizeof(deviceInfo.vendorName) - 1);
|
||
deviceInfo.vendorName[sizeof(deviceInfo.vendorName) - 1] = '\0';
|
||
strncpy(deviceInfo.modelName, devInfo.ModelName, sizeof(deviceInfo.modelName) - 1);
|
||
deviceInfo.modelName[sizeof(deviceInfo.modelName) - 1] = '\0';
|
||
strncpy(deviceInfo.serialNumber, devInfo.SerialNumber, sizeof(deviceInfo.serialNumber) - 1);
|
||
deviceInfo.serialNumber[sizeof(deviceInfo.serialNumber) - 1] = '\0';
|
||
strncpy(deviceInfo.userDefinedName, devInfo.UserDefinedName, sizeof(deviceInfo.userDefinedName) - 1);
|
||
deviceInfo.userDefinedName[sizeof(deviceInfo.userDefinedName) - 1] = '\0';
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::OpenDevice(unsigned int nIndex, int accessMode)
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_mutex);
|
||
|
||
if (m_bDeviceOpened)
|
||
{
|
||
std::cerr << "Device already opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
if (!m_bLibInitialized.load())
|
||
{
|
||
std::cerr << "Library not initialized" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 打开设备
|
||
ITKSTATUS status = ItkDevOpen(nIndex, accessMode, &m_hDevice);
|
||
if (status != ITKSTATUS_OK || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "ItkDevOpen failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
m_nDeviceIndex = nIndex;
|
||
m_bDeviceOpened = true;
|
||
|
||
// 创建数据流和Buffer
|
||
if (CreateStreamAndBuffer() != 0)
|
||
{
|
||
CloseDevice();
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::CloseDevice()
|
||
{
|
||
std::lock_guard<std::mutex> lock(m_mutex);
|
||
|
||
if (!m_bDeviceOpened)
|
||
return 0;
|
||
|
||
// 释放数据流和Buffer
|
||
ReleaseStreamAndBuffer();
|
||
|
||
// 关闭设备
|
||
if (m_hDevice != nullptr)
|
||
{
|
||
ITKSTATUS status = ItkDevClose(m_hDevice);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkDevClose failed, status: " << status << std::endl;
|
||
}
|
||
m_hDevice = nullptr;
|
||
}
|
||
|
||
m_bDeviceOpened = false;
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::IsConnected(bool& bConnected)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
bConnected = false;
|
||
return 0;
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
// Windows 平台:使用新版 API
|
||
bool connected = false;
|
||
ITKSTATUS status = ItkDevIsConnected(m_hDevice, &connected);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkDevIsConnected failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
bConnected = connected;
|
||
#else
|
||
// Linux/ARM 平台:旧版 SDK 没有 IsConnected 接口,简化实现
|
||
// 假设设备打开后就是连接状态
|
||
bConnected = (m_hDevice != nullptr);
|
||
#endif
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::CreateStreamAndBuffer()
|
||
{
|
||
if (m_hDevice == nullptr)
|
||
return -1;
|
||
|
||
// 获取数据流数量
|
||
uint32_t streamCount = 0;
|
||
ITKSTATUS status = ItkDevGetStreamCount(m_hDevice, &streamCount);
|
||
if (status != ITKSTATUS_OK || streamCount == 0)
|
||
{
|
||
std::cerr << "ItkDevGetStreamCount failed or no stream available" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
// Windows 平台:使用新版 API ItkDevAllocStreamEx(自动分配buffer)
|
||
status = ItkDevAllocStreamEx(m_hDevice, 0, m_nBufferCount, &m_hStream);
|
||
if (status != ITKSTATUS_OK || m_hStream == nullptr)
|
||
{
|
||
std::cerr << "ItkDevAllocStreamEx failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 获取所有 Buffer
|
||
m_vecBuffers.resize(m_nBufferCount);
|
||
for (unsigned int i = 0; i < m_nBufferCount; ++i)
|
||
{
|
||
status = ItkStreamGetBuffer(m_hStream, i, &m_vecBuffers[i]);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkStreamGetBuffer failed for index " << i << std::endl;
|
||
ReleaseStreamAndBuffer();
|
||
return -1;
|
||
}
|
||
}
|
||
#else
|
||
// Linux/ARM 平台:使用旧版 API ItkDevAllocStream(需要手动创建buffer)
|
||
// 旧版 API 需要传入单个 buffer,简化实现:使用 nullptr 让SDK自动分配
|
||
status = ItkDevAllocStream(m_hDevice, 0, nullptr, &m_hStream);
|
||
if (status != ITKSTATUS_OK || m_hStream == nullptr)
|
||
{
|
||
std::cerr << "ItkDevAllocStream failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// ARM 版本不支持 GetBuffer,buffer 由 SDK 内部管理
|
||
m_vecBuffers.clear();
|
||
#endif
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::ReleaseStreamAndBuffer()
|
||
{
|
||
// 清空 Buffer 列表
|
||
m_vecBuffers.clear();
|
||
|
||
// 释放数据流
|
||
if (m_hStream != nullptr)
|
||
{
|
||
ItkDevFreeStream(m_hStream);
|
||
m_hStream = nullptr;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GrabSingleFrame(ImageData& imageData, unsigned int nTimeout)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr || m_hStream == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
#ifdef _WIN32
|
||
// Windows 平台:使用主动取图 API
|
||
// 开始采集单帧
|
||
ITKSTATUS status = ItkStreamStart(m_hStream, 1);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkStreamStart failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 等待单帧图像
|
||
ITKBUFFER hReadyBuffer = nullptr;
|
||
status = ItkStreamWaitOneFrameReady(m_hStream, &hReadyBuffer, (long long)nTimeout);
|
||
if (status != ITKSTATUS_OK || hReadyBuffer == nullptr)
|
||
{
|
||
std::cerr << "ItkStreamWaitOneFrameReady failed or timeout, status: " << status << std::endl;
|
||
ItkStreamStop(m_hStream);
|
||
return -1;
|
||
}
|
||
|
||
// 复制图像数据
|
||
int ret = CopyImageData(hReadyBuffer, imageData);
|
||
|
||
// 清除buffer(返还给stream)
|
||
ItkStreamClearBuffer(m_hStream, hReadyBuffer);
|
||
|
||
// 停止采集
|
||
ItkStreamStop(m_hStream);
|
||
|
||
return ret;
|
||
#else
|
||
// Linux/ARM 平台:旧版 SDK 不支持 WaitOneFrameReady
|
||
// 简化实现:返回不支持错误
|
||
std::cerr << "GrabSingleFrame not supported on ARM platform (old SDK)" << std::endl;
|
||
return -1;
|
||
#endif
|
||
}
|
||
|
||
int CIKapDevice::CopyImageData(ITKBUFFER hBuffer, ImageData& imageData)
|
||
{
|
||
if (hBuffer == nullptr)
|
||
return -1;
|
||
|
||
// 获取 Buffer 信息
|
||
ITK_BUFFER_INFO bufferInfo;
|
||
memset(&bufferInfo, 0, sizeof(bufferInfo));
|
||
|
||
ITKSTATUS status = ItkBufferGetInfo(hBuffer, &bufferInfo);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkBufferGetInfo failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 填充图像数据结构
|
||
imageData.width = static_cast<unsigned int>(bufferInfo.ImageWidth);
|
||
imageData.height = static_cast<unsigned int>(bufferInfo.ImageHeight);
|
||
imageData.pixelFormat = static_cast<unsigned int>(bufferInfo.PixelFormat);
|
||
imageData.imageSize = static_cast<unsigned int>(bufferInfo.ImageSize);
|
||
imageData.timestamp = bufferInfo.TimestampNs;
|
||
imageData.frameID = bufferInfo.BlockID;
|
||
|
||
// 分配内存并复制图像数据
|
||
if (imageData.pData != nullptr)
|
||
{
|
||
delete[] imageData.pData;
|
||
}
|
||
imageData.pData = new unsigned char[imageData.imageSize];
|
||
|
||
// 使用ItkBufferRead读取数据
|
||
status = ItkBufferRead(hBuffer, 0, imageData.pData, (uint32_t)imageData.imageSize);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkBufferRead failed, status: " << status << std::endl;
|
||
delete[] imageData.pData;
|
||
imageData.pData = nullptr;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::StartGrabbing(ImageCallback callback, void* pUser)
|
||
{
|
||
if (m_bIsGrabbing.load())
|
||
{
|
||
std::cerr << "Already grabbing" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr || m_hStream == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
if (callback == nullptr)
|
||
{
|
||
std::cerr << "Callback is null" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
m_imageCallback = callback;
|
||
m_pUserData = pUser;
|
||
m_bIsGrabbing.store(true);
|
||
|
||
// 启动采集线程
|
||
m_grabbingThread = std::thread(&CIKapDevice::GrabbingThread, this);
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::StopGrabbing()
|
||
{
|
||
if (!m_bIsGrabbing.load())
|
||
return 0;
|
||
|
||
// 设置停止标志
|
||
m_bIsGrabbing.store(false);
|
||
|
||
// 等待线程结束
|
||
if (m_grabbingThread.joinable())
|
||
{
|
||
m_grabbingThread.join();
|
||
}
|
||
|
||
m_imageCallback = nullptr;
|
||
m_pUserData = nullptr;
|
||
|
||
return 0;
|
||
}
|
||
|
||
bool CIKapDevice::IsGrabbing()
|
||
{
|
||
return m_bIsGrabbing.load();
|
||
}
|
||
|
||
void CIKapDevice::GrabbingThread()
|
||
{
|
||
// 开始连续采集
|
||
ITKSTATUS status = ItkStreamStart(m_hStream, 0); // 0表示连续采集
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkStreamStart failed in thread" << std::endl;
|
||
m_bIsGrabbing.store(false);
|
||
return;
|
||
}
|
||
|
||
while (m_bIsGrabbing.load())
|
||
{
|
||
// 等待图像
|
||
ITKBUFFER hReadyBuffer = nullptr;
|
||
status = ItkStreamWaitOneFrameReady(m_hStream, &hReadyBuffer, 1000);
|
||
|
||
if (status == ITKSTATUS_OK && hReadyBuffer != nullptr)
|
||
{
|
||
// 复制图像数据
|
||
ImageData imageData;
|
||
imageData.pData = nullptr;
|
||
|
||
if (CopyImageData(hReadyBuffer, imageData) == 0)
|
||
{
|
||
// 调用回调函数
|
||
if (m_imageCallback)
|
||
{
|
||
m_imageCallback(imageData, m_pUserData);
|
||
}
|
||
|
||
// 释放图像数据内存
|
||
if (imageData.pData != nullptr)
|
||
{
|
||
delete[] imageData.pData;
|
||
imageData.pData = nullptr;
|
||
}
|
||
}
|
||
|
||
// 清除buffer(返还给stream继续使用)
|
||
ItkStreamClearBuffer(m_hStream, hReadyBuffer);
|
||
}
|
||
}
|
||
|
||
// 停止采集
|
||
ItkStreamStop(m_hStream);
|
||
}
|
||
|
||
int CIKapDevice::SetExposureTime(double exposureTime)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status = ItkDevSetDouble(m_hDevice, "ExposureTime", exposureTime);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "SetExposureTime failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GetExposureTime(double& exposureTime)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status = ItkDevGetDouble(m_hDevice, "ExposureTime", &exposureTime);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "GetExposureTime failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::SetGain(double gain)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status = ItkDevSetDouble(m_hDevice, "Gain", gain);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "SetGain failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GetGain(double& gain)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status = ItkDevGetDouble(m_hDevice, "Gain", &gain);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "GetGain failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::SetTriggerMode(bool bEnable)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
const char* triggerMode = bEnable ? "On" : "Off";
|
||
ITKSTATUS status = ItkDevFromString(m_hDevice, "TriggerMode", triggerMode);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "SetTriggerMode failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::ExecuteSoftwareTrigger()
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status = ItkDevExecuteCommand(m_hDevice, "TriggerSoftware");
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ExecuteSoftwareTrigger failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GetWidth(unsigned int& width)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
int64_t value = 0;
|
||
ITKSTATUS status = ItkDevGetInt64(m_hDevice, "Width", &value);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "GetWidth failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
width = static_cast<unsigned int>(value);
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GetHeight(unsigned int& height)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
int64_t value = 0;
|
||
ITKSTATUS status = ItkDevGetInt64(m_hDevice, "Height", &value);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "GetHeight failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
height = static_cast<unsigned int>(value);
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::SetROI(unsigned int offsetX, unsigned int offsetY,
|
||
unsigned int width, unsigned int height)
|
||
{
|
||
if (!m_bDeviceOpened || m_hDevice == nullptr)
|
||
{
|
||
std::cerr << "Device not opened" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
ITKSTATUS status;
|
||
|
||
// 设置 OffsetX
|
||
status = ItkDevSetInt64(m_hDevice, "OffsetX", offsetX);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "Set OffsetX failed" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 设置 OffsetY
|
||
status = ItkDevSetInt64(m_hDevice, "OffsetY", offsetY);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "Set OffsetY failed" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 设置 Width
|
||
status = ItkDevSetInt64(m_hDevice, "Width", width);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "Set Width failed" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
// 设置 Height
|
||
status = ItkDevSetInt64(m_hDevice, "Height", height);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "Set Height failed" << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
int CIKapDevice::GetLibraryVersion(unsigned int& version)
|
||
{
|
||
ITKSTATUS status = ItkManGetVersion(&version);
|
||
if (status != ITKSTATUS_OK)
|
||
{
|
||
std::cerr << "ItkManGetVersion failed, status: " << status << std::endl;
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|