File: MS\Internal\Ink\GestureRecognizer\NativeRecognizer.cs
Web Access
Project: src\src\Microsoft.DotNet.Wpf\src\PresentationCore\PresentationCore.csproj (PresentationCore)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
// Description:
//      A wrapper class which interoperates with the unmanaged recognition APIS
//      in mshwgst.dll
 
using Microsoft.Win32;
using MS.Win32;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Media;
using System.Windows.Ink;
using System.Windows.Input;
 
#pragma warning disable 1634, 1691  // suppressing PreSharp warnings
 
namespace MS.Internal.Ink.GestureRecognition
{
    /// <summary>
    /// NativeRecognizer class
    /// </summary>
    internal sealed class NativeRecognizer : IDisposable
    {
        //-------------------------------------------------------------------------------
        //
        // Constructors
        //
        //-------------------------------------------------------------------------------
 
        #region Constructors
 
        /// <summary>
        /// Static constructor
        /// </summary>
        static NativeRecognizer()
        {
            s_isSupported = LoadRecognizerDll();
        }
 
        /// <summary>
        /// Private constructor
        /// </summary>
        private NativeRecognizer()
        {
            Debug.Assert(NativeRecognizer.RecognizerHandleSingleton != null);
 
            int hr = MS.Win32.Recognizer.UnsafeNativeMethods.CreateContext(NativeRecognizer.RecognizerHandleSingleton,
                                                                        out _hContext);
            if (HRESULT.Failed(hr))
            {
                //don't throw a com exception here, we don't need to pass out any details
                throw new InvalidOperationException(SR.UnspecifiedGestureConstructionException);
            }
 
            // We add a reference of the recognizer to the context handle.
            // The context will dereference the recognizer reference when it gets disposed.
            // This trick will prevent the GC from disposing the recognizer before all contexts.
            _hContext.AddReferenceOnRecognizer(NativeRecognizer.RecognizerHandleSingleton);
        }
 
        #endregion Constructors
 
        //-------------------------------------------------------------------------------
        //
        // Internal Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Internal Methods
 
        /// <summary>
        /// Create an Instance of the NativeRecognizer.
        /// </summary>
        /// <returns>null if it fails</returns>
        internal static NativeRecognizer CreateInstance()
        {
            if (NativeRecognizer.RecognizerHandleSingleton != null)
            {
                return new NativeRecognizer();
            }
            else
            {
                return null;
            }
        }
 
        /// <summary>
        /// Set the enabled gestures
        /// </summary>
        /// <param name="applicationGestures"></param>
        internal ApplicationGesture[] SetEnabledGestures(IEnumerable<ApplicationGesture> applicationGestures)
        {
            ObjectDisposedException.ThrowIf(_disposed, typeof(NativeRecognizer));
 
            //validate and get an array out
            ApplicationGesture[] enabledGestures =
                GetApplicationGestureArrayAndVerify(applicationGestures);
 
            // Set enabled Gestures.
            int hr = SetEnabledGestures(_hContext, enabledGestures);
            if (HRESULT.Failed(hr))
            {
                //don't throw a com exception here, we don't need to pass out any details
                throw new InvalidOperationException(SR.UnspecifiedSetEnabledGesturesException);
            }
 
            return enabledGestures;
        }
 
