File: Base\MS\Internal\Security\RightsManagement\CallbackHandler.cs
Project: wpf\src\WindowsBase.csproj (WindowsBase)
//-----------------------------------------------------------------------------
//
// <copyright file="CallbackHandler.cs" company="Microsoft">
//    Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// Description:
//  These are the internal helpers that used to process callbacks from RM SDK 
//
// History:
//  06/13/2005: IgorBel:  Initial implementation.
//
//-----------------------------------------------------------------------------
 
using System;
using System.Threading;
using System.Runtime.InteropServices;
using System.Security;
 
// Enable presharp pragma warning suppress directives.
#pragma warning disable 1634, 1691
 
namespace MS.Internal.Security.RightsManagement
{
    
    internal delegate int CallbackDelegate(StatusMessage status, 
                                                            int hr, 
                                                            IntPtr pvParam,         // in the unmanaged SDK these 2 declared as void *
                                                            IntPtr pvContext);     // so the IntPtr is the right equivalent for both 64 and 32 bits
 
    // No need to synchronize access to these methods because they are only called on the user thread
    // This object is re-used during the same session which is why the event must be reset after the wait.
    // As a result of calling GC.SuppressFinalize(this) we also need to seal the class. As there is a danger 
    // of subclass introducing it's own Finalizer which will not be called.     
    /// <SecurityNote>
    ///     Critical:    This class expose access to methods that eventually do one or more of the the following
    ///             1. call into unmanaged code 
    ///             2. affects state/data that will eventually cross over unmanaged code boundary
    ///             3. Return some RM related information which is considered private 
    /// </SecurityNote>
    [SecurityCritical(SecurityCriticalScope.Everything)]  
    internal sealed class CallbackHandler : IDisposable
    {
        internal CallbackHandler()
        {
            _resetEvent = new AutoResetEvent(false); // initialized to a false non-signaled state
            _callbackDelegate = new CallbackDelegate(OnStatus);
        }
 
        internal CallbackDelegate CallbackDelegate 
        {
            get
            {
                return _callbackDelegate;
            }
        }
 
        // this property is not to be accessed until WaitForCompletion returns
        internal string CallbackData
        {
            get
            {
                return _callbackData;
            }
        }
 
        // this is called from the user thread
        internal void WaitForCompletion()
        {
            _resetEvent.WaitOne(); // return to the reset state after unblocking current transaction (as it is an "Auto"ResetEvent)
 
            // second process possible managed exception from the other thread 
            if (_exception != null)
            {
                // rethrow exception that was cought from a worker thread 
                throw _exception;
            }
 
            Errors.ThrowOnErrorCode(_hr);             
        }
 
        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
 
        // this is the callback from the RM thread
        private int OnStatus(StatusMessage status, int hr, IntPtr pvParam, IntPtr pvContext)        
        {
            if (hr == S_DRM_COMPLETED || hr < 0)
            {
                _exception = null; // we are resetting this variable, so that the instance of this class will be reusable even after exception 
                
                try
                {
                    _hr = hr; // we are assigning hr first as the next command can potentially throw, and we would like to have hr value preserved 
                    
                    if (pvParam != IntPtr.Zero)
                    {
                        _callbackData = Marshal.PtrToStringUni(pvParam);
                    }
                }
// disabling PreSharp false positive. In this case we are actually re-throwing the same exception 
// on the application thread inside WaitForCompletion() function
#pragma warning disable 56500                  
                catch (Exception e)
                {
                    // We catch all exceptions of the second worker thread (created by the unmanaged RM SDK)
                    // preserve them , and then rethrow later  on the main thread from the WaitForCompletion method
                    _exception = e;
                }
#pragma warning restore 56500
                finally
                {
                    // After this signal, OnStatus will NOT be invoked until we instigate another "transaction"
                    // from the user thread.
                    _resetEvent.Set();
                }
            }
            return 0;
        }
 
        private void Dispose(bool disposing)
        {
            if (disposing)
            {   
                if (_resetEvent != null)
                {
                    _resetEvent.Set();
                    ((IDisposable)_resetEvent).Dispose();
                }
            }
        }
 
 
        private CallbackDelegate _callbackDelegate;
 
        private AutoResetEvent _resetEvent;
        private string _callbackData;
        private int _hr;
        private Exception _exception;
 
        private const uint S_DRM_COMPLETED                 = 0x0004CF04;  //success code 
    }
}