├── .github └── workflows │ └── build.yml ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.ps1 ├── img ├── S7-Project-Explorer.svg ├── step1.png ├── step2.png └── step3.png └── src ├── CFilePage.cpp ├── CFilePage.h ├── CFinishPage.cpp ├── CFinishPage.h ├── CMainWindow.cpp ├── CMainWindow.h ├── CPage.h ├── CVariablesPage.cpp ├── CVariablesPage.h ├── CWarningsWindow.cpp ├── CWarningsWindow.h ├── Compatibility.manifest ├── EnlyzeS7PLib └── src │ ├── CMc5ArrayDimension.h │ ├── CMc5ArrayEnumerator.h │ ├── CMc5codeParser.cpp │ ├── CMc5codeParser.h │ ├── CS7PError.h │ ├── EnlyzeS7PLib.vcxproj │ ├── EnlyzeS7PLib.vcxproj.filters │ ├── s7p_db_parser.cpp │ ├── s7p_db_parser.h │ ├── s7p_device_id_info_parser.cpp │ ├── s7p_device_id_info_parser.h │ ├── s7p_parser.cpp │ ├── s7p_parser.h │ ├── s7p_symbol_list_parser.cpp │ └── s7p_symbol_list_parser.h ├── PerMonitorV2.manifest ├── S7-Project-Explorer.cpp ├── S7-Project-Explorer.h ├── S7-Project-Explorer.rc ├── S7-Project-Explorer.sln ├── S7-Project-Explorer.vcxproj ├── S7-Project-Explorer.vcxproj.filters ├── VisualStyles.manifest ├── csv_exporter.cpp ├── csv_exporter.h ├── lang ├── de-DE.rc └── en-US.rc ├── res ├── blank-file.png ├── blank-file.svg ├── enlyze-logo.bmp ├── enlyze-s7.ico ├── s7p-file.png └── s7p-file.svg ├── resource.h ├── targetver.h ├── utils.cpp ├── utils.h ├── version.h └── win32_wrappers.h /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | build: 9 | runs-on: windows-2022 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v3 14 | with: 15 | submodules: recursive 16 | 17 | - name: Build 18 | run: docker run -v "${{ github.workspace }}:C:\Source" ghcr.io/enlyze/windows-build-environment powershell \Source\build.ps1 19 | 20 | - name: Upload Artifacts 21 | uses: actions/upload-artifact@v3 22 | with: 23 | name: S7-Project-Explorer Executable and PDB 24 | path: build/Release/S7-Project-Explorer/bin/S7-Project-Explorer.* 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Exclude Visual Studio autogenerated files. 2 | .vs 3 | *.aps 4 | *.sdf 5 | *.suo 6 | *.user 7 | 8 | # Exclude the configured build directory. 9 | build 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/scope-guard"] 2 | path = src/scope-guard 3 | url = https://github.com/enlyze/scope-guard.git 4 | [submodule "src/EnlyzeWinCompatLib"] 5 | path = src/EnlyzeWinCompatLib 6 | url = https://github.com/enlyze/EnlyzeWinCompatLib.git 7 | [submodule "src/EnlyzeWinStringLib"] 8 | path = src/EnlyzeWinStringLib 9 | url = https://github.com/enlyze/EnlyzeWinStringLib.git 10 | [submodule "src/EnlyzeDbfLib"] 11 | path = src/EnlyzeDbfLib 12 | url = https://github.com/enlyze/EnlyzeDbfLib.git 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). 5 | 6 | 7 | ## [2.4] - 2023-11-30 8 | - Added a Byte Order Mark (BOM) when writing a CSV file to fix special characters when opening the resulting file in Excel (#4, #5) 9 | 10 | ## [2.3] - 2023-03-01 11 | - Updated graphics to new ENLYZE Corporate Identity (#3) 12 | 13 | ## [2.2] - 2022-10-20 14 | - Fixed graphical glitch when selecting a file and resizing the window (#2) 15 | 16 | ## [2.1] - 2022-10-19 17 | - Fixed accessing paths with non-ASCII characters (#1) 18 | - Added the version number to the title bar 19 | 20 | ## [2.0] - 2022-10-05 21 | - Initial open-source release 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2022 Colin Finck, ENLYZE GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 | software and associated documentation files (the "Software"), to deal in the Software 5 | without restriction, including without limitation the rights to use, copy, modify, 6 | merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 | permit persons to whom the Software is furnished to do so, subject to the following 8 | conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies 11 | or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 15 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 16 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 17 | CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 18 | OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # ENLYZE S7-Project-Explorer 4 | 5 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) 6 | 7 | A standalone application for opening a STEP 7 project file (.s7p format) for Siemens S7-300/400 PLCs, browsing through its variables, and exporting the list as a CSV file. 8 | 9 | Runs on Windows XP or any later Windows version. 10 | 11 | ## Screenshots 12 | S7-Project Explorer GUI Step 1: Selecting a project file 13 | S7-Project-Explorer GUI Step 2: Browse through all variables 14 | S7-Project-Explorer GUI Step 3: Finish the wizard 15 | 16 | ## Usage 17 | 18 | You need to have access to the S7 Project structure, the top level will have a file ending in `.s7p` next to a number of other files and directories. The project explorer 19 | needs to open files from a number of directories, so having just the `.s7p` file alone is not enough. 20 | 21 | ## Download 22 | Check the [Releases](https://github.com/enlyze/S7-Project-Explorer/releases) page to download the latest version of the S7-Project-Explorer. 23 | 24 | ## Building 25 | To build the S7-Project-Explorer code, you need **Visual Studio 2019 with Clang**. 26 | This codebase is not yet compatible to any newer Visual Studio version. 27 | Likewise, older Visual Studio versions don't support Clang. 28 | 29 | You can download a free Community Edition of Visual Studio 2019 at https://docs.microsoft.com/visualstudio/releases/2019/history 30 | 31 | ## CSV Format 32 | ENLYZE S7-Project-Explorer exports the variable list in a standardized CSV format. 33 | This file type is suitable for viewing as well as post-processing in another application. 34 | 35 | The CSV file has the following table headers: 36 | 37 | * _DEVICE_ 38 | The path to the S7 device/program containing this variable. 39 | Can be arbitrarily simple or complex depending on your STEP 7 project structure. 40 | 41 | Examples: 42 | * `S7 Program` 43 | * `S7-300: My First PLC -> CPU 317-2 DP -> S7 Program` 44 | * `S7-400: Client S7 400 -> CPU 414-2 DP -> S7 Program` 45 | 46 | * _BLOCK_ 47 | The block that this variable is part of. 48 | Can be either "Symbol List" or a numbered Data Block (DB). 49 | If the DB has a name, the name is appended in parentheses. 50 | 51 | Examples: 52 | * `Symbol List` 53 | * `DB6 (HMI_Vars)` 54 | 55 | * _VARIABLE_ 56 | The variable name as set in your project. 57 | Structure variables are flattened in the process. 58 | Structure variable name and structure field name are separated via a dot. 59 | 60 | Arrays of structures are permuted over all elements. 61 | This also applies to multi-dimensional arrays of structures. 62 | 63 | Examples: 64 | * `HeaterOnOff` 65 | * `StatusStruct.MotorStatus.MotorTemp` 66 | * `SPArray[0].Velocity` 67 | * `PVMatrix[1,1].Torque` 68 | 69 | * _CODE_ 70 | The code/address to access this variable. 71 | Codes from the Symbol List are output in their English form as-is, with e.g. `I` for inputs, `Q` for outputs, and `M` for memory. 72 | They may be followed by a length indicator, like `W` to indicate a `WORD` data type. 73 | 74 | On the other hand, codes from Data Blocks (DB) always follow the `DBn:a.b` format, where `n` is the DB number, `a` is the byte address within the DB, and `b` is the bit of that byte (nonzero only for `BOOL` variables). 75 | 76 | Examples: 77 | * `I1.0` 78 | * `Q5.2` 79 | * `M76.1` 80 | * `MW200` 81 | * `DB6:26.0` 82 | 83 | * _DATATYPE_ 84 | The variable's datatype. 85 | Can be one of the scalar types or an array of one of the scalar types. 86 | Multi-dimensional arrays are also supported. 87 | 88 | Note that arrays of struct variables are instead permuted over all their elements (see _VARIABLE_). 89 | 90 | Examples: 91 | * `BOOL` 92 | * `WORD` 93 | * `INT` 94 | * `ARRAY [0..10] OF INT` 95 | * `ARRAY [-5..5, 2..3] OF BOOL` 96 | 97 | * _COMMENT_ 98 | Variable description entered in STEP 7. 99 | May also be prepended by special markers (such as "Struct" to indicate a struct variable) through S7-Project-Explorer. 100 | 101 | Note that any warnings emitted while importing the .s7p file are also written into the .csv file. 102 | Warning rows only have information in their _DEVICE_ and _COMMENT_ cells, and all other cells are empty. 103 | This is how you can distinguish them from actual variable rows. 104 | 105 | The CSV format output by S7-Project-Explorer uses the `;` delimiter (which is common in Europe for CSV files despite the name). 106 | S7-Project-Explorer also filters out any semicolons or quotation marks from names and comments before writing them into the CSV file. 107 | This obviates the need for putting each cell in quotation marks or escaping any other character, keeping the CSV export code simple. 108 | This way, importing the created file should also be possible using any CSV engine. 109 | 110 | ## Contact 111 | Deniz Saner ([d.saner@enlyze.com](mailto:d.saner@enlyze.com)) 112 | 113 | All credits go to Colin Finck ([c.finck@enlyze.com](mailto:c.finck@enlyze.com)) 114 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | cd $PSScriptRoot 2 | 3 | # Set the current Git revision in version.h 4 | $gitRevision = & git rev-parse HEAD 5 | ((Get-Content -Path src\version.h -Raw) -Replace 'unknown revision',$gitRevision) | Set-Content -Path src\version.h 6 | 7 | # Build S7-Project-Explorer 8 | cd src 9 | cmd /c "C:\BuildTools\Common7\Tools\VsDevCmd.bat && msbuild S7-Project-Explorer.sln /m /t:Rebuild /p:Configuration=Release" 10 | -------------------------------------------------------------------------------- /img/S7-Project-Explorer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /img/step1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/img/step1.png -------------------------------------------------------------------------------- /img/step2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/img/step2.png -------------------------------------------------------------------------------- /img/step3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/img/step3.png -------------------------------------------------------------------------------- /src/CFilePage.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | #define IDC_FILE 500 10 | #define IDC_BROWSE 501 11 | 12 | 13 | void 14 | CFilePage::_OnBrowseButton() 15 | { 16 | std::wstring wstrSelectedFilePath = std::wstring(MAX_PATH, L'\0'); 17 | 18 | OPENFILENAMEW ofn = {}; 19 | ofn.lStructSize = sizeof(ofn); 20 | ofn.hwndOwner = m_pMainWindow->GetHwnd(); 21 | ofn.lpstrFilter = m_wstrBrowseFilter.c_str(); 22 | ofn.lpstrFile = wstrSelectedFilePath.data(); 23 | ofn.nMaxFile = MAX_PATH; 24 | ofn.lpstrTitle = m_wstrBrowseTitle.c_str(); 25 | ofn.Flags = OFN_DONTADDTORECENT | OFN_FILEMUSTEXIST; 26 | 27 | if (GetOpenFileNameW(&ofn)) 28 | { 29 | // Copy the newly selected file path into the member variable. 30 | m_wstrSelectedFilePath.assign(wstrSelectedFilePath, 0, wstrSelectedFilePath.find(L'\0')); 31 | 32 | // Set the file static control to the s7p file bitmap. 33 | m_pCurrentFileBitmap = m_pS7PFileBitmap.get(); 34 | RedrawWindow(m_hFile, nullptr, nullptr, RDW_INVALIDATE); 35 | 36 | // Set the file name. 37 | size_t BackslashPosition = m_wstrSelectedFilePath.find_last_of(L'\\'); 38 | PCWSTR pwszFileName = m_wstrSelectedFilePath.c_str() + BackslashPosition + 1; 39 | SetWindowTextW(m_hFileName, pwszFileName); 40 | 41 | // Enable the "Next" button. 42 | m_pMainWindow->EnableNextButton(TRUE); 43 | } 44 | } 45 | 46 | LRESULT 47 | CFilePage::_OnCommand(WPARAM wParam) 48 | { 49 | switch (LOWORD(wParam)) 50 | { 51 | // Open the Browse dialog when the user clicks on the File icon. 52 | case IDC_FILE: 53 | { 54 | if (HIWORD(wParam) == STN_CLICKED) 55 | { 56 | _OnBrowseButton(); 57 | } 58 | 59 | break; 60 | } 61 | 62 | // Also open the Browse dialog when the user clicks on the "Browse" button. 63 | case IDC_BROWSE: 64 | { 65 | _OnBrowseButton(); 66 | break; 67 | } 68 | } 69 | 70 | return 0; 71 | } 72 | 73 | LRESULT 74 | CFilePage::_OnCreate() 75 | { 76 | // Load resources. 77 | m_pBlankFileBitmap = LoadPNGAsGdiplusBitmap(m_pMainWindow->GetHInstance(), IDP_BLANK_FILE); 78 | m_pS7PFileBitmap = LoadPNGAsGdiplusBitmap(m_pMainWindow->GetHInstance(), IDP_S7P_FILE); 79 | m_wstrBrowseTitle = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_BROWSE_TITLE); 80 | m_wstrHeader = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FILEPAGE_HEADER); 81 | m_wstrSubHeader = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FILEPAGE_SUBHEADER); 82 | m_wstrText = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FILEPAGE_TEXT); 83 | m_wstrNext = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_NEXT); 84 | 85 | // Load the browse filter and replace the '|' characters in the resource string by NUL-characters to create the required double-NUL-terminated string. 86 | // This is needed, because you cannot reliably store double-NUL-terminated strings in resources. 87 | m_wstrBrowseFilter = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_BROWSE_FILTER); 88 | std::replace(m_wstrBrowseFilter.begin(), m_wstrBrowseFilter.end(), L'|', L'\0'); 89 | 90 | // Create controls. 91 | m_hFile = CreateWindowExW(0, WC_STATICW, L"", WS_CHILD | WS_VISIBLE | SS_OWNERDRAW | SS_NOTIFY, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_FILE), nullptr, nullptr); 92 | 93 | m_hFileName = CreateWindowExW(0, WC_STATICW, L"", WS_CHILD | WS_VISIBLE | SS_CENTER, 0, 0, 0, 0, m_hWnd, nullptr, nullptr, nullptr); 94 | SendMessageW(m_hFileName, WM_SETFONT, reinterpret_cast(m_pMainWindow->GetGuiFont()), MAKELPARAM(TRUE, 0)); 95 | 96 | std::wstring wstrBrowse = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_BROWSE); 97 | m_hFileBrowse = CreateWindowExW(0, WC_BUTTONW, wstrBrowse.c_str(), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_BROWSE), nullptr, nullptr); 98 | SendMessageW(m_hFileBrowse, WM_SETFONT, reinterpret_cast(m_pMainWindow->GetGuiFont()), MAKELPARAM(TRUE, 0)); 99 | 100 | // Set the file static control to the blank file bitmap for now. 101 | m_pCurrentFileBitmap = m_pBlankFileBitmap.get(); 102 | 103 | return 0; 104 | } 105 | 106 | LRESULT 107 | CFilePage::_OnDrawItem(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 108 | { 109 | // We only draw the file icon. 110 | if (wParam != IDC_FILE) 111 | { 112 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 113 | } 114 | 115 | // We only need to draw when the entire control needs to be drawn (not on focus or selection changes). 116 | PDRAWITEMSTRUCT pDis = reinterpret_cast(lParam); 117 | if (pDis->itemAction != ODA_DRAWENTIRE) 118 | { 119 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 120 | } 121 | 122 | // Draw the file bitmap using anti-aliased GDI+ drawing. 123 | Gdiplus::Graphics g(pDis->hDC); 124 | Gdiplus::Color BackgroundColor; 125 | BackgroundColor.SetFromCOLORREF(GetSysColor(COLOR_BTNFACE)); 126 | g.Clear(BackgroundColor); 127 | g.DrawImage(m_pCurrentFileBitmap, 0, 0, pDis->rcItem.right, pDis->rcItem.bottom); 128 | 129 | return TRUE; 130 | } 131 | 132 | LRESULT 133 | CFilePage::_OnEraseBkgnd() 134 | { 135 | // We always repaint the entire window in _OnPaint. 136 | // Therefore, don't forward WM_ERASEBKGND messages to DefWindowProcW to prevent flickering. 137 | return TRUE; 138 | } 139 | 140 | LRESULT 141 | CFilePage::_OnPaint() 142 | { 143 | // Get the window size. 144 | RECT rcWindow; 145 | GetClientRect(m_hWnd, &rcWindow); 146 | 147 | // Begin a double-buffered paint. 148 | PAINTSTRUCT ps; 149 | HDC hDC = BeginPaint(m_hWnd, &ps); 150 | HDC hMemDC = CreateCompatibleDC(hDC); 151 | HBITMAP hMemBitmap = CreateCompatibleBitmap(hDC, rcWindow.right, rcWindow.bottom); 152 | SelectObject(hMemDC, hMemBitmap); 153 | 154 | // Fill the window with the window background color. 155 | FillRect(hMemDC, &rcWindow, GetSysColorBrush(COLOR_BTNFACE)); 156 | 157 | // Draw the intro text. 158 | SelectObject(hMemDC, m_pMainWindow->GetGuiFont()); 159 | SetBkColor(hMemDC, GetSysColor(COLOR_BTNFACE)); 160 | DrawTextW(hMemDC, m_wstrText.c_str(), m_wstrText.size(), &rcWindow, DT_WORDBREAK); 161 | 162 | // End painting by copying the in-memory DC. 163 | BitBlt(hDC, 0, 0, rcWindow.right, rcWindow.bottom, hMemDC, 0, 0, SRCCOPY); 164 | DeleteObject(hMemBitmap); 165 | DeleteDC(hMemDC); 166 | EndPaint(m_hWnd, &ps); 167 | 168 | return 0; 169 | } 170 | 171 | LRESULT 172 | CFilePage::_OnSetCursor(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 173 | { 174 | // Set the hand cursor when the mouse moves over the file image. 175 | if (reinterpret_cast(wParam) == m_hFile) 176 | { 177 | HCURSOR hHandCursor = LoadCursorW(nullptr, IDC_HAND); 178 | SetCursor(hHandCursor); 179 | return TRUE; 180 | } 181 | 182 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 183 | } 184 | 185 | LRESULT 186 | CFilePage::_OnSize() 187 | { 188 | // Get the window size. 189 | RECT rcWindow; 190 | GetClientRect(m_hWnd, &rcWindow); 191 | 192 | // Invalidate all areas with text. 193 | InvalidateRect(m_hWnd, &rcWindow, FALSE); 194 | InvalidateRect(m_hFileName, nullptr, FALSE); 195 | 196 | // Move the file icon. 197 | HDWP hDwp = BeginDeferWindowPos(3); 198 | if (!hDwp) 199 | { 200 | return 0; 201 | } 202 | 203 | int iFileHeight = m_pMainWindow->ScaleControl(140); 204 | int iFileWidth = m_pMainWindow->ScaleControl(105); 205 | int iFileX = (rcWindow.right - iFileWidth) / 2; 206 | int iFileY = (rcWindow.bottom - iFileHeight) / 2; 207 | hDwp = DeferWindowPos(hDwp, m_hFile, nullptr, iFileX, iFileY, iFileWidth, iFileHeight, 0); 208 | if (!hDwp) 209 | { 210 | return 0; 211 | } 212 | 213 | // Move the file name label. 214 | int iFileNameHeight = m_pMainWindow->ScaleFont(20); 215 | int iFileNameWidth = rcWindow.right; 216 | int iFileNameX = 0; 217 | int iFileNameY = iFileY + iFileHeight; 218 | hDwp = DeferWindowPos(hDwp, m_hFileName, nullptr, iFileNameX, iFileNameY, iFileNameWidth, iFileNameHeight, 0); 219 | if (!hDwp) 220 | { 221 | return 0; 222 | } 223 | 224 | // Move the Browse button. 225 | int iBrowseHeight = m_pMainWindow->ScaleControl(30); 226 | int iBrowseWidth = m_pMainWindow->ScaleControl(105); 227 | int iBrowseX = (rcWindow.right - iBrowseWidth) / 2; 228 | int iBrowseY = iFileNameY + iFileNameHeight; 229 | hDwp = DeferWindowPos(hDwp, m_hFileBrowse, nullptr, iBrowseX, iBrowseY, iBrowseWidth, iBrowseHeight, 0); 230 | if (!hDwp) 231 | { 232 | return 0; 233 | } 234 | 235 | EndDeferWindowPos(hDwp); 236 | 237 | return 0; 238 | } 239 | 240 | LRESULT CALLBACK 241 | CFilePage::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 242 | { 243 | CFilePage* pPage = InstanceFromWndProc(hWnd, uMsg, lParam); 244 | 245 | if (pPage) 246 | { 247 | switch (uMsg) 248 | { 249 | case WM_COMMAND: return pPage->_OnCommand(wParam); 250 | case WM_CREATE: return pPage->_OnCreate(); 251 | case WM_DRAWITEM: return pPage->_OnDrawItem(hWnd, uMsg, wParam, lParam); 252 | case WM_ERASEBKGND: return pPage->_OnEraseBkgnd(); 253 | case WM_PAINT: return pPage->_OnPaint(); 254 | case WM_SETCURSOR: return pPage->_OnSetCursor(hWnd, uMsg, wParam, lParam); 255 | case WM_SIZE: return pPage->_OnSize(); 256 | } 257 | } 258 | 259 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 260 | } 261 | 262 | void 263 | CFilePage::SwitchTo() 264 | { 265 | m_pMainWindow->SetHeader(&m_wstrHeader, &m_wstrSubHeader); 266 | m_pMainWindow->EnableBackButton(FALSE); 267 | m_pMainWindow->SetNextButtonText(m_wstrNext); 268 | m_pMainWindow->ShowWarningsButton(FALSE); 269 | ShowWindow(m_hWnd, SW_SHOW); 270 | } 271 | 272 | void 273 | CFilePage::UpdateDPI() 274 | { 275 | // Update the control fonts. 276 | SendMessageW(m_hFileName, WM_SETFONT, reinterpret_cast(m_pMainWindow->GetGuiFont()), MAKELPARAM(TRUE, 0)); 277 | SendMessageW(m_hFileBrowse, WM_SETFONT, reinterpret_cast(m_pMainWindow->GetGuiFont()), MAKELPARAM(TRUE, 0)); 278 | } 279 | -------------------------------------------------------------------------------- /src/CFilePage.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | class CFilePage final : public CPage 10 | { 11 | public: 12 | static std::unique_ptr Create(CMainWindow* pMainWindow) { return CPage::Create(pMainWindow); } 13 | const std::wstring& GetSelectedFilePath() const { return m_wstrSelectedFilePath; } 14 | void SwitchTo(); 15 | void UpdateDPI(); 16 | 17 | private: 18 | friend class CPage; 19 | static constexpr WCHAR _wszWndClass[] = L"FilePageWndClass"; 20 | 21 | HWND m_hFile; 22 | HWND m_hFileName; 23 | HWND m_hFileBrowse; 24 | Gdiplus::Bitmap* m_pCurrentFileBitmap; 25 | std::unique_ptr m_pBlankFileBitmap; 26 | std::unique_ptr m_pS7PFileBitmap; 27 | std::wstring m_wstrBrowseFilter; 28 | std::wstring m_wstrBrowseTitle; 29 | std::wstring m_wstrHeader; 30 | std::wstring m_wstrSubHeader; 31 | std::wstring m_wstrText; 32 | std::wstring m_wstrNext; 33 | std::wstring m_wstrSelectedFilePath; 34 | 35 | CFilePage(CMainWindow* pMainWindow) : CPage(pMainWindow) {} 36 | void _OnBrowseButton(); 37 | LRESULT _OnCommand(WPARAM wParam); 38 | LRESULT _OnCreate(); 39 | LRESULT _OnDrawItem(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 40 | LRESULT _OnEraseBkgnd(); 41 | LRESULT _OnPaint(); 42 | LRESULT _OnSetCursor(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 43 | LRESULT _OnSize(); 44 | static LRESULT CALLBACK _WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 45 | }; 46 | -------------------------------------------------------------------------------- /src/CFinishPage.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | LRESULT 10 | CFinishPage::_OnCreate() 11 | { 12 | // Load resources. 13 | m_wstrHeader = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FINISHPAGE_HEADER); 14 | m_wstrSubHeader = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FINISHPAGE_SUBHEADER); 15 | m_wstrText = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FINISHPAGE_TEXT); 16 | m_wstrFinish = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_FINISH); 17 | 18 | return 0; 19 | } 20 | 21 | LRESULT 22 | CFinishPage::_OnEraseBkgnd() 23 | { 24 | // We always repaint the entire window in _OnPaint. 25 | // Therefore, don't forward WM_ERASEBKGND messages to DefWindowProcW to prevent flickering. 26 | return TRUE; 27 | } 28 | 29 | LRESULT 30 | CFinishPage::_OnPaint() 31 | { 32 | // Get the window size. 33 | RECT rcWindow; 34 | GetClientRect(m_hWnd, &rcWindow); 35 | 36 | // Begin a double-buffered paint. 37 | PAINTSTRUCT ps; 38 | HDC hDC = BeginPaint(m_hWnd, &ps); 39 | HDC hMemDC = CreateCompatibleDC(hDC); 40 | HBITMAP hMemBitmap = CreateCompatibleBitmap(hDC, rcWindow.right, rcWindow.bottom); 41 | SelectObject(hMemDC, hMemBitmap); 42 | 43 | // Fill the window with the window background color. 44 | FillRect(hMemDC, &rcWindow, GetSysColorBrush(COLOR_BTNFACE)); 45 | 46 | // Draw the finish text. 47 | SelectObject(hMemDC, m_pMainWindow->GetGuiFont()); 48 | SetBkColor(hMemDC, GetSysColor(COLOR_BTNFACE)); 49 | DrawTextW(hMemDC, m_wstrText.c_str(), m_wstrText.size(), &rcWindow, DT_WORDBREAK); 50 | 51 | // End painting by copying the in-memory DC. 52 | BitBlt(hDC, 0, 0, rcWindow.right, rcWindow.bottom, hMemDC, 0, 0, SRCCOPY); 53 | DeleteObject(hMemBitmap); 54 | DeleteDC(hMemDC); 55 | EndPaint(m_hWnd, &ps); 56 | 57 | return 0; 58 | } 59 | 60 | LRESULT CALLBACK 61 | CFinishPage::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 62 | { 63 | CFinishPage* pPage = InstanceFromWndProc(hWnd, uMsg, lParam); 64 | 65 | if (pPage) 66 | { 67 | switch (uMsg) 68 | { 69 | case WM_CREATE: return pPage->_OnCreate(); 70 | case WM_ERASEBKGND: return pPage->_OnEraseBkgnd(); 71 | case WM_PAINT: return pPage->_OnPaint(); 72 | } 73 | } 74 | 75 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 76 | } 77 | 78 | void 79 | CFinishPage::SwitchTo() 80 | { 81 | m_pMainWindow->SetHeader(&m_wstrHeader, &m_wstrSubHeader); 82 | m_pMainWindow->EnableBackButton(FALSE); 83 | m_pMainWindow->EnableCancelButton(FALSE); 84 | m_pMainWindow->SetNextButtonText(m_wstrFinish); 85 | m_pMainWindow->ShowWarningsButton(FALSE); 86 | ShowWindow(m_hWnd, SW_SHOW); 87 | } 88 | 89 | void 90 | CFinishPage::UpdateDPI() 91 | { 92 | } 93 | -------------------------------------------------------------------------------- /src/CFinishPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | class CFinishPage final : public CPage 10 | { 11 | public: 12 | static std::unique_ptr Create(CMainWindow* pMainWindow) { return CPage::Create(pMainWindow); } 13 | void SwitchTo(); 14 | void UpdateDPI(); 15 | 16 | private: 17 | friend class CPage; 18 | static constexpr WCHAR _wszWndClass[] = L"FinishPageWndClass"; 19 | 20 | std::wstring m_wstrHeader; 21 | std::wstring m_wstrSubHeader; 22 | std::wstring m_wstrText; 23 | std::wstring m_wstrFinish; 24 | 25 | CFinishPage(CMainWindow* pMainWindow) : CPage(pMainWindow) {} 26 | LRESULT _OnCreate(); 27 | LRESULT _OnEraseBkgnd(); 28 | LRESULT _OnPaint(); 29 | static LRESULT CALLBACK _WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 30 | }; 31 | -------------------------------------------------------------------------------- /src/CMainWindow.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022-2023 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | static const int iHeaderHeight = 70; 10 | static const int iMinWindowHeight = 500; 11 | static const int iMinWindowWidth = 700; 12 | 13 | #define IDC_BACK 500 14 | #define IDC_NEXT 501 15 | #define IDC_CANCEL 502 16 | #define IDC_WARNINGS 503 17 | 18 | 19 | CMainWindow::CMainWindow(HINSTANCE hInstance, int nShowCmd) 20 | : m_hInstance(hInstance), m_nShowCmd(nShowCmd) 21 | { 22 | } 23 | 24 | LRESULT CALLBACK 25 | CMainWindow::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 26 | { 27 | CMainWindow* pMainWindow = InstanceFromWndProc(hWnd, uMsg, lParam); 28 | 29 | // The first WM_GETMINMAXINFO comes before WM_NCCREATE, before we got our CMainWindow pointer. 30 | if (pMainWindow) 31 | { 32 | switch (uMsg) 33 | { 34 | case WM_COMMAND: return pMainWindow->_OnCommand(wParam); 35 | case WM_CREATE: return pMainWindow->_OnCreate(); 36 | case WM_DESTROY: return pMainWindow->_OnDestroy(); 37 | case WM_DPICHANGED: return pMainWindow->_OnDpiChanged(wParam, lParam); 38 | case WM_ERASEBKGND: return pMainWindow->_OnEraseBkgnd(); 39 | case WM_GETMINMAXINFO: return pMainWindow->_OnGetMinMaxInfo(lParam); 40 | case WM_PAINT: return pMainWindow->_OnPaint(); 41 | case WM_SIZE: return pMainWindow->_OnSize(); 42 | } 43 | } 44 | 45 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 46 | } 47 | 48 | void 49 | CMainWindow::_OnBackButton() 50 | { 51 | if (m_pCurrentPage == m_pVariablesPage.get()) 52 | { 53 | // Switch back to the first page. 54 | _SwitchPage(m_pFilePage.get()); 55 | } 56 | 57 | // The Back button is disabled for all other pages, so we don't have to handle these cases. 58 | } 59 | 60 | void 61 | CMainWindow::_OnCancelButton() 62 | { 63 | DestroyWindow(m_hWnd); 64 | } 65 | 66 | void 67 | CMainWindow::_OnNextButton() 68 | { 69 | if (m_pCurrentPage == m_pFilePage.get()) 70 | { 71 | _OnFilePageNextButton(); 72 | } 73 | else if (m_pCurrentPage == m_pVariablesPage.get()) 74 | { 75 | _OnVariablesPageNextButton(); 76 | } 77 | else if (m_pCurrentPage == m_pFinishPage.get()) 78 | { 79 | _OnFinishPageNextButton(); 80 | } 81 | } 82 | 83 | void 84 | CMainWindow::_OnFilePageNextButton() 85 | { 86 | // Parse the Step7 project. 87 | auto ParseResult = ParseS7P(m_pFilePage->GetSelectedFilePath()); 88 | if (const auto pError = std::get_if(&ParseResult)) 89 | { 90 | ErrorBox(LoadStringAsWstr(m_hInstance, IDS_PARSE_ERROR) + pError->Message()); 91 | return; 92 | } 93 | 94 | m_DeviceSymbolInfos = std::get>(std::move(ParseResult)); 95 | if (m_DeviceSymbolInfos.empty()) 96 | { 97 | ErrorBox(LoadStringAsWstr(m_hInstance, IDS_NO_VARIABLES)); 98 | return; 99 | } 100 | 101 | // Add the devices to the Device ComboBox and count warnings. 102 | HWND hDeviceComboBox = m_pVariablesPage->GetDeviceComboBox(); 103 | ComboBox_ResetContent(hDeviceComboBox); 104 | size_t cWarnings = 0; 105 | 106 | for (const auto& DeviceSymbolInfo : m_DeviceSymbolInfos) 107 | { 108 | ComboBox_AddString(hDeviceComboBox, StrToWstr(DeviceSymbolInfo.strName).c_str()); 109 | cWarnings += DeviceSymbolInfo.Warnings.size(); 110 | } 111 | 112 | // Select the first device. 113 | ComboBox_SetCurSel(hDeviceComboBox, 0); 114 | m_pVariablesPage->OnDeviceComboBoxSelectionChange(); 115 | 116 | // Show the "Warnings" button if we have warnings. 117 | if (cWarnings > 0) 118 | { 119 | std::wstring wstrWarnings = std::to_wstring(cWarnings) + L" " + LoadStringAsWstr(m_hInstance, IDS_WARNINGS); 120 | SetWindowTextW(m_hWarnings, wstrWarnings.c_str()); 121 | SetWindowTextW(m_pWarningsWindow->GetHwnd(), wstrWarnings.c_str()); 122 | ShowWarningsButton(TRUE); 123 | } 124 | 125 | // Switch to the Variables page. 126 | _SwitchPage(m_pVariablesPage.get()); 127 | } 128 | 129 | void 130 | CMainWindow::_OnVariablesPageNextButton() 131 | { 132 | std::wstring wstrFileToSave = std::wstring(MAX_PATH, L'\0'); 133 | 134 | OPENFILENAMEW ofn = {}; 135 | ofn.lStructSize = sizeof(ofn); 136 | ofn.hwndOwner = m_hWnd; 137 | ofn.lpstrFilter = m_wstrSaveFilter.c_str(); 138 | ofn.lpstrFile = wstrFileToSave.data(); 139 | ofn.nMaxFile = MAX_PATH; 140 | ofn.lpstrTitle = m_wstrSaveTitle.c_str(); 141 | ofn.lpstrDefExt = L".csv"; 142 | 143 | if (GetSaveFileNameW(&ofn)) 144 | { 145 | wstrFileToSave.resize(wstrFileToSave.find(L'\0')); 146 | 147 | // Export the list into the chosen file. 148 | auto ExportResult = ExportCSV(wstrFileToSave, m_DeviceSymbolInfos); 149 | if (const auto pError = std::get_if(&ExportResult)) 150 | { 151 | ErrorBox(LoadStringAsWstr(m_hInstance, IDS_SAVE_ERROR) + pError->Message()); 152 | return; 153 | } 154 | 155 | // Switch to the Finish page. 156 | _SwitchPage(m_pFinishPage.get()); 157 | } 158 | } 159 | 160 | void 161 | CMainWindow::_OnFinishPageNextButton() 162 | { 163 | DestroyWindow(m_hWnd); 164 | } 165 | 166 | void 167 | CMainWindow::_OnWarningsButton() 168 | { 169 | m_pWarningsWindow->Open(); 170 | } 171 | 172 | LRESULT 173 | CMainWindow::_OnCommand(WPARAM wParam) 174 | { 175 | switch (LOWORD(wParam)) 176 | { 177 | case IDC_BACK: _OnBackButton(); break; 178 | case IDC_NEXT: _OnNextButton(); break; 179 | case IDC_CANCEL: _OnCancelButton(); break; 180 | case IDC_WARNINGS: _OnWarningsButton(); break; 181 | } 182 | 183 | return 0; 184 | } 185 | 186 | LRESULT 187 | CMainWindow::_OnCreate() 188 | { 189 | // Get the DPI setting for the monitor where the main window is shown. 190 | m_wCurrentDPI = GetWindowDPI(m_hWnd); 191 | 192 | // Load resources. 193 | m_hLogoBitmap = LoadBitmapW(m_hInstance, MAKEINTRESOURCEW(IDB_LOGO)); 194 | m_wstrSaveFilter = LoadStringAsWstr(m_hInstance, IDS_SAVE_FILTER); 195 | m_wstrSaveTitle = LoadStringAsWstr(m_hInstance, IDS_SAVE_TITLE); 196 | 197 | // Load the save filter and replace the '|' characters in the resource string by NUL-characters to create the required double-NUL-terminated string. 198 | // This is needed, because you cannot reliably store double-NUL-terminated strings in resources. 199 | m_wstrSaveFilter = LoadStringAsWstr(m_hInstance, IDS_SAVE_FILTER); 200 | std::replace(m_wstrSaveFilter.begin(), m_wstrSaveFilter.end(), L'|', L'\0'); 201 | 202 | // Create the main GUI font. 203 | m_lfGuiFont = {}; 204 | wcscpy_s(m_lfGuiFont.lfFaceName, L"MS Shell Dlg 2"); 205 | m_lfGuiFont.lfHeight = -ScaleFont(10); 206 | m_hGuiFont = make_unique_font(CreateFontIndirectW(&m_lfGuiFont)); 207 | 208 | // Create the bold GUI font. 209 | m_lfBoldGuiFont = m_lfGuiFont; 210 | m_lfBoldGuiFont.lfWeight = FW_BOLD; 211 | m_hBoldGuiFont = make_unique_font(CreateFontIndirectW(&m_lfBoldGuiFont)); 212 | 213 | // Create the line above the buttons. 214 | m_hLine = CreateWindowExW(0, WC_STATICW, L"", WS_CHILD | WS_VISIBLE | SS_SUNKEN, 0, 0, 0, 0, m_hWnd, nullptr, nullptr, nullptr); 215 | 216 | // Create the bottom buttons. 217 | m_hWarnings = CreateWindowExW(0, WC_BUTTONW, L"", WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_WARNINGS), nullptr, nullptr); 218 | SendMessageW(m_hWarnings, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 219 | 220 | std::wstring wstrBack = LoadStringAsWstr(m_hInstance, IDS_BACK); 221 | m_hBack = CreateWindowExW(0, WC_BUTTONW, wstrBack.c_str(), WS_CHILD | WS_VISIBLE | WS_DISABLED, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_BACK), nullptr, nullptr); 222 | SendMessageW(m_hBack, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 223 | 224 | m_hNext = CreateWindowExW(0, WC_BUTTONW, L"", WS_CHILD | WS_VISIBLE | WS_DISABLED, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_NEXT), nullptr, nullptr); 225 | SendMessageW(m_hNext, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 226 | 227 | std::wstring wstrCancel = LoadStringAsWstr(m_hInstance, IDS_CANCEL); 228 | m_hCancel = CreateWindowExW(0, WC_BUTTONW, wstrCancel.c_str(), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_CANCEL), nullptr, nullptr); 229 | SendMessageW(m_hCancel, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 230 | 231 | // Create all pages and windows. 232 | m_pFilePage = CFilePage::Create(this); 233 | m_pVariablesPage = CVariablesPage::Create(this); 234 | m_pFinishPage = CFinishPage::Create(this); 235 | m_pWarningsWindow = CWarningsWindow::Create(this); 236 | _SwitchPage(m_pFilePage.get()); 237 | 238 | // Set the main window size. 239 | int iHeight = ScaleControl(iMinWindowHeight); 240 | int iWidth = ScaleControl(iMinWindowWidth); 241 | SetWindowPos(m_hWnd, nullptr, 0, 0, iWidth, iHeight, SWP_NOMOVE); 242 | 243 | // Finally, show the window. 244 | ShowWindow(m_hWnd, m_nShowCmd); 245 | 246 | return 0; 247 | } 248 | 249 | LRESULT 250 | CMainWindow::_OnDestroy() 251 | { 252 | PostQuitMessage(0); 253 | return 0; 254 | } 255 | 256 | LRESULT 257 | CMainWindow::_OnDpiChanged(WPARAM wParam, LPARAM lParam) 258 | { 259 | m_wCurrentDPI = LOWORD(wParam); 260 | 261 | // Redraw the entire window on every DPI change. 262 | InvalidateRect(m_hWnd, nullptr, FALSE); 263 | 264 | // Recalculate the main GUI font. 265 | m_lfGuiFont.lfHeight = -ScaleFont(10); 266 | m_hGuiFont = make_unique_font(CreateFontIndirectW(&m_lfGuiFont)); 267 | 268 | // Recalculate the bold GUI font. 269 | m_lfBoldGuiFont.lfHeight = m_lfGuiFont.lfHeight; 270 | m_hBoldGuiFont = make_unique_font(CreateFontIndirectW(&m_lfBoldGuiFont)); 271 | 272 | // Update the control fonts. 273 | SendMessageW(m_hWarnings, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 274 | SendMessageW(m_hBack, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 275 | SendMessageW(m_hNext, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 276 | SendMessageW(m_hCancel, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 277 | 278 | // Update the DPI for our custom child windows. 279 | m_pFilePage->UpdateDPI(); 280 | m_pVariablesPage->UpdateDPI(); 281 | 282 | // Use the suggested new window size. 283 | RECT* const prcNewWindow = reinterpret_cast(lParam); 284 | SetWindowPos(m_hWnd, nullptr, prcNewWindow->left, prcNewWindow->top, prcNewWindow->right - prcNewWindow->left, prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE); 285 | 286 | return 0; 287 | } 288 | 289 | LRESULT 290 | CMainWindow::_OnEraseBkgnd() 291 | { 292 | // We always repaint the entire window in _OnPaint. 293 | // Therefore, don't forward WM_ERASEBKGND messages to DefWindowProcW to prevent flickering. 294 | return TRUE; 295 | } 296 | 297 | LRESULT 298 | CMainWindow::_OnGetMinMaxInfo(LPARAM lParam) 299 | { 300 | PMINMAXINFO pMinMaxInfo = reinterpret_cast(lParam); 301 | 302 | pMinMaxInfo->ptMinTrackSize.x = ScaleControl(iMinWindowWidth); 303 | pMinMaxInfo->ptMinTrackSize.y = ScaleControl(iMinWindowHeight); 304 | 305 | return 0; 306 | } 307 | 308 | LRESULT 309 | CMainWindow::_OnPaint() 310 | { 311 | // Get the window size. 312 | RECT rcWindow; 313 | GetClientRect(m_hWnd, &rcWindow); 314 | 315 | // Begin a double-buffered paint. 316 | PAINTSTRUCT ps; 317 | HDC hDC = BeginPaint(m_hWnd, &ps); 318 | HDC hMemDC = CreateCompatibleDC(hDC); 319 | HBITMAP hMemBitmap = CreateCompatibleBitmap(hDC, rcWindow.right, rcWindow.bottom); 320 | SelectObject(hMemDC, hMemBitmap); 321 | 322 | // Draw a white rectangle completely filling the header of the window. 323 | RECT rcHeader = rcWindow; 324 | rcHeader.bottom = ScaleControl(iHeaderHeight); 325 | FillRect(hMemDC, &rcHeader, static_cast(GetStockObject(WHITE_BRUSH))); 326 | 327 | // Draw the header text. 328 | RECT rcHeaderText = rcHeader; 329 | rcHeaderText.left = ScaleControl(15); 330 | rcHeaderText.top = ScaleControl(15); 331 | SelectObject(hMemDC, m_hBoldGuiFont.get()); 332 | DrawTextW(hMemDC, m_pwstrHeader->c_str(), m_pwstrHeader->size(), &rcHeaderText, 0); 333 | 334 | // Draw the subheader text. 335 | RECT rcSubHeaderText = rcHeader; 336 | rcSubHeaderText.left = ScaleControl(20); 337 | rcSubHeaderText.top = ScaleControl(32); 338 | SelectObject(hMemDC, m_hGuiFont.get()); 339 | DrawTextW(hMemDC, m_pwstrSubHeader->c_str(), m_pwstrSubHeader->size(), &rcSubHeaderText, 0); 340 | 341 | // Draw the ENLYZE logo into the upper right corner. 342 | BITMAP bmLogo; 343 | GetObject(m_hLogoBitmap, sizeof(BITMAP), &bmLogo); 344 | 345 | const int iLogoPadding = ScaleControl(iUnifiedControlPadding); 346 | int iDestBitmapHeight = rcHeader.bottom - 2 * iLogoPadding; 347 | int iDestBitmapWidth = bmLogo.bmWidth * iDestBitmapHeight / bmLogo.bmHeight; 348 | int iDestBitmapX = rcWindow.right - iLogoPadding - iDestBitmapWidth; 349 | int iDestBitmapY = iLogoPadding; 350 | 351 | HDC hBitmapDC = CreateCompatibleDC(hDC); 352 | SelectObject(hBitmapDC, m_hLogoBitmap); 353 | 354 | SetStretchBltMode(hMemDC, HALFTONE); 355 | SetBrushOrgEx(hMemDC, 0, 0, nullptr); 356 | StretchBlt(hMemDC, iDestBitmapX, iDestBitmapY, iDestBitmapWidth, iDestBitmapHeight, hBitmapDC, 0, 0, bmLogo.bmWidth, bmLogo.bmHeight, SRCCOPY); 357 | 358 | DeleteDC(hBitmapDC); 359 | 360 | // Fill the rest of the window with the window background color. 361 | RECT rcBackground = rcWindow; 362 | rcBackground.top = rcHeader.bottom; 363 | FillRect(hMemDC, &rcBackground, GetSysColorBrush(COLOR_BTNFACE)); 364 | 365 | // End painting by copying the in-memory DC. 366 | BitBlt(hDC, 0, 0, rcWindow.right, rcWindow.bottom, hMemDC, 0, 0, SRCCOPY); 367 | DeleteObject(hMemBitmap); 368 | DeleteDC(hMemDC); 369 | EndPaint(m_hWnd, &ps); 370 | 371 | return 0; 372 | } 373 | 374 | LRESULT 375 | CMainWindow::_OnSize() 376 | { 377 | // Get the window size. 378 | RECT rcWindow; 379 | GetClientRect(m_hWnd, &rcWindow); 380 | 381 | // Redraw the header on every size change. 382 | RECT rcHeader = rcWindow; 383 | rcHeader.bottom = ScaleControl(iHeaderHeight); 384 | InvalidateRect(m_hWnd, &rcHeader, FALSE); 385 | 386 | // Move the buttons. 387 | HDWP hDwp = BeginDeferWindowPos(8); 388 | if (!hDwp) 389 | { 390 | return 0; 391 | } 392 | 393 | const int iControlPadding = ScaleControl(iUnifiedControlPadding); 394 | const int iButtonHeight = ScaleControl(iUnifiedButtonHeight); 395 | const int iButtonWidth = ScaleControl(iUnifiedButtonWidth); 396 | int iButtonX = iControlPadding; 397 | int iButtonY = rcWindow.bottom - iControlPadding - iButtonHeight; 398 | hDwp = DeferWindowPos(hDwp, m_hWarnings, nullptr, iButtonX, iButtonY, iButtonWidth, iButtonHeight, 0); 399 | 400 | iButtonX = rcWindow.right - iControlPadding - iButtonWidth; 401 | hDwp = DeferWindowPos(hDwp, m_hCancel, nullptr, iButtonX, iButtonY, iButtonWidth, iButtonHeight, 0); 402 | if (!hDwp) 403 | { 404 | return 0; 405 | } 406 | 407 | iButtonX = iButtonX - iControlPadding - iButtonWidth; 408 | hDwp = DeferWindowPos(hDwp, m_hNext, nullptr, iButtonX, iButtonY, iButtonWidth, iButtonHeight, 0); 409 | if (!hDwp) 410 | { 411 | return 0; 412 | } 413 | 414 | iButtonX = iButtonX - iButtonWidth; 415 | hDwp = DeferWindowPos(hDwp, m_hBack, nullptr, iButtonX, iButtonY, iButtonWidth, iButtonHeight, 0); 416 | if (!hDwp) 417 | { 418 | return 0; 419 | } 420 | 421 | // Move the line above the buttons. 422 | int iLineHeight = 2; 423 | int iLineWidth = rcWindow.right; 424 | int iLineX = 0; 425 | int iLineY = iButtonY - iControlPadding; 426 | hDwp = DeferWindowPos(hDwp, m_hLine, nullptr, iLineX, iLineY, iLineWidth, iLineHeight, 0); 427 | if (!hDwp) 428 | { 429 | return 0; 430 | } 431 | 432 | // Move all page windows. 433 | int iPageX = iControlPadding; 434 | int iPageY = rcHeader.bottom + iControlPadding; 435 | int iPageHeight = iLineY - iPageY - iControlPadding; 436 | int iPageWidth = rcWindow.right - iPageX - iControlPadding; 437 | hDwp = DeferWindowPos(hDwp, m_pFilePage->GetHwnd(), nullptr, iPageX, iPageY, iPageWidth, iPageHeight, 0); 438 | if (!hDwp) 439 | { 440 | return 0; 441 | } 442 | 443 | hDwp = DeferWindowPos(hDwp, m_pVariablesPage->GetHwnd(), nullptr, iPageX, iPageY, iPageWidth, iPageHeight, 0); 444 | if (!hDwp) 445 | { 446 | return 0; 447 | } 448 | 449 | hDwp = DeferWindowPos(hDwp, m_pFinishPage->GetHwnd(), nullptr, iPageX, iPageY, iPageWidth, iPageHeight, 0); 450 | if (!hDwp) 451 | { 452 | return 0; 453 | } 454 | 455 | EndDeferWindowPos(hDwp); 456 | 457 | return 0; 458 | } 459 | 460 | void 461 | CMainWindow::_SwitchPage(CPage* pNewPage) 462 | { 463 | m_pCurrentPage = pNewPage; 464 | ShowWindow(m_pFilePage->GetHwnd(), SW_HIDE); 465 | ShowWindow(m_pVariablesPage->GetHwnd(), SW_HIDE); 466 | ShowWindow(m_pFinishPage->GetHwnd(), SW_HIDE); 467 | pNewPage->SwitchTo(); 468 | } 469 | 470 | std::unique_ptr 471 | CMainWindow::Create(HINSTANCE hInstance, int nShowCmd) 472 | { 473 | // Register the main window class. 474 | WNDCLASSW wc = {}; 475 | wc.lpfnWndProc = CMainWindow::_WndProc; 476 | wc.hInstance = hInstance; 477 | wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); 478 | wc.hIcon = LoadIconW(hInstance, MAKEINTRESOURCEW(IDI_ICON)); 479 | wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); 480 | wc.lpszClassName = CMainWindow::_wszWndClass; 481 | 482 | if (RegisterClassW(&wc) == 0) 483 | { 484 | ErrorBox(L"RegisterClassW failed, last error is " + std::to_wstring(GetLastError())); 485 | return nullptr; 486 | } 487 | 488 | // Create the main window. 489 | auto pMainWindow = std::unique_ptr(new CMainWindow(hInstance, nShowCmd)); 490 | HWND hWnd = CreateWindowExW( 491 | 0, 492 | CMainWindow::_wszWndClass, 493 | wszAppName, 494 | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 495 | CW_USEDEFAULT, 496 | CW_USEDEFAULT, 497 | CW_USEDEFAULT, 498 | CW_USEDEFAULT, 499 | nullptr, 500 | nullptr, 501 | hInstance, 502 | pMainWindow.get()); 503 | if (hWnd == nullptr) 504 | { 505 | ErrorBox(L"CreateWindowExW failed for CMainWindow, last error is " + std::to_wstring(GetLastError())); 506 | return nullptr; 507 | } 508 | 509 | return pMainWindow; 510 | } 511 | 512 | void 513 | CMainWindow::EnableBackButton(BOOL bEnable) 514 | { 515 | EnableWindow(m_hBack, bEnable); 516 | } 517 | 518 | void 519 | CMainWindow::EnableNextButton(BOOL bEnable) 520 | { 521 | EnableWindow(m_hNext, bEnable); 522 | } 523 | 524 | void 525 | CMainWindow::EnableCancelButton(BOOL bEnable) 526 | { 527 | EnableWindow(m_hCancel, bEnable); 528 | } 529 | 530 | void 531 | CMainWindow::_RedrawHeader() 532 | { 533 | RECT rcHeader; 534 | GetClientRect(m_hWnd, &rcHeader); 535 | rcHeader.bottom = ScaleControl(iHeaderHeight); 536 | InvalidateRect(m_hWnd, &rcHeader, FALSE); 537 | } 538 | 539 | int 540 | CMainWindow::ScaleControl(int iControlSize) 541 | { 542 | return MulDiv(iControlSize, m_wCurrentDPI, iWindowsReferenceDPI); 543 | } 544 | 545 | int 546 | CMainWindow::ScaleFont(int iFontSize) 547 | { 548 | return MulDiv(iFontSize, m_wCurrentDPI, iFontReferenceDPI); 549 | } 550 | 551 | void 552 | CMainWindow::SetHeader(std::wstring* pwstrHeader, std::wstring* pwstrSubHeader) 553 | { 554 | m_pwstrHeader = pwstrHeader; 555 | m_pwstrSubHeader = pwstrSubHeader; 556 | _RedrawHeader(); 557 | } 558 | 559 | void 560 | CMainWindow::SetNextButtonText(const std::wstring& wstrText) 561 | { 562 | SetWindowTextW(m_hNext, wstrText.c_str()); 563 | } 564 | 565 | void 566 | CMainWindow::ShowWarningsButton(BOOL bShow) 567 | { 568 | ShowWindow(m_hWarnings, bShow ? SW_SHOW : SW_HIDE); 569 | } 570 | 571 | int 572 | CMainWindow::WorkLoop() 573 | { 574 | // Process window messages. 575 | MSG msg; 576 | for (;;) 577 | { 578 | BOOL bRet = GetMessageW(&msg, 0, 0, 0); 579 | if (bRet > 0) 580 | { 581 | TranslateMessage(&msg); 582 | DispatchMessageW(&msg); 583 | } 584 | else if (bRet == 0) 585 | { 586 | // WM_QUIT message terminated the message loop. 587 | return msg.wParam; 588 | } 589 | else 590 | { 591 | ErrorBox(L"GetMessageW failed, last error is " + std::to_wstring(GetLastError())); 592 | return 1; 593 | } 594 | } 595 | } 596 | -------------------------------------------------------------------------------- /src/CMainWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022-2023 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | class CMainWindow 10 | { 11 | public: 12 | const std::vector& GetDeviceSymbolInfos() const { return m_DeviceSymbolInfos; } 13 | HFONT GetGuiFont() const { return m_hGuiFont.get(); } 14 | HINSTANCE GetHInstance() const { return m_hInstance; } 15 | HWND GetHwnd() const { return m_hWnd; } 16 | 17 | static std::unique_ptr Create(HINSTANCE hInstance, int nShowCmd); 18 | void EnableBackButton(BOOL bEnable); 19 | void EnableNextButton(BOOL bEnable); 20 | void EnableCancelButton(BOOL bEnable); 21 | int ScaleControl(int iControlSize); 22 | int ScaleFont(int iFontSize); 23 | void SetHeader(std::wstring* pwstrHeader, std::wstring* pwstrSubHeader); 24 | void SetNextButtonText(const std::wstring& wstrText); 25 | void ShowWarningsButton(BOOL bShow); 26 | int WorkLoop(); 27 | 28 | private: 29 | static constexpr WCHAR _wszWndClass[] = L"MainWndClass"; 30 | 31 | HBITMAP m_hLogoBitmap; 32 | sr::unique_resource m_hBoldGuiFont; 33 | sr::unique_resource m_hGuiFont; 34 | HINSTANCE m_hInstance; 35 | HWND m_hWnd; 36 | HWND m_hLine; 37 | HWND m_hWarnings; 38 | HWND m_hBack; 39 | HWND m_hNext; 40 | HWND m_hCancel; 41 | int m_nShowCmd; 42 | LOGFONTW m_lfBoldGuiFont; 43 | LOGFONTW m_lfGuiFont; 44 | CPage* m_pCurrentPage; 45 | std::vector m_DeviceSymbolInfos; 46 | std::unique_ptr m_pFilePage; 47 | std::unique_ptr m_pVariablesPage; 48 | std::unique_ptr m_pFinishPage; 49 | std::unique_ptr m_pWarningsWindow; 50 | std::wstring m_wstrSelectedFilePath; 51 | std::wstring m_wstrSaveFilter; 52 | std::wstring m_wstrSaveTitle; 53 | std::wstring* m_pwstrHeader; 54 | std::wstring* m_pwstrSubHeader; 55 | WORD m_wCurrentDPI; 56 | 57 | CMainWindow(HINSTANCE hInstance, int nShowCmd); 58 | static LRESULT CALLBACK _WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 59 | void _OnBackButton(); 60 | void _OnCancelButton(); 61 | LRESULT _OnCommand(WPARAM wParam); 62 | LRESULT _OnCreate(); 63 | LRESULT _OnDestroy(); 64 | LRESULT _OnDpiChanged(WPARAM wParam, LPARAM lParam); 65 | LRESULT _OnEraseBkgnd(); 66 | LRESULT _OnGetMinMaxInfo(LPARAM lParam); 67 | void _OnNextButton(); 68 | void _OnFilePageNextButton(); 69 | void _OnVariablesPageNextButton(); 70 | void _OnFinishPageNextButton(); 71 | void _OnWarningsButton(); 72 | LRESULT _OnPaint(); 73 | LRESULT _OnSize(); 74 | void _RedrawHeader(); 75 | void _SwitchPage(CPage* pNewPage); 76 | }; 77 | -------------------------------------------------------------------------------- /src/CPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | class CPage 10 | { 11 | public: 12 | HWND GetHwnd() const { return m_hWnd; } 13 | virtual void SwitchTo() = 0; 14 | virtual void UpdateDPI() = 0; 15 | 16 | protected: 17 | CMainWindow* m_pMainWindow; 18 | HWND m_hWnd; 19 | 20 | CPage(CMainWindow* pMainWindow) : m_pMainWindow(pMainWindow) {}; 21 | 22 | template static std::unique_ptr 23 | Create(CMainWindow* pMainWindow) 24 | { 25 | // Register the window class. 26 | WNDCLASSW wc = {}; 27 | wc.lpfnWndProc = T::_WndProc; 28 | wc.hInstance = pMainWindow->GetHInstance(); 29 | wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); 30 | wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); 31 | wc.lpszClassName = T::_wszWndClass; 32 | if (RegisterClassW(&wc) == 0) 33 | { 34 | ErrorBox(L"RegisterClassW failed, last error is " + std::to_wstring(GetLastError())); 35 | return nullptr; 36 | } 37 | 38 | // Create the page window. 39 | auto pPage = std::unique_ptr(new T(pMainWindow)); 40 | HWND hWnd = CreateWindowExW( 41 | 0, 42 | T::_wszWndClass, 43 | L"", 44 | WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 45 | 0, 46 | 0, 47 | 0, 48 | 0, 49 | pMainWindow->GetHwnd(), 50 | nullptr, 51 | pMainWindow->GetHInstance(), 52 | pPage.get()); 53 | if (hWnd == nullptr) 54 | { 55 | ErrorBox(L"CreateWindowExW failed, last error is " + std::to_wstring(GetLastError())); 56 | return nullptr; 57 | } 58 | 59 | return pPage; 60 | } 61 | }; 62 | -------------------------------------------------------------------------------- /src/CVariablesPage.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | #define IDC_DEVICE_COMBOBOX 500 10 | 11 | 12 | LRESULT 13 | CVariablesPage::_OnCommand(WPARAM wParam) 14 | { 15 | switch (LOWORD(wParam)) 16 | { 17 | case IDC_DEVICE_COMBOBOX: 18 | { 19 | if (HIWORD(wParam) == CBN_SELCHANGE) 20 | { 21 | OnDeviceComboBoxSelectionChange(); 22 | } 23 | 24 | break; 25 | } 26 | } 27 | 28 | return 0; 29 | } 30 | 31 | LRESULT 32 | CVariablesPage::_OnCreate() 33 | { 34 | // Load resources. 35 | m_wstrHeader = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_VARIABLESPAGE_HEADER); 36 | m_wstrSubHeader = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_VARIABLESPAGE_SUBHEADER); 37 | m_wstrText = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_VARIABLESPAGE_TEXT); 38 | m_wstrSave = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_SAVE); 39 | 40 | // Set up the Device ComboBox. 41 | m_hDeviceComboBox = CreateWindowExW(0, WC_COMBOBOXW, L"", WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_DEVICE_COMBOBOX), nullptr, nullptr); 42 | SendMessageW(m_hDeviceComboBox, WM_SETFONT, reinterpret_cast(m_pMainWindow->GetGuiFont()), MAKELPARAM(TRUE, 0)); 43 | 44 | // Set up the ListView. 45 | m_hList = CreateWindowExW(WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL, 0, 0, 0, 0, m_hWnd, nullptr, nullptr, nullptr); 46 | ListView_SetExtendedListViewStyle(m_hList, LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); 47 | 48 | LVCOLUMNW lvColumn = {}; 49 | lvColumn.mask = LVCF_TEXT; 50 | 51 | std::wstring wstrColumn = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_NAME); 52 | lvColumn.pszText = wstrColumn.data(); 53 | ListView_InsertColumn(m_hList, 0, &lvColumn); 54 | 55 | wstrColumn = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_CODE); 56 | lvColumn.pszText = wstrColumn.data(); 57 | ListView_InsertColumn(m_hList, 1, &lvColumn); 58 | 59 | wstrColumn = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_DATATYPE); 60 | lvColumn.pszText = wstrColumn.data(); 61 | ListView_InsertColumn(m_hList, 2, &lvColumn); 62 | 63 | wstrColumn = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_COMMENT); 64 | lvColumn.pszText = wstrColumn.data(); 65 | ListView_InsertColumn(m_hList, 3, &lvColumn); 66 | 67 | return 0; 68 | } 69 | 70 | LRESULT 71 | CVariablesPage::_OnEraseBkgnd() 72 | { 73 | // We always repaint the entire window in _OnPaint. 74 | // Therefore, don't forward WM_ERASEBKGND messages to DefWindowProcW to prevent flickering. 75 | return TRUE; 76 | } 77 | 78 | LRESULT 79 | CVariablesPage::_OnPaint() 80 | { 81 | // Get the window size. 82 | RECT rcWindow; 83 | GetClientRect(m_hWnd, &rcWindow); 84 | 85 | // Begin a double-buffered paint. 86 | PAINTSTRUCT ps; 87 | HDC hDC = BeginPaint(m_hWnd, &ps); 88 | HDC hMemDC = CreateCompatibleDC(hDC); 89 | HBITMAP hMemBitmap = CreateCompatibleBitmap(hDC, rcWindow.right, rcWindow.bottom); 90 | SelectObject(hMemDC, hMemBitmap); 91 | 92 | // Fill the window with the window background color. 93 | FillRect(hMemDC, &rcWindow, GetSysColorBrush(COLOR_BTNFACE)); 94 | 95 | // Draw the info text. 96 | SelectObject(hMemDC, m_pMainWindow->GetGuiFont()); 97 | SetBkColor(hMemDC, GetSysColor(COLOR_BTNFACE)); 98 | DrawTextW(hMemDC, m_wstrText.c_str(), m_wstrText.size(), &rcWindow, DT_WORDBREAK); 99 | 100 | // End painting by copying the in-memory DC. 101 | BitBlt(hDC, 0, 0, rcWindow.right, rcWindow.bottom, hMemDC, 0, 0, SRCCOPY); 102 | DeleteObject(hMemBitmap); 103 | DeleteDC(hMemDC); 104 | EndPaint(m_hWnd, &ps); 105 | 106 | return 0; 107 | } 108 | 109 | LRESULT 110 | CVariablesPage::_OnSize() 111 | { 112 | // Get the window size. 113 | RECT rcWindow; 114 | GetClientRect(m_hWnd, &rcWindow); 115 | 116 | // The text is drawn on most of the window, so invalidate that. 117 | InvalidateRect(m_hWnd, &rcWindow, FALSE); 118 | 119 | // Move the Device ComboBox. 120 | HDWP hDwp = BeginDeferWindowPos(2); 121 | if (!hDwp) 122 | { 123 | return 0; 124 | } 125 | 126 | int iComboX = 0; 127 | int iComboY = m_pMainWindow->ScaleFont(30); 128 | int iComboHeight = m_pMainWindow->ScaleFont(10); 129 | int iComboWidth = rcWindow.right; 130 | hDwp = DeferWindowPos(hDwp, m_hDeviceComboBox, nullptr, iComboX, iComboY, iComboWidth, iComboHeight, 0); 131 | if (!hDwp) 132 | { 133 | return 0; 134 | } 135 | 136 | // Move the ListView. 137 | int iListX = 0; 138 | int iListY = m_pMainWindow->ScaleFont(50); 139 | int iListHeight = rcWindow.bottom - iListY; 140 | int iListWidth = rcWindow.right; 141 | hDwp = DeferWindowPos(hDwp, m_hList, nullptr, iListX, iListY, iListWidth, iListHeight, 0); 142 | if (!hDwp) 143 | { 144 | return 0; 145 | } 146 | 147 | EndDeferWindowPos(hDwp); 148 | 149 | // Adjust the list column widths. 150 | LONG lLargeColumnWidth = rcWindow.right / 3; 151 | LONG lSmallColumnWidth = lLargeColumnWidth / 2; 152 | LONG lMediumColumnWidth = lSmallColumnWidth + lSmallColumnWidth / 2; 153 | ListView_SetColumnWidth(m_hList, 0, lMediumColumnWidth); 154 | ListView_SetColumnWidth(m_hList, 1, lSmallColumnWidth); 155 | ListView_SetColumnWidth(m_hList, 2, lSmallColumnWidth); 156 | ListView_SetColumnWidth(m_hList, 3, lLargeColumnWidth); 157 | 158 | return 0; 159 | } 160 | 161 | LRESULT CALLBACK 162 | CVariablesPage::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 163 | { 164 | CVariablesPage* pPage = InstanceFromWndProc(hWnd, uMsg, lParam); 165 | 166 | if (pPage) 167 | { 168 | switch (uMsg) 169 | { 170 | case WM_COMMAND: return pPage->_OnCommand(wParam); 171 | case WM_CREATE: return pPage->_OnCreate(); 172 | case WM_ERASEBKGND: return pPage->_OnEraseBkgnd(); 173 | case WM_PAINT: return pPage->_OnPaint(); 174 | case WM_SIZE: return pPage->_OnSize(); 175 | } 176 | } 177 | 178 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 179 | } 180 | 181 | void 182 | CVariablesPage::OnDeviceComboBoxSelectionChange() 183 | { 184 | // Get the symbol information for the selected device. 185 | int iSelectedDeviceIndex = ComboBox_GetCurSel(m_hDeviceComboBox); 186 | const S7DeviceSymbolInfo& DeviceSymbolInfo = m_pMainWindow->GetDeviceSymbolInfos()[iSelectedDeviceIndex]; 187 | 188 | SendMessageW(m_hList, WM_SETREDRAW, FALSE, 0); 189 | 190 | // Remove all existing items and groups that may exist from a previous project (the user can click "Back" and select a new project). 191 | // Windows XP seems to have a bug here in that it forgets about the group view when all groups have been removed. Therefore, we have to reenable group view as well. 192 | ListView_DeleteAllItems(m_hList); 193 | ListView_RemoveAllGroups(m_hList); 194 | ListView_EnableGroupView(m_hList, TRUE); 195 | 196 | // Add the symbols to the Variables ListView. 197 | int iGroupId = 0; 198 | int iItem = 0; 199 | 200 | for (const S7Block& Block : DeviceSymbolInfo.Blocks) 201 | { 202 | std::wstring wstrHeader = StrToWstr(Block.strName); 203 | 204 | LVGROUP lvGroup = {}; 205 | lvGroup.cbSize = sizeof(LVGROUP); 206 | lvGroup.mask = LVGF_HEADER | LVGF_GROUPID; 207 | lvGroup.pszHeader = wstrHeader.data(); 208 | lvGroup.iGroupId = iGroupId; 209 | ListView_InsertGroup(m_hList, iGroupId, &lvGroup); 210 | 211 | for (const auto& Symbol : Block.Symbols) 212 | { 213 | std::wstring wstrName = StrToWstr(Symbol.strName); 214 | std::wstring wstrCode = StrToWstr(Symbol.strCode); 215 | std::wstring wstrDatatype = StrToWstr(Symbol.strDatatype); 216 | std::wstring wstrComment = StrToWstr(Symbol.strComment); 217 | 218 | LVITEMW lvItem = {}; 219 | lvItem.mask = LVIF_TEXT | LVIF_GROUPID; 220 | lvItem.iItem = iItem; 221 | lvItem.iGroupId = iGroupId; 222 | lvItem.pszText = wstrName.data(); 223 | ListView_InsertItem(m_hList, &lvItem); 224 | 225 | lvItem.mask = LVIF_TEXT; 226 | 227 | lvItem.iSubItem++; 228 | lvItem.pszText = wstrCode.data(); 229 | ListView_SetItem(m_hList, &lvItem); 230 | 231 | lvItem.iSubItem++; 232 | lvItem.pszText = wstrDatatype.data(); 233 | ListView_SetItem(m_hList, &lvItem); 234 | 235 | lvItem.iSubItem++; 236 | lvItem.pszText = wstrComment.data(); 237 | ListView_SetItem(m_hList, &lvItem); 238 | 239 | iItem++; 240 | } 241 | 242 | iGroupId++; 243 | } 244 | 245 | SendMessageW(m_hList, WM_SETREDRAW, TRUE, 0); 246 | } 247 | 248 | void 249 | CVariablesPage::SwitchTo() 250 | { 251 | m_pMainWindow->SetHeader(&m_wstrHeader, &m_wstrSubHeader); 252 | m_pMainWindow->EnableBackButton(TRUE); 253 | m_pMainWindow->SetNextButtonText(m_wstrSave); 254 | ShowWindow(m_hWnd, SW_SHOW); 255 | } 256 | 257 | void 258 | CVariablesPage::UpdateDPI() 259 | { 260 | // Update the control fonts. 261 | SendMessageW(m_hDeviceComboBox, WM_SETFONT, reinterpret_cast(m_pMainWindow->GetGuiFont()), MAKELPARAM(TRUE, 0)); 262 | } 263 | -------------------------------------------------------------------------------- /src/CVariablesPage.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | class CVariablesPage final : public CPage 10 | { 11 | public: 12 | static std::unique_ptr Create(CMainWindow* pMainWindow) { return CPage::Create(pMainWindow); } 13 | 14 | HWND GetDeviceComboBox() const { return m_hDeviceComboBox; } 15 | HWND GetList() const { return m_hList; } 16 | void OnDeviceComboBoxSelectionChange(); 17 | void SwitchTo(); 18 | void UpdateDPI(); 19 | 20 | private: 21 | friend class CPage; 22 | static constexpr WCHAR _wszWndClass[] = L"VariablesPageWndClass"; 23 | 24 | HWND m_hDeviceComboBox; 25 | HWND m_hList; 26 | std::wstring m_wstrHeader; 27 | std::wstring m_wstrSubHeader; 28 | std::wstring m_wstrText; 29 | std::wstring m_wstrSave; 30 | 31 | CVariablesPage(CMainWindow* pMainWindow) : CPage(pMainWindow) {} 32 | LRESULT _OnCommand(WPARAM wParam); 33 | LRESULT _OnCreate(); 34 | LRESULT _OnEraseBkgnd(); 35 | LRESULT _OnPaint(); 36 | LRESULT _OnSize(); 37 | static LRESULT CALLBACK _WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 38 | }; 39 | -------------------------------------------------------------------------------- /src/CWarningsWindow.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | static const int iMinWindowHeight = 400; 10 | static const int iMinWindowWidth = 700; 11 | 12 | #define IDC_CLOSE 500 13 | 14 | 15 | CWarningsWindow::CWarningsWindow(CMainWindow* pMainWindow) 16 | : m_pMainWindow(pMainWindow) 17 | { 18 | } 19 | 20 | LRESULT CALLBACK 21 | CWarningsWindow::_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 22 | { 23 | CWarningsWindow* pWarningsWindow = InstanceFromWndProc(hWnd, uMsg, lParam); 24 | 25 | // The first WM_GETMINMAXINFO comes before WM_NCCREATE, before we got our CMainWindow pointer. 26 | if (pWarningsWindow) 27 | { 28 | switch (uMsg) 29 | { 30 | case WM_COMMAND: return pWarningsWindow->_OnCommand(wParam); 31 | case WM_CLOSE: return pWarningsWindow->_OnClose(); 32 | case WM_CREATE: return pWarningsWindow->_OnCreate(); 33 | case WM_DPICHANGED: return pWarningsWindow->_OnDpiChanged(wParam, lParam); 34 | case WM_GETMINMAXINFO: return pWarningsWindow->_OnGetMinMaxInfo(lParam); 35 | case WM_SIZE: return pWarningsWindow->_OnSize(); 36 | } 37 | } 38 | 39 | return DefWindowProcW(hWnd, uMsg, wParam, lParam); 40 | } 41 | 42 | LRESULT 43 | CWarningsWindow::_OnClose() 44 | { 45 | EnableWindow(m_pMainWindow->GetHwnd(), TRUE); 46 | ShowWindow(m_hWnd, SW_HIDE); 47 | 48 | return 0; 49 | } 50 | 51 | LRESULT 52 | CWarningsWindow::_OnCommand(WPARAM wParam) 53 | { 54 | switch (LOWORD(wParam)) 55 | { 56 | case IDC_CLOSE: _OnClose(); break; 57 | } 58 | 59 | return 0; 60 | } 61 | 62 | LRESULT 63 | CWarningsWindow::_OnCreate() 64 | { 65 | // We are a separate window that may be on a monitor with a different DPI setting. 66 | m_wCurrentDPI = GetWindowDPI(m_hWnd); 67 | 68 | // Create the main GUI font. 69 | m_lfGuiFont = {}; 70 | wcscpy_s(m_lfGuiFont.lfFaceName, L"MS Shell Dlg 2"); 71 | m_lfGuiFont.lfHeight = -_ScaleFont(10); 72 | m_hGuiFont = make_unique_font(CreateFontIndirectW(&m_lfGuiFont)); 73 | 74 | // Set up the ListView. 75 | m_hList = CreateWindowExW(WS_EX_CLIENTEDGE, WC_LISTVIEWW, L"", WS_CHILD | WS_VISIBLE | LVS_REPORT | LVS_SINGLESEL, 0, 0, 0, 0, m_hWnd, nullptr, nullptr, nullptr); 76 | ListView_SetExtendedListViewStyle(m_hList, LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT | LVS_EX_LABELTIP); 77 | 78 | LVCOLUMNW lvColumn = {}; 79 | lvColumn.mask = LVCF_TEXT; 80 | 81 | std::wstring wstrColumn = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_DEVICE); 82 | lvColumn.pszText = wstrColumn.data(); 83 | ListView_InsertColumn(m_hList, 0, &lvColumn); 84 | 85 | wstrColumn = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_WARNING); 86 | lvColumn.pszText = wstrColumn.data(); 87 | ListView_InsertColumn(m_hList, 1, &lvColumn); 88 | 89 | // Create the Close button. 90 | std::wstring wstrClose = LoadStringAsWstr(m_pMainWindow->GetHInstance(), IDS_CLOSE); 91 | m_hClose = CreateWindowExW(0, WC_BUTTONW, wstrClose.c_str(), WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, m_hWnd, reinterpret_cast(IDC_CLOSE), nullptr, nullptr); 92 | SendMessageW(m_hClose, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 93 | 94 | // Set the main window size. 95 | int iHeight = _ScaleControl(iMinWindowHeight); 96 | int iWidth = _ScaleControl(iMinWindowWidth); 97 | SetWindowPos(m_hWnd, nullptr, 0, 0, iWidth, iHeight, SWP_NOMOVE); 98 | 99 | return 0; 100 | } 101 | 102 | LRESULT 103 | CWarningsWindow::_OnDpiChanged(WPARAM wParam, LPARAM lParam) 104 | { 105 | m_wCurrentDPI = LOWORD(wParam); 106 | 107 | // Recalculate the main GUI font. 108 | m_lfGuiFont.lfHeight = -_ScaleFont(10); 109 | m_hGuiFont = make_unique_font(CreateFontIndirectW(&m_lfGuiFont)); 110 | 111 | // Update the control fonts. 112 | SendMessageW(m_hClose, WM_SETFONT, reinterpret_cast(m_hGuiFont.get()), MAKELPARAM(TRUE, 0)); 113 | 114 | // Use the suggested new window size. 115 | RECT* const prcNewWindow = reinterpret_cast(lParam); 116 | SetWindowPos(m_hWnd, nullptr, prcNewWindow->left, prcNewWindow->top, prcNewWindow->right - prcNewWindow->left, prcNewWindow->bottom - prcNewWindow->top, SWP_NOZORDER | SWP_NOACTIVATE); 117 | 118 | return 0; 119 | } 120 | 121 | LRESULT 122 | CWarningsWindow::_OnGetMinMaxInfo(LPARAM lParam) 123 | { 124 | PMINMAXINFO pMinMaxInfo = reinterpret_cast(lParam); 125 | 126 | pMinMaxInfo->ptMinTrackSize.x = _ScaleControl(iMinWindowWidth); 127 | pMinMaxInfo->ptMinTrackSize.y = _ScaleControl(iMinWindowHeight); 128 | 129 | return 0; 130 | } 131 | 132 | LRESULT 133 | CWarningsWindow::_OnSize() 134 | { 135 | // Get the window size. 136 | RECT rcWindow; 137 | GetClientRect(m_hWnd, &rcWindow); 138 | 139 | // Move the Close button. 140 | HDWP hDwp = BeginDeferWindowPos(2); 141 | if (!hDwp) 142 | { 143 | return 0; 144 | } 145 | 146 | const int iControlPadding = _ScaleControl(iUnifiedControlPadding); 147 | const int iButtonHeight = _ScaleControl(iUnifiedButtonHeight); 148 | const int iButtonWidth = _ScaleControl(iUnifiedButtonWidth); 149 | int iButtonX = (rcWindow.right - iButtonWidth) / 2; 150 | int iButtonY = rcWindow.bottom - iControlPadding - iButtonHeight; 151 | hDwp = DeferWindowPos(hDwp, m_hClose, nullptr, iButtonX, iButtonY, iButtonWidth, iButtonHeight, 0); 152 | if (!hDwp) 153 | { 154 | return 0; 155 | } 156 | 157 | // Move the ListView. 158 | int iListX = iControlPadding; 159 | int iListY = iControlPadding; 160 | int iListHeight = rcWindow.bottom - iControlPadding - iButtonHeight - iControlPadding - iListY; 161 | int iListWidth = rcWindow.right - iControlPadding - iListX; 162 | hDwp = DeferWindowPos(hDwp, m_hList, nullptr, iListX, iListY, iListWidth, iListHeight, 0); 163 | if (!hDwp) 164 | { 165 | return 0; 166 | } 167 | 168 | EndDeferWindowPos(hDwp); 169 | 170 | // Adjust the list column widths. 171 | LONG lColumnWidth = iListWidth / 2 - iControlPadding; 172 | ListView_SetColumnWidth(m_hList, 0, lColumnWidth); 173 | ListView_SetColumnWidth(m_hList, 1, lColumnWidth); 174 | 175 | return 0; 176 | } 177 | 178 | int 179 | CWarningsWindow::_ScaleControl(int iControlSize) 180 | { 181 | return MulDiv(iControlSize, m_wCurrentDPI, iWindowsReferenceDPI); 182 | } 183 | 184 | int 185 | CWarningsWindow::_ScaleFont(int iFontSize) 186 | { 187 | return MulDiv(iFontSize, m_wCurrentDPI, iFontReferenceDPI); 188 | } 189 | 190 | std::unique_ptr 191 | CWarningsWindow::Create(CMainWindow* pMainWindow) 192 | { 193 | // Register the warnings window class. 194 | WNDCLASSW wc = {}; 195 | wc.lpfnWndProc = CWarningsWindow::_WndProc; 196 | wc.hInstance = pMainWindow->GetHInstance(); 197 | wc.hCursor = LoadCursorW(nullptr, IDC_ARROW); 198 | wc.hIcon = LoadIconW(pMainWindow->GetHInstance(), MAKEINTRESOURCEW(IDI_ICON)); 199 | wc.hbrBackground = GetSysColorBrush(COLOR_BTNFACE); 200 | wc.lpszClassName = CWarningsWindow::_wszWndClass; 201 | 202 | if (RegisterClassW(&wc) == 0) 203 | { 204 | ErrorBox(L"RegisterClassW failed for CWarningsWindow, last error is " + std::to_wstring(GetLastError())); 205 | return nullptr; 206 | } 207 | 208 | // Create the warnings window. 209 | auto pWarningsWindow = std::unique_ptr(new CWarningsWindow(pMainWindow)); 210 | HWND hWnd = CreateWindowExW( 211 | 0, 212 | CWarningsWindow::_wszWndClass, 213 | L"", 214 | WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN | WS_CLIPSIBLINGS, 215 | CW_USEDEFAULT, 216 | CW_USEDEFAULT, 217 | CW_USEDEFAULT, 218 | CW_USEDEFAULT, 219 | pMainWindow->GetHwnd(), 220 | nullptr, 221 | pMainWindow->GetHInstance(), 222 | pWarningsWindow.get()); 223 | if (hWnd == nullptr) 224 | { 225 | ErrorBox(L"CreateWindowExW failed for CWarningsWindow, last error is " + std::to_wstring(GetLastError())); 226 | return nullptr; 227 | } 228 | 229 | return pWarningsWindow; 230 | } 231 | 232 | void 233 | CWarningsWindow::Open() 234 | { 235 | const auto& DeviceSymbolInfos = m_pMainWindow->GetDeviceSymbolInfos(); 236 | 237 | // Add the warnings to the ListView. 238 | SendMessageW(m_hList, WM_SETREDRAW, FALSE, 0); 239 | ListView_DeleteAllItems(m_hList); 240 | int iItem = 0; 241 | 242 | for (const auto& DeviceSymbolInfo : DeviceSymbolInfos) 243 | { 244 | std::wstring wstrDevice = StrToWstr(DeviceSymbolInfo.strName); 245 | 246 | for (const CS7PError& Warning : DeviceSymbolInfo.Warnings) 247 | { 248 | LVITEMW lvItem = {}; 249 | lvItem.mask = LVIF_TEXT; 250 | lvItem.iItem = iItem; 251 | lvItem.pszText = wstrDevice.data(); 252 | ListView_InsertItem(m_hList, &lvItem); 253 | 254 | lvItem.iSubItem++; 255 | lvItem.pszText = const_cast(Warning.Message().c_str()); 256 | ListView_SetItem(m_hList, &lvItem); 257 | 258 | iItem++; 259 | } 260 | } 261 | 262 | SendMessageW(m_hList, WM_SETREDRAW, TRUE, 0); 263 | 264 | // Finally, disable the main window and show our window (modal behavior). 265 | EnableWindow(m_pMainWindow->GetHwnd(), FALSE); 266 | ShowWindow(m_hWnd, SW_SHOW); 267 | } 268 | -------------------------------------------------------------------------------- /src/CWarningsWindow.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | class CWarningsWindow 10 | { 11 | public: 12 | HWND GetHwnd() const { return m_hWnd; } 13 | 14 | static std::unique_ptr Create(CMainWindow* pMainWindow); 15 | void Open(); 16 | 17 | private: 18 | static constexpr WCHAR _wszWndClass[] = L"WarningsWndClass"; 19 | 20 | sr::unique_resource m_hGuiFont; 21 | CMainWindow* m_pMainWindow; 22 | HWND m_hClose; 23 | HWND m_hList; 24 | HWND m_hWnd; 25 | LOGFONTW m_lfGuiFont; 26 | WORD m_wCurrentDPI; 27 | 28 | CWarningsWindow(CMainWindow* pMainWindow); 29 | static LRESULT CALLBACK _WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); 30 | void _OnCloseButton(); 31 | LRESULT _OnCommand(WPARAM wParam); 32 | LRESULT _OnClose(); 33 | LRESULT _OnCreate(); 34 | LRESULT _OnDpiChanged(WPARAM wParam, LPARAM lParam); 35 | LRESULT _OnGetMinMaxInfo(LPARAM lParam); 36 | LRESULT _OnSize(); 37 | int _ScaleControl(int iControlSize); 38 | int _ScaleFont(int iFontSize); 39 | }; 40 | -------------------------------------------------------------------------------- /src/Compatibility.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/CMc5ArrayDimension.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | struct CMc5ArrayDimension 12 | { 13 | public: 14 | std::string AsString() const { return std::to_string(StartIndex) + ".." + std::to_string(EndIndex); }; 15 | 16 | short StartIndex; 17 | short EndIndex; 18 | }; 19 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/CMc5ArrayEnumerator.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | #include "CMc5ArrayDimension.h" 12 | 13 | class CMc5ArrayEnumerator 14 | { 15 | public: 16 | class InvalidIterator 17 | { 18 | public: 19 | InvalidIterator() 20 | { 21 | } 22 | }; 23 | 24 | class Iterator 25 | { 26 | public: 27 | Iterator(std::vector&& Indexes, const std::vector& Dimensions) 28 | : m_bInvalid(false), m_Dimensions(Dimensions), m_Indexes(Indexes) 29 | { 30 | } 31 | 32 | const std::vector& operator*() const 33 | { 34 | return m_Indexes; 35 | } 36 | 37 | void operator++() 38 | { 39 | // Increment the index, from the last to the first dimension until we have processed all elements. 40 | // For an ARRAY [-5..5, 1..2, 3..4], the order would be: 41 | // -5,1,3 | -5,1,4 | -5,2,3 | -5,2,4 | -4,1,3 | ... 42 | for (int i = m_Indexes.size(); --i >= 0;) 43 | { 44 | if (m_Indexes[i] != m_Dimensions[i].EndIndex) 45 | { 46 | m_Indexes[i]++; 47 | 48 | // Reset the indexes of subsequent dimensions to their start indexes when 49 | // incrementing this dimension's index. 50 | for (size_t j = i + 1; j < m_Dimensions.size(); j++) 51 | { 52 | m_Indexes[j] = m_Dimensions[j].StartIndex; 53 | } 54 | 55 | return; 56 | } 57 | } 58 | 59 | // We couldn't increment any index, so this iterator is no longer valid. 60 | m_bInvalid = true; 61 | } 62 | 63 | bool operator!=([[maybe_unused]] const InvalidIterator& other) const 64 | { 65 | // Only implemented as required for the `it != enumerator.end()` condition. 66 | return !m_bInvalid; 67 | } 68 | 69 | private: 70 | bool m_bInvalid; 71 | const std::vector& m_Dimensions; 72 | std::vector m_Indexes; 73 | }; 74 | 75 | CMc5ArrayEnumerator(const std::vector& Dimensions) 76 | : m_Dimensions(Dimensions) 77 | { 78 | } 79 | 80 | Iterator begin() 81 | { 82 | std::vector Indexes; 83 | for (const auto& Dimension : m_Dimensions) 84 | { 85 | Indexes.push_back(Dimension.StartIndex); 86 | } 87 | 88 | return Iterator(std::move(Indexes), m_Dimensions); 89 | } 90 | 91 | InvalidIterator end() 92 | { 93 | return InvalidIterator(); 94 | } 95 | 96 | private: 97 | const std::vector& m_Dimensions; 98 | }; 99 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/CMc5codeParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "CMc5ArrayDimension.h" 15 | #include "CS7PError.h" 16 | #include "s7p_parser.h" 17 | 18 | class CMc5codeParser 19 | { 20 | public: 21 | CMc5codeParser(std::vector& Symbols, size_t& BitAddressCounter, const size_t DbNumber, const std::string& strMc5code, const std::map>& Mc5codeMap); 22 | 23 | std::variant Parse(const std::string& strPrefix = std::string()); 24 | 25 | private: 26 | const char* m_pszMc5codePosition; 27 | const std::map>& m_Mc5codeMap; 28 | size_t& m_BitAddressCounter; 29 | size_t m_DbNumber; 30 | std::vector& m_Symbols; 31 | 32 | std::variant _AddArrayVariable(const std::string& strStructureType, const std::string& strVariableName); 33 | std::variant _AddBlockVariable(const std::string& strVariableName, const std::string& strVariableType); 34 | std::variant _AddPrimitiveVariable(const std::string& strCurrentStructureType, const std::string& strVariableName, const std::string& strVariableType, const std::vector* pArrayDimensions = nullptr); 35 | std::variant _AddSingleVariable(const std::string& strStructureType, const std::string& strVariableName, const std::string& strVariableType); 36 | std::variant _AddStructVariable(const std::string& strVariableName); 37 | std::variant _AddVariable(const std::string& strStructureType, const std::string& strVariableName); 38 | void _AlignUp(const size_t BitAlignment); 39 | std::variant _GetNextArrayDimensionInfo(const std::string& strVariableName); 40 | std::variant _GetNextToken(const char* szTokens = "", bool bGetComments = false); 41 | std::variant _ParseStructureType(std::string& strStructureType); 42 | std::variant _ParseInnerStructure(const std::string& strStructureType, const std::string& strPrefix); 43 | }; 44 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/CS7PError.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class CS7PError 12 | { 13 | public: 14 | explicit CS7PError() {} 15 | explicit CS7PError(const std::wstring& wstrMessage) : m_wstrMessage(wstrMessage) {} 16 | 17 | const std::wstring& Message() const { return m_wstrMessage; } 18 | 19 | private: 20 | std::wstring m_wstrMessage; 21 | }; 22 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/EnlyzeS7PLib.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | {7990079f-51f8-4e89-b382-cfab391312ae} 33 | 34 | 35 | {cf14a29c-e25e-4faf-8c98-2f5006800132} 36 | 37 | 38 | {d3faca21-d165-4b1e-9a06-a9b58964886c} 39 | 40 | 41 | {95d0b318-d75c-4fa5-85a4-2a36835bf518} 42 | 43 | 44 | 45 | 16.0 46 | Win32Proj 47 | {06B41AD5-3D7E-4C9D-8DFB-E53D8FC52723} 48 | EnlyzeS7PLib 49 | 50 | 51 | 52 | StaticLibrary 53 | true 54 | ClangCL 55 | Unicode 56 | 57 | 58 | StaticLibrary 59 | false 60 | ClangCL 61 | Unicode 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\bin\ 76 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\obj\ 77 | 78 | 79 | false 80 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\bin\ 81 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\obj\ 82 | 83 | 84 | 85 | Level4 86 | true 87 | true 88 | stdcpplatest 89 | false 90 | MultiThreadedDebug 91 | -march=pentium-mmx 92 | $(SolutionDir)\EnlyzeWinCompatLib\src\libcxx\include;$(ProjectDir)\EnlyzeDbfLib;%(AdditionalIncludeDirectories) 93 | 94 | 95 | 96 | 97 | true 98 | 99 | 100 | 101 | 102 | 103 | Level4 104 | true 105 | true 106 | true 107 | true 108 | stdcpplatest 109 | AnySuitable 110 | MultiThreaded 111 | false 112 | -flto -march=pentium-mmx 113 | $(SolutionDir)\EnlyzeWinCompatLib\src\libcxx\include;$(ProjectDir)\EnlyzeDbfLib;%(AdditionalIncludeDirectories) 114 | 115 | 116 | 117 | 118 | true 119 | true 120 | true 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/EnlyzeS7PLib.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {c75ebc4d-b79f-445d-b3f8-38b37fe8bfc2} 6 | 7 | 8 | {c936c468-8f1b-4669-8623-bec7d13f31bf} 9 | 10 | 11 | 12 | 13 | Header Files 14 | 15 | 16 | Header Files 17 | 18 | 19 | Header Files 20 | 21 | 22 | Header Files 23 | 24 | 25 | Header Files 26 | 27 | 28 | Header Files 29 | 30 | 31 | Header Files 32 | 33 | 34 | Header Files 35 | 36 | 37 | 38 | 39 | Source Files 40 | 41 | 42 | Source Files 43 | 44 | 45 | Source Files 46 | 47 | 48 | Source Files 49 | 50 | 51 | Source Files 52 | 53 | 54 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_db_parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "CMc5codeParser.h" 14 | #include "s7p_db_parser.h" 15 | 16 | 17 | static std::variant 18 | _ParseSingleDB(std::vector& Symbols, const size_t DbNumber, const std::string& strMc5code, const std::map>& Mc5codeMap) 19 | { 20 | size_t BitAddressCounter = 0; 21 | 22 | CMc5codeParser Parser(Symbols, BitAddressCounter, DbNumber, strMc5code, Mc5codeMap); 23 | auto Result = Parser.Parse(); 24 | if (const auto pError = std::get_if(&Result)) 25 | { 26 | return *pError; 27 | } 28 | 29 | return std::monostate(); 30 | } 31 | 32 | // A DB reference info subblock (subblock type "00066") has a weird format, which I didn't fully understand yet. 33 | // However, when the DB block Mc5code is empty, testing has shown that the DB reference info subblock contains a reference to an FB block just at the beginning. 34 | // This function is only meant to extract that reference. 35 | static bool 36 | _ExtractFBFromDBReferenceMap(const std::map& DbReferenceMc5codeMap, const size_t DbNumber, size_t& FbNumber) 37 | { 38 | // Check if we have a DB reference info subblock for this DB number. 39 | const auto it = DbReferenceMc5codeMap.find(DbNumber); 40 | if (it == DbReferenceMc5codeMap.end()) 41 | { 42 | return false; 43 | } 44 | 45 | const std::string& strDbReferenceMc5code = it->second; 46 | 47 | // Check if this DB reference info subblock begins with a reference to an FB block. 48 | if (!strDbReferenceMc5code.starts_with("FB")) 49 | { 50 | return false; 51 | } 52 | 53 | // Extract the FB number string. 54 | const char* pszStart = strDbReferenceMc5code.c_str() + 2; 55 | const char* pszCurrent = pszStart; 56 | while (*pszCurrent && isdigit(*pszCurrent)) 57 | { 58 | pszCurrent++; 59 | } 60 | 61 | size_t Length = pszCurrent - pszStart; 62 | if (Length == 0) 63 | { 64 | return false; 65 | } 66 | 67 | std::string strNumber = std::string(pszStart, Length); 68 | 69 | // Convert it to a size_t. 70 | auto Option = StrToSizeT(strNumber); 71 | if (!Option.has_value()) 72 | { 73 | return false; 74 | } 75 | 76 | FbNumber = Option.value(); 77 | return true; 78 | } 79 | 80 | static std::variant 81 | _ParseDBs(S7DeviceSymbolInfo& DeviceSymbolInfo, const std::map>& Mc5codeMap) 82 | { 83 | const std::map& DbMc5codeMap = Mc5codeMap.at("DB"); 84 | const std::map& DbReferenceMc5codeMap = Mc5codeMap.at("DBREF"); 85 | 86 | for (const auto& [DbNumber, strMc5code] : DbMc5codeMap) 87 | { 88 | std::variant Result; 89 | std::vector Symbols; 90 | size_t FbNumber; 91 | 92 | if (!strMc5code.empty()) 93 | { 94 | // Parse the MC5 Code for this DB. 95 | Result = _ParseSingleDB(Symbols, DbNumber, strMc5code, Mc5codeMap); 96 | } 97 | else if (_ExtractFBFromDBReferenceMap(DbReferenceMc5codeMap, DbNumber, FbNumber)) 98 | { 99 | // Find the referenced FB block. 100 | const std::map& FbMc5codeMap = Mc5codeMap.at("FB"); 101 | const auto it = FbMc5codeMap.find(FbNumber); 102 | if (it == FbMc5codeMap.end()) 103 | { 104 | // Ignore this DB, but extract all possible information from the remaining ones. 105 | DeviceSymbolInfo.Warnings.push_back( 106 | CS7PError( 107 | L"Could not find referenced FB" + std::to_wstring(FbNumber) + 108 | L" while parsing DB" + std::to_wstring(DbNumber) 109 | ) 110 | ); 111 | continue; 112 | } 113 | 114 | // Parse this DB using the MC5 Code of the referenced FB block. 115 | Result = _ParseSingleDB(Symbols, DbNumber, it->second, Mc5codeMap); 116 | } 117 | else 118 | { 119 | // This DB apparently has no information we can use, so continue with the next one. 120 | continue; 121 | } 122 | 123 | if (const auto pError = std::get_if(&Result)) 124 | { 125 | // We couldn't completely extract information for this DB - note down a warning. 126 | // Anyway, we may have successfully extracted the first few symbols, so add what we have. 127 | // And also try to extract all possible information from the remaining DBs. 128 | DeviceSymbolInfo.Warnings.push_back(*pError); 129 | } 130 | 131 | if (Symbols.empty()) 132 | { 133 | continue; 134 | } 135 | 136 | // Construct the block name, which is "DB#" and a human-readable name appended (if available from the DbNamesMap). 137 | S7Block& Block = DeviceSymbolInfo.Blocks.emplace_back(); 138 | Block.strName = "DB" + std::to_string(DbNumber); 139 | 140 | const auto it = DeviceSymbolInfo.DbNamesMap.find(DbNumber); 141 | if (it != DeviceSymbolInfo.DbNamesMap.end()) 142 | { 143 | Block.strName += " (" + it->second + ")"; 144 | } 145 | 146 | // Insert the symbols for this block. 147 | Block.Symbols = std::move(Symbols); 148 | } 149 | 150 | return std::monostate(); 151 | } 152 | 153 | static std::variant 154 | _ParseSingleOmbstxSubblock(S7DeviceSymbolInfo& DeviceSymbolInfo, const std::wstring& wstrSubblockFilePath) 155 | { 156 | // Parse the SUBBLK.DBF dBASE file. 157 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrSubblockFilePath); 158 | if (const auto pError = std::get_if(&ReadDbfResult)) 159 | { 160 | return CS7PError(pError->Message()); 161 | } 162 | 163 | auto Reader = std::get>(std::move(ReadDbfResult)); 164 | 165 | // Get some indexes. 166 | auto GetIndexResult = Reader->GetFieldIndex("SUBBLKTYP"); 167 | if (const auto pError = std::get_if(&GetIndexResult)) 168 | { 169 | return CS7PError(L"SUBBLK.DBF: " + pError->Message()); 170 | } 171 | 172 | size_t SubblktypIndex = std::get(GetIndexResult); 173 | 174 | GetIndexResult = Reader->GetFieldIndex("BLKNUMBER"); 175 | if (const auto pError = std::get_if(&GetIndexResult)) 176 | { 177 | return CS7PError(L"SUBBLK.DBF: " + pError->Message()); 178 | } 179 | 180 | size_t BlknumberIndex = std::get(GetIndexResult); 181 | 182 | GetIndexResult = Reader->GetFieldIndex("MC5LEN"); 183 | if (const auto pError = std::get_if(&GetIndexResult)) 184 | { 185 | return CS7PError(L"SUBBLK.DBF: " + pError->Message()); 186 | } 187 | 188 | size_t Mc5lenIndex = std::get(GetIndexResult); 189 | 190 | GetIndexResult = Reader->GetFieldIndex("MC5CODE"); 191 | if (const auto pError = std::get_if(&GetIndexResult)) 192 | { 193 | return CS7PError(L"SUBBLK.DBF: " + pError->Message()); 194 | } 195 | 196 | size_t Mc5codeIndex = std::get(GetIndexResult); 197 | 198 | // Iterate through all records to collect DB and UDT code information. 199 | std::map DbMc5codeMap; 200 | std::map DbReferenceMc5codeMap; 201 | std::map FbMc5codeMap; 202 | std::map SfbMc5codeMap; 203 | std::map UdtMc5codeMap; 204 | 205 | for (;;) 206 | { 207 | // Read a record. 208 | auto ReadResult = Reader->ReadNextRecord(); 209 | if (const auto pError = std::get_if(&ReadResult)) 210 | { 211 | return CS7PError(L"Could not read from " + wstrSubblockFilePath + L": " + pError->Message()); 212 | } 213 | 214 | if (std::holds_alternative(ReadResult)) 215 | { 216 | // No more records, we are done! 217 | break; 218 | } 219 | 220 | auto Record = std::get>(std::move(ReadResult)); 221 | 222 | // Get the BLKNUMBER column and try to convert it to a size_t. 223 | auto Option = StrToSizeT(Record[BlknumberIndex]); 224 | if (!Option.has_value()) 225 | { 226 | // It can't be converted, so this can't be a record we are interested in. 227 | continue; 228 | } 229 | 230 | size_t BlockNumber = Option.value(); 231 | 232 | // Get the MC5LEN column and try to convert it to a size_t. 233 | Option = StrToSizeT(Record[Mc5lenIndex]); 234 | if (!Option.has_value()) 235 | { 236 | // It can't be converted, so this can't be a record we are interested in. 237 | continue; 238 | } 239 | 240 | size_t BlockLength = Option.value(); 241 | 242 | // Truncate the MC5 code to the block length. 243 | std::string strMc5code = std::move(Record[Mc5codeIndex]); 244 | if (BlockLength < strMc5code.size()) 245 | { 246 | strMc5code.resize(BlockLength); 247 | } 248 | 249 | // What type of record is this? 250 | if (Record[SubblktypIndex] == "00006") 251 | { 252 | // This is a DB block (data block). 253 | // It's basically everything we are interested in, but its Mc5code may reference FB and UDT blocks. 254 | // And if it's empty, we have to look into the record with the same block number and subblock type index "00066". 255 | DbMc5codeMap[BlockNumber] = strMc5code; 256 | } 257 | else if (Record[SubblktypIndex] == "00066") 258 | { 259 | // This is part of a DB block. 260 | // This subblock type contains information about all blocks referenced by a DB block. 261 | // We are only interested in it if a DB block is empty and this subblock contains a reference to an FB block instead. 262 | DbReferenceMc5codeMap[BlockNumber] = strMc5code; 263 | } 264 | else if (Record[SubblktypIndex] == "00004") 265 | { 266 | // This is an FB block (function block). 267 | FbMc5codeMap[BlockNumber] = strMc5code; 268 | } 269 | else if (Record[SubblktypIndex] == "00009") 270 | { 271 | // This is an SFB block (system function block). 272 | SfbMc5codeMap[BlockNumber] = strMc5code; 273 | } 274 | else if (Record[SubblktypIndex] == "00001") 275 | { 276 | // This is a UDT block (custom datatype). 277 | UdtMc5codeMap[BlockNumber] = strMc5code; 278 | } 279 | } 280 | 281 | // Put them into one big map to rule them all! 282 | std::map> Mc5codeMap; 283 | Mc5codeMap["DB"] = std::move(DbMc5codeMap); 284 | Mc5codeMap["DBREF"] = std::move(DbReferenceMc5codeMap); 285 | Mc5codeMap["FB"] = std::move(FbMc5codeMap); 286 | Mc5codeMap["SFB"] = std::move(SfbMc5codeMap); 287 | Mc5codeMap["UDT"] = std::move(UdtMc5codeMap); 288 | 289 | // Parse all DBs from the MC5 Code. 290 | auto ParseResult = _ParseDBs(DeviceSymbolInfo, Mc5codeMap); 291 | if (const auto pError = std::get_if(&ParseResult)) 292 | { 293 | return *pError; 294 | } 295 | 296 | return std::monostate(); 297 | } 298 | 299 | std::variant 300 | ParseOmbstx(std::vector& DeviceSymbolInfos, const std::vector& DeviceIdInfos, const std::wstring& wstrS7PFolderPath) 301 | { 302 | // Parse the BSTCNTOF.DBF dBASE file. 303 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrS7PFolderPath + L"\\ombstx\\offline\\BSTCNTOF.DBF"); 304 | if (const auto pError = std::get_if(&ReadDbfResult)) 305 | { 306 | return CS7PError(pError->Message()); 307 | } 308 | 309 | auto Reader = std::get>(std::move(ReadDbfResult)); 310 | 311 | // Get the index of the ID field. 312 | auto GetIndexResult = Reader->GetFieldIndex("ID"); 313 | if (const auto pError = std::get_if(&GetIndexResult)) 314 | { 315 | return CS7PError(L"BSTCNTOF.DBF: " + pError->Message()); 316 | } 317 | 318 | size_t IdIndex = std::get(GetIndexResult); 319 | 320 | // Iterate through all records. 321 | for (;;) 322 | { 323 | // Get the Subblock List ID (which is also the path to the Subblock List). 324 | auto ReadResult = Reader->ReadNextRecord(); 325 | if (const auto pError = std::get_if(&ReadResult)) 326 | { 327 | return CS7PError(L"Could not read from BSTCNTOF.DBF: " + pError->Message()); 328 | } 329 | 330 | if (std::holds_alternative(ReadResult)) 331 | { 332 | break; 333 | } 334 | 335 | auto Record = std::get>(std::move(ReadResult)); 336 | 337 | // Convert it to a size_t. 338 | auto Option = StrToSizeT(Record[IdIndex]); 339 | if (!Option.has_value()) 340 | { 341 | return CS7PError(L"Invalid BSTCNFOF.DBF ID: " + StrToWstr(Record[IdIndex])); 342 | } 343 | 344 | size_t SubblockListId = Option.value(); 345 | 346 | // Construct the full path to the SUBBLK.DBF in the Subblock List subdirectory. 347 | // (e.g. "...\ombstx\offline\00000005\SUBBLK.DBF") 348 | std::wostringstream wssSubblockFilePath; 349 | wssSubblockFilePath << wstrS7PFolderPath << L"\\ombstx\\offline\\"; 350 | wssSubblockFilePath << std::hex << std::setfill(L'0') << std::setw(8) << SubblockListId; 351 | wssSubblockFilePath << L"\\SUBBLK.DBF"; 352 | 353 | // Find the Device that corresponds to this Subblock List. 354 | const auto& DeviceIdInfoIt = std::find_if(DeviceIdInfos.begin(), DeviceIdInfos.end(), [&](const S7DeviceIdInfo& other) 355 | { 356 | return other.SubblockListId == SubblockListId; 357 | }); 358 | 359 | if (DeviceIdInfoIt == DeviceIdInfos.end()) 360 | { 361 | // BSTCNTOF.DBF references a Subblock List that has no corresponding Device in the other files. 362 | // I've had this situation quite a few times during testing. 363 | // However, the SUBBLK.DBF of those Subblock Lists without a Device has always been empty, so no information is lost if we just silently skip them. 364 | continue; 365 | } 366 | 367 | // Find the corresponding entry in the DeviceSymbolInfos vector. 368 | const std::string& strDeviceName = DeviceIdInfoIt->strName; 369 | auto DeviceSymbolInfoIt = std::find_if(DeviceSymbolInfos.begin(), DeviceSymbolInfos.end(), [&](const S7DeviceSymbolInfo& other) 370 | { 371 | return other.strName == strDeviceName; 372 | }); 373 | 374 | if (DeviceSymbolInfoIt == DeviceSymbolInfos.end()) 375 | { 376 | return CS7PError( 377 | L"Could not find DeviceSymbolInfo for \"" + StrToWstr(strDeviceName) + 378 | L"\" and Subblock List " + std::to_wstring(SubblockListId) 379 | ); 380 | } 381 | 382 | // Parse this subblock. 383 | auto ParseResult = _ParseSingleOmbstxSubblock(*DeviceSymbolInfoIt, wssSubblockFilePath.str()); 384 | if (const auto pError = std::get_if(&ParseResult)) 385 | { 386 | return *pError; 387 | } 388 | } 389 | 390 | return std::monostate(); 391 | } 392 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_db_parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "CS7PError.h" 14 | #include "s7p_device_id_info_parser.h" 15 | #include "s7p_parser.h" 16 | 17 | std::variant ParseOmbstx( 18 | std::vector& DeviceSymbolInfos, 19 | const std::vector& DeviceIdInfos, 20 | const std::wstring& wstrS7PFolderPath 21 | ); 22 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_device_id_info_parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "s7p_device_id_info_parser.h" 14 | 15 | struct IntermediateInfo 16 | { 17 | std::string strName; 18 | std::string strObjId; 19 | std::string strObjTyp; 20 | }; 21 | 22 | 23 | static bool 24 | _FileExists(const std::wstring& wstrFilePath) 25 | { 26 | // libc++'s std::filesystem doesn't build for Windows yet, so we have to use this inefficient approach :( 27 | std::ifstream f(wstrFilePath.c_str()); 28 | return f.good(); 29 | } 30 | 31 | static std::variant 32 | _ParseStations(std::vector& StationInfos, const std::wstring& wstrDbfFilePath) 33 | { 34 | static const std::map S7ObjTypMap = { 35 | { "1314969", "S7-300" }, 36 | { "1314970", "S7-400" }, 37 | { "1315650", "S7-400H" }, 38 | { "1315651", "S7-PC" } 39 | }; 40 | 41 | // Read the HOBJECT1.DBF file. 42 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrDbfFilePath); 43 | if (const auto pError = std::get_if(&ReadDbfResult)) 44 | { 45 | return CS7PError(pError->Message()); 46 | } 47 | 48 | auto Reader = std::get>(std::move(ReadDbfResult)); 49 | 50 | // Get the indexes we are interested in. 51 | auto GetIndexResult = Reader->GetFieldIndex("ID"); 52 | if (const auto pError = std::get_if(&GetIndexResult)) 53 | { 54 | return CS7PError(pError->Message()); 55 | } 56 | 57 | size_t IdIndex = std::get(GetIndexResult); 58 | 59 | GetIndexResult = Reader->GetFieldIndex("OBJTYP"); 60 | if (const auto pError = std::get_if(&GetIndexResult)) 61 | { 62 | return CS7PError(pError->Message()); 63 | } 64 | 65 | size_t ObjTypIndex = std::get(GetIndexResult); 66 | 67 | GetIndexResult = Reader->GetFieldIndex("NAME"); 68 | if (const auto pError = std::get_if(&GetIndexResult)) 69 | { 70 | return CS7PError(pError->Message()); 71 | } 72 | 73 | size_t NameIndex = std::get(GetIndexResult); 74 | 75 | // Iterate through all records and collect information about potentially interesting ones. 76 | for (;;) 77 | { 78 | auto ReadResult = Reader->ReadNextRecord(); 79 | if (const auto pError = std::get_if(&ReadResult)) 80 | { 81 | return CS7PError(pError->Message()); 82 | } 83 | 84 | if (std::holds_alternative(ReadResult)) 85 | { 86 | break; 87 | } 88 | 89 | auto Record = std::get>(std::move(ReadResult)); 90 | 91 | // Check if this station is an S7 PLC. 92 | const auto it = S7ObjTypMap.find(Record[ObjTypIndex]); 93 | if (it == S7ObjTypMap.end()) 94 | { 95 | continue; 96 | } 97 | 98 | // Yes, then collect information about it. 99 | IntermediateInfo& Info = StationInfos.emplace_back(); 100 | Info.strName = it->second + ": " + Str1252ToStr(Record[NameIndex]); 101 | Info.strObjId = Record[IdIndex]; 102 | Info.strObjTyp = Record[ObjTypIndex]; 103 | } 104 | 105 | return std::monostate(); 106 | } 107 | 108 | static std::variant 109 | _ParseRelations(std::vector& RelationInfos, const std::wstring& wstrDbfFilePath, std::vector& PreviousInfos, const std::string& strRelID) 110 | { 111 | // Read the HRELATI1.DBF file. 112 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrDbfFilePath); 113 | if (const auto pError = std::get_if(&ReadDbfResult)) 114 | { 115 | return CS7PError(pError->Message()); 116 | } 117 | 118 | auto Reader = std::get>(std::move(ReadDbfResult)); 119 | 120 | // Get the indexes we are interested in. 121 | auto GetIndexResult = Reader->GetFieldIndex("SOBJID"); 122 | if (const auto pError = std::get_if(&GetIndexResult)) 123 | { 124 | return CS7PError(pError->Message()); 125 | } 126 | 127 | size_t SObjIdIndex = std::get(GetIndexResult); 128 | 129 | GetIndexResult = Reader->GetFieldIndex("SOBJTYP"); 130 | if (const auto pError = std::get_if(&GetIndexResult)) 131 | { 132 | return CS7PError(pError->Message()); 133 | } 134 | 135 | size_t SObjTypIndex = std::get(GetIndexResult); 136 | 137 | GetIndexResult = Reader->GetFieldIndex("RELID"); 138 | if (const auto pError = std::get_if(&GetIndexResult)) 139 | { 140 | return CS7PError(pError->Message()); 141 | } 142 | 143 | size_t RelIdIndex = std::get(GetIndexResult); 144 | 145 | GetIndexResult = Reader->GetFieldIndex("TOBJID"); 146 | if (const auto pError = std::get_if(&GetIndexResult)) 147 | { 148 | return CS7PError(pError->Message()); 149 | } 150 | 151 | size_t TObjIdIndex = std::get(GetIndexResult); 152 | 153 | GetIndexResult = Reader->GetFieldIndex("TOBJTYP"); 154 | if (const auto pError = std::get_if(&GetIndexResult)) 155 | { 156 | return CS7PError(pError->Message()); 157 | } 158 | 159 | size_t TObjTypIndex = std::get(GetIndexResult); 160 | 161 | // Iterate through all records and previous infos, and collect information about matching ones. 162 | for (;;) 163 | { 164 | auto ReadResult = Reader->ReadNextRecord(); 165 | if (const auto pError = std::get_if(&ReadResult)) 166 | { 167 | return CS7PError(pError->Message()); 168 | } 169 | 170 | if (std::holds_alternative(ReadResult)) 171 | { 172 | break; 173 | } 174 | 175 | auto Record = std::get>(std::move(ReadResult)); 176 | 177 | for (const auto& PreviousInfo : PreviousInfos) 178 | { 179 | // Does the source part of the record refer to an object we collected from the previous database? 180 | if (Record[SObjIdIndex] != PreviousInfo.strObjId) 181 | { 182 | continue; 183 | } 184 | 185 | if (Record[SObjTypIndex] != PreviousInfo.strObjTyp) 186 | { 187 | continue; 188 | } 189 | 190 | // Is the relation ID the one we are looking for? 191 | if (Record[RelIdIndex] != strRelID) 192 | { 193 | continue; 194 | } 195 | 196 | // All fine, then collect information about the target object. 197 | IntermediateInfo& Info = RelationInfos.emplace_back(); 198 | Info.strName = PreviousInfo.strName; 199 | Info.strObjId = Record[TObjIdIndex]; 200 | Info.strObjTyp = Record[TObjTypIndex]; 201 | } 202 | } 203 | 204 | return std::monostate(); 205 | } 206 | 207 | static std::variant 208 | _ParseDevices(std::vector& DeviceInfos, const std::wstring& wstrDbfFilePath, std::vector& StationRelationInfos) 209 | { 210 | // Read the HOBJECT1.DBF file. 211 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrDbfFilePath); 212 | if (const auto pError = std::get_if(&ReadDbfResult)) 213 | { 214 | return CS7PError(pError->Message()); 215 | } 216 | 217 | auto Reader = std::get>(std::move(ReadDbfResult)); 218 | 219 | // Get the indexes we are interested in. 220 | auto GetIndexResult = Reader->GetFieldIndex("ID"); 221 | if (const auto pError = std::get_if(&GetIndexResult)) 222 | { 223 | return CS7PError(pError->Message()); 224 | } 225 | 226 | size_t IdIndex = std::get(GetIndexResult); 227 | 228 | GetIndexResult = Reader->GetFieldIndex("OBJTYP"); 229 | if (const auto pError = std::get_if(&GetIndexResult)) 230 | { 231 | return CS7PError(pError->Message()); 232 | } 233 | 234 | size_t ObjTypIndex = std::get(GetIndexResult); 235 | 236 | GetIndexResult = Reader->GetFieldIndex("NAME"); 237 | if (const auto pError = std::get_if(&GetIndexResult)) 238 | { 239 | return CS7PError(pError->Message()); 240 | } 241 | 242 | size_t NameIndex = std::get(GetIndexResult); 243 | 244 | // Iterate through all records and previous infos, and collect information about matching ones. 245 | for (;;) 246 | { 247 | auto ReadResult = Reader->ReadNextRecord(); 248 | if (const auto pError = std::get_if(&ReadResult)) 249 | { 250 | return CS7PError(pError->Message()); 251 | } 252 | 253 | if (std::holds_alternative(ReadResult)) 254 | { 255 | break; 256 | } 257 | 258 | auto Record = std::get>(std::move(ReadResult)); 259 | 260 | for (const auto& PreviousInfo : StationRelationInfos) 261 | { 262 | // Does the record refer to an object we collected from the previous database? 263 | if (Record[IdIndex] != PreviousInfo.strObjId) 264 | { 265 | continue; 266 | } 267 | 268 | if (Record[ObjTypIndex] != PreviousInfo.strObjTyp) 269 | { 270 | continue; 271 | } 272 | 273 | // Yes, then extend the name and collect it. 274 | IntermediateInfo& Info = DeviceInfos.emplace_back(); 275 | Info.strName = PreviousInfo.strName + " -> " + Str1252ToStr(Record[NameIndex]); 276 | Info.strObjId = PreviousInfo.strObjId; 277 | Info.strObjTyp = PreviousInfo.strObjTyp; 278 | } 279 | } 280 | 281 | return std::monostate(); 282 | } 283 | 284 | static std::variant 285 | _ParseResoffAndLinkhrs(std::vector& DeviceIdInfos, const std::wstring& wstrS7PFolderPath, std::vector& PreviousInfos) 286 | { 287 | static const uint32_t SubblockListIdMagic = 0x00116001; 288 | static const uint32_t SymbolListIdMagic = 0x00113001; 289 | 290 | // Read the S7RESOFF.DBF file. 291 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrS7PFolderPath + L"\\hrs\\S7RESOFF.DBF"); 292 | if (const auto pError = std::get_if(&ReadDbfResult)) 293 | { 294 | return CS7PError(pError->Message()); 295 | } 296 | 297 | auto ResoffReader = std::get>(std::move(ReadDbfResult)); 298 | 299 | // Get the indexes we are interested in. 300 | auto GetIndexResult = ResoffReader->GetFieldIndex("ID"); 301 | if (const auto pError = std::get_if(&GetIndexResult)) 302 | { 303 | return CS7PError(pError->Message()); 304 | } 305 | 306 | size_t IdIndex = std::get(GetIndexResult); 307 | 308 | GetIndexResult = ResoffReader->GetFieldIndex("NAME"); 309 | if (const auto pError = std::get_if(&GetIndexResult)) 310 | { 311 | return CS7PError(pError->Message()); 312 | } 313 | 314 | size_t NameIndex = std::get(GetIndexResult); 315 | 316 | GetIndexResult = ResoffReader->GetFieldIndex("RSRVD4_L"); 317 | if (const auto pError = std::get_if(&GetIndexResult)) 318 | { 319 | return CS7PError(pError->Message()); 320 | } 321 | 322 | size_t Rsrvd4LIndex = std::get(GetIndexResult); 323 | 324 | // Open the linkhrs.lnk file. 325 | std::wstring wstrLinkhrsFilePath = wstrS7PFolderPath + L"\\hrs\\linkhrs.lnk"; 326 | std::ifstream Linkhrs(wstrLinkhrsFilePath.c_str(), std::ios::binary); 327 | if (!Linkhrs) 328 | { 329 | return CS7PError(L"Could not open linkhrs.lnk"); 330 | } 331 | 332 | // Iterate through all records. 333 | for (;;) 334 | { 335 | auto ReadResult = ResoffReader->ReadNextRecord(); 336 | if (const auto pError = std::get_if(&ReadResult)) 337 | { 338 | return CS7PError(pError->Message()); 339 | } 340 | 341 | if (std::holds_alternative(ReadResult)) 342 | { 343 | break; 344 | } 345 | 346 | auto Record = std::get>(std::move(ReadResult)); 347 | 348 | // Try to find a matching device from the previous device infos to build a full name. 349 | S7DeviceIdInfo& Info = DeviceIdInfos.emplace_back(); 350 | for (const auto& PreviousInfo : PreviousInfos) 351 | { 352 | if (Record[IdIndex] == PreviousInfo.strObjId) 353 | { 354 | Info.strName = PreviousInfo.strName + " -> "; 355 | break; 356 | } 357 | } 358 | 359 | Info.strName += Str1252ToStr(Record[NameIndex]); 360 | 361 | // Convert the RSRVD4_L column value to a size_t. 362 | // It describes an offset in the linkhrs.lnk file. 363 | auto Option = StrToSizeT(Record[Rsrvd4LIndex]); 364 | if (!Option.has_value()) 365 | { 366 | return CS7PError(L"Invalid RSRVD4_L for " + StrToWstr(Info.strName) + L": " + StrToWstr(Record[Rsrvd4LIndex])); 367 | } 368 | 369 | size_t Offset = Option.value(); 370 | 371 | // Read 512 bytes (as 128x uint32_t) at that offset from the linkhrs.lnk file. 372 | // See http://www.plctalk.net/qanda/showpost.php?p=355358&postcount=14 373 | Linkhrs.seekg(Offset); 374 | if (!Linkhrs) 375 | { 376 | return CS7PError(L"Could not seek to linkhrs.lnk offset " + std::to_wstring(Offset)); 377 | } 378 | 379 | std::array Buffer; 380 | if (!Linkhrs.read(reinterpret_cast(Buffer.data()), sizeof(Buffer))) 381 | { 382 | return CS7PError(L"Could not read linkhrs.lnk offset " + std::to_wstring(Offset)); 383 | } 384 | 385 | // Extract the Subblock List ID. 386 | auto it = std::find(Buffer.begin(), Buffer.end(), SubblockListIdMagic); 387 | if (it != Buffer.end() && ++it != Buffer.end()) 388 | { 389 | Info.SubblockListId = *it; 390 | } 391 | 392 | // Extract the YDB ID. 393 | it = std::find(Buffer.begin(), Buffer.end(), SymbolListIdMagic); 394 | if (it != Buffer.end() && ++it != Buffer.end()) 395 | { 396 | Info.SymbolListId = *it; 397 | } 398 | } 399 | 400 | return std::monostate(); 401 | } 402 | 403 | 404 | std::variant 405 | ParseDeviceIdInfos(std::vector& DeviceIdInfos, const std::wstring& wstrS7PFolderPath) 406 | { 407 | static const std::string strStationRelID = "1315838"; 408 | static const std::string strSecondLevelRelID = "16"; 409 | 410 | const std::wstring wstrStationsDbfFilePath = wstrS7PFolderPath + L"\\hOmSave7\\s7hstatx\\HOBJECT1.DBF"; 411 | const std::wstring wstrStationRelationsDbfFilePath = wstrS7PFolderPath + L"\\hOmSave7\\s7hstatx\\HRELATI1.DBF"; 412 | 413 | const struct 414 | { 415 | std::wstring wstrDevicesDbfFilePath; 416 | std::wstring wstrDeviceRelationsDbfFilePath; 417 | } 418 | DeviceDbfInfos[] = { 419 | // S7-31x series of CPUs 420 | { wstrS7PFolderPath + L"\\hOmSave7\\S7HK31AX\\HOBJECT1.DBF", wstrS7PFolderPath + L"\\hOmSave7\\S7HK31AX\\HRELATI1.DBF" }, 421 | 422 | // S7-41x series of CPUs 423 | { wstrS7PFolderPath + L"\\hOmSave7\\S7HK41AX\\HOBJECT1.DBF", wstrS7PFolderPath + L"\\hOmSave7\\S7HK41AX\\HRELATI1.DBF" }, 424 | }; 425 | 426 | std::vector PreviousInfosForResoff; 427 | if (_FileExists(wstrStationsDbfFilePath) && _FileExists(wstrStationRelationsDbfFilePath)) 428 | { 429 | // Stations 430 | std::vector StationInfos; 431 | auto ParseResult = _ParseStations(StationInfos, wstrStationsDbfFilePath); 432 | if (const auto pError = std::get_if(&ParseResult)) 433 | { 434 | return CS7PError(L"Stations: " + pError->Message()); 435 | } 436 | 437 | // Station -> Device 438 | std::vector StationRelationInfos; 439 | ParseResult = _ParseRelations(StationRelationInfos, wstrStationRelationsDbfFilePath, StationInfos, strStationRelID); 440 | if (const auto pError = std::get_if(&ParseResult)) 441 | { 442 | return CS7PError(L"Station relations: " + pError->Message()); 443 | } 444 | 445 | // Devices 446 | for (const auto& DeviceDbfInfo : DeviceDbfInfos) 447 | { 448 | if (_FileExists(DeviceDbfInfo.wstrDevicesDbfFilePath) && _FileExists(DeviceDbfInfo.wstrDeviceRelationsDbfFilePath)) 449 | { 450 | // Devices (CPUs only here) 451 | std::vector DeviceInfos; 452 | ParseResult = _ParseDevices(DeviceInfos, DeviceDbfInfo.wstrDevicesDbfFilePath, StationRelationInfos); 453 | if (const auto pError = std::get_if(&ParseResult)) 454 | { 455 | return CS7PError(DeviceDbfInfo.wstrDevicesDbfFilePath + L": " + pError->Message()); 456 | } 457 | 458 | // Device -> Device Content 459 | std::vector DeviceRelationInfos; 460 | ParseResult = _ParseRelations(DeviceRelationInfos, DeviceDbfInfo.wstrDeviceRelationsDbfFilePath, DeviceInfos, strSecondLevelRelID); 461 | if (const auto pError = std::get_if(&ParseResult)) 462 | { 463 | return CS7PError(DeviceDbfInfo.wstrDeviceRelationsDbfFilePath + L": " + pError->Message()); 464 | } 465 | 466 | PreviousInfosForResoff.insert(PreviousInfosForResoff.end(), DeviceRelationInfos.begin(), DeviceRelationInfos.end()); 467 | } 468 | } 469 | } 470 | 471 | // Device Contents (S7 Programs only here) 472 | auto ParseResoffResult = _ParseResoffAndLinkhrs(DeviceIdInfos, wstrS7PFolderPath, PreviousInfosForResoff); 473 | if (const auto pError = std::get_if(&ParseResoffResult)) 474 | { 475 | return CS7PError(L"Resoff/Linkhrs: " + pError->Message()); 476 | } 477 | 478 | return std::monostate(); 479 | } 480 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_device_id_info_parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "CS7PError.h" 16 | 17 | struct S7DeviceIdInfo 18 | { 19 | std::string strName; 20 | std::optional SubblockListId; 21 | std::optional SymbolListId; 22 | }; 23 | 24 | std::variant ParseDeviceIdInfos( 25 | std::vector& DeviceIdInfos, 26 | const std::wstring& wstrS7PFolderPath 27 | ); 28 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | 9 | #include "s7p_db_parser.h" 10 | #include "s7p_device_id_info_parser.h" 11 | #include "s7p_parser.h" 12 | #include "s7p_symbol_list_parser.h" 13 | 14 | 15 | static std::variant 16 | _GetS7PFolderPath(std::wstring& wstrS7PFolderPath, const std::wstring& wstrS7PFilePath) 17 | { 18 | size_t BackslashPosition = wstrS7PFilePath.find_last_of(L'\\'); 19 | if (BackslashPosition == std::wstring::npos) 20 | { 21 | return CS7PError(L"Did not find any backslash in the .s7p file path"); 22 | } 23 | 24 | wstrS7PFolderPath = wstrS7PFilePath.substr(0, BackslashPosition); 25 | return std::monostate(); 26 | } 27 | 28 | 29 | std::variant, CS7PError> 30 | ParseS7P(const std::wstring& wstrS7PFilePath) 31 | { 32 | // Get the .s7p folder path for subsequent calls. 33 | std::wstring wstrS7PFolderPath; 34 | auto Result = _GetS7PFolderPath(wstrS7PFolderPath, wstrS7PFilePath); 35 | if (const auto pError = std::get_if(&Result)) 36 | { 37 | return *pError; 38 | } 39 | 40 | // Get the names of all PLCs in this project and their corresponding Symbol List IDs and Subblock List IDs. 41 | std::vector DeviceIdInfos; 42 | Result = ParseDeviceIdInfos(DeviceIdInfos, wstrS7PFolderPath); 43 | if (const auto pError = std::get_if(&Result)) 44 | { 45 | return *pError; 46 | } 47 | 48 | // Parse the Symbol Tables in the YDBs directory. 49 | std::vector DeviceSymbolInfos; 50 | Result = ParseYDBs(DeviceSymbolInfos, DeviceIdInfos, wstrS7PFolderPath); 51 | if (const auto pError = std::get_if(&Result)) 52 | { 53 | return *pError; 54 | } 55 | 56 | // Parse the Subblock Lists in the ombstx directory. 57 | Result = ParseOmbstx(DeviceSymbolInfos, DeviceIdInfos, wstrS7PFolderPath); 58 | if (const auto pError = std::get_if(&Result)) 59 | { 60 | return *pError; 61 | } 62 | 63 | return DeviceSymbolInfos; 64 | } 65 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "CS7PError.h" 15 | 16 | struct S7Symbol 17 | { 18 | std::string strName; 19 | std::string strCode; 20 | std::string strDatatype; 21 | std::string strComment; 22 | }; 23 | 24 | struct S7Block 25 | { 26 | std::string strName; 27 | std::vector Symbols; 28 | }; 29 | 30 | struct S7DeviceSymbolInfo 31 | { 32 | std::string strName; 33 | std::vector Blocks; 34 | std::map DbNamesMap; 35 | std::vector Warnings; 36 | }; 37 | 38 | std::variant, CS7PError> ParseS7P( 39 | const std::wstring& wstrS7PFilePath 40 | ); 41 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_symbol_list_parser.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include 8 | #include 9 | 10 | #include "s7p_symbol_list_parser.h" 11 | 12 | 13 | static std::variant 14 | _ParseSingleYDBSymbolList(const std::wstring& wstrSymbolListFilePath, std::vector& Symbols, std::map& DbNamesMap) 15 | { 16 | // Parse the SYMLIST.DBF dBASE file. 17 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrSymbolListFilePath); 18 | if (const auto pError = std::get_if(&ReadDbfResult)) 19 | { 20 | return CS7PError(pError->Message()); 21 | } 22 | 23 | auto Reader = std::get>(std::move(ReadDbfResult)); 24 | 25 | // Get some indexes. 26 | auto GetIndexResult = Reader->GetFieldIndex("_SKZ"); 27 | if (const auto pError = std::get_if(&GetIndexResult)) 28 | { 29 | return CS7PError(L"SYMLIST.DBF: " + pError->Message()); 30 | } 31 | 32 | size_t SkzIndex = std::get(GetIndexResult); 33 | 34 | GetIndexResult = Reader->GetFieldIndex("_OPIEC"); 35 | if (const auto pError = std::get_if(&GetIndexResult)) 36 | { 37 | return CS7PError(L"SYMLIST.DBF: " + pError->Message()); 38 | } 39 | 40 | size_t OpiecIndex = std::get(GetIndexResult); 41 | 42 | GetIndexResult = Reader->GetFieldIndex("_DATATYP"); 43 | if (const auto pError = std::get_if(&GetIndexResult)) 44 | { 45 | return CS7PError(L"SYMLIST.DBF: " + pError->Message()); 46 | } 47 | 48 | size_t DatatypeIndex = std::get(GetIndexResult); 49 | 50 | GetIndexResult = Reader->GetFieldIndex("_COMMENT"); 51 | if (const auto pError = std::get_if(&GetIndexResult)) 52 | { 53 | return CS7PError(L"SYMLIST.DBF: " + pError->Message()); 54 | } 55 | 56 | size_t CommentIndex = std::get(GetIndexResult); 57 | 58 | // Iterate through all records. 59 | for (;;) 60 | { 61 | // Read a record containing symbol information. 62 | auto ReadResult = Reader->ReadNextRecord(); 63 | if (const auto pError = std::get_if(&ReadResult)) 64 | { 65 | return CS7PError(L"Could not read from SYMLIST.DBF: " + pError->Message()); 66 | } 67 | 68 | if (std::holds_alternative(ReadResult)) 69 | { 70 | // We have read all records, so we are done! 71 | return std::monostate(); 72 | } 73 | 74 | auto Record = std::get>(std::move(ReadResult)); 75 | 76 | // Get and sanitize the symbol code. 77 | std::string strCode = Record[OpiecIndex]; 78 | strCode.erase(std::remove(strCode.begin(), strCode.end(), ' '), strCode.end()); 79 | 80 | // Only add inputs, memory ("Merker"), and output symbols to the Symbols vector. 81 | if (const char c = *strCode.c_str(); c == 'I' || c == 'M' || c == 'Q') 82 | { 83 | S7Symbol& Symbol = Symbols.emplace_back(); 84 | Symbol.strName = Str1252ToStr(Record[SkzIndex]); 85 | Symbol.strCode = std::move(strCode); 86 | Symbol.strDatatype = Record[DatatypeIndex]; 87 | Symbol.strComment = Str1252ToStr(Record[CommentIndex]); 88 | } 89 | else if (strCode.starts_with("DB")) 90 | { 91 | // The YDB symlist also contains names for the DBs we parse later (in _ParseOmbstx). 92 | // Save these names in the DbNames map. 93 | auto Option = StrToSizeT(strCode.substr(2)); 94 | if (Option.has_value()) 95 | { 96 | size_t DbNumber = Option.value(); 97 | std::string strName = Str1252ToStr(Record[SkzIndex]); 98 | DbNamesMap[DbNumber] = std::move(strName); 99 | } 100 | } 101 | } 102 | } 103 | 104 | std::variant 105 | ParseYDBs(std::vector& DeviceSymbolInfos, const std::vector& DeviceIdInfos, const std::wstring& wstrS7PFolderPath) 106 | { 107 | // Parse the SYMLISTS.DBF dBASE file. 108 | auto ReadDbfResult = CDbfReader::ReadDbf(wstrS7PFolderPath + L"\\YDBs\\SYMLISTS.DBF"); 109 | if (const auto pError = std::get_if(&ReadDbfResult)) 110 | { 111 | return CS7PError(pError->Message()); 112 | } 113 | 114 | auto Reader = std::get>(std::move(ReadDbfResult)); 115 | 116 | // Get the indexes we are interested in. 117 | auto GetIndexResult = Reader->GetFieldIndex("_ID"); 118 | if (const auto pError = std::get_if(&GetIndexResult)) 119 | { 120 | return CS7PError(L"SYMLISTS.DBF: " + pError->Message()); 121 | } 122 | 123 | size_t IdIndex = std::get(GetIndexResult); 124 | 125 | GetIndexResult = Reader->GetFieldIndex("_DBPATH"); 126 | if (const auto pError = std::get_if(&GetIndexResult)) 127 | { 128 | return CS7PError(L"SYMLISTS.DBF: " + pError->Message()); 129 | } 130 | 131 | size_t DbPathIndex = std::get(GetIndexResult); 132 | 133 | // Iterate through all records. 134 | for (;;) 135 | { 136 | // Get the Symbol Table ID and database path (usually equal but not necessarily, see e.g. the Zes01_05 example project) 137 | auto ReadResult = Reader->ReadNextRecord(); 138 | if (const auto pError = std::get_if(&ReadResult)) 139 | { 140 | return CS7PError(L"Could not read from SYMLISTS.DBF: " + pError->Message()); 141 | } 142 | 143 | if (std::holds_alternative(ReadResult)) 144 | { 145 | break; 146 | } 147 | 148 | auto Record = std::get>(std::move(ReadResult)); 149 | 150 | // Convert it to a size_t. 151 | auto Option = StrToSizeT(Record[IdIndex]); 152 | if (!Option.has_value()) 153 | { 154 | return CS7PError(L"Invalid SYMLISTS.DBF _ID: " + StrToWstr(Record[IdIndex])); 155 | } 156 | 157 | size_t SymbolListId = Option.value(); 158 | 159 | // Construct the full path to the SYMLIST.DBF in the Symbol List's subdirectory. 160 | std::wstring wstrSymbolListFilePath = wstrS7PFolderPath; 161 | wstrSymbolListFilePath += L"\\YDBs\\"; 162 | wstrSymbolListFilePath += StrToWstr(Record[DbPathIndex]); 163 | wstrSymbolListFilePath += L"\\SYMLIST.DBF"; 164 | 165 | // Find the Device that corresponds to this Symbol List. 166 | const auto& DeviceIdInfoIt = std::find_if(DeviceIdInfos.begin(), DeviceIdInfos.end(), [&](const S7DeviceIdInfo& other) 167 | { 168 | return other.SymbolListId == SymbolListId; 169 | }); 170 | 171 | if (DeviceIdInfoIt == DeviceIdInfos.end()) 172 | { 173 | return CS7PError(L"Could not find DeviceIdInfo for Symbol List " + std::to_wstring(SymbolListId)); 174 | } 175 | 176 | // Add it to the final DeviceSymbolInfos vector. 177 | S7DeviceSymbolInfo& DeviceSymbolInfo = DeviceSymbolInfos.emplace_back(); 178 | DeviceSymbolInfo.strName = DeviceIdInfoIt->strName; 179 | 180 | // Parse this Symbol List. 181 | S7Block& Block = DeviceSymbolInfo.Blocks.emplace_back(); 182 | Block.strName = "Symbol List"; 183 | 184 | auto ParseResult = _ParseSingleYDBSymbolList(wstrSymbolListFilePath, Block.Symbols, DeviceSymbolInfo.DbNamesMap); 185 | if (const auto pError = std::get_if(&ParseResult)) 186 | { 187 | return *pError; 188 | } 189 | } 190 | 191 | return std::monostate(); 192 | } 193 | -------------------------------------------------------------------------------- /src/EnlyzeS7PLib/src/s7p_symbol_list_parser.h: -------------------------------------------------------------------------------- 1 | // 2 | // EnlyzeS7PLib - Library for parsing symbols in Siemens STEP 7 project files 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "CS7PError.h" 14 | #include "s7p_device_id_info_parser.h" 15 | #include "s7p_parser.h" 16 | 17 | std::variant ParseYDBs( 18 | std::vector& DeviceSymbolInfos, 19 | const std::vector& DeviceIdInfos, 20 | const std::wstring& wstrS7PFolderPath 21 | ); 22 | -------------------------------------------------------------------------------- /src/PerMonitorV2.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | True/PM 6 | PerMonitorV2, PerMonitor 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/S7-Project-Explorer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | const int iFontReferenceDPI = 72; 10 | const int iWindowsReferenceDPI = 96; 11 | 12 | const int iUnifiedControlPadding = 10; 13 | const int iUnifiedButtonHeight = 23; 14 | const int iUnifiedButtonWidth = 100; 15 | 16 | const WCHAR wszAppName[] = L"ENLYZE S7-Project-Explorer " APP_VERSION_WSTRING; 17 | 18 | int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR lpCmdLine, int nShowCmd) 19 | { 20 | int iReturnValue = 1; 21 | 22 | UNREFERENCED_PARAMETER(hPrevInstance); 23 | UNREFERENCED_PARAMETER(lpCmdLine); 24 | 25 | // Initialize GDI+. 26 | Gdiplus::GdiplusStartupInput gpStartupInput; 27 | ULONG_PTR gpToken; 28 | Gdiplus::GdiplusStartup(&gpToken, &gpStartupInput, nullptr); 29 | 30 | // Initialize the standard controls we use. 31 | // Required for at least Windows XP. 32 | INITCOMMONCONTROLSEX icc; 33 | icc.dwSize = sizeof(icc); 34 | icc.dwICC = ICC_STANDARD_CLASSES | ICC_LISTVIEW_CLASSES; 35 | InitCommonControlsEx(&icc); 36 | 37 | // Create the main window and let it handle the rest. 38 | { 39 | auto pMainWindow = CMainWindow::Create(hInstance, nShowCmd); 40 | if (pMainWindow) 41 | { 42 | iReturnValue = pMainWindow->WorkLoop(); 43 | } 44 | } 45 | 46 | // Cleanup 47 | Gdiplus::GdiplusShutdown(gpToken); 48 | 49 | return iReturnValue; 50 | } 51 | -------------------------------------------------------------------------------- /src/S7-Project-Explorer.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "targetver.h" 15 | #include 16 | #include 17 | #include 18 | 19 | #pragma warning(push) 20 | #pragma warning(disable:4458) 21 | #include 22 | #pragma warning(pop) 23 | 24 | #if !defined(WM_DPICHANGED) 25 | #define WM_DPICHANGED 0x02E0 26 | #endif 27 | 28 | #include "unique_resource.h" 29 | #include "win32_wrappers.h" 30 | 31 | #include 32 | #include 33 | 34 | #include "resource.h" 35 | #include "csv_exporter.h" 36 | #include "utils.h" 37 | #include "version.h" 38 | 39 | // Forward declarations 40 | class CFilePage; 41 | class CFinishPage; 42 | class CMainWindow; 43 | class CPage; 44 | class CVariablesPage; 45 | class CWarningsWindow; 46 | 47 | #include "CPage.h" 48 | #include "CFilePage.h" 49 | #include "CFinishPage.h" 50 | #include "CMainWindow.h" 51 | #include "CVariablesPage.h" 52 | #include "CWarningsWindow.h" 53 | 54 | // S7-Project-Explorer.cpp 55 | extern const int iFontReferenceDPI; 56 | extern const int iWindowsReferenceDPI; 57 | extern const int iUnifiedControlPadding; 58 | extern const int iUnifiedButtonHeight; 59 | extern const int iUnifiedButtonWidth; 60 | extern const WCHAR wszAppName[]; 61 | -------------------------------------------------------------------------------- /src/S7-Project-Explorer.rc: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022-2023 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma code_page(65001) 8 | 9 | #include "targetver.h" 10 | #include 11 | 12 | #include "resource.h" 13 | #include "version.h" 14 | 15 | LANGUAGE LANG_NEUTRAL, SUBLANG_NEUTRAL 16 | 17 | // Icon 18 | IDI_ICON ICON "res\\enlyze-s7.ico" 19 | 20 | // Bitmap 21 | IDB_LOGO BITMAP "res\\enlyze-logo.bmp" 22 | 23 | // PNG 24 | IDP_BLANK_FILE PNG "res\\blank-file.png" 25 | IDP_S7P_FILE PNG "res\\s7p-file.png" 26 | 27 | // Version 28 | VS_VERSION_INFO VERSIONINFO 29 | FILEVERSION APP_MAJOR_VERSION,APP_MINOR_VERSION,0,0 30 | PRODUCTVERSION APP_MAJOR_VERSION,APP_MINOR_VERSION,0,0 31 | FILEFLAGSMASK 0x3fL 32 | #ifdef _DEBUG 33 | FILEFLAGS 0x1L 34 | #else 35 | FILEFLAGS 0x0L 36 | #endif 37 | FILEOS 0x40004L 38 | FILETYPE 0x1L 39 | FILESUBTYPE 0x0L 40 | BEGIN 41 | BLOCK "StringFileInfo" 42 | BEGIN 43 | BLOCK "040904b0" 44 | BEGIN 45 | VALUE "CompanyName", "ENLYZE GmbH" 46 | VALUE "FileDescription", "S7-Project-Explorer" 47 | VALUE "FileVersion", APP_VERSION_COMBINED 48 | VALUE "InternalName", "S7-Project-Explorer.exe" 49 | VALUE "LegalCopyright", "Copyright © 2020-2023 ENLYZE GmbH" 50 | VALUE "OriginalFilename", "S7-Project-Explorer.exe" 51 | VALUE "ProductName", "S7-Project-Explorer" 52 | VALUE "ProductVersion", APP_VERSION_COMBINED 53 | END 54 | END 55 | BLOCK "VarFileInfo" 56 | BEGIN 57 | VALUE "Translation", 0x409, 1200 58 | END 59 | END 60 | 61 | // Localized resources 62 | #include "lang/de-DE.rc" 63 | #include "lang/en-US.rc" 64 | -------------------------------------------------------------------------------- /src/S7-Project-Explorer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31005.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "S7-Project-Explorer", "S7-Project-Explorer.vcxproj", "{B5EBA992-C161-435C-BD28-24DBE19234C3}" 7 | EndProject 8 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnlyzeWinCompatLib", "EnlyzeWinCompatLib\src\EnlyzeWinCompatLib.vcxproj", "{C2C396B8-B585-4F0D-BD37-CF6D4347140F}" 9 | EndProject 10 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "libc++", "EnlyzeWinCompatLib\src\libcxx\src\libc++.vcxproj", "{CF14A29C-E25E-4FAF-8C98-2F5006800132}" 11 | EndProject 12 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winpthreads", "EnlyzeWinCompatLib\src\libcxx\src\winpthreads\src\winpthreads.vcxproj", "{D3FACA21-D165-4B1E-9A06-A9B58964886C}" 13 | EndProject 14 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnlyzeS7PLib", "EnlyzeS7PLib\src\EnlyzeS7PLib.vcxproj", "{06B41AD5-3D7E-4C9D-8DFB-E53D8FC52723}" 15 | EndProject 16 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnlyzeWinStringLib", "EnlyzeWinStringLib\src\EnlyzeWinStringLib.vcxproj", "{95D0B318-D75C-4FA5-85A4-2A36835BF518}" 17 | EndProject 18 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "EnlyzeDbfLib", "EnlyzeDbfLib\src\EnlyzeDbfLib.vcxproj", "{7990079F-51F8-4E89-B382-CFAB391312AE}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|x86 = Debug|x86 23 | Release|x86 = Release|x86 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {B5EBA992-C161-435C-BD28-24DBE19234C3}.Debug|x86.ActiveCfg = Debug|Win32 27 | {B5EBA992-C161-435C-BD28-24DBE19234C3}.Debug|x86.Build.0 = Debug|Win32 28 | {B5EBA992-C161-435C-BD28-24DBE19234C3}.Release|x86.ActiveCfg = Release|Win32 29 | {B5EBA992-C161-435C-BD28-24DBE19234C3}.Release|x86.Build.0 = Release|Win32 30 | {C2C396B8-B585-4F0D-BD37-CF6D4347140F}.Debug|x86.ActiveCfg = Debug|Win32 31 | {C2C396B8-B585-4F0D-BD37-CF6D4347140F}.Debug|x86.Build.0 = Debug|Win32 32 | {C2C396B8-B585-4F0D-BD37-CF6D4347140F}.Release|x86.ActiveCfg = Release|Win32 33 | {C2C396B8-B585-4F0D-BD37-CF6D4347140F}.Release|x86.Build.0 = Release|Win32 34 | {CF14A29C-E25E-4FAF-8C98-2F5006800132}.Debug|x86.ActiveCfg = Debug|Win32 35 | {CF14A29C-E25E-4FAF-8C98-2F5006800132}.Debug|x86.Build.0 = Debug|Win32 36 | {CF14A29C-E25E-4FAF-8C98-2F5006800132}.Release|x86.ActiveCfg = Release|Win32 37 | {CF14A29C-E25E-4FAF-8C98-2F5006800132}.Release|x86.Build.0 = Release|Win32 38 | {D3FACA21-D165-4B1E-9A06-A9B58964886C}.Debug|x86.ActiveCfg = Debug|Win32 39 | {D3FACA21-D165-4B1E-9A06-A9B58964886C}.Debug|x86.Build.0 = Debug|Win32 40 | {D3FACA21-D165-4B1E-9A06-A9B58964886C}.Release|x86.ActiveCfg = Release|Win32 41 | {D3FACA21-D165-4B1E-9A06-A9B58964886C}.Release|x86.Build.0 = Release|Win32 42 | {06B41AD5-3D7E-4C9D-8DFB-E53D8FC52723}.Debug|x86.ActiveCfg = Debug|Win32 43 | {06B41AD5-3D7E-4C9D-8DFB-E53D8FC52723}.Debug|x86.Build.0 = Debug|Win32 44 | {06B41AD5-3D7E-4C9D-8DFB-E53D8FC52723}.Release|x86.ActiveCfg = Release|Win32 45 | {06B41AD5-3D7E-4C9D-8DFB-E53D8FC52723}.Release|x86.Build.0 = Release|Win32 46 | {95D0B318-D75C-4FA5-85A4-2A36835BF518}.Debug|x86.ActiveCfg = Debug|Win32 47 | {95D0B318-D75C-4FA5-85A4-2A36835BF518}.Debug|x86.Build.0 = Debug|Win32 48 | {95D0B318-D75C-4FA5-85A4-2A36835BF518}.Release|x86.ActiveCfg = Release|Win32 49 | {95D0B318-D75C-4FA5-85A4-2A36835BF518}.Release|x86.Build.0 = Release|Win32 50 | {7990079F-51F8-4E89-B382-CFAB391312AE}.Debug|x86.ActiveCfg = Debug|Win32 51 | {7990079F-51F8-4E89-B382-CFAB391312AE}.Debug|x86.Build.0 = Debug|Win32 52 | {7990079F-51F8-4E89-B382-CFAB391312AE}.Release|x86.ActiveCfg = Release|Win32 53 | {7990079F-51F8-4E89-B382-CFAB391312AE}.Release|x86.Build.0 = Release|Win32 54 | EndGlobalSection 55 | GlobalSection(SolutionProperties) = preSolution 56 | HideSolutionNode = FALSE 57 | EndGlobalSection 58 | GlobalSection(ExtensibilityGlobals) = postSolution 59 | SolutionGuid = {B10CA5DB-8FDB-4C08-B51E-C586F05AE6BF} 60 | EndGlobalSection 61 | EndGlobal 62 | -------------------------------------------------------------------------------- /src/S7-Project-Explorer.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | 14 | 16.0 15 | {B5EBA992-C161-435C-BD28-24DBE19234C3} 16 | Win32Proj 17 | S7ProjectExplorer 18 | 19 | 20 | 21 | Application 22 | true 23 | Unicode 24 | ClangCL 25 | 26 | 27 | Application 28 | false 29 | Unicode 30 | ClangCL 31 | true 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | true 46 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\bin\ 47 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\obj\ 48 | S7-Project-Explorer 49 | 50 | 51 | false 52 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\obj\ 53 | $(SolutionDir)\..\build\$(Configuration)\$(ProjectName)\bin\ 54 | S7-Project-Explorer 55 | 56 | 57 | 58 | Level4 59 | Disabled 60 | WIN32;_WINDOWS;_DEBUG;%(PreprocessorDefinitions) 61 | stdcpplatest 62 | MultiThreadedDebug 63 | false 64 | $(SolutionDir)\EnlyzeS7PLib\src;$(SolutionDir)\EnlyzeWinCompatLib\src\libcxx\include;$(SolutionDir)\scope-guard\include;%(AdditionalIncludeDirectories) 65 | Disabled 66 | true 67 | 68 | 69 | 70 | 71 | Windows 72 | DebugFull 73 | $(SolutionDir)\..\build\$(Configuration)\EnlyzeWinCompatLib\bin\EnlyzeWinCompatLib.lib;$(SolutionDir)\..\build\$(Configuration)\libc++\bin\libc++.lib;$(SolutionDir)\..\build\$(Configuration)\winpthreads\bin\winpthreads.lib;comctl32.lib;gdiplus.lib;%(AdditionalDependencies) 74 | false 75 | 5.01 76 | false 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | Level4 87 | MaxSpeed 88 | true 89 | true 90 | WIN32;_WINDOWS;NDEBUG;%(PreprocessorDefinitions) 91 | stdcpplatest 92 | MultiThreaded 93 | true 94 | false 95 | $(SolutionDir)\EnlyzeS7PLib\src;$(SolutionDir)\EnlyzeWinCompatLib\src\libcxx\include;$(SolutionDir)\scope-guard\include;%(AdditionalIncludeDirectories) 96 | AnySuitable 97 | true 98 | -flto -march=pentium-mmx 99 | 100 | 101 | Windows 102 | true 103 | true 104 | true 105 | $(SolutionDir)\..\build\$(Configuration)\EnlyzeWinCompatLib\bin\EnlyzeWinCompatLib.lib;$(SolutionDir)\..\build\$(Configuration)\libc++\bin\libc++.lib;$(SolutionDir)\..\build\$(Configuration)\winpthreads\bin\winpthreads.lib;comctl32.lib;gdiplus.lib;%(AdditionalDependencies) 106 | UseLinkTimeCodeGeneration 107 | false 108 | 5.01 109 | false 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | true 145 | true 146 | 147 | 148 | true 149 | true 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | {06b41ad5-3d7e-4c9d-8dfb-e53d8fc52723} 164 | 165 | 166 | {c2c396b8-b585-4f0d-bd37-cf6d4347140f} 167 | 168 | 169 | {cf14a29c-e25e-4faf-8c98-2f5006800132} 170 | 171 | 172 | {d3faca21-d165-4b1e-9a06-a9b58964886c} 173 | 174 | 175 | {95d0b318-d75c-4fa5-85a4-2a36835bf518} 176 | 177 | 178 | 179 | -------------------------------------------------------------------------------- /src/S7-Project-Explorer.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 6 | h;hpp;hxx;hm;inl;inc;xsd 7 | 8 | 9 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 10 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 11 | 12 | 13 | {94d23c28-d3d0-41b6-8dd7-c43775eca5db} 14 | 15 | 16 | 17 | 18 | Source Files 19 | 20 | 21 | Source Files 22 | 23 | 24 | Source Files 25 | 26 | 27 | Source Files 28 | 29 | 30 | Source Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Source Files 37 | 38 | 39 | Source Files 40 | 41 | 42 | 43 | 44 | Header Files 45 | 46 | 47 | Header Files 48 | 49 | 50 | Header Files 51 | 52 | 53 | Header Files 54 | 55 | 56 | Header Files 57 | 58 | 59 | Header Files 60 | 61 | 62 | Header Files 63 | 64 | 65 | Header Files 66 | 67 | 68 | Header Files 69 | 70 | 71 | Header Files 72 | 73 | 74 | Header Files 75 | 76 | 77 | Header Files 78 | 79 | 80 | Header Files 81 | 82 | 83 | 84 | 85 | Resource Files 86 | 87 | 88 | Resource Files 89 | 90 | 91 | Resource Files 92 | 93 | 94 | 95 | 96 | Resource Files 97 | 98 | 99 | 100 | 101 | Resource Files 102 | 103 | 104 | Resource Files 105 | 106 | 107 | Resource Files 108 | 109 | 110 | -------------------------------------------------------------------------------- /src/VisualStyles.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/csv_exporter.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | 10 | static std::string 11 | _SanitizeString(std::string str) 12 | { 13 | str.erase(std::remove(str.begin(), str.end(), ';'), str.end()); 14 | str.erase(std::remove(str.begin(), str.end(), '\"'), str.end()); 15 | return str; 16 | } 17 | 18 | std::variant 19 | ExportCSV(const std::wstring& wstrCSVFilePath, const std::vector& DeviceSymbolInfos) 20 | { 21 | // Convert the variables data into CSV. 22 | std::string strCSV = "DEVICE;BLOCK;VARIABLE;CODE;DATATYPE;COMMENT\n"; 23 | 24 | for (const S7DeviceSymbolInfo& DeviceSymbolInfo : DeviceSymbolInfos) 25 | { 26 | std::string strSanitizedDeviceName = _SanitizeString(DeviceSymbolInfo.strName); 27 | 28 | for (const S7Block& Block : DeviceSymbolInfo.Blocks) 29 | { 30 | std::string strSanitizedBlockName = _SanitizeString(Block.strName); 31 | 32 | for (const S7Symbol& Symbol : Block.Symbols) 33 | { 34 | strCSV += strSanitizedDeviceName + ";"; 35 | strCSV += strSanitizedBlockName + ";"; 36 | strCSV += _SanitizeString(Symbol.strName) + ";"; 37 | strCSV += Symbol.strCode + ";"; 38 | strCSV += Symbol.strDatatype + ";"; 39 | strCSV += _SanitizeString(Symbol.strComment) + "\n"; 40 | } 41 | } 42 | 43 | // Use the "DEVICE" and "COMMENT" columns to output per-device warnings, leaving all other columns empty. 44 | for (const CS7PError& Warning : DeviceSymbolInfo.Warnings) 45 | { 46 | std::string strWarning = WstrToStr(Warning.Message()); 47 | strCSV += strSanitizedDeviceName + ";"; 48 | strCSV += ";"; 49 | strCSV += ";"; 50 | strCSV += ";"; 51 | strCSV += ";"; 52 | strCSV += strWarning + "\n"; 53 | } 54 | } 55 | 56 | // Open the output file for writing. 57 | auto hFile = make_unique_handle(CreateFileW(wstrCSVFilePath.c_str(), GENERIC_WRITE, 0, nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr)); 58 | if (hFile.get() == INVALID_HANDLE_VALUE) 59 | { 60 | return CS7PError(L"CreateFileW failed with error " + std::to_wstring(GetLastError())); 61 | } 62 | 63 | // Write the BOM to indicate UTF-8 output. 64 | const unsigned char ByteOrderMark[] = { 0xEF, 0xBB, 0xBF }; 65 | DWORD cbWritten; 66 | if (!WriteFile(hFile.get(), ByteOrderMark, sizeof(ByteOrderMark), &cbWritten, nullptr)) 67 | { 68 | return CS7PError(L"WriteFile failed for the ByteOrderMark with error " + std::to_wstring(GetLastError())); 69 | } 70 | 71 | // Write the CSV output. 72 | if (!WriteFile(hFile.get(), strCSV.c_str(), strCSV.size(), &cbWritten, nullptr)) 73 | { 74 | return CS7PError(L"WriteFile failed for the CSV content with error " + std::to_wstring(GetLastError())); 75 | } 76 | 77 | return std::monostate(); 78 | } 79 | -------------------------------------------------------------------------------- /src/csv_exporter.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | std::variant ExportCSV(const std::wstring& wstrCSVFilePath, const std::vector& DeviceSymbolInfos); 10 | -------------------------------------------------------------------------------- /src/lang/de-DE.rc: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | LANGUAGE LANG_GERMAN, SUBLANG_GERMAN 8 | 9 | STRINGTABLE 10 | BEGIN 11 | IDS_BACK "< Zurück" 12 | IDS_NEXT "Weiter >" 13 | IDS_CANCEL "Abbrechen" 14 | IDS_SAVE "Speichern..." 15 | IDS_FINISH "Fertig stellen" 16 | 17 | IDS_FILEPAGE_HEADER "Willkommen" 18 | IDS_FILEPAGE_SUBHEADER "Wählen Sie eine Projektdatei." 19 | IDS_FILEPAGE_TEXT "Der ENLYZE S7-Project-Explorer ermöglicht Ihnen, alle Variablen aus einem STEP 7-Projekt (.s7p-Datei) anzuzeigen und die Liste in eine .csv-Datei zu exportieren.\n\nBitte wählen Sie zunächst eine Projektdatei und klicken dann auf „Weiter“." 20 | IDS_BROWSE "Durchsuchen..." 21 | IDS_BROWSE_FILTER "STEP 7-Projektdateien (*.s7p)|*.s7p||" 22 | IDS_BROWSE_TITLE "Wählen Sie eine STEP 7-Projektdatei" 23 | IDS_PARSE_ERROR "Das ausgewählte STEP 7-Projekt konnte nicht verarbeitet werden.\n\nTechnische Informationen:\n" 24 | IDS_NO_VARIABLES "Das ausgewählte STEP 7-Projekt enthält keine Variablen." 25 | 26 | IDS_VARIABLESPAGE_HEADER "Variablen" 27 | IDS_VARIABLESPAGE_SUBHEADER "Blättern durch alle Variablen." 28 | IDS_VARIABLESPAGE_TEXT "Die folgenden S7-Geräte und Variablen wurden in diesem Projekt gefunden. Klicken Sie auf „Speichern“, um diese Liste in eine Datei zu exportieren." 29 | IDS_NAME "Name" 30 | IDS_CODE "Code" 31 | IDS_DATATYPE "Datentyp" 32 | IDS_COMMENT "Kommentar" 33 | IDS_SAVE_FILTER "CSV-Dateien (*.csv)|*.csv||" 34 | IDS_SAVE_TITLE "Exportieren der Variablenliste in eine Datei" 35 | IDS_SAVE_ERROR "Die Variablenliste konnte nicht in eine Datei exportiert werden.\n\nTechnische Informationen:\n" 36 | 37 | IDS_FINISHPAGE_HEADER "Fertigstellen" 38 | IDS_FINISHPAGE_SUBHEADER "Fertigstellen des Assistenten." 39 | IDS_FINISHPAGE_TEXT "Die Variablenliste wurde erfolgreich exportiert.\n\nKlicken Sie auf „Fertig stellen“, um das Programm zu beenden." 40 | 41 | IDS_WARNINGS "Warnungen" 42 | IDS_DEVICE "Gerät" 43 | IDS_WARNING "Warnung" 44 | IDS_CLOSE "Schließen" 45 | END 46 | -------------------------------------------------------------------------------- /src/lang/en-US.rc: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 8 | 9 | STRINGTABLE 10 | BEGIN 11 | IDS_BACK "< Back" 12 | IDS_NEXT "Next >" 13 | IDS_CANCEL "Cancel" 14 | IDS_SAVE "Save..." 15 | IDS_FINISH "Finish" 16 | 17 | IDS_FILEPAGE_HEADER "Welcome" 18 | IDS_FILEPAGE_SUBHEADER "Select a project file." 19 | IDS_FILEPAGE_TEXT "The ENLYZE S7-Project-Explorer enables you to browse all variables of a STEP 7 project (.s7p file) and export the list into a single .csv file.\n\nPlease select a project file first, then click “Next”." 20 | IDS_BROWSE "Browse..." 21 | IDS_BROWSE_FILTER "STEP 7 Project Files (*.s7p)|*.s7p||" 22 | IDS_BROWSE_TITLE "Select a STEP 7 project file" 23 | IDS_PARSE_ERROR "The selected STEP 7 project could not be processed.\n\nTechnical information:\n" 24 | IDS_NO_VARIABLES "The selected STEP 7 project does not contain any variables." 25 | 26 | IDS_VARIABLESPAGE_HEADER "Variables" 27 | IDS_VARIABLESPAGE_SUBHEADER "Browse through all variables." 28 | IDS_VARIABLESPAGE_TEXT "The following S7 devices and variables have been found in this project. Click “Save” to export this list into a file." 29 | IDS_NAME "Name" 30 | IDS_CODE "Code" 31 | IDS_DATATYPE "Datatype" 32 | IDS_COMMENT "Comment" 33 | IDS_SAVE_FILTER "CSV Files (*.csv)|*.csv||" 34 | IDS_SAVE_TITLE "Export the variable list into a file" 35 | IDS_SAVE_ERROR "The variable list could not be exported into a file.\n\nTechnical information:\n" 36 | 37 | IDS_FINISHPAGE_HEADER "Finish" 38 | IDS_FINISHPAGE_SUBHEADER "Finish the wizard." 39 | IDS_FINISHPAGE_TEXT "The variable list has been exported successfully.\n\nClick “Finish” to exit this program." 40 | 41 | IDS_WARNINGS "Warnings" 42 | IDS_DEVICE "Device" 43 | IDS_WARNING "Warning" 44 | IDS_CLOSE "Close" 45 | END 46 | -------------------------------------------------------------------------------- /src/res/blank-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/src/res/blank-file.png -------------------------------------------------------------------------------- /src/res/blank-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | 30 | 31 | 32 | 52 | 54 | 56 | 59 | 63 | 64 | 66 | 68 | 72 | 73 | 75 | 77 | 82 | 83 | 89 | 92 | 93 | 95 | 98 | 99 | 107 | 115 | 124 | 125 | 128 | 134 | 138 | 145 | 149 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /src/res/enlyze-logo.bmp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/src/res/enlyze-logo.bmp -------------------------------------------------------------------------------- /src/res/enlyze-s7.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/src/res/enlyze-s7.ico -------------------------------------------------------------------------------- /src/res/s7p-file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enlyze/S7-Project-Explorer/dbd1da1491d3bd1eaaf01640d0661d062b464d77/src/res/s7p-file.png -------------------------------------------------------------------------------- /src/res/s7p-file.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 23 | 24 | 26 | image/svg+xml 27 | 29 | 30 | 31 | 32 | 52 | 54 | 56 | 59 | 63 | 64 | 66 | 68 | 72 | 73 | 75 | 77 | 82 | 83 | 89 | 92 | 93 | 95 | 98 | 99 | 107 | 115 | 124 | 125 | 128 | 134 | 138 | 145 | 149 | 153 | 154 | .s7p 165 | 166 | -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022-2023 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #define IDI_ICON 1 10 | 11 | #define IDB_LOGO 100 12 | 13 | #define IDP_BLANK_FILE 101 14 | #define IDP_S7P_FILE 102 15 | 16 | #define IDS_BACK 1000 17 | #define IDS_NEXT 1001 18 | #define IDS_CANCEL 1002 19 | #define IDS_SAVE 1003 20 | #define IDS_FINISH 1004 21 | 22 | #define IDS_FILEPAGE_HEADER 2000 23 | #define IDS_FILEPAGE_SUBHEADER 2001 24 | #define IDS_FILEPAGE_TEXT 2002 25 | #define IDS_BROWSE 2003 26 | #define IDS_BROWSE_FILTER 2004 27 | #define IDS_BROWSE_TITLE 2005 28 | #define IDS_PARSE_ERROR 2006 29 | #define IDS_NO_VARIABLES 2007 30 | 31 | #define IDS_VARIABLESPAGE_HEADER 3000 32 | #define IDS_VARIABLESPAGE_SUBHEADER 3001 33 | #define IDS_VARIABLESPAGE_TEXT 3002 34 | #define IDS_NAME 3003 35 | #define IDS_CODE 3004 36 | #define IDS_DATATYPE 3005 37 | #define IDS_COMMENT 3006 38 | #define IDS_SAVE_FILTER 3007 39 | #define IDS_SAVE_TITLE 3008 40 | #define IDS_SAVE_ERROR 3009 41 | 42 | #define IDS_FINISHPAGE_HEADER 4000 43 | #define IDS_FINISHPAGE_SUBHEADER 4001 44 | #define IDS_FINISHPAGE_TEXT 4002 45 | 46 | #define IDS_WARNINGS 5000 47 | #define IDS_DEVICE 5001 48 | #define IDS_WARNING 5002 49 | #define IDS_CLOSE 5003 50 | -------------------------------------------------------------------------------- /src/targetver.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #define _WIN32_WINNT _WIN32_WINNT_WINXP 10 | #include 11 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #include "S7-Project-Explorer.h" 8 | 9 | typedef HRESULT (WINAPI *PGetDpiForMonitor)(HMONITOR hmonitor, int dpiType, UINT* dpiX, UINT* dpiY); 10 | 11 | #ifndef LOAD_LIBRARY_SEARCH_SYSTEM32 12 | #define LOAD_LIBRARY_SEARCH_SYSTEM32 0x00000800 13 | #endif 14 | 15 | void 16 | ErrorBox(const std::wstring& wstrMessage) 17 | { 18 | MessageBoxW(nullptr, wstrMessage.c_str(), wszAppName, MB_ICONERROR); 19 | } 20 | 21 | WORD 22 | GetWindowDPI(HWND hWnd) 23 | { 24 | // Try to get the DPI setting for the monitor where the given window is located. 25 | // This API is Windows 8.1+. 26 | HMODULE hShcore = SafeLoadSystemLibrary(L"shcore.dll"); 27 | if (hShcore) 28 | { 29 | PGetDpiForMonitor pGetDpiForMonitor = reinterpret_cast(GetProcAddress(hShcore, "GetDpiForMonitor")); 30 | if (pGetDpiForMonitor) 31 | { 32 | HMONITOR hMonitor = MonitorFromWindow(hWnd, MONITOR_DEFAULTTOPRIMARY); 33 | UINT uiDpiX; 34 | UINT uiDpiY; 35 | HRESULT hr = pGetDpiForMonitor(hMonitor, 0, &uiDpiX, &uiDpiY); 36 | if (SUCCEEDED(hr)) 37 | { 38 | return static_cast(uiDpiX); 39 | } 40 | } 41 | } 42 | 43 | // We couldn't get the window's DPI above, so get the DPI of the primary monitor 44 | // using an API that is available in all Windows versions. 45 | HDC hScreenDC = GetDC(0); 46 | int iDpiX = GetDeviceCaps(hScreenDC, LOGPIXELSX); 47 | ReleaseDC(0, hScreenDC); 48 | 49 | return static_cast(iDpiX); 50 | } 51 | 52 | std::wstring 53 | LoadStringAsWstr(HINSTANCE hInstance, UINT uID) 54 | { 55 | PCWSTR pwsz; 56 | int cch = LoadStringW(hInstance, uID, reinterpret_cast(&pwsz), 0); 57 | return std::wstring(pwsz, cch); 58 | } 59 | 60 | std::unique_ptr 61 | LoadPNGAsGdiplusBitmap(HINSTANCE hInstance, UINT uID) 62 | { 63 | HRSRC hFindResource = FindResourceW(hInstance, MAKEINTRESOURCEW(uID), L"PNG"); 64 | if (hFindResource == nullptr) 65 | { 66 | return nullptr; 67 | } 68 | 69 | DWORD dwSize = SizeofResource(hInstance, hFindResource); 70 | if (dwSize == 0) 71 | { 72 | return nullptr; 73 | } 74 | 75 | HGLOBAL hLoadResource = LoadResource(hInstance, hFindResource); 76 | if (hLoadResource == nullptr) 77 | { 78 | return nullptr; 79 | } 80 | 81 | const void* pResource = LockResource(hLoadResource); 82 | if (!pResource) 83 | { 84 | return nullptr; 85 | } 86 | 87 | std::unique_ptr pBitmap; 88 | HGLOBAL hBuffer = GlobalAlloc(GMEM_MOVEABLE, dwSize); 89 | if (hBuffer) 90 | { 91 | void* pBuffer = GlobalLock(hBuffer); 92 | if (pBuffer) 93 | { 94 | CopyMemory(pBuffer, pResource, dwSize); 95 | 96 | IStream* pStream; 97 | if (CreateStreamOnHGlobal(pBuffer, FALSE, &pStream) == S_OK) 98 | { 99 | pBitmap.reset(Gdiplus::Bitmap::FromStream(pStream)); 100 | pStream->Release(); 101 | } 102 | 103 | GlobalUnlock(pBuffer); 104 | } 105 | 106 | GlobalFree(hBuffer); 107 | } 108 | 109 | return pBitmap; 110 | } 111 | 112 | HMODULE 113 | SafeLoadSystemLibrary(const std::wstring& wstrLibraryFile) 114 | { 115 | // LOAD_LIBRARY_SEARCH_SYSTEM32 is only supported if KB2533623 is installed on Vista / Win7, and starting from Win8. 116 | // This update also adds SetDefaultDllDirectories, so we can query that API to check for KB2533623. 117 | FARPROC pSetDefaultDllDirectories = GetProcAddress(GetModuleHandleW(L"kernel32"), "SetDefaultDllDirectories"); 118 | if (pSetDefaultDllDirectories) 119 | { 120 | return LoadLibraryExW(wstrLibraryFile.c_str(), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); 121 | } 122 | 123 | // LOAD_LIBRARY_SEARCH_SYSTEM32 is not supported, so use the next best LOAD_WITH_ALTERED_SEARCH_PATH instead. 124 | std::wstring wstrLibraryFilePath(MAX_PATH, L'\0'); 125 | UINT cch = GetSystemDirectoryW(wstrLibraryFilePath.data(), wstrLibraryFilePath.size()); 126 | if (cch == 0 || cch >= wstrLibraryFilePath.size()) 127 | { 128 | return nullptr; 129 | } 130 | 131 | wstrLibraryFilePath.resize(cch); 132 | wstrLibraryFilePath += L"\\" + wstrLibraryFile; 133 | 134 | return LoadLibraryExW(wstrLibraryFilePath.data(), NULL, LOAD_WITH_ALTERED_SEARCH_PATH); 135 | } 136 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | template T* 10 | InstanceFromWndProc(HWND hWnd, UINT uMsg, LPARAM lParam) 11 | { 12 | // Get the pointer to the class instance. 13 | T* pInstance; 14 | if (uMsg == WM_NCCREATE) 15 | { 16 | // The pointer has been passed via CreateWindowExW and now needs to be saved via SetWindowLongPtrW. 17 | LPCREATESTRUCT pCreateStruct = reinterpret_cast(lParam); 18 | pInstance = reinterpret_cast(pCreateStruct->lpCreateParams); 19 | SetWindowLongPtrW(hWnd, GWLP_USERDATA, reinterpret_cast(pInstance)); 20 | 21 | // We are handling the first useful window message, so this is the perfect place to also set the hWnd member variable. 22 | pInstance->*m_hWnd = hWnd; 23 | } 24 | else 25 | { 26 | // Get the pointer saved via SetWindowLongPtrW above. 27 | pInstance = reinterpret_cast(GetWindowLongPtrW(hWnd, GWLP_USERDATA)); 28 | } 29 | 30 | return pInstance; 31 | } 32 | 33 | // utils.cpp 34 | void ErrorBox(const std::wstring& wstrMessage); 35 | WORD GetWindowDPI(HWND hWnd); 36 | std::wstring LoadStringAsWstr(HINSTANCE hInstance, UINT uID); 37 | std::unique_ptr LoadPNGAsGdiplusBitmap(HINSTANCE hInstance, UINT uID); 38 | HMODULE SafeLoadSystemLibrary(const std::wstring& LibraryName); 39 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022-2023 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | #define APP_MAJOR_VERSION 2 10 | #define APP_MINOR_VERSION 4 11 | 12 | 13 | // The following macro magic turns arbitrary preprocessor constants into strings. 14 | #define STRINGIFY_INTERNAL(x) #x 15 | #define STRINGIFY(x) STRINGIFY_INTERNAL(x) 16 | #define WSTRINGIFY_INTERNAL(x) L##x 17 | #define WSTRINGIFY(x) WSTRINGIFY_INTERNAL(x) 18 | 19 | #define APP_VERSION_STRING STRINGIFY(APP_MAJOR_VERSION) "." STRINGIFY(APP_MINOR_VERSION) 20 | #define APP_VERSION_WSTRING WSTRINGIFY(APP_VERSION_STRING) 21 | 22 | #define APP_REVISION_STRING "unknown revision" 23 | 24 | #define APP_VERSION_COMBINED APP_VERSION_STRING " (" APP_REVISION_STRING ")" 25 | -------------------------------------------------------------------------------- /src/win32_wrappers.h: -------------------------------------------------------------------------------- 1 | // 2 | // S7-Project-Explorer - GUI for browsing variables in Siemens STEP 7 projects and exporting the list 3 | // Copyright (c) 2022 Colin Finck, ENLYZE GmbH 4 | // SPDX-License-Identifier: MIT 5 | // 6 | 7 | #pragma once 8 | 9 | static inline auto make_unique_font(HFONT hFont) 10 | { 11 | return sr::make_unique_resource_checked(hFont, nullptr, DeleteObject); 12 | } 13 | 14 | static inline auto make_unique_handle(HANDLE h) 15 | { 16 | return sr::make_unique_resource_checked(h, INVALID_HANDLE_VALUE, CloseHandle); 17 | } 18 | --------------------------------------------------------------------------------