        /// <summary>
        /// Recognize the strokes.
        /// </summary>
        /// <param name="strokes"></param>
        /// <returns></returns>
        internal GestureRecognitionResult[] Recognize(StrokeCollection strokes)
        {
            ObjectDisposedException.ThrowIf(_disposed, typeof(NativeRecognizer));
 
            //
            // note that we validate this argument from GestureRecognizer 
            // but since this is marked TAS, we want to do it here as well
            //
            ArgumentNullException.ThrowIfNull(strokes);
            if (strokes.Count > 2)
            {
                throw new ArgumentException(SR.StrokeCollectionCountTooBig, "strokes");
            }
 
            // Create an empty result.
            GestureRecognitionResult[] recResults = Array.Empty<GestureRecognitionResult>();
 
            if ( strokes.Count == 0 )
            {
                return recResults;
            }
 
            int hr = 0;
 
            try
            {
                // Reset the context
                hr = MS.Win32.Recognizer.UnsafeNativeMethods.ResetContext(_hContext);
                if (HRESULT.Failed(hr))
                {
                    //finally block will clean up and throw
                    return recResults;
                }
 
                // Add strokes
                hr = AddStrokes(_hContext, strokes);
                if (HRESULT.Failed(hr))
                {
                    //AddStrokes's finally block will clean up this finally block will throw
                    return recResults;
                }
 
                // recognize the ink
                bool bIncremental;
                hr = MS.Win32.Recognizer.UnsafeNativeMethods.Process(_hContext, out bIncremental);
 
                if (HRESULT.Succeeded(hr))
                {
                    if ( s_GetAlternateListExists )
                    {
                        recResults = InvokeGetAlternateList();
                    }
                    else
                    {
                        recResults = InvokeGetLatticePtr();
                    }
                }
            }
            finally
            {
                // Check if we should report any error.
                if ( HRESULT.Failed(hr) )
                {
                    //don't throw a com exception here, we don't need to pass out any details
                    throw new InvalidOperationException(SR.UnspecifiedGestureException);
                }
            }
 
            return recResults;
        }
 
 
        internal static ApplicationGesture[] GetApplicationGestureArrayAndVerify(IEnumerable<ApplicationGesture> applicationGestures)
        {
            ArgumentNullException.ThrowIfNull(applicationGestures);
 
            uint count = 0;
            //we need to make a disconnected copy
            ICollection<ApplicationGesture> collection = applicationGestures as ICollection<ApplicationGesture>;
            if (collection != null)
            {
                count = (uint)collection.Count;
            }
            else
            {
                foreach (ApplicationGesture gesture in applicationGestures)
                {
                    count++;
                }
            }
 
            // Cannot be empty
            if (count == 0)
            {
                // An empty array is not allowed.
                throw new ArgumentException(SR.ApplicationGestureArrayLengthIsZero, "applicationGestures");
            }
 
            bool foundAllGestures = false;
            List<ApplicationGesture> gestures = new List<ApplicationGesture>();
            foreach (ApplicationGesture gesture in applicationGestures)
            {
                if (!ApplicationGestureHelper.IsDefined(gesture))
                {
                    throw new ArgumentException(SR.ApplicationGestureIsInvalid, "applicationGestures");
                }
 
                //check for allgestures
                if (gesture == ApplicationGesture.AllGestures)
                {
                    foundAllGestures = true;
                }
 
                //check for dupes
                if (gestures.Contains(gesture))
                {
                    throw new ArgumentException(SR.DuplicateApplicationGestureFound, "applicationGestures");
                }
 
                gestures.Add(gesture);
            }
 
            // AllGesture cannot be specified with other gestures
            if (foundAllGestures && gestures.Count != 1)
            {
                // no dupes allowed
                throw new ArgumentException(SR.AllGesturesMustExistAlone, "applicationGestures");
            }
 
            return gestures.ToArray();
        }
 
        #endregion Internal Methods
 
        //-------------------------------------------------------------------------------
        //
        // IDisposable
        //
        //-------------------------------------------------------------------------------
 
        #region IDisposable
 
        /// <summary>
        /// A simple pattern of the dispose implementation.
        /// There is no finalizer since the SafeHandle will take care of releasing the context.
        /// </summary>
        public void Dispose()
        {
            if (_disposed)
            {
                return;
            }
 
            _hContext.Dispose();
            _disposed = true;
        }
 
        #endregion IDisposable
 
        //-------------------------------------------------------------------------------
        //
        // Private Methods
        //
        //-------------------------------------------------------------------------------
 
        #region Private Methods
 
