diff --git a/src/WinDepends/CConsts.cs b/src/WinDepends/CConsts.cs
index 349104c..8df2926 100644
--- a/src/WinDepends/CConsts.cs
+++ b/src/WinDepends/CConsts.cs
@@ -58,6 +58,7 @@ public static class CConsts
public const int TagResolveAPIsets = 123;
public const int TagUpperCaseModuleNames = 124;
public const int TagClearLogOnFileOpen = 125;
+ public const int TagSimplifyStlNames = 126;
public const int TagViewExternalViewer = 200;
public const int TagViewProperties = 201;
public const int TagSystemInformation = 300;
diff --git a/src/WinDepends/Configuration/CConfigMgr.cs b/src/WinDepends/Configuration/CConfigMgr.cs
index 21c0e2b..d1ede74 100644
--- a/src/WinDepends/Configuration/CConfigMgr.cs
+++ b/src/WinDepends/Configuration/CConfigMgr.cs
@@ -42,6 +42,8 @@ public class CConfiguration
[DataMember]
public bool ViewUndecorated { get; set; }
[DataMember]
+ public bool SimplifyStlNames { get; set; }
+ [DataMember]
public bool ResolveAPIsets { get; set; }
[DataMember]
public bool FullPaths { get; set; }
@@ -163,6 +165,7 @@ public CConfiguration(CConfiguration other)
SortColumnModules = other.SortColumnModules;
ModuleNodeDepthMax = other.ModuleNodeDepthMax;
ViewUndecorated = other.ViewUndecorated;
+ SimplifyStlNames = other.SimplifyStlNames;
ResolveAPIsets = other.ResolveAPIsets;
FullPaths = other.FullPaths;
AutoExpands = other.AutoExpands;
diff --git a/src/WinDepends/Forms/ConfigurationForm.Designer.cs b/src/WinDepends/Forms/ConfigurationForm.Designer.cs
index 49ce74c..e094836 100644
--- a/src/WinDepends/Forms/ConfigurationForm.Designer.cs
+++ b/src/WinDepends/Forms/ConfigurationForm.Designer.cs
@@ -59,6 +59,7 @@ private void InitializeComponent()
chBoxUpperCase = new CheckBox();
chBoxResolveApiSets = new CheckBox();
chBoxUndecorateSymbols = new CheckBox();
+ chBoxSimplifyStlNames = new CheckBox();
chBoxAutoExpands = new CheckBox();
groupBox2 = new GroupBox();
chBoxUseESCKey = new CheckBox();
@@ -366,6 +367,7 @@ private void InitializeComponent()
groupBox3.Controls.Add(chBoxUpperCase);
groupBox3.Controls.Add(chBoxResolveApiSets);
groupBox3.Controls.Add(chBoxUndecorateSymbols);
+ groupBox3.Controls.Add(chBoxSimplifyStlNames);
groupBox3.Controls.Add(chBoxAutoExpands);
groupBox3.Location = new Point(6, 71);
groupBox3.Name = "groupBox3";
@@ -489,7 +491,19 @@ private void InitializeComponent()
chBoxUndecorateSymbols.Text = "Undecorate C++ Functions";
chBoxUndecorateSymbols.UseVisualStyleBackColor = true;
chBoxUndecorateSymbols.Click += ChBox_Click;
- //
+ //
+ // chBoxSimplifyStlNames
+ //
+ chBoxSimplifyStlNames.AutoSize = true;
+ chBoxSimplifyStlNames.Location = new Point(250, 83);
+ chBoxSimplifyStlNames.Name = "chBoxSimplifyStlNames";
+ chBoxSimplifyStlNames.Size = new Size(200, 19);
+ chBoxSimplifyStlNames.TabIndex = 19;
+ chBoxSimplifyStlNames.Tag = "126";
+ chBoxSimplifyStlNames.Text = "Simplify STL Template Names";
+ chBoxSimplifyStlNames.UseVisualStyleBackColor = true;
+ chBoxSimplifyStlNames.Click += ChBox_Click;
+ //
// chBoxAutoExpands
//
chBoxAutoExpands.AutoSize = true;
@@ -1681,6 +1695,7 @@ private void InitializeComponent()
private GroupBox groupBox3;
private CheckBox chBoxResolveApiSets;
private CheckBox chBoxUndecorateSymbols;
+ private CheckBox chBoxSimplifyStlNames;
private CheckBox chBoxAutoExpands;
private CheckBox chBoxFullPaths;
private TabPage tabExternalViewer;
diff --git a/src/WinDepends/Forms/ConfigurationForm.cs b/src/WinDepends/Forms/ConfigurationForm.cs
index 78b26d7..0fef7ef 100644
--- a/src/WinDepends/Forms/ConfigurationForm.cs
+++ b/src/WinDepends/Forms/ConfigurationForm.cs
@@ -337,6 +337,7 @@ private void ConfigurationForm_Load(object sender, EventArgs e)
chBoxAutoExpands.Checked = _config.AutoExpands;
chBoxFullPaths.Checked = _config.FullPaths;
chBoxUndecorateSymbols.Checked = _config.ViewUndecorated;
+ chBoxSimplifyStlNames.Checked = _config.SimplifyStlNames;
chBoxResolveApiSets.Checked = _config.ResolveAPIsets;
chBoxHighlightApiSet.Checked = _config.HighlightApiSet;
chBoxApiSetNamespace.Checked = _config.UseApiSetSchemaFile;
@@ -506,6 +507,10 @@ private void ChBox_Click(object sender, EventArgs e)
_config.ViewUndecorated = isChecked;
break;
+ case CConsts.TagSimplifyStlNames:
+ _config.SimplifyStlNames = isChecked;
+ break;
+
case CConsts.TagResolveAPIsets:
_config.ResolveAPIsets = isChecked;
break;
diff --git a/src/WinDepends/MainForm/MainForm.cs b/src/WinDepends/MainForm/MainForm.cs
index a5b2d6e..635ca33 100644
--- a/src/WinDepends/MainForm/MainForm.cs
+++ b/src/WinDepends/MainForm/MainForm.cs
@@ -198,6 +198,7 @@ public MainForm()
_configuration.SymbolsStorePath = _symbolResolver.StorePath;
}
+ _symbolResolver.SimplifyTemplateDefaults = _configuration.SimplifyStlNames;
_symbolResolver.SymbolLoadStatusChanged += SymbolResolver_SymbolLoadStatusChanged;
//
@@ -793,6 +794,7 @@ private void ShowConfigurationForm(int pageIndex)
var bFullPathPrev = _configuration.FullPaths;
var bUpperCaseModulesNamesPrev = _configuration.UpperCaseModuleNames;
var bUndecoratedPrev = _configuration.ViewUndecorated;
+ var bSimplifyStlPrev = _configuration.SimplifyStlNames;
var bResolveAPISetsPrev = _configuration.ResolveAPIsets;
var bHighlightAPISetsPrev = _configuration.HighlightApiSet;
var bUseApiSetSchemaFilePrev = _configuration.UseApiSetSchemaFile;
@@ -837,7 +839,18 @@ private void ShowConfigurationForm(int pageIndex)
UpdateFileView(FileViewUpdateAction.ModulesTreeAndListChange);
}
- if (_configuration.ViewUndecorated != bUndecoratedPrev)
+ if (_configuration.SimplifyStlNames != bSimplifyStlPrev)
+ {
+ //
+ // The simplified spelling is baked into the cached undecorated
+ // name, so drop the cache to force re-decoration on next view.
+ //
+ _symbolResolver.SimplifyTemplateDefaults = _configuration.SimplifyStlNames;
+ ClearUndecoratedNameCache();
+ }
+
+ if (_configuration.ViewUndecorated != bUndecoratedPrev ||
+ _configuration.SimplifyStlNames != bSimplifyStlPrev)
{
UpdateFileView(FileViewUpdateAction.FunctionsUndecorateChange);
}
@@ -1490,6 +1503,32 @@ private void MenuExitItem_Click(object sender, EventArgs e)
Close();
}
+ ///
+ /// Clears cached undecorated names across all loaded modules so they are
+ /// recomputed with the current decoration / STL-simplification settings.
+ ///
+ private void ClearUndecoratedNameCache()
+ {
+ foreach (CModule module in _loadedModulesList)
+ {
+ if (module.ModuleData?.Exports is { } exports)
+ {
+ foreach (CFunction function in exports)
+ {
+ function.UndecoratedName = string.Empty;
+ }
+ }
+
+ if (module.ParentImports is { } imports)
+ {
+ foreach (CFunction function in imports)
+ {
+ function.UndecoratedName = string.Empty;
+ }
+ }
+ }
+ }
+
private void CopyFunctionNamesToClipboard(ListView listView, List functions)
{
var textBuilder = new StringBuilder();
diff --git a/src/WinDepends/Symbols/CStlNameSimplifier.cs b/src/WinDepends/Symbols/CStlNameSimplifier.cs
new file mode 100644
index 0000000..65bbf24
--- /dev/null
+++ b/src/WinDepends/Symbols/CStlNameSimplifier.cs
@@ -0,0 +1,376 @@
+/*******************************************************************************
+*
+* (C) COPYRIGHT AUTHORS, 2024 - 2026
+*
+* TITLE: CSTLNAMESIMPLIFIER.CS
+*
+* VERSION: 1.00
+*
+* DATE: 03 Jun 2026
+*
+* Collapses the canonical (default-argument) forms produced by the MSVC
+* demangler into their conventional STL spellings, e.g.
+*
+* std::basic_string,std::allocator >
+* -> std::string
+* std::map,std::allocator > >
+* -> std::map
+* std::vector >
+* -> std::vector
+*
+* Only the default-value template forms of the standard library are matched;
+* user templates and non-default arguments are left untouched. The transform
+* is bracket-aware (it walks balanced angle brackets) because regular
+* expressions cannot reliably split nested template argument lists.
+*
+* THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
+* ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
+* TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
+* PARTICULAR PURPOSE.
+*
+*******************************************************************************/
+using System.Text;
+
+namespace WinDepends;
+
+///
+/// Folds MSVC default template-argument forms of the C++ standard library into
+/// their conventional typedef spellings. Intended purely for display.
+///
+internal static class CStlNameSimplifier
+{
+ ///
+ /// Returns with recognized STL default-argument
+ /// template forms collapsed. On any unexpected input the original string is
+ /// returned unchanged so the feature can never corrupt a displayed name.
+ ///
+ public static string Simplify(string name)
+ {
+ if (string.IsNullOrEmpty(name) || name.IndexOf('<') < 0)
+ {
+ return name;
+ }
+
+ try
+ {
+ return Transform(name);
+ }
+ catch
+ {
+ return name;
+ }
+ }
+
+ ///
+ /// Recursively rewrites every template-id found in .
+ /// Inner argument lists are transformed before the enclosing template is
+ /// folded, so nested defaults collapse bottom-up.
+ ///
+ private static string Transform(string s)
+ {
+ var sb = new StringBuilder(s.Length);
+ int i = 0;
+
+ while (i < s.Length)
+ {
+ char c = s[i];
+
+ if (c == '<')
+ {
+ string ident = PeekLastIdentifier(sb);
+
+ // Skip operator<, operator<<, lambdas () and anything
+ // not preceded by a real identifier - treat '<' literally.
+ if (ident.Length == 0 || ident.EndsWith("operator", StringComparison.Ordinal))
+ {
+ sb.Append(c);
+ i++;
+ continue;
+ }
+
+ int end = FindMatchingAngle(s, i);
+ if (end < 0)
+ {
+ sb.Append(c);
+ i++;
+ continue;
+ }
+
+ string inner = s.Substring(i + 1, end - i - 1);
+ List args = SplitTopLevel(Transform(inner));
+
+ sb.Length -= ident.Length; // remove identifier, re-emit folded
+ sb.Append(Fold(ident, args));
+ i = end + 1;
+ }
+ else
+ {
+ sb.Append(c);
+ i++;
+ }
+ }
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Applies the STL folding rules for a single template-id whose argument
+ /// list has already been simplified. Unrecognized templates are rebuilt
+ /// verbatim.
+ ///
+ private static string Fold(string ident, List args)
+ {
+ switch (ident)
+ {
+ case "std::basic_string":
+ // , allocator>
+ if (args.Count == 3 &&
+ IsSpecializationOf(args[1], "std::char_traits") &&
+ IsSpecializationOf(args[2], "std::allocator"))
+ {
+ string alias = StringAlias(args[0], isView: false);
+ if (alias != null) return alias;
+ }
+ break;
+
+ case "std::basic_string_view":
+ // >
+ if (args.Count == 2 &&
+ IsSpecializationOf(args[1], "std::char_traits"))
+ {
+ string alias = StringAlias(args[0], isView: true);
+ if (alias != null) return alias;
+ }
+ break;
+
+ case "std::vector":
+ case "std::deque":
+ case "std::list":
+ case "std::forward_list":
+ // >
+ if (args.Count == 2 && IsSpecializationOf(args[1], "std::allocator"))
+ {
+ return Rebuild(ident, args[0]);
+ }
+ break;
+
+ case "std::set":
+ case "std::multiset":
+ // , allocator>
+ if (args.Count == 3 &&
+ IsSpecializationOf(args[1], "std::less") &&
+ IsSpecializationOf(args[2], "std::allocator"))
+ {
+ return Rebuild(ident, args[0]);
+ }
+ break;
+
+ case "std::map":
+ case "std::multimap":
+ // , allocator>>
+ if (args.Count == 4 &&
+ IsSpecializationOf(args[2], "std::less") &&
+ IsSpecializationOf(args[3], "std::allocator"))
+ {
+ return Rebuild(ident, args[0], args[1]);
+ }
+ break;
+
+ case "std::unordered_set":
+ case "std::unordered_multiset":
+ // , equal_to, allocator>
+ if (args.Count == 4 &&
+ IsSpecializationOf(args[1], "std::hash") &&
+ IsSpecializationOf(args[2], "std::equal_to") &&
+ IsSpecializationOf(args[3], "std::allocator"))
+ {
+ return Rebuild(ident, args[0]);
+ }
+ break;
+
+ case "std::unordered_map":
+ case "std::unordered_multimap":
+ // , equal_to, allocator>>
+ if (args.Count == 5 &&
+ IsSpecializationOf(args[2], "std::hash") &&
+ IsSpecializationOf(args[3], "std::equal_to") &&
+ IsSpecializationOf(args[4], "std::allocator"))
+ {
+ return Rebuild(ident, args[0], args[1]);
+ }
+ break;
+
+ case "std::unique_ptr":
+ // >
+ if (args.Count == 2 && IsSpecializationOf(args[1], "std::default_delete"))
+ {
+ return Rebuild(ident, args[0]);
+ }
+ break;
+ }
+
+ return Rebuild(ident, args);
+ }
+
+ ///
+ /// Maps a character element type to the std::string / std::string_view family
+ /// alias, or null when the element type is not a known character type.
+ ///
+ private static string StringAlias(string element, bool isView)
+ {
+ switch (StripLeadingKeywords(element).Trim())
+ {
+ case "char": return isView ? "std::string_view" : "std::string";
+ case "wchar_t": return isView ? "std::wstring_view" : "std::wstring";
+ case "char8_t": return isView ? "std::u8string_view" : "std::u8string";
+ case "char16_t": return isView ? "std::u16string_view" : "std::u16string";
+ case "char32_t": return isView ? "std::u32string_view" : "std::u32string";
+ default: return null;
+ }
+ }
+
+ ///
+ /// True when is a specialization of
+ /// (e.g. "std::allocator<...>").
+ ///
+ private static bool IsSpecializationOf(string arg, string template)
+ {
+ arg = StripLeadingKeywords(arg).TrimStart();
+ return arg.StartsWith(template, StringComparison.Ordinal) &&
+ arg.Length > template.Length &&
+ arg[template.Length] == '<';
+ }
+
+ private static string Rebuild(string ident, params string[] args)
+ => Rebuild(ident, (IReadOnlyList)args);
+
+ ///
+ /// Reconstructs "ident<args>" keeping the demangler convention of a
+ /// space before a closing angle bracket that follows another '>'.
+ ///
+ private static string Rebuild(string ident, IReadOnlyList args)
+ {
+ if (args.Count == 0)
+ {
+ return ident;
+ }
+
+ var sb = new StringBuilder(ident.Length + 16);
+ sb.Append(ident).Append('<');
+
+ for (int i = 0; i < args.Count; i++)
+ {
+ if (i > 0) sb.Append(',');
+ sb.Append(args[i]);
+ }
+
+ if (args[args.Count - 1].EndsWith(">", StringComparison.Ordinal))
+ {
+ sb.Append(' ');
+ }
+ sb.Append('>');
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Splits a template argument list on top-level commas, ignoring commas
+ /// nested inside angle brackets, parentheses or square brackets.
+ ///
+ private static List SplitTopLevel(string s)
+ {
+ var result = new List();
+ int depth = 0, start = 0;
+
+ for (int i = 0; i < s.Length; i++)
+ {
+ char c = s[i];
+ switch (c)
+ {
+ case '<':
+ case '(':
+ case '[':
+ depth++;
+ break;
+ case '>':
+ case ')':
+ case ']':
+ if (depth > 0) depth--;
+ break;
+ case ',':
+ if (depth == 0)
+ {
+ result.Add(s.Substring(start, i - start).Trim());
+ start = i + 1;
+ }
+ break;
+ }
+ }
+
+ result.Add(s.Substring(start).Trim());
+ return result;
+ }
+
+ ///
+ /// Returns the index of the '>' that matches the '<' at
+ /// , or -1 when the brackets are unbalanced.
+ ///
+ private static int FindMatchingAngle(string s, int open)
+ {
+ int depth = 0;
+ for (int i = open; i < s.Length; i++)
+ {
+ if (s[i] == '<') depth++;
+ else if (s[i] == '>')
+ {
+ depth--;
+ if (depth == 0) return i;
+ }
+ }
+ return -1;
+ }
+
+ ///
+ /// Returns the identifier characters immediately preceding the current end
+ /// of without modifying it.
+ ///
+ private static string PeekLastIdentifier(StringBuilder sb)
+ {
+ int end = sb.Length;
+ int i = end - 1;
+ while (i >= 0 && IsIdentifierChar(sb[i]))
+ {
+ i--;
+ }
+ return sb.ToString(i + 1, end - (i + 1));
+ }
+
+ private static bool IsIdentifierChar(char c)
+ => char.IsLetterOrDigit(c) || c == '_' || c == ':';
+
+ ///
+ /// Strips MSVC type keywords ("class ", "struct ", "enum ", "union ") that
+ /// may prefix a type when the demangler is not run with NoMsKeyWords.
+ ///
+ private static string StripLeadingKeywords(string s)
+ {
+ s = s.TrimStart();
+ bool changed = true;
+ while (changed)
+ {
+ changed = false;
+ foreach (string kw in s_typeKeywords)
+ {
+ if (s.StartsWith(kw, StringComparison.Ordinal))
+ {
+ s = s.Substring(kw.Length).TrimStart();
+ changed = true;
+ }
+ }
+ }
+ return s;
+ }
+
+ private static readonly string[] s_typeKeywords =
+ ["class ", "struct ", "enum ", "union "];
+}
diff --git a/src/WinDepends/Symbols/CSymbolResolver.cs b/src/WinDepends/Symbols/CSymbolResolver.cs
index 5536a0a..023d8a2 100644
--- a/src/WinDepends/Symbols/CSymbolResolver.cs
+++ b/src/WinDepends/Symbols/CSymbolResolver.cs
@@ -232,6 +232,12 @@ delegate uint UnDecorateSymbolNameDelegate(
public bool SymbolsInitialized { get; private set; }
public bool UndecorationReady { get; private set; }
+
+ ///
+ /// When set, recognized STL default-argument template forms in undecorated
+ /// names are collapsed to their conventional spellings (e.g. std::string).
+ ///
+ public bool SimplifyTemplateDefaults { get; set; }
public string DllPath { get; private set; }
public string StorePath { get; private set; }
@@ -777,7 +783,10 @@ internal string UndecorateFunctionName(string functionName)
// Note: DependencyWalker uses UNDNAME.NoAllocateLanguage | UNDNAME.NoMsKeyWords | UNDNAME.NoFunctionReturns | UNDNAME.NoAccessSpecifiers
if (UnDecorateSymbolName(functionName, sb, sb.Capacity, UNDNAME.NoMsKeyWords) > 0)
{
- return sb.ToString();
+ string undecorated = sb.ToString();
+ return SimplifyTemplateDefaults
+ ? CStlNameSimplifier.Simplify(undecorated)
+ : undecorated;
}
}