Get chm title

Peter Volz 1,295 Reputation points
2023-09-03T17:09:11.43+00:00

Hello

Using Microsoft HTML Help Workshop making CHM files, now how to get the title from those chm files in vb.net (or c#)?

Developer technologies | VB
Developer technologies | C#
{count} votes

Accepted answer
  1. Castorix31 90,681 Reputation points
    2023-09-07T15:34:57.51+00:00

    A test in C# (in a Button Click) with IITStorage/IStorage interfaces

    (change the sCHMFile variable)

    I tested with several .chm files and I get the correct title :

    public partial class Form1 : Form
    {
        public enum HRESULT : int
        {
            S_OK = 0,
            S_FALSE = 1,
            E_NOTIMPL = unchecked((int)0x80004001),
            E_NOINTERFACE = unchecked((int)0x80004002),
            E_POINTER = unchecked((int)0x80004003),
            E_FAIL = unchecked((int)0x80004005),
            E_UNEXPECTED = unchecked((int)0x8000FFFF),
            E_OUTOFMEMORY = unchecked((int)0x8007000E),
        }
    
        [StructLayout(LayoutKind.Sequential)]
        public struct ITS_Control_Data
        {
            public uint cdwControlData; 
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1)]
            public uint[] adwControlData;
        };
    
        public enum ECompactionLev
        {
            COMPACT_DATA = 0,
            COMPACT_DATA_AND_PATH
        }
    
        Guid CLSID_ITStorage = new Guid("5d02926a-212e-11d0-9df9-00a0c922e6ec");
    
        [ComImport]
        [Guid("88CC31DE-27AB-11D0-9DF9-00A0C922E6EC")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IITStorage
        {
            HRESULT StgCreateDocfile(string pwcsName, uint grfMode, uint reserved, out IStorage ppstgOpen);
            HRESULT StgCreateDocfileOnILockBytes(IntPtr/*ILockBytes*/ plkbyt, uint grfMode, uint reserved, out IStorage ppstgOpen);
            HRESULT StgIsStorageFile(string pwcsName);
            HRESULT StgIsStorageILockBytes(IntPtr/*ILockBytes*/ plkbyt);
            HRESULT StgOpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstgOpen);
            HRESULT StgOpenStorageOnILockBytes(IntPtr/*ILockBytes*/ plkbyt, IStorage pStgPriority, uint grfMode,
                    IntPtr snbExclude, uint reserved, out IStorage ppstgOpen);
            HRESULT StgSetTimes(string lpszName, System.Runtime.InteropServices.ComTypes.FILETIME pctime, System.Runtime.InteropServices.ComTypes.FILETIME patime, System.Runtime.InteropServices.ComTypes.FILETIME pmtime);
            HRESULT SetControlData(ITS_Control_Data pControlData);
            HRESULT DefaultControlData(out ITS_Control_Data ppControlData);
            HRESULT Compact(string pwcsName, ECompactionLev iLev);
        }
    
        /* Storage instantiation modes */
        public const int STGM_DIRECT = 0x00000000;
        public const int STGM_TRANSACTED = 0x00010000;
        public const int STGM_SIMPLE = 0x08000000;
    
        public const int STGM_READ = 0x00000000;
        public const int STGM_WRITE = 0x00000001;
        public const int STGM_READWRITE = 0x00000002;
    
        public const int STGM_SHARE_DENY_NONE = 0x00000040;
        public const int STGM_SHARE_DENY_READ = 0x00000030;
        public const int STGM_SHARE_DENY_WRITE = 0x00000020;
        public const int STGM_SHARE_EXCLUSIVE = 0x00000010;
    
        public const int STGM_PRIORITY = 0x00040000;
        public const int STGM_DELETEONRELEASE = 0x04000000;
    
        public const int STGM_NOSCRATCH = 0x00100000;
    
        public const int STGM_CREATE = 0x00001000;
        public const int STGM_CONVERT = 0x00020000;
        public const int STGM_FAILIFTHERE = 0x00000000;
    
        public const int STGM_NOSNAPSHOT = 0x00200000;
    
        public const int STGM_DIRECT_SWMR = 0x00400000;
    
        [ComImport]
        [Guid("0000000b-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IStorage
        {
            HRESULT CreateStream(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStream ppstm);
            HRESULT OpenStream(string pwcsName, IntPtr reserved1, uint grfMode, uint reserved2, out IStream ppstm);
            HRESULT CreateStorage(string pwcsName, uint grfMode, uint reserved1, uint reserved2, out IStorage ppstg);
            HRESULT OpenStorage(string pwcsName, IStorage pstgPriority, uint grfMode, IntPtr snbExclude, uint reserved, out IStorage ppstg);
            HRESULT CopyTo(uint ciidExclude, Guid rgiidExclude, IntPtr snbExclude, IStorage pstgDest);
            HRESULT MoveElementTo(string pwcsName, IStorage pstgDest, string pwcsNewName, uint grfFlags);
            HRESULT Commit(uint grfCommitFlags);
            HRESULT Revert();
            HRESULT EnumElements(uint reserved1, IntPtr reserved2, uint reserved3, out IEnumSTATSTG ppenum);
            HRESULT DestroyElement(string pwcsName);
            HRESULT RenameElement(string pwcsOldName, string pwcsNewName);
            HRESULT SetElementTimes(string pwcsName, System.Runtime.InteropServices.ComTypes.FILETIME pctime, System.Runtime.InteropServices.ComTypes.FILETIME patime,
                System.Runtime.InteropServices.ComTypes.FILETIME pmtime);
            HRESULT SetClass(Guid clsid);
            HRESULT SetStateBits(uint grfStateBits, uint grfMask);
            HRESULT Stat(out System.Runtime.InteropServices.ComTypes.STATSTG pstatstg, uint grfStatFlag);
        }
    
        [ComImport]
        [Guid("0000000d-0000-0000-C000-000000000046")]
        [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
        public interface IEnumSTATSTG
        {
            // The user needs to allocate an STATSTG array whose size is celt.
            [PreserveSig]
            HRESULT Next(uint celt, [MarshalAs(UnmanagedType.LPArray), Out] System.Runtime.InteropServices.ComTypes.STATSTG[] rgelt, out uint pceltFetched);
            HRESULT Skip(uint celt);
            HRESULT Reset();
            [return: MarshalAs(UnmanagedType.Interface)]
            IEnumSTATSTG Clone();
        }
    
        public enum STGTY : int
        {
            STGTY_STORAGE = 1,
            STGTY_STREAM = 2,
            STGTY_LOCKBYTES = 3,
            STGTY_PROPERTY = 4
        }
    
        [StructLayout(LayoutKind.Explicit)]
        public struct LARGE_INTEGER
        {
            [FieldOffset(0)]
            public uint LowPart;
            [FieldOffset(4)]
            public uint HighPart;
            [FieldOffset(0)]
            public long QuadPart;
        }
    
    
    
        public Form1()
        {
            InitializeComponent();
        }
    
        private void button1_Click(object sender, EventArgs e)
        {        
            string sCHMFile = @"E:\Test.chm";
            object oIITStorage = Activator.CreateInstance(Type.GetTypeFromCLSID(CLSID_ITStorage, true));
            IITStorage pITStorage = (IITStorage)oIITStorage;
            if (pITStorage != null)
            {
                IStorage pStorage;
                HRESULT hr = pITStorage.StgOpenStorage(sCHMFile, null, STGM_SHARE_EXCLUSIVE | STGM_READ, IntPtr.Zero, 0, out pStorage);
                if (hr == HRESULT.S_OK)
                {
                    IEnumSTATSTG pEnum;
                    hr = pStorage.EnumElements(0, IntPtr.Zero, 0, out pEnum);
                    System.Runtime.InteropServices.ComTypes.STATSTG[] ss = new System.Runtime.InteropServices.ComTypes.STATSTG[1];
                    while (HRESULT.S_OK == pEnum.Next(1, ss, out uint c))
                    {
                        if (ss[0].pwcsName == "#SYSTEM")
                        {
                            string sTitle = null;
                            IStream pStream = null;
                            hr = pStorage.OpenStream(ss[0].pwcsName, IntPtr.Zero, STGM_SHARE_EXCLUSIVE | STGM_READ, 0, out pStream);
                            if (hr == HRESULT.S_OK)
                            {
                                uint nSize = 4;
                                byte[] pBuffer = new byte[nSize];
                                IntPtr pcbRead = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(uint)));
                                pStream.Read(pBuffer, (int)nSize, pcbRead);
                                int nRead = Marshal.ReadInt32(pcbRead);
                                Marshal.FreeCoTaskMem(pcbRead);
                                while (true)
                                {
                                    nSize = 2;
                                    pBuffer = new byte[nSize];
                                    pcbRead = Marshal.AllocCoTaskMem(Marshal.SizeOf(typeof(uint)));    
                                    pStream.Read(pBuffer, (int)nSize, pcbRead);
                                    nRead = Marshal.ReadInt32(pcbRead);
                                    Marshal.FreeCoTaskMem(pcbRead);
                                    if (nRead == 0)
                                        break;
                                    int nCode = pBuffer[0];                                 
    
                                    pBuffer = new byte[nSize];
                                    pcbRead = IntPtr.Zero;
                                    pStream.Read(pBuffer, (int)nSize, pcbRead);
    
                                    nSize = pBuffer[0];
                                    pBuffer = new byte[nSize];
                                    pcbRead = IntPtr.Zero;
                                    pStream.Read(pBuffer, (int)nSize, pcbRead);
                                    if (nCode == 3)
                                    {
                                        IntPtr pBytesPtr = Marshal.AllocHGlobal(pBuffer.Length);
                                        Marshal.Copy(pBuffer, 0, pBytesPtr, pBuffer.Length);
                                        sTitle = Marshal.PtrToStringAnsi(pBytesPtr);
                                        Marshal.FreeHGlobal(pBytesPtr);
                                        break;
                                    }
                                }
                                if (sTitle != null)
                                    System.Windows.Forms.MessageBox.Show("Title = " + sTitle, "Information", MessageBoxButtons.OK, MessageBoxIcon.Information);
    
                                Marshal.ReleaseComObject(pStream);
                            }
                        }
                    }
                    Marshal.ReleaseComObject(pStorage);
                }
                Marshal.ReleaseComObject(pITStorage);
            }
        }
    }
    
    1 person found this answer helpful.

2 additional answers

Sort by: Most helpful
  1. Jiachen Li-MSFT 34,221 Reputation points Microsoft External Staff
    2023-09-04T01:54:54.2633333+00:00

    Hi @Peter Volz ,

    Since CHM is a specially formatted HTML file, you can't get the title using the usual method of parsing HTML files.

    You can consider getting it from the window title, or search for some libraries that can parse CHM.

    Due to some forum bugs, I can't publish this code as text directly, you can refer to the screenshot below.

    enter image description here

    Best Regards.

    Jiachen Li


    If the answer is helpful, please click "Accept Answer" and upvote it.

    Note: Please follow the steps in our documentation to enable e-mail notifications if you want to receive the related email notification for this thread.


  2. Peter Volz 1,295 Reputation points
    2023-09-07T12:44:36.0733333+00:00

    Hello dude, yep since they're going to use it in SDK and that process thing cannot be used, just though might be a way to extract it, thanks for being a kind help as always :)

    0 comments No comments

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.