        /// <summary>
        ///  Load the mshwgst.dll from the path in the registry. Make sure this loading action prior to invoking
        ///  any native functions marked with DllImport in mshwgst.dll
        ///  This method is called from the NativeRecognizer's static construtor.
        /// </summary>
        private static bool LoadRecognizerDll()
        {
            // ISSUE-2005/01/14-WAYNEZEN,
            // We may hit the problem when an application already load mshwgst.dll from somewhere rather than the
            // directory we are looking for. The options to resolve this -
            //  1. We fail the recognition functionality.
            //  2. We unload the previous mshwgst.dll
            //  3. We switch the DllImport usage to the new dynamic PInvoke mechanism in Whidbey. Please refer to the blog
            //     http://blogs.msdn.com/junfeng/archive/2004/07/14/181932.aspx. Then we don't have to unload the existing
            //     mshwgst.dll.
            String path = null;
            RegistryKey regkey = Registry.LocalMachine;
            RegistryKey recognizerKey = regkey.OpenSubKey(GestureRecognizerPath);
            if (recognizerKey != null)
            {
                try
                {
                    // Try to read the recognizer path subkey
                    path = recognizerKey.GetValue(GestureRecognizerValueName) as string;
                    if (path == null)
                    {
                        return false;
                    } 
                }
                finally
                {
                    recognizerKey.Close();
                }
            }
            else
            {
                // we couldn't find the path in the registry
                // no key to close
                return false;
            }
 
            if (path != null)
            {
                IntPtr hModule = MS.Win32.UnsafeNativeMethods.LoadLibrary(path);
 
                // Check whether GetAlternateList exists in the loaded Dll.
                s_GetAlternateListExists = false;
                if ( hModule != IntPtr.Zero )
                {
                    s_GetAlternateListExists = MS.Win32.UnsafeNativeMethods.GetProcAddressNoThrow(
                        new HandleRef(null, hModule), "GetAlternateList") != IntPtr.Zero ?
                        true : false;
                }
 
                return hModule != IntPtr.Zero ? true : false; 
            }
            return false; //path was null 
        }
 
        /// <summary>
        /// Set the enabled gestures.
        /// This method is called from the internal SetEnabledGestures method.
        /// </summary>
        private int SetEnabledGestures(MS.Win32.Recognizer.ContextSafeHandle recContext, ApplicationGesture[] enabledGestures)
        {
            Debug.Assert(recContext != null && !recContext.IsInvalid);
 
            // NOTICE-2005/01/11-WAYNEZEN,
            // The following usage was copied from drivers\tablet\recognition\ink\core\twister\src\wispapis.c
            // SetEnabledUnicodeRanges
            //      Set ranges of gestures enabled in this recognition context
            //      The behavior of this function is the following:
            //          (a) (A part of) One of the requested ranges lies outside
            //              gesture interval---currently  [GESTURE_NULL, GESTURE_NULL + 255)
            //              return E_UNEXPECTED and keep the previously set ranges
            //          (b) All requested ranges are within the gesture interval but
            //              some of them are not supported:
            //              return S_TRUNCATED and set those requested gestures that are
            //              supported (possibly an empty set)
            //          (c) All requested gestures are supported
            //              return S_OK and set all requested gestures.
            //      Note:  An easy way to set all supported gestures as enabled is to use
            //              SetEnabledUnicodeRanges() with one range=(GESTURE_NULL,255).
 
            // Enabel gestures
            uint cRanges = (uint)( enabledGestures.Length );
 
            MS.Win32.Recognizer.CHARACTER_RANGE[] charRanges = new MS.Win32.Recognizer.CHARACTER_RANGE[cRanges];
 
            if ( cRanges == 1 && enabledGestures[0] == ApplicationGesture.AllGestures )
            {
                charRanges[0].cChars = MAX_GESTURE_COUNT;
                charRanges[0].wcLow = GESTURE_NULL;
            }
            else
            {
                for ( int i = 0; i < cRanges; i++ )
                {
                    charRanges[i].cChars = 1;
                    charRanges[i].wcLow = (ushort)( enabledGestures[i] );
                }
            }
            int hr = MS.Win32.Recognizer.UnsafeNativeMethods.SetEnabledUnicodeRanges(recContext, cRanges, charRanges);
            return hr;
        }
 
