已添加1个文件
已修改11个文件
366 ■■■■■ 文件已修改
SourceCode/Bond/SGMeasurement/Logger.cpp 14 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/PLCSignalListener.h 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.cpp 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.rc 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj.filters 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj.user 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/res/menu_open_dir.ico 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/resource.h 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
SourceCode/Bond/SGMeasurement/Logger.cpp
@@ -21,8 +21,17 @@
{
    CSingleLock lock(&m_csLogLock, TRUE);
    TCHAR szPath[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szPath, MAX_PATH);
    CString strPath = szPath;
    int pos = strPath.ReverseFind('\\');
    if (pos != -1) {
        strPath = strPath.Left(pos + 1);
    }
    CTime now = CTime::GetCurrentTime();
    CString strLogDir = _T("Log");
    CString strLogDir = strPath + _T("Log");
    if (!PathFileExists(strLogDir)) {
        CreateDirectory(strLogDir, NULL);
@@ -37,8 +46,7 @@
            m_logFile.Close();
        }
        if (m_logFile.Open(strNewPath,
            CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::typeBinary)) {
        if (m_logFile.Open(strNewPath, CFile::modeCreate | CFile::modeNoTruncate | CFile::modeWrite | CFile::typeBinary | CFile::shareDenyWrite)) {
            if (m_logFile.GetLength() == 0) {
                WCHAR bom = 0xFEFF;
SourceCode/Bond/SGMeasurement/PLCSignalListener.cpp
@@ -255,7 +255,7 @@
                        std::string strProductID;
                        if (ReadProductID(strProductID)) {
                            CString msg;
                            msg.Format(_T("读取到产品ID:%s"), strProductID);
                            msg.Format(_T("读取到产品ID:%s"), CString(strProductID.c_str()));
                            LOG_MSG(msg, LOG_TYPE_SUCCESS);
                        }
                    }
@@ -309,15 +309,22 @@
    }
    for (int i = 0; i < PLC_RESULT_ADDR_COUNT; ++i) {
        // 放大1000倍并四舍五入,转为PLC整数
        int32_t  nScaled = static_cast<int32_t>(std::round(values[i] * 1000.0));
        DWordContainer vec = { static_cast<uint32_t>(nScaled) };
        double dVal = values[i];
        int32_t nScaled = 0;
        if (dVal == DBL_MAX || dVal == DBL_MIN || std::isnan(dVal)) {
            nScaled = static_cast<int32_t>(dVal);
        }
        else {
            nScaled = static_cast<int32_t>(std::round(dVal * 1000.0));
        }
        short nTargetAddr = PLC_RESULT_ADDR_START + i * 2;
        DWordContainer vec = { static_cast<uint32_t>(nScaled) };
        int ret = m_pPlc->WriteDWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, nTargetAddr, vec);
        if (ret != 0) {
            CString msg;
            msg.Format(_T("写入OUT%d到地址%d失败,值=%.2f"), i + 1, nTargetAddr, values[i]);
            msg.Format(_T("写入OUT%d到地址%d失败,值=%.2f"), i + 1, nTargetAddr, dVal);
            LOG_MSG(msg, LOG_TYPE_ERROR);
            return false;
        }
@@ -360,4 +367,43 @@
    }
    return true;
}
bool CPLCSignalListener::WriteProductID(const std::string& strProductID)
{
    if (!m_pPlc || !m_bConnected) {
        LOG_MSG(_T("PLC未连接或未初始化,无法写入产品ID。"), LOG_TYPE_ERROR);
        return false;
    }
    WordContainer vec;
    vec.reserve(PLC_PRODUCT_ID_WORDS);
    for (size_t i = 0; i < strProductID.size();) {
        unsigned char c1 = static_cast<unsigned char>(strProductID[i]);
        unsigned char c2 = 0;
        if (i + 1 < strProductID.size()) {
            c2 = static_cast<unsigned char>(strProductID[i + 1]);
        }
        uint16_t w = static_cast<uint16_t>(c2 << 8 | c1);
        vec.push_back(w);
        i += 2;
    }
    while (vec.size() < PLC_PRODUCT_ID_WORDS) {
        vec.push_back(0);
    }
    int ret = m_pPlc->WriteWordDataEx(m_station, PLC_WORD_DEVICE_TYPE, PLC_PRODUCT_ID_ADDR, vec);
    if (ret != 0) {
        CString msg;
        msg.Format(_T("写入产品ID失败,错误码=%d"), ret);
        LOG_MSG(msg, LOG_TYPE_ERROR);
        return false;
    }
    return true;
}
SourceCode/Bond/SGMeasurement/PLCSignalListener.h
@@ -87,6 +87,17 @@
     */
    bool ReadProductID(std::string& strProductID);
    /**
     * @brief 将产品 ID(字符串形式)写入 PLC 内部。
     *
     * @param strProductID  输入参数,要写入的产品 ID。
     *                      字符串会按字节对齐,每两个字节组成一个 Word,
     *                      写入 PLC 的指定寄存器区域,不足时自动补零。
     * @return true         写入成功。
     * @return false        写入失败。
     */
    bool WriteProductID(const std::string& strProductID);
