Assim que você cruza o limiar de dor do verificador de empréstimos e percebe que o Rust permite que você faça coisas inimagináveis (e às vezes perigosas) em outros idiomas, você também pode ter o mesmo desejo irresistível de reescrever tudo para o Rust . Embora na melhor das hipóteses isso seja improdutivo banal (esforços desperdiçadores sem sentido para vários projetos), na pior das hipóteses leva a uma diminuição na qualidade do código (afinal, por que você se considera mais experiente no campo do uso da biblioteca do que seu autor original?)
Seria muito mais útil fornecer uma interface segura para a biblioteca original, reutilizando seu código.
• Primeiros passos
• Coletamos chmlib-sys
• Escrevendo um invólucro seguro no Rust
• Procure itens por nome
• Ignorar elementos por filtro
• Lendo o conteúdo do arquivo
Adicionar exemplos
• índice do arquivo CHM
• Descompactando o arquivo CHM no disco
• o que vem depois?
Este artigo discute um projeto real. Eu tive que extrair informações de arquivos CHM existentes, mas não houve tempo para entender o formato. A preguiça é o motor do progresso.
O chmlib crate é publicado em crates.io e seu código fonte está disponível no GitHub . Se você achar útil ou encontrar problemas, informe-me através do rastreador de erros .
Primeiros passos
Para começar, vale a pena entender como o trabalho com a biblioteca foi originalmente concebido.
Isso não apenas ensinará como usá-lo, mas também garantirá que tudo esteja indo bem. Se você tiver sorte, encontrará até testes e exemplos prontos.
Não pule esta etapa!
Trabalharemos com o CHMLib , uma biblioteca C para ler arquivos de Ajuda HTML Compilada da Microsoft ( .chm
).
Vamos começar criando um novo projeto e conectando o CHMLib como um submódulo git:
$ git init chmlib && cd chmlib Initialized empty Git repository in /home/michael/Documents/chmlib/.git/ $ touch README.md Cargo.toml $ cargo new --lib chmlib Created library `chmlib` package $ cargo new --lib chmlib-sys Created library `chmlib-sys` package $ cat Cargo.toml [workspace] members = ["chmlib", "chmlib-sys"] $ git submodule add git@github.com:jedwing/CHMLib.git vendor/CHMLib Cloning into '/home/michael/Documents/chmlib/vendor/CHMLib'... remote: Enumerating objects: 99, done. remote: Total 99 (delta 0), reused 0 (delta 0), pack-reused 99 Receiving objects: 100% (99/99), 375.51 KiB | 430.00 KiB/s, done. Resolving deltas: 100% (45/45), done.
Depois disso, dê uma olhada no que há dentro usando tree
:
$ tree vendor/CHMLib vendor/CHMLib ├── acinclude.m4 ├── AUTHORS ├── ChangeLog ├── ChmLib-ce.zip ├── ChmLib-ds6.zip ├── configure.in ├── contrib │ └── mozilla_helper.sh ├── COPYING ├── Makefile.am ├── NEWS ├── NOTES ├── README └── src ├── chm_http.c ├── chm_lib.c ├── chm_lib.h ├── enum_chmLib.c ├── enumdir_chmLib.c ├── extract_chmLib.c ├── lzx.c ├── lzx.h ├── Makefile.am ├── Makefile.simple └── test_chmLib.c 2 directories, 23 files
Parece que a biblioteca usa o GNU Autotools para construir. Isso não é bom, porque todos os usuários do chmlib crate (e seus usuários) precisarão instalar o Autotools.
Vamos tentar nos livrar dessa dependência "contagiosa" coletando o código C manualmente, mas mais sobre isso mais tarde.
Os arquivos lzx.he lzx.c contêm uma implementação do algoritmo de compactação LZX . Em geral, seria melhor usar algum tipo de biblioteca liblzx para obter atualizações de graça e tudo mais, mas talvez fosse mais fácil compilar estupidamente esses arquivos.
enum_chmLib.c, enumdir_chmLib.c, extract_chmLib.c parecem ser exemplos do uso das funções chm_enumerate (), chm_enumerate_dir (), chm_retrieve_object (). Será útil ...
O arquivo test_chmLib.c contém outro exemplo, desta vez extraindo uma página do arquivo CHM para o disco.
chm_http.c implementa um servidor HTTP simples, mostrando um arquivo .chm em um navegador. Provavelmente isso não será mais útil.
Então, resolvemos tudo o que está no fornecedor / CHMLib / src. Vamos coletar a biblioteca?
Honestamente, é pequeno o suficiente para aplicar o método científico do puxão.
$ clang chm_lib.c enum_chmLib.c -o enum_chmLib /usr/bin/ld: /tmp/chm_lib-537dfe.o: in function `chm_close': chm_lib.c:(.text+0x8fa): undefined reference to `LZXteardown' /usr/bin/ld: /tmp/chm_lib-537dfe.o: in function `_chm_decompress_region': chm_lib.c:(.text+0x18ca): undefined reference to `LZXinit' /usr/bin/ld: /tmp/chm_lib-537dfe.o: in function `_chm_decompress_block': chm_lib.c:(.text+0x2900): undefined reference to `LZXreset' /usr/bin/ld: chm_lib.c:(.text+0x2a4b): undefined reference to `LZXdecompress' /usr/bin/ld: chm_lib.c:(.text+0x2abe): undefined reference to `LZXreset' /usr/bin/ld: chm_lib.c:(.text+0x2bf4): undefined reference to `LZXdecompress' clang: error: linker command failed with exit code 1 (use -v to see invocation)
Ok, talvez esse LZX ainda seja necessário ...
$ clang chm_lib.c enum_chmLib.c lzx.c -o enum_chmLib
Uh ... tudo?
Para garantir que o código esteja funcionando, baixei um exemplo da Internet:
$ curl http://www.innovasys.com/static/hs/samples/topics.classic.chm.zip \ -o topics.classic.chm.zip $ unzip topics.classic.chm.zip Archive: topics.classic.chm.zip inflating: output/compiled/topics.classic.chm $ file output/compiled/topics.classic.chm output/compiled/topics.classic.chm: MS Windows HtmlHelp Data
Vamos ver como o enum_chmLib lida com isso:
$ ./enum_chmLib output/compiled/topics.classic.chm output/compiled/topics.classic.chm: spc start length type name === ===== ====== ==== ==== 0 0 0 normal dir / 1 5125797 4096 special file /#IDXHDR ... 1 4944434 11234 normal file /BrowserView.html ... 0 0 0 normal dir /flash/ 1 532689 727 normal file /flash/expressinstall.swf 0 0 0 normal dir /Images/Commands/RealWorld/ 1 24363 1254 normal file /Images/Commands/RealWorld/BrowserBack.bmp ... 1 35672 1021 normal file /Images/Employees24.gif ... 1 3630715 200143 normal file /template/packages/jquery-mobile/script/ jquery.mobile-1.4.5.min.js ... 0 134 1296 meta file ::DataSpace/Storage/MSCompressed/Transform/ {7FC28940-9D31-11D0-9B27-00A0C91E9C7C}/ InstanceData/ResetTable
Senhor, mesmo aqui jQuery ¯ \ _ (ツ) _ / ¯
Construa chmlib-sys
Agora sabemos o suficiente para usar o CHMLib na caixa chmlib -sys , responsável pela criação da biblioteca nativa, vinculando-a ao compilador Rast e uma interface para as funções C.
Para construir a biblioteca, você precisa gravar o arquivo build.rs
. Usando o cc crate, ele chamará o compilador C e fará outras amizades para que tudo funcione como deveria.
Temos a sorte de poder mudar a maior parte do trabalho para cc, mas às vezes é muito mais difícil. Leia mais na documentação para scripts de montagem .
Primeiro adicione cc como uma dependência para chmlib-sys:
$ cd chmlib-sys $ cargo add --build cc Updating 'https://github.com/rust-lang/crates.io-index' index Adding cc v1.0.46 to build-dependencies
Em seguida, escrevemos build.rs
:
Você também precisa informar ao Cargo que o chmlib-sys está vinculado à biblioteca chmlib. O Cargo pode garantir que em todo o gráfico de dependência haja apenas um rack, dependendo da biblioteca nativa específica. Isso evita mensagens de erro obscuras sobre caracteres repetidos ou o uso acidental de bibliotecas incompatíveis.
Em seguida, precisamos declarar todas as funções exportadas pela biblioteca chmlib para que possam ser usadas no Rast.
É por isso que existe um maravilhoso projeto bindgen . O arquivo de cabeçalho C é fornecido à entrada e o arquivo com as ligações FFI para Rast é gerado.
$ cargo install bindgen $ bindgen ../vendor/CHMLib/src/chm_lib.h \ -o src/lib.rs \ --raw-line '#![allow(non_snake_case, non_camel_case_types)]' $ head src/lib.rs /* automatically generated by rust-bindgen */ #![allow(non_snake_case, non_camel_case_types)] pub const CHM_UNCOMPRESSED: u32 = 0; pub const CHM_COMPRESSED: u32 = 1; pub const CHM_MAX_PATHLEN: u32 = 512; pub const CHM_PARAM_MAX_BLOCKS_CACHED: u32 = 0; pub const CHM_RESOLVE_SUCCESS: u32 = 0; pub const CHM_RESOLVE_FAILURE: u32 = 1; $ tail src/lib.rs extern "C" { pub fn chm_enumerate_dir( h: *mut chmFile, prefix: *const ::std::os::raw::c_char, what: ::std::os::raw::c_int, e: CHM_ENUMERATOR, context: *mut ::std::os::raw::c_void, ) -> ::std::os::raw::c_int; }
Eu recomendo a leitura do manual do usuário Bindgen, se você precisar consertar algo em seu escapamento.
Nesse estágio, será útil escrever um teste de fumaça que verifique se tudo funciona conforme o esperado e que podemos realmente chamar as funções da biblioteca C original.
cargo test
diz que tudo parece estar em ordem:
$ cargo test Finished test [unoptimized + debuginfo] target(s) in 0.03s Running ~/chmlib/target/debug/deps/chmlib_sys-2ffd7b11a9fd8437 running 1 test test bindgen_test_layout_chmUnitInfo ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Running ~/chmlib/target/debug/deps/smoke_test-f7be9810412559dc running 1 test test open_example_file ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out Doc-tests chmlib-sys running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out
Escrevendo um invólucro seguro no Rust
Tecnicamente e tecnicamente, agora podemos chamar CHMLib de Rasta, mas isso requer uma pilha insegura . Pode funcionar para um ofício hackney, mas para publicar em crates.io vale a pena escrever um invólucro seguro para todo o código não seguro.
Se você olhar a API chmlib-sys usando cargo doc --open
, poderá ver muitas funções que usam *mut ChmFile
como o primeiro argumento. Isso é semelhante aos objetos e métodos.
Arquivo de cabeçalho CHMLib #ifndef INCLUDED_CHMLIB_H #define INCLUDED_CHMLIB_H #ifdef __cplusplus extern "C" { #endif #ifdef PPC_BSTR #include <wtypes.h> #endif #ifdef WIN32 #ifdef __MINGW32__ #define __int64 long long #endif typedef unsigned __int64 LONGUINT64; typedef __int64 LONGINT64; #else typedef unsigned long long LONGUINT64; typedef long long LONGINT64; #endif /* the two available spaces in a CHM file */ /* NB: The format supports arbitrarily many spaces, but only */ /* two appear to be used at present. */ #define CHM_UNCOMPRESSED (0) #define CHM_COMPRESSED (1) /* structure representing an ITS (CHM) file stream */ struct chmFile; /* structure representing an element from an ITS file stream */ #define CHM_MAX_PATHLEN (512) struct chmUnitInfo { LONGUINT64 start; LONGUINT64 length; int space; int flags; char path[CHM_MAX_PATHLEN+1]; }; /* open an ITS archive */ #ifdef PPC_BSTR /* RWE 6/12/2003 */ struct chmFile* chm_open(BSTR filename); #else struct chmFile* chm_open(const char *filename); #endif /* close an ITS archive */ void chm_close(struct chmFile *h); /* methods for ssetting tuning parameters for particular file */ #define CHM_PARAM_MAX_BLOCKS_CACHED 0 void chm_set_param(struct chmFile *h, int paramType, int paramVal); /* resolve a particular object from the archive */ #define CHM_RESOLVE_SUCCESS (0) #define CHM_RESOLVE_FAILURE (1) int chm_resolve_object(struct chmFile *h, const char *objPath, struct chmUnitInfo *ui); /* retrieve part of an object from the archive */ LONGINT64 chm_retrieve_object(struct chmFile *h, struct chmUnitInfo *ui, unsigned char *buf, LONGUINT64 addr, LONGINT64 len); /* enumerate the objects in the .chm archive */ typedef int (*CHM_ENUMERATOR)(struct chmFile *h, struct chmUnitInfo *ui, void *context); #define CHM_ENUMERATE_NORMAL (1) #define CHM_ENUMERATE_META (2) #define CHM_ENUMERATE_SPECIAL (4) #define CHM_ENUMERATE_FILES (8) #define CHM_ENUMERATE_DIRS (16) #define CHM_ENUMERATE_ALL (31) #define CHM_ENUMERATOR_FAILURE (0) #define CHM_ENUMERATOR_CONTINUE (1) #define CHM_ENUMERATOR_SUCCESS (2) int chm_enumerate(struct chmFile *h, int what, CHM_ENUMERATOR e, void *context); int chm_enumerate_dir(struct chmFile *h, const char *prefix, int what, CHM_ENUMERATOR e, void *context); #ifdef __cplusplus } #endif #endif /* INCLUDED_CHMLIB_H */
Vamos começar com o tipo de dados, que chama chm_open () no construtor e chm_close () no destruidor.
pub unsafe extern "C" fn chm_open(filename: *const c_char) -> *mut chmFile; pub unsafe extern "C" fn chm_close(h: *mut chmFile);
Para simplificar o tratamento de erros, usamos a caixa thiserror , que std::error::Error
implementa automaticamente.
$ cd chmlib $ cargo add thiserror
Agora você precisa descobrir como transformar std::path::Path
em *const c_char
. Infelizmente, isso não é tão fácil de fazer devido a várias piadas com compatibilidade .
Agora defina a estrutura do ChmFile . Ele armazena um ponteiro não nulo para chmlib_sys :: chmFile. Se chm_open () retornar um ponteiro nulo, significa que ela não pôde abrir o arquivo devido a algum tipo de erro.
Para garantir que não haja vazamento de memória, execute um teste simples no Valgrind . Ele criará um ChmFile e o liberará imediatamente.
Valgrind diz que não há memória não contabilizada:
$ valgrind ../target/debug/deps/chmlib-8d8c740d578324 open_valid_chm_file ==8953== Memcheck, a memory error detector ==8953== Copyright (C) 2002-2017, and GNU GPL'd, by Julian Seward et al. ==8953== Using Valgrind-3.14.0 and LibVEX; rerun with -h for copyright info ==8953== Command: ~/chmlib/target/debug/deps/chmlib-8d8c740d578324 open_valid_chm_file ==8953== running 1 test test tests::open_valid_chm_file ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out ==8953== ==8953== HEAP SUMMARY: ==8953== in use at exit: 0 bytes in 0 blocks ==8953== total heap usage: 249 allocs, 249 frees, 43,273 bytes allocated ==8953== ==8953== All heap blocks were freed -- no leaks are possible ==8953== ==8953== For counts of detected and suppressed errors, rerun with: -v ==8953== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
Procure itens por nome
O próximo na linha é a função chm_resolve_object ():
pub const CHM_RESOLVE_SUCCESS: u32 = 0; pub const CHM_RESOLVE_FAILURE: u32 = 1; pub unsafe extern "C" fn chm_resolve_object( h: *mut chmFile, objPath: *const c_char, ui: *mut chmUnitInfo ) -> c_int;
A pesquisa pode falhar; portanto, chm_resolve_object () retorna um código de erro que relata êxito ou falha e as informações sobre o objeto encontrado serão registradas pelo ponteiro passado em chmUnitInfo .
O tipo std::mem::MaybeUninit
criado apenas para o nosso caso com o parâmetro out ui .
Por enquanto, vamos deixar a estrutura UnitInfo vazia - este é o equivalente em Rust da estrutura chmUnitInfo C. Adicionaremos os campos quando começarmos a ler do ChmFile.
Observe que ChmFile :: find () aceita &mut self
, embora o código no Rast não contenha uma alteração explícita de estado. O fato é que a implementação C usa todos os tipos de fseek () para se mover pelo arquivo, portanto o estado interno ainda muda durante a pesquisa.
Vamos verificar o ChmFile :: find () no arquivo experimental que baixamos anteriormente:
Filtrar itens de desvio
CHMLib fornece uma API para exibir o conteúdo de um arquivo CHM através de um filtro de máscara de bit.
Pegue a caixa de bitflags conveniente para trabalhar com máscaras e bandeiras:
$ cargo add bitflags Updating 'https://github.com/rust-lang/crates.io-index' index Adding bitflags v1.2.1 to dependencies
E defina as caixas de seleção Filtro com base nas constantes de chm_lib.h:
Também precisamos de um adaptador extern "C"
para fechamentos Rastovyh, que pode ser passado para C na forma de um ponteiro para uma função:
function_wrapper
contém um código inseguro complicado que você precisa para poder usar:
- O ponteiro de
state
deve apontar para a instância do fechamento F. - Código Rasta executado por um fechamento pode causar pânico. Não deve cruzar a fronteira entre Rast e C, pois a promoção de pilhas em idiomas diferentes é um comportamento indefinido. Um possível pânico deve ser interceptado usando
std::panic::catch_unwind()
. - Um ponteiro para chmlib_sys :: chmFile passado para function_wrapper também é armazenado no ChmFile chamado. Durante a chamada, você deve garantir que apenas o fechamento possa manipular chmlib_sys :: chmFile, caso contrário, poderá ocorrer uma condição de corrida.
- O fechamento precisa ser passado
&mut ChmFile
e, para isso, você precisa criar um objeto temporário na pilha usando o ponteiro existente. No entanto, se o destruidor ChmFile for executado nesse caso, o chmlib_sys :: chmFile será liberado muito cedo. Para resolver esse problema, existe std::mem::ManuallyDrop
.
É assim que function_wrapper é usado para implementar ChmFile::for_each()
:
Observe como o parâmetro F interage com a função function_wrapper genérica. Essa técnica é frequentemente usada quando você precisa passar o fechamento do Rust pelo FFI para codificar em outro idioma.
Lendo o conteúdo do arquivo
A última função que precisamos é responsável por realmente ler o arquivo usando chm_retrieve_object ().
Sua implementação é bastante trivial. Isso é semelhante a uma característica típica std :: io :: Read, com exceção de um deslocamento explícito de arquivo.
Obviamente, seria bom ter uma mensagem de erro mais detalhada do que "falha ao ler", mas, a julgar pelo código fonte, chm_retrieve_object () não distingue particularmente entre erros:
- retorna 0 quando o arquivo é lido até o final;
- retorna 0 para argumentos inválidos: ponteiros nulos ou fora dos limites;
- retorna -1 em erros na leitura de arquivos pelo sistema (e preenche errno);
- retorna -1 para erros de descompressão, sem distinguir a corrupção de dados e, por exemplo, a incapacidade de alocar memória para um buffer temporário via malloc ().
Você pode testar o ChmFile :: read () usando arquivos com conteúdo conhecido:
Adicione exemplos
Cobrimos a maior parte das APIs da biblioteca CHMLib e muitas terminariam com isso, considerando que a transferência foi concluída com êxito. No entanto, seria bom tornar nosso rack ainda mais fácil de usar. — , Rust Go ( , rustdoc godoc ).
, CHMLib , .
, , .
CHM-
CHM- .
#include "chm_lib.h" #include <stdio.h> #include <stdlib.h> #include <string.h> /* * callback function for enumerate API */ int _print_ui(struct chmFile *h, struct chmUnitInfo *ui, void *context) { static char szBuf[128]; memset(szBuf, 0, 128); if(ui->flags & CHM_ENUMERATE_NORMAL) strcpy(szBuf, "normal "); else if(ui->flags & CHM_ENUMERATE_SPECIAL) strcpy(szBuf, "special "); else if(ui->flags & CHM_ENUMERATE_META) strcpy(szBuf, "meta "); if(ui->flags & CHM_ENUMERATE_DIRS) strcat(szBuf, "dir"); else if(ui->flags & CHM_ENUMERATE_FILES) strcat(szBuf, "file"); printf(" %1d %8d %8d %s\t\t%s\n", (int)ui->space, (int)ui->start, (int)ui->length, szBuf, ui->path); return CHM_ENUMERATOR_CONTINUE; } int main(int c, char **v) { struct chmFile *h; int i; for (i=1; i<c; i++) { h = chm_open(v[i]); if (h == NULL) { fprintf(stderr, "failed to open %s\n", v[i]); exit(1); } printf("%s:\n", v[i]); printf(" spc start length type\t\t\tname\n"); printf(" === ===== ====== ====\t\t\t====\n"); if (! chm_enumerate(h, CHM_ENUMERATE_ALL, _print_ui, NULL)) printf(" *** ERROR ***\n"); chm_close(h); } return 0; }
_print_ui() Rust. UnitInfo , , .
main() , , describe_item() ChmFile::for_each().
:
$ cargo run --example enumerate-items topics.classic.chm > rust-example.txt $ cd vendor/CHMLib/src $ clang chm_lib.c enum_chmLib.c lzx.c -o enum_chmLib $ cd ../../.. $ ./vendor/CHMLib/src/enum_chmLib topics.classic.chm > c-example.txt $ diff -u rust-example.txt c-example.txt $ echo $? 0
diff , , , , . - , diff.
diff --git a/chmlib/examples/enumerate-items.rs b/chmlib/examples/enumerate-items.rs index e68fa58..ef855ac 100644 --- a/chmlib/examples/enumerate-items.rs +++ b/chmlib/examples/enumerate-items.rs @@ -36,6 +36,10 @@ fn describe_item(item: UnitInfo) { description.push_str("file"); } + if item.length() % 7 == 0 { + description.push_str(" :)"); + } + println!( " {} {:8} {:8} {}\t\t{}", item.space(),
:
$ cargo run --example enumerate-items topics.classic.chm > rust-example.txt $ diff -u rust-example.txt c-example.txt --- rust-example.txt 2019-10-20 16:51:53.933560892 +0800 +++ c-example.txt 2019-10-20 16:40:42.007053966 +0800 @@ -1,9 +1,9 @@ topics.classic.chm: spc start length type name
!
CHM-
, CHMLib, «» .
#include "chm_lib.h" #include <stdio.h> #include <stdlib.h> #include <string.h> #ifdef WIN32 #include <windows.h> #include <direct.h> #define mkdir(X, Y) _mkdir(X) #define snprintf _snprintf #else #include <unistd.h> #include <sys/stat.h> #include <sys/types.h> #endif struct extract_context { const char *base_path; }; static int dir_exists(const char *path) { #ifdef WIN32 /* why doesn't this work?!? */ HANDLE hFile; hFile = CreateFileA(path, FILE_LIST_DIRECTORY, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { CloseHandle(hFile); return 1; } else return 0; #else struct stat statbuf; if (stat(path, &statbuf) != -1) return 1; else return 0; #endif } static int rmkdir(char *path) { /* * strip off trailing components unless we can stat the directory, or we * have run out of components */ char *i = strrchr(path, '/'); if(path[0] == '\0' || dir_exists(path)) return 0; if (i != NULL) { *i = '\0'; rmkdir(path); *i = '/'; mkdir(path, 0777); } #ifdef WIN32 return 0; #else if (dir_exists(path)) return 0; else return -1; #endif } /* * callback function for enumerate API */ int _extract_callback(struct chmFile *h, struct chmUnitInfo *ui, void *context) { LONGUINT64 ui_path_len; char buffer[32768]; struct extract_context *ctx = (struct extract_context *)context; char *i; if (ui->path[0] != '/') return CHM_ENUMERATOR_CONTINUE; /* quick hack for security hole mentioned by Sven Tantau */ if (strstr(ui->path, "/../") != NULL) { /* fprintf(stderr, "Not extracting %s (dangerous path)\n", ui->path); */ return CHM_ENUMERATOR_CONTINUE; } if (snprintf(buffer, sizeof(buffer), "%s%s", ctx->base_path, ui->path) > 1024) return CHM_ENUMERATOR_FAILURE; /* Get the length of the path */ ui_path_len = strlen(ui->path)-1; /* Distinguish between files and dirs */ if (ui->path[ui_path_len] != '/' ) { FILE *fout; LONGINT64 len, remain=ui->length; LONGUINT64 offset = 0; printf("--> %s\n", ui->path); if ((fout = fopen(buffer, "wb")) == NULL) { /* make sure that it isn't just a missing directory before we abort */ char newbuf[32768]; strcpy(newbuf, buffer); i = strrchr(newbuf, '/'); *i = '\0'; rmkdir(newbuf); if ((fout = fopen(buffer, "wb")) == NULL) return CHM_ENUMERATOR_FAILURE; } while (remain != 0) { len = chm_retrieve_object(h, ui, (unsigned char *)buffer, offset, 32768); if (len > 0) { fwrite(buffer, 1, (size_t)len, fout); offset += len; remain -= len; } else { fprintf(stderr, "incomplete file: %s\n", ui->path); break; } } fclose(fout); } else { if (rmkdir(buffer) == -1) return CHM_ENUMERATOR_FAILURE; } return CHM_ENUMERATOR_CONTINUE; } int main(int c, char **v) { struct chmFile *h; struct extract_context ec; if (c < 3) { fprintf(stderr, "usage: %s <chmfile> <outdir>\n", v[0]); exit(1); } h = chm_open(v[1]); if (h == NULL) { fprintf(stderr, "failed to open %s\n", v[1]); exit(1); } printf("%s:\n", v[1]); ec.base_path = v[2]; if (! chm_enumerate(h, CHM_ENUMERATE_ALL, _extract_callback, (void *)&ec)) printf(" *** ERROR ***\n"); chm_close(h); return 0; }
. , .
extract(). , .
main() , extract(), .
CHM- HTML-, -.
$ cargo run --example extract -- ./topics.classic.chm ./extracted $ tree ./extracted ./extracted ├── default.html ├── BrowserForward.html ... ├── Images │ ├── Commands │ │ └── RealWorld │ │ ├── BrowserBack.bmp ... ├── script │ ├── _community │ │ └── disqus.js │ ├── hs-common.js ... └── userinterface.html $ firefox topics.classic/default.html ( default.html Firefox)
JavaScript ( - Microsoft Help), , .
O que vem a seguir?
chmlib , , crates.io.
:
- ChmFile::for_each() ChmFile::for_each_item_in_dir() , , .
- , ChmFile
Continuation::Continue
. , F: FnMut(&mut ChmFile, UnitInfo) -> C
C: Into<Continuation>
, impl From<()> for Continuation
. - (, extract()) ChmFile::for_each() .
impl<E> From<Result<(), E>> for Continuation where E: Error + 'static
. - -
std::fs::File
. , ChmFile::read() - std::io::Writer
.