        /// <summary>
        /// Add the strokes to the recoContext.
        /// The method is called from the internal Recognize method.
        /// </summary>
        private int AddStrokes(MS.Win32.Recognizer.ContextSafeHandle recContext, StrokeCollection strokes)
        {
            Debug.Assert(recContext != null && !recContext.IsInvalid);
 
            int hr;
 
            foreach ( Stroke stroke in strokes )
            {
                MS.Win32.Recognizer.PACKET_DESCRIPTION packetDescription = 
                    new MS.Win32.Recognizer.PACKET_DESCRIPTION();
                IntPtr packets = IntPtr.Zero;
                try
                {
                    int countOfBytes;
                    NativeMethods.XFORM xForm;
                    GetPacketData(stroke, out packetDescription, out countOfBytes, out packets, out xForm);
                    if (packets == IntPtr.Zero)
                    {
                        return -2147483640; //E_FAIL - 0x80000008.  We never raise this in an exception
                    }
 
                    hr = MS.Win32.Recognizer.UnsafeNativeMethods.AddStroke(recContext, ref packetDescription, (uint)countOfBytes, packets, xForm);
                    if ( HRESULT.Failed(hr) )
                    {
                        // Return from here. The finally block will free the memory and report the error properly.
                        return hr;
                    }
                }
                finally
                {
                    // Release the resources in the finally block
                    ReleaseResourcesinPacketDescription(packetDescription, packets);
                }
            }
 
            return MS.Win32.Recognizer.UnsafeNativeMethods.EndInkInput(recContext);
}
 