private:
    /**
     * @brief 输出日志信息(封装日志回调)。
SourceCode/Bond/SGMeasurement/SGMeasurement.cpp
@@ -103,7 +103,7 @@
    CWinApp::InitInstance();
    // 唯一实例运行检测
    m_hMutex = ::CreateMutex(NULL, FALSE, _T("MutexEdgeInspector_App"));
    m_hMutex = ::CreateMutex(NULL, FALSE, _T("MutexSGMeasurementApp"));
    if (m_hMutex != NULL) {
        if (::GetLastError() == ERROR_ALREADY_EXISTS) {
            AfxMessageBox(_T("The Program is already running. Exit this Program."), MB_OK | MB_ICONERROR);
SourceCode/Bond/SGMeasurement/SGMeasurement.rc
Binary files differ
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj
@@ -238,6 +238,7 @@
  </ItemGroup>
  <ItemGroup>
    <Image Include="res\menu_close.ico" />
    <Image Include="res\menu_open_dir.ico" />
    <Image Include="res\menu_restore.ico" />
    <Image Include="res\SGMeasurement.ico" />
  </ItemGroup>
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj.filters
@@ -98,5 +98,8 @@
    <Image Include="res\menu_close.ico">
      <Filter>资源文件</Filter>
    </Image>
    <Image Include="res\menu_open_dir.ico">
      <Filter>资源文件</Filter>
    </Image>
  </ItemGroup>
</Project>
SourceCode/Bond/SGMeasurement/SGMeasurement.vcxproj.user
@@ -4,12 +4,12 @@
    <RESOURCE_FILE>SGMeasurement.rc</RESOURCE_FILE>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
    <DebuggerFlavor>WindowsRemoteDebugger</DebuggerFlavor>
  </PropertyGroup>
  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
    <RemoteDebuggerCommand>\\DESKTOP-IODBVIQ\SGMeasurement\$(ProjectName).exe</RemoteDebuggerCommand>
    <RemoteDebuggerWorkingDirectory>\\DESKTOP-IODBVIQ\SGMeasurement</RemoteDebuggerWorkingDirectory>
    <RemoteDebuggerServerName>DESKTOP-IODBVIQ</RemoteDebuggerServerName>
    <DebuggerFlavor>WindowsRemoteDebugger</DebuggerFlavor>
    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
  </PropertyGroup>
</Project>
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.cpp
@@ -17,6 +17,7 @@
#define DeviceID 0
// 托盘图标 ID 与消息宏
#define ID_TRAY_OPEN_DIR       2000                // 打开目录
#define ID_TRAY_RESTORE           2001                // 恢复窗口
#define ID_TRAY_EXIT           2002                // 退出程序
#define WM_TRAY_ICON_NOTIFY    (WM_USER + 1000) // 托盘图标回调消息 ID
@@ -94,6 +95,7 @@
    , m_nTrayIconID(0)
    , m_bTrayIconCreated(FALSE)
    , m_bExitingFromTray(FALSE)
    , m_nAutoStart(TRUE)
{
    m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
@@ -131,6 +133,9 @@
    }
    m_plcListener.Stop();
    // 保存配置文件
    SaveConfig(GetConfigPath());
    DestroyWindow();
    CDialogEx::OnClose();
@@ -301,6 +306,134 @@
            strLine.Empty();
        }
    }
}
CString CSGMeasurementDlg::GetAppDirectory()
{
    TCHAR szPath[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szPath, MAX_PATH);
    CString strPath = szPath;
    int pos = strPath.ReverseFind('\\');
    if (pos != -1) {
        strPath = strPath.Left(pos + 1);
    }
    return strPath;
}
CString CSGMeasurementDlg::GetConfigPath()
{
    return GetAppDirectory() + _T("SGConfig.ini");
}
bool CSGMeasurementDlg::LoadConfig(const CString& strFile)
{
    CString strSection = _T("StorageConfig");
    TCHAR buf[256];
    GetPrivateProfileString(strSection, _T("UseTrigger"), _T("0"), buf, 256, strFile);
    m_nUseTrigger = _ttoi(buf);
    GetPrivateProfileString(strSection, _T("SavePointCount"), _T("100000"), buf, 256, strFile);
    m_nSavePointCount = _ttoi(buf);
    // 输出端口
    GetPrivateProfileString(strSection, _T("OutputPort"), _T("OUT1"), buf, 256, strFile);
    {
        int idx = m_comboOutputPort.FindStringExact(-1, buf);
        if (idx != CB_ERR) {
            m_comboOutputPort.SetCurSel(idx);
        }
        else {
            m_comboOutputPort.SetCurSel(0);
        }
    }
    // 跳变检测参数
    GetPrivateProfileString(strSection, _T("JumpThreshold"), _T("0.2"), buf, 256, strFile);
    m_fJumpThreshold = static_cast<float>(_tstof(buf));
    GetPrivateProfileString(strSection, _T("JumpWindow"), _T("3"), buf, 256, strFile);
    m_nJumpWindow = _ttoi(buf);
    GetPrivateProfileString(strSection, _T("ValleyMargin"), _T("0"), buf, 256, strFile);
    m_nValleyMargin = _ttoi(buf);
    GetPrivateProfileString(strSection, _T("MinGlass1Count"), _T("10"), buf, 256, strFile);
    m_nMinGlass1Count = _ttoi(buf);
    // 稳定区域参数
    GetPrivateProfileString(strSection, _T("FixedCount"), _T("5"), buf, 256, strFile);
    m_nFixedCount = _ttoi(buf);
    GetPrivateProfileString(strSection, _T("MaxDelta"), _T("0.05"), buf, 256, strFile);
    m_fMaxDelta = static_cast<float>(_tstof(buf));
    // 自启动
    GetPrivateProfileString(strSection, _T("AutoStart"), _T("1"), buf, 256, strFile);
    m_nAutoStart = _ttoi(buf);
    return true;
}
bool CSGMeasurementDlg::SaveConfig(const CString& strFile)
{
    CString strSection = _T("StorageConfig");
    WritePrivateProfileString(strSection, _T("UseTrigger"), std::to_wstring(m_nUseTrigger).c_str(), strFile);
    WritePrivateProfileString(strSection, _T("SavePointCount"), std::to_wstring(m_nSavePointCount).c_str(), strFile);
    // 输出端口下拉框
    CString strPort;
    m_comboOutputPort.GetWindowText(strPort);
    WritePrivateProfileString(strSection, _T("OutputPort"), strPort, strFile);
    // 跳变检测
    WritePrivateProfileString(strSection, _T("JumpThreshold"), std::to_wstring(m_fJumpThreshold).c_str(), strFile);
    WritePrivateProfileString(strSection, _T("JumpWindow"), std::to_wstring(m_nJumpWindow).c_str(), strFile);
    WritePrivateProfileString(strSection, _T("ValleyMargin"), std::to_wstring(m_nValleyMargin).c_str(), strFile);
    WritePrivateProfileString(strSection, _T("MinGlass1Count"), std::to_wstring(m_nMinGlass1Count).c_str(), strFile);
    // 稳定区
    WritePrivateProfileString(strSection, _T("FixedCount"), std::to_wstring(m_nFixedCount).c_str(), strFile);
    WritePrivateProfileString(strSection, _T("MaxDelta"), std::to_wstring(m_fMaxDelta).c_str(), strFile);
    // 自启动
    WritePrivateProfileString(strSection, _T("AutoStart"), std::to_wstring(m_nAutoStart).c_str(), strFile);
    return true;
}
bool CSGMeasurementDlg::SetAutoStart(bool bEnable)
{
    // 获取当前程序路径
    TCHAR szPath[MAX_PATH] = { 0 };
    GetModuleFileName(NULL, szPath, MAX_PATH);
    CString strAppPath = szPath;
    // 获取应用程序名称
    CString strAppName = ::PathFindFileName(strAppPath);
    strAppName = strAppName.Left(strAppName.ReverseFind('.'));
    HKEY hKey;
    LONG lRet = RegOpenKeyEx(HKEY_CURRENT_USER, _T("Software\\Microsoft\\Windows\\CurrentVersion\\Run"), 0, KEY_WRITE, &hKey);
    if (lRet != ERROR_SUCCESS) {
        return false;
    }
    if (bEnable) {
        // 设置自启
        lRet = RegSetValueEx(hKey, strAppName, 0, REG_SZ, (BYTE*)(LPCTSTR)strAppPath, (strAppPath.GetLength() + 1) * sizeof(TCHAR));
    }
    else {
        // 取消自启
        lRet = RegDeleteValue(hKey, strAppName);
    }
    RegCloseKey(hKey);
    return (lRet == ERROR_SUCCESS);
}
bool CSGMeasurementDlg::ConnectToDevice()
@@ -715,7 +848,7 @@
    return false;
}
float CSGMeasurementDlg::AnalyzeStoredData(int nOutNo)
double CSGMeasurementDlg::AnalyzeStoredData(int nOutNo)
{
    MEASURE_FUNC_START();
@@ -724,27 +857,27 @@
    if (m_nUseTrigger) {
        UpdateControlStatus(m_bConnected, m_bSaving);
        AfxMessageBox(_T("当前是硬触发模式,请检查触发器状态。"), MB_ICONINFORMATION);
        return 0xFF;
        return DBL_MAX;
    }
    if (!m_bConnected) {
        AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
        return 0xFF;
        return DBL_MAX;
    }
    if (m_bSaving) {
        AppendLogLineRichStyled(_T("数据存储正在进行中,请先停止存储。"), LOG_COLOR_WARNING);
        return 0xFF;
        return DBL_MAX;
    }
    if (nOutNo < 1 || nOutNo > 4) {
        AppendLogLineRichStyled(_T("输出端口编号无效,必须在 1 到 4 之间。"), LOG_COLOR_ERROR);
        return 0xFF;
        return DBL_MAX;
    }
    if (m_nSavePointCount < 0) {
        AppendLogLineRichStyled(_T("数据点数必须大于 0。"), LOG_COLOR_ERROR);
        return 0xFF;
        return DBL_MAX;
    }
    std::vector<float> vecBuffer(m_nSavePointCount, 0.0f);
@@ -755,7 +888,7 @@
        CString strError;
        strError.Format(_T("读取 OUT%d 数据失败,错误码:%#X"), nOutNo, nRet);
        AppendLogLineRichStyled(strError, LOG_COLOR_ERROR);
        return 0xFF;
        return DBL_MAX;
    }
    vecBuffer.resize(nReceived);
@@ -764,7 +897,7 @@
    std::vector<float> vecGlass1, vecGlass2;
    if (!SplitGlassSegments(nOutNo, vecBuffer, vecGlass1, vecGlass2, m_fJumpThreshold, m_nJumpWindow, m_nValleyMargin, m_nMinGlass1Count)) {
        AppendLogLineRichStyled(_T("未能识别出两片玻璃的数据。"), LOG_COLOR_WARNING);
        return 0xFF;
        return DBL_MAX;
    }
    std::vector<float> vecGlass1Filtered, vecGlass2Filtered;
@@ -815,6 +948,7 @@
    ON_WM_DRAWITEM()
    ON_WM_CLOSE()
    ON_MESSAGE(WM_TRAY_ICON_NOTIFY, &CSGMeasurementDlg::OnTrayIconClick)
    ON_COMMAND(ID_TRAY_OPEN_DIR, &CSGMeasurementDlg::OnTrayOpenDir)
    ON_COMMAND(ID_TRAY_RESTORE, &CSGMeasurementDlg::OnTrayRestore)
    ON_COMMAND(ID_TRAY_EXIT, &CSGMeasurementDlg::OnTrayExit)
    ON_BN_CLICKED(IDC_BUTTON_CONNECT, &CSGMeasurementDlg::OnBnClickedButtonConnect)
@@ -924,7 +1058,7 @@
    m_plcListener.SetAnalyzeCallback([this]() {
        if (!m_bConnected) {
            AppendLogLineRichStyled(_T("设备未连接,请先连接设备。"), LOG_COLOR_WARNING);
            return std::array<double, 4>{ 0xFF, 0xFF, 0xFF, 0xFF };
            return std::array<double, 4>{ DBL_MAX, DBL_MAX, DBL_MAX, DBL_MAX };
        }
        std::array<double, 4> result;
@@ -941,6 +1075,27 @@
        return result;
    });
    m_plcListener.Start();
    // 加载配置文件
    if (LoadConfig(GetConfigPath())) {
        AppendLogLineRichStyled(_T("配置已从 SGConfig.ini 加载成功"), LOG_COLOR_SUCCESS);
    }
    else {
        AppendLogLineRichStyled(_T("配置加载失败,使用默认参数"), LOG_COLOR_WARNING);
    }
    // 设置自动启动
    if (SetAutoStart(m_nAutoStart)) {
        if (m_nAutoStart) {
            AppendLogLineRichStyled(_T("已启用开机自启动"), LOG_COLOR_SUCCESS);
        }
        else {
            AppendLogLineRichStyled(_T("已取消开机自启动"), LOG_COLOR_WARNING);
        }
    }
    else {
        AppendLogLineRichStyled(_T("设置开机自启动失败,请检查权限"), LOG_COLOR_ERROR);
    }
    // 初始化日志框
    AppendLogLineRichStyled(_T("准备就绪..."), LOG_COLOR_SUCCESS);
@@ -1053,6 +1208,10 @@
    // 图标
    HICON hIcon = nullptr;
    if (id == ID_TRAY_OPEN_DIR) {
        hIcon = AfxGetApp()->LoadIcon(IDI_ICON_OPEN_DIR);
    }
    if (id == ID_TRAY_RESTORE) {
        hIcon = AfxGetApp()->LoadIcon(IDI_ICON_RESTORE);
    }
@@ -1067,6 +1226,10 @@
    // 文本
    CString str;
    if (id == ID_TRAY_OPEN_DIR) {
        str = _T("打开目录");
    }
    if (id == ID_TRAY_RESTORE) { 
        str = _T("恢复界面");
    }
@@ -1110,22 +1273,23 @@
            // 右键点击弹出菜单
            CMenu menu;
            menu.CreatePopupMenu();
            menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_OPEN_DIR, (LPCTSTR)ID_TRAY_OPEN_DIR);
            menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_RESTORE, (LPCTSTR)ID_TRAY_RESTORE);
            menu.AppendMenu(MF_OWNERDRAW, ID_TRAY_EXIT, (LPCTSTR)ID_TRAY_EXIT);
            // 加载图标
            HICON hIconRestore = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_RESTORE), IMAGE_ICON, 16, 16, LR_SHARED);
            HICON hIconExit = (HICON)::LoadImage(AfxGetInstanceHandle(), MAKEINTRESOURCE(IDI_ICON_EXIT), IMAGE_ICON, 16, 16, LR_SHARED);
            // 设置图标到菜单项
            MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
            mii.fMask = MIIM_BITMAP;
            // 恢复菜单项图标
            // 打开目录菜单项图标
            mii.hbmpItem = HBMMENU_CALLBACK;
            menu.SetMenuItemInfo(ID_TRAY_OPEN_DIR, &mii);
            // 恢复窗口菜单项图标
            mii.hbmpItem = HBMMENU_CALLBACK;
            menu.SetMenuItemInfo(ID_TRAY_RESTORE, &mii);
            // 退出菜单项图标
            // 退出程序菜单项图标
            mii.hbmpItem = HBMMENU_CALLBACK;
            menu.SetMenuItemInfo(ID_TRAY_EXIT, &mii);
@@ -1139,6 +1303,18 @@
    return 0;
}
void CSGMeasurementDlg::OnTrayOpenDir()
{
    CString strDir = GetAppDirectory();
    if (PathFileExists(strDir)) {
        ShellExecute(NULL, _T("open"), strDir, NULL, NULL, SW_SHOWDEFAULT);
        AppendLogLineRichStyled(_T("已打开程序目录"), LOG_COLOR_SUCCESS);
    }
    else {
        AppendLogLineRichStyled(_T("目录不存在,无法打开"), LOG_COLOR_ERROR);
    }
}
void CSGMeasurementDlg::OnTrayRestore()
{
    ShowWindow(SW_SHOW);      // 恢复窗口
SourceCode/Bond/SGMeasurement/SGMeasurementDlg.h
@@ -41,6 +41,7 @@
    afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct);
    afx_msg void OnClose();
    afx_msg LRESULT OnTrayIconClick(WPARAM wParam, LPARAM lParam);
    afx_msg void OnTrayOpenDir();
    afx_msg void OnTrayRestore();
    afx_msg void OnTrayExit();
    afx_msg void OnBnClickedButtonConnect();
@@ -116,6 +117,60 @@
     * @param vecBuffer 测量数据缓存,将被逐行打印到日志中。
     */
    void PrintSampleData(int nOutNo, const std::vector<float>& vecBuffer);
    /**
     * @brief 获取当前应用程序所在的目录路径。
     *
     * 通过 GetModuleFileName 获取当前可执行文件的完整路径,
     * 并截取掉文件名部分,返回目录路径,末尾自带反斜杠。
     *
     * @return CString 应用程序所在目录,例如 "C:\\Program Files\\SGMeasurement\\"
     */
    CString GetAppDirectory();
    /**
     * @brief 获取配置文件的完整路径。
     *
     * 配置文件名固定为 "config.ini",位于应用程序目录下。
     *
     * @return CString 配置文件完整路径,例如 "C:\\Program Files\\SGMeasurement\\config.ini"
     */
    CString GetConfigPath();
    /**
     * @brief 从 ini 配置文件加载存储与分析参数。
     *
     * 读取指定 ini 文件中的参数值,并更新对话框类中的成员变量,
     * 包括存储设置(触发方式、采样点数)、跳变检测参数、稳定区提取参数等。
     * 若文件中缺少某些字段,将使用默认值。
     *
     * @param strFile ini 文件路径。
     * @return true 表示加载成功,false 表示加载失败(如文件不存在)。
     */
    bool LoadConfig(const CString& strFile);
    /**
     * @brief 将当前存储与分析参数保存到 ini 配置文件。
     *
     * 把对话框类中的成员变量(存储设置、跳变检测、稳定区提取等参数)
     * 序列化并写入到指定的 ini 文件中,以便下次启动时恢复。
     *
     * @param strFile ini 文件路径。
     * @return true 表示保存成功,false 表示保存失败。
     */
    bool SaveConfig(const CString& strFile);
    /**
     * @brief 设置或取消当前程序的开机自启动。
     *
     * 该函数会在注册表 `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`
     * 下写入或删除当前程序的路径,从而控制是否随 Windows 启动自动运行。
     *
     * @param bEnable 是否启用自启动。true 表示设置开机自启,false 表示取消自启。
     *
     * @return true 表示操作成功;false 表示操作失败(如注册表权限不足)。
     */
    bool SetAutoStart(bool bEnable);
    /**
     * @brief 尝试连接到测量设备。
@@ -234,9 +289,9 @@
     * @brief 分析指定端口的存储数据,并提取两段玻璃数据与稳定区,计算偏移。
     *
     * @param nOutNo     输出端口编号(1~4)
     * @return float     成功返回计算出的偏移量,失败返回 -1.0f
     * @return double     成功返回计算出的偏移量,失败返回 -1.0f
     */
    float AnalyzeStoredData(int nOutNo);
    double AnalyzeStoredData(int nOutNo);
    // === 系统状态与运行数据 ===
@@ -335,6 +390,13 @@
     */
    BOOL m_bExitingFromTray;
    // === 自启动相关 ===
    /**
     * @brief 是否开机自启动
     */
    BOOL m_nAutoStart;
    // === PLC 信号监听器 ===
    /**
SourceCode/Bond/SGMeasurement/res/menu_open_dir.ico
SourceCode/Bond/SGMeasurement/resource.h
@@ -9,6 +9,7 @@
#define IDR_MAINFRAME                   128
#define IDI_ICON_RESTORE                130
#define IDI_ICON_EXIT                   131
#define IDI_ICON_OPEN_DIR               132
#define IDC_IPADDRESS                   1000
#define IDC_BUTTON_CONNECT              1001
#define IDC_BUTTON_DISCONNECT           1002
@@ -37,7 +38,7 @@
// 
#ifdef APSTUDIO_INVOKED
#ifndef APSTUDIO_READONLY_SYMBOLS
#define _APS_NEXT_RESOURCE_VALUE        132
#define _APS_NEXT_RESOURCE_VALUE        133
#define _APS_NEXT_COMMAND_VALUE         32771
#define _APS_NEXT_CONTROL_VALUE         1023
#define _APS_NEXT_SYMED_VALUE           101