From e29374c1f01556e6d161c73a86da926246d27163 Mon Sep 17 00:00:00 2001 From: John Lambert Date: Fri, 22 May 2026 19:42:37 -0400 Subject: [PATCH] Reduce optional COM usage --- Build/RegFree.targets | 2 +- .../FwBuildTasksTests/RegFreeCreatorTests.cs | 79 ++++++++++++++++++- Build/mkall.targets | 3 +- Src/Common/FieldWorks/BuildInclude.targets | 2 +- Src/Common/FwUtils/Win32Wrappers.cs | 16 ---- Src/Generic/ModuleEntry.cpp | 37 --------- Src/Generic/ModuleEntry.h | 4 - Src/ManagedLgIcuCollator/LgIcuCollator.cs | 4 +- 8 files changed, 82 insertions(+), 65 deletions(-) diff --git a/Build/RegFree.targets b/Build/RegFree.targets index 9775afbf1c..e83d420156 100644 --- a/Build/RegFree.targets +++ b/Build/RegFree.targets @@ -66,7 +66,7 @@ - + diff --git a/Build/Src/FwBuildTasks/FwBuildTasksTests/RegFreeCreatorTests.cs b/Build/Src/FwBuildTasks/FwBuildTasksTests/RegFreeCreatorTests.cs index 64b543f996..06560ce753 100644 --- a/Build/Src/FwBuildTasks/FwBuildTasksTests/RegFreeCreatorTests.cs +++ b/Build/Src/FwBuildTasks/FwBuildTasksTests/RegFreeCreatorTests.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2025 SIL International +// Copyright (c) 2025 SIL International // This software is licensed under the LGPL, version 2.1 or later // (http://www.gnu.org/licenses/lgpl-2.1.html) @@ -21,6 +21,7 @@ namespace SIL.FieldWorks.Build.Tasks.FwBuildTasksTests public sealed class RegFreeCreatorTests { private const string AsmNamespace = "urn:schemas-microsoft-com:asm.v1"; + private const string RemovedManagedLgIcuCollatorClsid = "{e771361c-ff54-4120-9525-98a0b7a9accf}"; [Test] public void ProcessManagedAssembly_PlacesClrClassAsChildOfAssembly() @@ -65,6 +66,76 @@ public void ProcessManagedAssembly_PlacesClrClassAsChildOfAssembly() } } + [Test] + public void ProcessManagedAssembly_TargetComVisibleFalseClass_DoesNotEmitClrClass() + { + var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(tempDir); + var assemblyPath = Path.Combine(tempDir, "SampleComVisibleFalseClass.dll"); + + try + { + const string source = @"using System.Runtime.InteropServices; +[assembly: ComVisible(true)] +[assembly: Guid(""3D757DD4-8985-4CA6-B2C4-FA2B950C9F6D"")] +namespace RegFreeCreatorTestAssembly +{ + [ComVisible(false)] + [Guid(""e771361c-ff54-4120-9525-98a0b7a9accf"")] + public class SampleComVisibleFalseClass + { + } +}"; + CompileAssembly(assemblyPath, source); + + var doc = new XmlDocument(); + var root = doc.CreateElement("assembly", AsmNamespace); + doc.AppendChild(root); + var logger = new TaskLoggingHelper(new TestBuildEngine(), nameof(RegFreeCreatorTests)); + var creator = new RegFreeCreator(doc, logger); + + var foundClrClass = creator.ProcessManagedAssembly(root, assemblyPath); + Assert.That(foundClrClass, Is.False, "Assembly with only ComVisible(false) class should not produce clrClass entries."); + + var ns = new XmlNamespaceManager(doc.NameTable); + ns.AddNamespace("asmv1", AsmNamespace); + var clrClassUnderAssembly = root.SelectSingleNode("asmv1:clrClass", ns); + Assert.That(clrClassUnderAssembly, Is.Null, "clrClass must NOT be produced for ComVisible(false) class."); + var removedClrClass = root.SelectSingleNode("asmv1:clrClass[@clsid='" + RemovedManagedLgIcuCollatorClsid + "']", ns); + Assert.That(removedClrClass, Is.Null, "Removed ManagedLgIcuCollator CLSID must not appear in generated clrClass entries."); + Assert.That(root.OuterXml, Does.Not.Contain(RemovedManagedLgIcuCollatorClsid)); + } + finally + { + if (Directory.Exists(tempDir)) + { + Directory.Delete(tempDir, true); + } + } + } + + [Test] + public void AddExcludedClsids_NormalizesClsidValues() + { + var doc = new XmlDocument(); + var root = doc.CreateElement("assembly", AsmNamespace); + doc.AppendChild(root); + var logger = new TaskLoggingHelper(new TestBuildEngine(), nameof(RegFreeCreatorTests)); + var creator = new RegFreeCreator(doc, logger); + + creator.AddExcludedClsids(new[] { "e771361c-ff54-4120-9525-98a0b7a9accf", "{3fb0fcd2-ac55-42a8-b580-73b89a2b6215}" }); + + // Use reflection to verify the private field _excludedClsids was populated and normalized + var field = typeof(RegFreeCreator).GetField("_excludedClsids", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); + Assert.That(field, Is.Not.Null, "Should find the private _excludedClsids field."); + + var excludedHashSet = (System.Collections.Generic.HashSet)field.GetValue(creator); + Assert.That(excludedHashSet, Is.Not.Null); + Assert.That(excludedHashSet.Count, Is.EqualTo(2)); + Assert.That(excludedHashSet.Contains(RemovedManagedLgIcuCollatorClsid), Is.True, "Should normalize Clsid without braces."); + Assert.That(excludedHashSet.Contains("{3fb0fcd2-ac55-42a8-b580-73b89a2b6215}"), Is.True, "Should preserve Clsid with braces."); + } + private static void CompileComVisibleAssembly(string outputPath) { const string source = @"using System.Runtime.InteropServices; @@ -79,7 +150,11 @@ public class SampleComClass { } }"; + CompileAssembly(outputPath, source); + } + private static void CompileAssembly(string outputPath, string source) + { var provider = new CSharpCodeProvider(); var parameters = new CompilerParameters { @@ -94,7 +169,7 @@ public class SampleComClass if (results.Errors.HasErrors) { var message = string.Join(Environment.NewLine, results.Errors.Cast().Select(e => e.ToString())); - throw new InvalidOperationException($"Failed to compile COM-visible test assembly:{Environment.NewLine}{message}"); + throw new InvalidOperationException($"Failed to compile test assembly:{Environment.NewLine}{message}"); } } } diff --git a/Build/mkall.targets b/Build/mkall.targets index 03670e185b..2e441e5e60 100644 --- a/Build/mkall.targets +++ b/Build/mkall.targets @@ -43,7 +43,8 @@ - + + diff --git a/Src/Common/FieldWorks/BuildInclude.targets b/Src/Common/FieldWorks/BuildInclude.targets index a9c9e96c43..47f39f2eee 100644 --- a/Src/Common/FieldWorks/BuildInclude.targets +++ b/Src/Common/FieldWorks/BuildInclude.targets @@ -7,7 +7,7 @@ - + diff --git a/Src/Common/FwUtils/Win32Wrappers.cs b/Src/Common/FwUtils/Win32Wrappers.cs index c4030d1a36..36c2981edd 100644 --- a/Src/Common/FwUtils/Win32Wrappers.cs +++ b/Src/Common/FwUtils/Win32Wrappers.cs @@ -2854,22 +2854,6 @@ public enum ToolBarButtonInfoFlags : long #endregion #region Ole32.dll - /// - /// Carries out the clipboard shutdown sequence. It also releases the IDataObject - /// pointer that was previously placed on the clipboard. - /// - /// true if the clipboard has been flushed. - [DllImport("ole32.dll")] - public static extern int OleFlushClipboard(); - - /// - /// Determines whether the data object pointer previously placed on the clipboard is - /// still on the clipboard. - /// - /// [in] Pointer to the data object previously copied or cut. - /// true if object still on the clipboard. - [DllImport("ole32.dll")] - public static extern bool OleIsCurrentClipboard([MarshalAs(UnmanagedType.IUnknown)]object pDataObject); #endregion #region Shell32.dll diff --git a/Src/Generic/ModuleEntry.cpp b/Src/Generic/ModuleEntry.cpp index b0636d54a5..73cfe2fd19 100644 --- a/Src/Generic/ModuleEntry.cpp +++ b/Src/Generic/ModuleEntry.cpp @@ -57,7 +57,6 @@ bool ModuleEntry::s_fPerUserRegistration = false; #endif // WIN32 #ifdef EXE_MODULE -IDataObjectPtr ModuleEntry::s_qdobjClipboard; // data stored in clipboard by this app. bool ModuleEntry::s_fIsExe = true; #else // EXE_MODULE @@ -84,11 +83,6 @@ ModuleEntry::~ModuleEntry() The code in this section only gets included for EXE servers. /**********************************************************************************************/ -void ModuleEntry::SetClipboard(IDataObject * pdobjClipboard) -{ - s_qdobjClipboard = pdobjClipboard; -} - /*---------------------------------------------------------------------------------------------- For an exe, we post a WM_QUIT message to the main thread when the module reference count goes to zero. @@ -154,7 +148,6 @@ bool ModuleEntry::Startup(HINSTANCE hinst, LPSTR pszCmdLine) // Initialize COM HRESULT hr = OleInitialize(NULL); - s_qdobjClipboard.Clear(); if (FAILED(hr)) { @@ -230,17 +223,6 @@ void ModuleEntry::ShutDown() } } - // Uninitialize COM, first shutting down the clipboard. - if (s_qdobjClipboard.Ptr()) - { - hr = OleIsCurrentClipboard(s_qdobjClipboard.Ptr()); - WarnHr(hr); - if (hr == S_OK) - { - WarnHr(OleFlushClipboard()); - } - s_qdobjClipboard.Clear(); - } OleUninitialize(); } @@ -266,7 +248,6 @@ int ModuleEntry::WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR pszCmdLine, // Initialize COM HRESULT hr = OleInitialize(NULL); - s_qdobjClipboard.Clear(); if (FAILED(hr)) { @@ -324,17 +305,6 @@ int ModuleEntry::WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR pszCmdLine, WarnHr(hr); } - // Uninitialize COM, first shutting down the clipboard. - if (s_qdobjClipboard.Ptr()) - { - hr = OleIsCurrentClipboard(s_qdobjClipboard.Ptr()); - WarnHr(hr); - if (hr == S_OK) - { - WarnHr(OleFlushClipboard()); - } - s_qdobjClipboard.Clear(); - } OleUninitialize(); return nRet; @@ -347,13 +317,6 @@ int ModuleEntry::WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR pszCmdLine, The code in this section only gets included for DLLs. /**********************************************************************************************/ -/*---------------------------------------------------------------------------------------------- - For a DLL, we don't have a global place to record this, so ignore it. -----------------------------------------------------------------------------------------------*/ -void ModuleEntry::SetClipboard(IDataObject * pdobjClipboard) -{ -} - /*---------------------------------------------------------------------------------------------- For a DLL, we just decrement the count. (But DON'T put this inline in the header! It messes up the strategy for linking in the right version of ModuleEntry for DLLs versus diff --git a/Src/Generic/ModuleEntry.h b/Src/Generic/ModuleEntry.h index e184b5a7f2..0279be7947 100644 --- a/Src/Generic/ModuleEntry.h +++ b/Src/Generic/ModuleEntry.h @@ -166,8 +166,6 @@ class ModuleEntry : public LLBase } #ifdef EXE_MODULE - // Data placed on the clipboard by this program. - static IDataObjectPtr s_qdobjClipboard; #ifdef USING_MFC static bool Startup(HINSTANCE hinst, LPSTR pszCmdLine); @@ -204,8 +202,6 @@ class ModuleEntry : public LLBase { return s_hmod; } static LPCTSTR GetModulePathName(void); - static void SetClipboard(IDataObject * pdobjClipboard); - // These methods increment and decrement the reference count for the module. A module // will not be unloaded from memory as long as something is still referencing it. // If these are not called properly, the module might be unloaded from memory too early, diff --git a/Src/ManagedLgIcuCollator/LgIcuCollator.cs b/Src/ManagedLgIcuCollator/LgIcuCollator.cs index 795c782d7c..f3bec438c9 100644 --- a/Src/ManagedLgIcuCollator/LgIcuCollator.cs +++ b/Src/ManagedLgIcuCollator/LgIcuCollator.cs @@ -17,9 +17,7 @@ namespace SIL.FieldWorks.Language /// Direct port of the C++ class LgIcuCollator /// [Serializable] - [ComVisible(true)] - [ClassInterface(ClassInterfaceType.None)] - [Guid("e771361c-ff54-4120-9525-98a0b7a9accf")] + [ComVisible(false)] public class ManagedLgIcuCollator : ILgCollatingEngine, IDisposable { #region Member variables