        /// <summary>
        /// Retrieve the packet description, packets data and XFORM which is the information the native recognizer needs.
        /// The method is called from AddStrokes.
        /// </summary>
        private void GetPacketData
        (
            Stroke stroke,
            out MS.Win32.Recognizer.PACKET_DESCRIPTION packetDescription, 
            out int countOfBytes, 
            out IntPtr packets, 
            out NativeMethods.XFORM xForm
        )
        {
            int i;
            countOfBytes = 0;
            packets = IntPtr.Zero;
            packetDescription = new MS.Win32.Recognizer.PACKET_DESCRIPTION();
            Matrix matrix = Matrix.Identity;
            xForm = new NativeMethods.XFORM((float)(matrix.M11), (float)(matrix.M12), (float)(matrix.M21),
                                            (float)(matrix.M22), (float)(matrix.OffsetX), (float)(matrix.OffsetY));
 
            StylusPointCollection stylusPoints = stroke.StylusPoints;
            if (stylusPoints.Count == 0)
            {
                return; //we'll fail when the calling routine sees that packets is IntPtr.Zer
            }
 
            if (stylusPoints.Description.PropertyCount > StylusPointDescription.RequiredCountOfProperties)
            {
                //
                // reformat to X, Y, P
                //
                StylusPointDescription reformatDescription
                    = new StylusPointDescription(
                            new StylusPointPropertyInfo[]{
                                new StylusPointPropertyInfo(StylusPointProperties.X),
                                new StylusPointPropertyInfo(StylusPointProperties.Y),
                                stylusPoints.Description.GetPropertyInfo(StylusPointProperties.NormalPressure)});
                stylusPoints = stylusPoints.Reformat(reformatDescription);
            }
 
            //
            // now make sure we only take a finite amount of data for the stroke
            //
            if (stylusPoints.Count > MaxStylusPoints)
            {
                stylusPoints = stylusPoints.Clone(MaxStylusPoints);
            }
 
            Guid[] propertyGuids = new Guid[]{  StylusPointPropertyIds.X, //required index for SPD
                                                StylusPointPropertyIds.Y, //required index for SPD
                                                StylusPointPropertyIds.NormalPressure}; //required index for SPD
 
            Debug.Assert(stylusPoints != null);
            Debug.Assert(propertyGuids.Length == StylusPointDescription.RequiredCountOfProperties);
 
            // Get the packet description
            packetDescription.cbPacketSize = (uint)(propertyGuids.Length * sizeof(Int32));
            packetDescription.cPacketProperties = (uint)propertyGuids.Length;
 
            //
            // use X, Y defaults for metrics, sometimes mouse metrics can be bogus
            // always use NormalPressure metrics, though.
            //
            StylusPointPropertyInfo[] infosToUse = new StylusPointPropertyInfo[StylusPointDescription.RequiredCountOfProperties];
            infosToUse[StylusPointDescription.RequiredXIndex] = StylusPointPropertyInfoDefaults.X;
            infosToUse[StylusPointDescription.RequiredYIndex] = StylusPointPropertyInfoDefaults.Y;
            infosToUse[StylusPointDescription.RequiredPressureIndex] =
                stylusPoints.Description.GetPropertyInfo(StylusPointProperties.NormalPressure);
 
            MS.Win32.Recognizer.PACKET_PROPERTY[] packetProperties = 
                new MS.Win32.Recognizer.PACKET_PROPERTY[packetDescription.cPacketProperties];
            
            StylusPointPropertyInfo propertyInfo;
            for ( i = 0; i < packetDescription.cPacketProperties; i++ )
            {
                packetProperties[i].guid = propertyGuids[i];
                propertyInfo = infosToUse[i];
 
                MS.Win32.Recognizer.PROPERTY_METRICS propertyMetrics = new MS.Win32.Recognizer.PROPERTY_METRICS( );
                propertyMetrics.nLogicalMin = propertyInfo.Minimum;
                propertyMetrics.nLogicalMax = propertyInfo.Maximum;
                propertyMetrics.Units = (int)(propertyInfo.Unit);
                propertyMetrics.fResolution = propertyInfo.Resolution;
                packetProperties[i].PropertyMetrics = propertyMetrics;
            }
 
            unsafe
            {
                int allocationSize = (int)(Marshal.SizeOf(typeof(MS.Win32.Recognizer.PACKET_PROPERTY)) * packetDescription.cPacketProperties);
                packetDescription.pPacketProperties = Marshal.AllocCoTaskMem(allocationSize);
                MS.Win32.Recognizer.PACKET_PROPERTY* pPacketProperty = 
                    (MS.Win32.Recognizer.PACKET_PROPERTY*)(packetDescription.pPacketProperties.ToPointer());
                MS.Win32.Recognizer.PACKET_PROPERTY* pElement = pPacketProperty;
                for ( i = 0 ; i < packetDescription.cPacketProperties ; i ++ )
                {
                    Marshal.StructureToPtr(packetProperties[i], new IntPtr(pElement), false);
                    pElement++;
                }
            }
 
            // Get packet data
            int[] rawPackets = stylusPoints.ToHiMetricArray();
            int packetCount = rawPackets.Length;
            if (packetCount != 0)
            {
                countOfBytes = packetCount * sizeof(Int32);
                packets = Marshal.AllocCoTaskMem(countOfBytes);
                Marshal.Copy(rawPackets, 0, packets, packetCount);
            }
        }
 
