All formats

CHMLib

LanguageC (usable from C++)
LicenseLGPL-2.1-or-later
Repositorygithub.com/jedwing/CHMLib
Documentationcomments in chm_lib.h and the example programs in src/
Depends onnothing (includes its own LZX decoder)
Read / writeyes / no

CHMLib (chmlib) reads the ITSS container used by CHM files: it looks up entries by path, reads any byte range of an entry (decompressing LZX as needed, with a block cache), and enumerates entries. It’s the library behind xCHM, KchmViewer, pychm and the extract_chmLib and chm_http tools.

The API has 6 functions and is stable. Development stopped with version 0.40 (2009); Linux distributions still ship it with security patches. For hostile input consider libmspack, which is maintained.

Installation#

Shell
# Debian / Ubuntu
sudo apt install libchm-dev
# Fedora
sudo dnf install chmlib-devel
# macOS
brew install chmlib

Compile with cc app.c -lchm.

Basic usage: list files and read a page#

C
#include <chm_lib.h>
#include <stdio.h>
#include <stdlib.h>

static int print_entry(struct chmFile *h, struct chmUnitInfo *ui, void *ctx) {
    (void)h; (void)ctx;
    printf("%10llu  %s\n", (unsigned long long)ui->length, ui->path);
    return CHM_ENUMERATOR_CONTINUE;
}

int main(void) {
    struct chmFile *h = chm_open("help.chm");
    if (!h) {
        fprintf(stderr, "cannot open file\n");
        return 1;
    }

    /* user files only, no directories or internal ::DataSpace files */
    chm_enumerate(h, CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_FILES, print_entry, NULL);

    struct chmUnitInfo ui;
    if (chm_resolve_object(h, "/index.html", &ui) == CHM_RESOLVE_SUCCESS) {
        unsigned char *buf = malloc(ui.length + 1);
        LONGINT64 n = chm_retrieve_object(h, &ui, buf, 0, ui.length);
        buf[n > 0 ? n : 0] = 0;
        printf("%s\n", buf);
        free(buf);
    }
    chm_close(h);
    return 0;
}

Paths start with / and are matched case-insensitively. chm_retrieve_object returns the number of bytes read.

How to extract all files from a CHM#

C
#include <chm_lib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static int extract(struct chmFile *h, struct chmUnitInfo *ui, void *ctx) {
    const char *outdir = ctx;
    if (ui->length == 0 || ui->path[0] != '/')
        return CHM_ENUMERATOR_CONTINUE;
    char dest[CHM_MAX_PATHLEN + 256];
    snprintf(dest, sizeof dest, "%s%s", outdir, ui->path);
    /* create parent directories of dest here (mkdir -p) */
    unsigned char *buf = malloc(ui->length);
    if (buf && chm_retrieve_object(h, ui, buf, 0, ui->length) == (LONGINT64)ui->length) {
        FILE *f = fopen(dest, "wb");
        if (f) {
            fwrite(buf, 1, ui->length, f);
            fclose(f);
        }
    }
    free(buf);
    return CHM_ENUMERATOR_CONTINUE;
}

/* chm_enumerate(h, CHM_ENUMERATE_NORMAL | CHM_ENUMERATE_FILES, extract, "out"); */

Return CHM_ENUMERATOR_SUCCESS from the callback to stop early, CHM_ENUMERATOR_FAILURE to abort with an error.

How to list one folder#

C
chm_enumerate_dir(h, "/images/", CHM_ENUMERATE_FILES, print_entry, NULL);

How to find the table of contents#

The .hhc file has no fixed name. Enumerate entries and pick the one ending in .hhc (the index ends in .hhk), or read the /#SYSTEM entry, which stores the names. The .hhc file is HTML with <object type="text/sitemap"> elements; each has <param name="Name"> (the title) and <param name="Local"> (the page path).

Tips#