From f4529164189de1590c65b42530ec9088cf7abdd5 Mon Sep 17 00:00:00 2001 From: Andrew Arnott Date: Wed, 17 Feb 2021 15:23:50 -0700 Subject: [PATCH] Add SpellChecker sample Shout out to @mikebattista who wrote this. I just refined it a little. --- Directory.Build.props | 2 +- Microsoft.Windows.CsWin32.sln | 7 ++ test/Directory.Build.props | 1 - .../GenerationSandbox.Tests.csproj | 1 + .../Microsoft.Windows.CsWin32.Tests.csproj | 1 + test/SpellChecker/NativeMethods.txt | 7 ++ test/SpellChecker/PInvoke.cs | 31 ++++++ test/SpellChecker/Program.cs | 98 +++++++++++++++++++ test/SpellChecker/SpellChecker.csproj | 21 ++++ 9 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 test/SpellChecker/NativeMethods.txt create mode 100644 test/SpellChecker/PInvoke.cs create mode 100644 test/SpellChecker/Program.cs create mode 100644 test/SpellChecker/SpellChecker.csproj diff --git a/Directory.Build.props b/Directory.Build.props index 68f9340b..55853567 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -32,7 +32,7 @@ - + diff --git a/Microsoft.Windows.CsWin32.sln b/Microsoft.Windows.CsWin32.sln index 0637d145..971d3b6b 100644 --- a/Microsoft.Windows.CsWin32.sln +++ b/Microsoft.Windows.CsWin32.sln @@ -39,6 +39,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Win32MetaGeneration", "src\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GenerationSandbox.Tests", "test\GenerationSandbox.Tests\GenerationSandbox.Tests.csproj", "{7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SpellChecker", "test\SpellChecker\SpellChecker.csproj", "{744BE74F-8C4A-49E8-9683-52D987224285}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -65,6 +67,10 @@ Global {7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Debug|Any CPU.Build.0 = Debug|Any CPU {7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Release|Any CPU.ActiveCfg = Release|Any CPU {7E8A5179-F94C-410F-8BBE-FDAAA95A19C3}.Release|Any CPU.Build.0 = Release|Any CPU + {744BE74F-8C4A-49E8-9683-52D987224285}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {744BE74F-8C4A-49E8-9683-52D987224285}.Debug|Any CPU.Build.0 = Debug|Any CPU + {744BE74F-8C4A-49E8-9683-52D987224285}.Release|Any CPU.ActiveCfg = Release|Any CPU + {744BE74F-8C4A-49E8-9683-52D987224285}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -75,6 +81,7 @@ Global {EB7D0834-4236-408F-B172-64FB45FF643A} = {9E154A29-1796-4B85-BD81-B6A385D8FF71} {6638957D-09ED-47C1-86B9-5D2DFD0FE625} = {9E154A29-1796-4B85-BD81-B6A385D8FF71} {7E8A5179-F94C-410F-8BBE-FDAAA95A19C3} = {36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A} + {744BE74F-8C4A-49E8-9683-52D987224285} = {36CCE840-6FE5-4DB9-A8D5-8CF3CB6D342A} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {E3944F6A-384B-4B0F-B93F-3BD513DC57BD} diff --git a/test/Directory.Build.props b/test/Directory.Build.props index dc7836c7..179b9901 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -2,7 +2,6 @@ - net5.0;netcoreapp3.1;net472 false true diff --git a/test/GenerationSandbox.Tests/GenerationSandbox.Tests.csproj b/test/GenerationSandbox.Tests/GenerationSandbox.Tests.csproj index 6f7d75c0..ad0476c2 100644 --- a/test/GenerationSandbox.Tests/GenerationSandbox.Tests.csproj +++ b/test/GenerationSandbox.Tests/GenerationSandbox.Tests.csproj @@ -2,6 +2,7 @@ + net5.0;netcoreapp3.1;net472 diff --git a/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj b/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj index 3c70e2c8..a1525483 100644 --- a/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj +++ b/test/Microsoft.Windows.CsWin32.Tests/Microsoft.Windows.CsWin32.Tests.csproj @@ -1,6 +1,7 @@  + net5.0;netcoreapp3.1;net472 diff --git a/test/SpellChecker/NativeMethods.txt b/test/SpellChecker/NativeMethods.txt new file mode 100644 index 00000000..0afd0e45 --- /dev/null +++ b/test/SpellChecker/NativeMethods.txt @@ -0,0 +1,7 @@ +CoCreateInstance +CoTaskMemFree +SpellCheckerFactory +ISpellCheckerFactory +ISpellChecker +CLSCTX +S_FALSE \ No newline at end of file diff --git a/test/SpellChecker/PInvoke.cs b/test/SpellChecker/PInvoke.cs new file mode 100644 index 00000000..ff5fd0f1 --- /dev/null +++ b/test/SpellChecker/PInvoke.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace Microsoft.Windows.Sdk +{ + using System; + using System.Runtime.InteropServices; + + internal partial class PInvoke + { + /// + /// + internal static unsafe HRESULT CoCreateInstance(in System.Guid rclsid, IUnknown* pUnkOuter, uint dwClsContext, in System.Guid riid, out T* ppv) + where T : unmanaged + { + HRESULT hr = CoCreateInstance(rclsid, pUnkOuter, dwClsContext, riid, out void* o); + ppv = (T*)o; + return hr; + } + + /// + /// The value from if it does not reflect an error. + /// + /// + internal static HRESULT ThrowOnFailure(this HRESULT errorCode, IntPtr errorInfo = default) + { + Marshal.ThrowExceptionForHR(errorCode, errorInfo); + return errorCode; + } + } +} diff --git a/test/SpellChecker/Program.cs b/test/SpellChecker/Program.cs new file mode 100644 index 00000000..a443483e --- /dev/null +++ b/test/SpellChecker/Program.cs @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System; +using System.Collections.Generic; +using Microsoft.Windows.Sdk; +using static Microsoft.Windows.Sdk.Constants; +using static Microsoft.Windows.Sdk.PInvoke; + +unsafe +{ + CoCreateInstance( + typeof(SpellCheckerFactory).GUID, + null, + (uint)CLSCTX.CLSCTX_INPROC_SERVER, // https://github.com/microsoft/win32metadata/issues/185 + typeof(ISpellCheckerFactory).GUID, + out ISpellCheckerFactory* spellCheckerFactory).ThrowOnFailure(); + + spellCheckerFactory->IsSupported( + "en-US", + out bool supported).ThrowOnFailure(); + + if (!supported) + { + return; + } + + spellCheckerFactory->CreateSpellChecker( + "en-US", + out ISpellChecker* spellChecker).ThrowOnFailure(); + + var text = @"""Cann I I haev some?"""; + + Console.WriteLine(@"Check {0}", text); + + spellChecker->Check( + text, + out IEnumSpellingError* errors).ThrowOnFailure(); + + while (true) + { + if (errors->Next(out ISpellingError* error).ThrowOnFailure() == S_FALSE) + { + break; + } + + error->get_StartIndex(out uint startIndex).ThrowOnFailure(); + error->get_Length(out uint length).ThrowOnFailure(); + + var word = text.Substring((int)startIndex, (int)length); + + error->get_CorrectiveAction(out CORRECTIVE_ACTION action).ThrowOnFailure(); + + switch (action) + { + case CORRECTIVE_ACTION.CORRECTIVE_ACTION_DELETE: + Console.WriteLine(@"Delete ""{0}""", word); + break; + case CORRECTIVE_ACTION.CORRECTIVE_ACTION_REPLACE: + // KNOWN ISSUE: ushort will be changed to string (https://github.com/microsoft/CsWin32/issues/121) + error->get_Replacement(out ushort* replacement).ThrowOnFailure(); + Console.WriteLine(@"Replace ""{0}"" with ""{1}""", word, new string((char*)replacement)); + CoTaskMemFree(replacement); + break; + case CORRECTIVE_ACTION.CORRECTIVE_ACTION_GET_SUGGESTIONS: + var l = new List(); + spellChecker->Suggest(word, out IEnumString* suggestions).ThrowOnFailure(); + while (true) + { + // KNOWN ISSUE: ushort will be changed to string (https://github.com/microsoft/CsWin32/issues/121) + ushort* suggestion; + if (suggestions->Next(1, &suggestion, null).ThrowOnFailure() != 0) + { + break; + } + + l.Add(new string((char*)suggestion)); + CoTaskMemFree(suggestion); + } + + suggestions->Release(); + Console.WriteLine(@"Suggest replacing ""{0}"" with:", word); + foreach (var s in l) + { + Console.WriteLine("\t{0}", s); + } + + break; + default: + break; + } + + error->Release(); + } + + errors->Release(); + spellChecker->Release(); +} diff --git a/test/SpellChecker/SpellChecker.csproj b/test/SpellChecker/SpellChecker.csproj new file mode 100644 index 00000000..2a9c5b01 --- /dev/null +++ b/test/SpellChecker/SpellChecker.csproj @@ -0,0 +1,21 @@ + + + + + Exe + net5.0 + + + + + + + + + + + + + + +