        /// <summary>
        /// Release the memory blocks which has been created for mashalling purpose.
        /// The method is called from AddStrokes.
        /// </summary>
        private void ReleaseResourcesinPacketDescription(MS.Win32.Recognizer.PACKET_DESCRIPTION pd, IntPtr packets)
        {
            if ( pd.pPacketProperties != IntPtr.Zero )
            {
                unsafe
                {
                    MS.Win32.Recognizer.PACKET_PROPERTY* pPacketProperty = 
                        (MS.Win32.Recognizer.PACKET_PROPERTY*)( pd.pPacketProperties.ToPointer( ) );
                    MS.Win32.Recognizer.PACKET_PROPERTY* pElement = pPacketProperty;
 
                    for ( int i = 0; i < pd.cPacketProperties; i++ )
                    {
                        Marshal.DestroyStructure(new IntPtr(pElement), typeof(MS.Win32.Recognizer.PACKET_PROPERTY));
                        pElement++;
                    }
                }
                Marshal.FreeCoTaskMem(pd.pPacketProperties);
                pd.pPacketProperties = IntPtr.Zero; 
            }
 
            if ( pd.pguidButtons != IntPtr.Zero )
            {
                Marshal.FreeCoTaskMem(pd.pguidButtons);
                pd.pguidButtons = IntPtr.Zero;
            }
 
            if ( packets != IntPtr.Zero )
            {
                Marshal.FreeCoTaskMem(packets);
                packets = IntPtr.Zero;
            }
        }
 
        /// <summary>
        /// Invokes GetAlternateList in the native dll
        /// </summary>
        /// <returns></returns>
        private GestureRecognitionResult[] InvokeGetAlternateList()
        {
            GestureRecognitionResult[] recResults = Array.Empty<GestureRecognitionResult>();
            int hr = 0;
 
            MS.Win32.Recognizer.RECO_RANGE recoRange;
            recoRange.iwcBegin = 0;
            recoRange.cCount = 1;
            uint countOfAlternates = IRAS_DefaultCount;
            IntPtr[] pRecoAlternates = new IntPtr[IRAS_DefaultCount];
 
            try
            {
                hr = MS.Win32.Recognizer.UnsafeNativeMethods.GetAlternateList(_hContext, ref recoRange, ref countOfAlternates, pRecoAlternates, MS.Win32.Recognizer.ALT_BREAKS.ALT_BREAKS_SAME);
 
                if ( HRESULT.Succeeded(hr) && countOfAlternates != 0 )
                {
                    List<GestureRecognitionResult> resultList = new List<GestureRecognitionResult>();
 
                    for ( int i = 0; i < countOfAlternates; i++ )
                    {
                        uint size = 1; // length of string == 1 since gesture id is a single WCHAR
                        StringBuilder recoString = new StringBuilder(1);
                        RecognitionConfidence confidenceLevel;
 
                        if ( HRESULT.Failed(MS.Win32.Recognizer.UnsafeNativeMethods.GetString(pRecoAlternates[i], out recoRange, ref size, recoString))
                            || HRESULT.Failed(MS.Win32.Recognizer.UnsafeNativeMethods.GetConfidenceLevel(pRecoAlternates[i], out recoRange, out confidenceLevel)) )
                        {
                            // Fail to retrieve the reco result, skip this one
                            continue;
                        }
 
                        ApplicationGesture gesture = (ApplicationGesture)recoString[0];
                        Debug.Assert(ApplicationGestureHelper.IsDefined(gesture));
                        if (ApplicationGestureHelper.IsDefined(gesture))
                        {
                            resultList.Add(new GestureRecognitionResult(confidenceLevel, gesture));
                        }
                    }
 
                    recResults = resultList.ToArray();
                }
            }
            finally
            {
                // Destroy the alternates
                for ( int i = 0; i < countOfAlternates; i++ )
                {
                    if (pRecoAlternates[i] != IntPtr.Zero)
                    {
                        #pragma warning suppress 6031, 56031 // Return value ignored on purpose.
                        MS.Win32.Recognizer.UnsafeNativeMethods.DestroyAlternate(pRecoAlternates[i]);
                        pRecoAlternates[i] = IntPtr.Zero;
                    }
                }
            }
 
            return recResults;
        }
 
