//---------------------------------------------------------------------
//  This file is part of the CLR Managed Debugger (mdbg) Sample.
// 
//  Copyright (C) Microsoft Corporation.  All rights reserved.
//---------------------------------------------------------------------


// These interfaces serve as an extension to the BCL's SymbolStore interfaces.
namespace Microsoft.Samples.Debugging.CorSymbolStore 
{
    using System.Diagnostics.SymbolStore;

    
    using System;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;

    [
        ComImport,
        Guid("AA544d42-28CB-11d3-bd22-0000f80849bd"),
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        ComVisible(false)
    ]
    internal interface ISymUnmanagedBinder
    {
        // These methods will often return error HRs in common cases.
        // If there are no symbols for the given target, a failing hr is returned.
        // This is pretty common.
        //
        // Using PreserveSig and manually handling error cases provides a big performance win.
        // Far fewer exceptions will be thrown and caught.
        // Exceptions should be reserved for truely "exceptional" cases.
        [PreserveSig]
        int GetReaderForFile(IntPtr importer,
                                  [MarshalAs(UnmanagedType.LPWStr)] String filename,
                                  [MarshalAs(UnmanagedType.LPWStr)] String SearchPath,
                                  [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader retVal);

        [PreserveSig]
        int GetReaderFromStream(IntPtr importer,
                                        IStream stream,
                                        [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader retVal);
    }

    [
        ComImport,
        Guid("ACCEE350-89AF-4ccb-8B40-1C2C4C6F9434"),
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        ComVisible(false)
    ]
    internal interface ISymUnmanagedBinder2 : ISymUnmanagedBinder
    {
        // ISymUnmanagedBinder methods (need to define the base interface methods also, per COM interop requirements)
        [PreserveSig]
        new int GetReaderForFile(IntPtr importer,
                                  [MarshalAs(UnmanagedType.LPWStr)] String filename,
                                  [MarshalAs(UnmanagedType.LPWStr)] String SearchPath,
                                  [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader retVal);

        [PreserveSig]
        new int GetReaderFromStream(IntPtr importer,
                                        IStream stream,
                                        [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader retVal);

        // ISymUnmanagedBinder2 methods 
        [PreserveSig]
        int GetReaderForFile2(IntPtr importer,
                                  [MarshalAs(UnmanagedType.LPWStr)] String fileName,
                                  [MarshalAs(UnmanagedType.LPWStr)] String searchPath,
                                  int searchPolicy,
                                  [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader pRetVal);
    }
    
    [
        ComImport,
        Guid("28AD3D43-B601-4d26-8A1B-25F9165AF9D7"),
        InterfaceType(ComInterfaceType.InterfaceIsIUnknown),
        ComVisible(false)
    ]
    internal interface ISymUnmanagedBinder3 : ISymUnmanagedBinder2
    {
        // ISymUnmanagedBinder methods (need to define the base interface methods also, per COM interop requirements)
        [PreserveSig]
        new int GetReaderForFile(IntPtr importer,
                                  [MarshalAs(UnmanagedType.LPWStr)] String filename,
                                  [MarshalAs(UnmanagedType.LPWStr)] String SearchPath,
                                  [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader retVal);

        [PreserveSig]
        new int GetReaderFromStream(IntPtr importer,
                                        IStream stream,
                                        [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader retVal);

        // ISymUnmanagedBinder2 methods (need to define the base interface methods also, per COM interop requirements)
        [PreserveSig]
        new int GetReaderForFile2(IntPtr importer,
                   [MarshalAs(UnmanagedType.LPWStr)] String fileName,
                   [MarshalAs(UnmanagedType.LPWStr)] String searchPath,
                   int searchPolicy,
                   [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader pRetVal);

        // ISymUnmanagedBinder3 methods 
        [PreserveSig]
        int GetReaderFromCallback(IntPtr importer,
                                   [MarshalAs(UnmanagedType.LPWStr)] String fileName,
                                   [MarshalAs(UnmanagedType.LPWStr)] String searchPath,
                                   int searchPolicy,
                                   IntPtr callback,
                                   [MarshalAs(UnmanagedType.Interface)] out ISymUnmanagedReader pRetVal);
    }

    /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder"]/*' />

    public class SymbolBinder: ISymbolBinder1, ISymbolBinder2
    {
        ISymUnmanagedBinder m_binder;

        /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder.SymbolBinder"]/*' />
        public SymbolBinder()
        {
            Guid CLSID_CorSymBinder = new Guid("0A29FF9E-7F9C-4437-8B11-F424491E3931");
            m_binder = (ISymUnmanagedBinder3)Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_CorSymBinder));
        }
        
        /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder.GetReader"]/*' />
        public ISymbolReader GetReader(IntPtr importer, String filename,
                                          String searchPath)
        {
            ISymUnmanagedReader reader = null;
            int hr = m_binder.GetReaderForFile(importer, filename, searchPath, out reader);
            if (IsFailingResultNormal(hr))
            {
                return null;
            }
            Marshal.ThrowExceptionForHR(hr);
            return new SymReader(reader);
        }

        /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder.GetReaderForFile"]/*' />
        public ISymbolReader GetReaderForFile(Object importer, String filename,
                                           String searchPath)
        {
            ISymUnmanagedReader reader = null;
            IntPtr uImporter = IntPtr.Zero;
            try
            {
                uImporter = Marshal.GetIUnknownForObject(importer);
                int hr = m_binder.GetReaderForFile(uImporter, filename, searchPath, out reader);
                if (IsFailingResultNormal(hr))
                {
                    return null;
                }
                Marshal.ThrowExceptionForHR(hr);
            }
            finally
            {
                if (uImporter != IntPtr.Zero)
                    Marshal.Release(uImporter);
            }
            return new SymReader(reader);
        }
        
        /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder.GetReaderForFile1"]/*' />
        public ISymbolReader GetReaderForFile(Object importer, String fileName,
                                           String searchPath, SymSearchPolicies searchPolicy)
        {
            ISymUnmanagedReader symReader = null;
            IntPtr uImporter = IntPtr.Zero;
            try
            {
                uImporter = Marshal.GetIUnknownForObject(importer);
                int hr = ((ISymUnmanagedBinder2)m_binder).GetReaderForFile2(uImporter, fileName, searchPath, (int)searchPolicy, out symReader);
                if (IsFailingResultNormal(hr))
                {
                    return null;
                }
                Marshal.ThrowExceptionForHR(hr);
            }
            finally
            {
                if (uImporter != IntPtr.Zero)
                    Marshal.Release(uImporter);
            }
            return new SymReader(symReader);
        }
        
        /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder.GetReaderForFile2"]/*' />
        public ISymbolReader GetReaderForFile(Object importer, String fileName,
                                           String searchPath, SymSearchPolicies searchPolicy,
                                           IntPtr callback)
        {
            ISymUnmanagedReader reader = null;
            IntPtr uImporter = IntPtr.Zero;
            try
            {
                uImporter = Marshal.GetIUnknownForObject(importer);
                int hr = ((ISymUnmanagedBinder3)m_binder).GetReaderFromCallback(uImporter, fileName, searchPath, (int)searchPolicy, callback, out reader);
                if (IsFailingResultNormal(hr))
                {
                    return null;
                }
                Marshal.ThrowExceptionForHR(hr);
            }
            finally {
                if (uImporter != IntPtr.Zero)
                    Marshal.Release(uImporter);
            }
            return new SymReader(reader);
        }
        
        /// <include file='doc\symbinder.uex' path='docs/doc[@for="SymbolBinder.GetReaderFromStream"]/*' />
        public ISymbolReader GetReaderFromStream(Object importer, IStream stream)
        {
            ISymUnmanagedReader reader = null;
            IntPtr uImporter = IntPtr.Zero;
            try
            {
                uImporter = Marshal.GetIUnknownForObject(importer);
                int hr = ((ISymUnmanagedBinder2)m_binder).GetReaderFromStream(uImporter, stream, out reader);
                if (IsFailingResultNormal(hr))
                {
                    return null;
                }
                Marshal.ThrowExceptionForHR(hr);
            }
            finally
            {
                if (uImporter != IntPtr.Zero)
                    Marshal.Release(uImporter);
            }
            return new SymReader(reader);
        }

        private static bool IsFailingResultNormal(int hr)
        {
            // If a pdb is not found, that's a pretty common thing.
            if (hr == unchecked((int)0x806D0005))   // E_PDB_NOT_FOUND
            {
                return true;
            }
            // Other fairly common things may happen here, but we don't want to hide
            // this from the programmer.
            // You may get 0x806D0014 if the pdb is there, but just old (mismatched)
            // Or if you ask for the symbol information on something that's not an assembly.
            // If that may happen for your application, wrap calls to GetReaderForFile in 
            // try-catch(COMException) blocks and use the error code in the COMException to report error.
            return false;
        }
    }
}