        /// <summary>
        /// Invokes GetLatticePtr in the native dll
        /// </summary>
        /// <returns></returns>
        private GestureRecognitionResult[] InvokeGetLatticePtr()
        {
            GestureRecognitionResult[] recResults = Array.Empty<GestureRecognitionResult>();
 
//            int hr = 0;
            IntPtr ptr = IntPtr.Zero;
 
            // NOTICE-2005/07/11-WAYNEZEN,
            // There is no need to free the returned the structure. 
            // The memory will be released when ResetContext, which is invoked in the callee - Recognize, is called.
            if ( HRESULT.Succeeded(
                MS.Win32.Recognizer.UnsafeNativeMethods.GetLatticePtr(
                _hContext, ref ptr)) )
            {
                unsafe
                {
                    MS.Win32.Recognizer.RECO_LATTICE* pRecoLattice = (MS.Win32.Recognizer.RECO_LATTICE*)ptr;
 
                    uint bestResultColumnCount = pRecoLattice->ulBestResultColumnCount;
                    Debug.Assert(!(bestResultColumnCount != 0 && pRecoLattice->pLatticeColumns == IntPtr.Zero), "Invalid results!");
                    if ( bestResultColumnCount > 0 && pRecoLattice->pLatticeColumns != IntPtr.Zero )
                    {
                        List<GestureRecognitionResult> resultList = new List<GestureRecognitionResult>();
 
                        MS.Win32.Recognizer.RECO_LATTICE_COLUMN* pLatticeColumns =
                            (MS.Win32.Recognizer.RECO_LATTICE_COLUMN*)(pRecoLattice->pLatticeColumns);
                        ulong* pulBestResultColumns = (ulong*)(pRecoLattice->pulBestResultColumns);
 
                        for ( uint i = 0; i < bestResultColumnCount; i++ )
                        {
                            ulong column = pulBestResultColumns[i];
                            MS.Win32.Recognizer.RECO_LATTICE_COLUMN recoColumn = pLatticeColumns[column];
 
                            Debug.Assert(0 < recoColumn.cLatticeElements, "Invalid results!");
 
                            for ( int j = 0; j < recoColumn.cLatticeElements; j++ )
                            {
                                MS.Win32.Recognizer.RECO_LATTICE_ELEMENT recoElement =
                                    ((MS.Win32.Recognizer.RECO_LATTICE_ELEMENT*)(recoColumn.pLatticeElements))[j];
 
                                Debug.Assert((RECO_TYPE)(recoElement.type) == RECO_TYPE.RECO_TYPE_WCHAR, "The Application gesture has to be WCHAR type" );
 
                                if ( (RECO_TYPE)(recoElement.type) == RECO_TYPE.RECO_TYPE_WCHAR )
                                {
                                    // Retrieve the confidence lever
                                    RecognitionConfidence confidenceLevel = RecognitionConfidence.Poor;
 
                                    MS.Win32.Recognizer.RECO_LATTICE_PROPERTIES recoProperties = recoElement.epProp;
 
                                    uint propertyCount = recoProperties.cProperties;
                                    MS.Win32.Recognizer.RECO_LATTICE_PROPERTY** apProps = 
                                        (MS.Win32.Recognizer.RECO_LATTICE_PROPERTY**)recoProperties.apProps;
                                    for ( int k = 0; k < propertyCount; k++ )
                                    {
                                        MS.Win32.Recognizer.RECO_LATTICE_PROPERTY* pProps = apProps[k];
                                        if ( pProps->guidProperty == GUID_CONFIDENCELEVEL )
                                        {
                                            Debug.Assert(pProps->cbPropertyValue == sizeof(uint) / sizeof(byte));
                                            RecognitionConfidence level = (RecognitionConfidence)(((uint*)pProps->pPropertyValue))[0];
                                            if ( level >= RecognitionConfidence.Strong && level <= RecognitionConfidence.Poor )
                                            {
                                                confidenceLevel = level;
                                            }
 
                                            break;
                                        }
                                    }
 
                                    ApplicationGesture gesture = (ApplicationGesture)((char)(recoElement.pData));
                                    Debug.Assert(ApplicationGestureHelper.IsDefined(gesture));
                                    if (ApplicationGestureHelper.IsDefined(gesture))
                                    {
                                        // Get the gesture result
                                        resultList.Add(new GestureRecognitionResult(confidenceLevel,gesture));
                                    }
                                }
                            }
}
 
                        recResults = (GestureRecognitionResult[])(resultList.ToArray());
                    }
                }
            }
 
            return recResults;
        }
 
        #endregion Private Methods
 
 
        /// <summary>
        /// RecognizerHandle is a static property. But it's a SafeHandle.
        /// So, we don't have to worry about releasing the handle since RecognizerSafeHandle when there is no reference on it.
        /// </summary>
        private static MS.Win32.Recognizer.RecognizerSafeHandle RecognizerHandleSingleton
        {
            get
            {
                if (s_isSupported && s_hRec == null)
                {
                    lock (_syncRoot)
                    {
                        if (s_isSupported && s_hRec == null)
                        {
                            if (HRESULT.Failed(MS.Win32.Recognizer.UnsafeNativeMethods.CreateRecognizer(ref s_Gesture, out s_hRec)))
                            {
                                s_hRec = null;
                            }
                        }
                    }
                }
 
                return s_hRec;
            }
        }
 
        enum RECO_TYPE : ushort
        {
            RECO_TYPE_WSTRING = 0,
            RECO_TYPE_WCHAR = 1
        }
 
        private const string GestureRecognizerPath = @"SOFTWARE\MICROSOFT\TPG\SYSTEM RECOGNIZERS\{BED9A940-7D48-48E3-9A68-F4887A5A1B2E}";
        private const string GestureRecognizerFullPath = "HKEY_LOCAL_MACHINE" + @"\" + GestureRecognizerPath;
        private const string GestureRecognizerValueName = "RECOGNIZER DLL";
        private const string GestureRecognizerGuid = "{BED9A940-7D48-48E3-9A68-F4887A5A1B2E}";
        
        
        // This constant is an identical value as the one in drivers\tablet\recognition\ink\core\common\inc\gesture\gesturedefs.h
        private const ushort MAX_GESTURE_COUNT = 256;
        // This constant is an identical value as the one in drivers\tablet\include\sdk\recdefs.h
        private const ushort GESTURE_NULL = 0xf000;
        // This constant is an identical value as the one in public\internal\drivers\inc\msinkaut.h
        private const ushort IRAS_DefaultCount = 10;
        private const ushort MaxStylusPoints = 10000;
 
        // The GUID has been copied from public\internal\drivers\inc\tpcguid.h
        //// {7DFE11A7-FB5D-4958-8765-154ADF0D833F}
        //DEFINE_GUID(GUID_CONFIDENCELEVEL, 0x7dfe11a7, 0xfb5d, 0x4958, 0x87, 0x65, 0x15, 0x4a, 0xdf, 0xd, 0x83, 0x3f);
        private static readonly Guid GUID_CONFIDENCELEVEL = new Guid("{7DFE11A7-FB5D-4958-8765-154ADF0D833F}");
 
 
        /// <summary>
        /// IDisposable support
        /// </summary>
        private bool                                    _disposed = false;
 
        /// <summary>
        /// Each NativeRecognizer instance has it's own recognizer context
        /// </summary>
        private MS.Win32.Recognizer.ContextSafeHandle _hContext;
 
        /// <summary>
        /// Used to lock for instancing the native recognizer handle
        /// </summary>
        private static readonly object                  _syncRoot = new object();
 
        /// <summary>
        /// All NativeRecognizer share a single handle to the recognizer
        /// </summary>
        private static MS.Win32.Recognizer.RecognizerSafeHandle s_hRec;
 
        /// <summary>
        /// The Guid of the GestureRecognizer used for registry lookup
        /// </summary>
        private static Guid                             s_Gesture = new Guid(GestureRecognizerGuid);
 
        /// <summary>
        /// can we load the recognizer?
        /// </summary>
        private static readonly bool s_isSupported;
 
        /// <summary>
        /// A flag indicates whether we can find the entry point of 
        /// GetAlternateList function in mshwgst.dll
        /// </summary>
        private static bool s_GetAlternateListExists;
}
}