بمجرد تجاوز حد الألم في Borrow-Checker وإدراك أن Rust يتيح لك القيام بأشياء لا يمكن تخيلها (وأحيانًا تكون خطرة) بلغات أخرى ، قد تكون لديك أيضًا نفس الرغبة التي لا تقاوم في إعادة كتابة كل شيء إلى Rust . على الرغم من أنه في أفضل الأحوال ، يكون هذا الأمر غير منتج (جهود تبديد لا معنى لها للعديد من المشاريع) ، ولكنه في أسوأ الأحوال يؤدي إلى انخفاض في جودة الكود (بعد كل شيء ، لماذا تعتبر نفسك أكثر خبرة في مجال استخدام المكتبة من مؤلفها الأصلي؟)
سيكون من المفيد توفير واجهة آمنة للمكتبة الأصلية من خلال إعادة استخدام الكود.
• الخطوات الأولى
• نقوم بتجميع chmlib-sys
• كتابة غلاف آمن في الصدأ
• البحث عن العناصر بالاسم
• تجاوز العناصر عن طريق التصفية
• قراءة محتويات الملف
• أضف أمثلة
• جدول محتويات ملف CHM
• تفريغ ملف CHM إلى القرص
• ماذا بعد؟
يناقش هذا المقال مشروع حقيقي. كان عليّ استخراج المعلومات من ملفات CHM الموجودة ، لكن لم يكن هناك وقت لفهم التنسيق. الكسل هو محرك التقدم.
يتم نشر صندوق chmlib على موقع crates.io ، ويتوفر كود المصدر الخاص به على GitHub . إذا وجدت أنها مفيدة أو وجدت مشكلات فيها ، فأخبرني بذلك من خلال bugtracker .
الخطوات الأولى
بادئ ذي بدء ، يجدر فهم كيف تم تصميم العمل مع المكتبة في الأصل.
هذا لن يعلمك فقط كيفية استخدامه ، ولكن أيضًا يتأكد من أن كل شيء يسير. إذا كنت محظوظًا ، فستجد اختبارات وأمثلة جاهزة.
لا تخطي هذه الخطوة!
سنعمل مع CHMLib ، مكتبة C لقراءة ملفات تعليمات HTML المترجمة من Microsoft ( .chm
).
لنبدأ بإنشاء مشروع جديد وتوصيل CHMLib كوحدة فرعية 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.
بعد ذلك ، ألقِ نظرة على ما بداخل 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
يبدو أن المكتبة تستخدم GNU Autotools للبناء. هذا ليس جيدًا ، لأن جميع مستخدمي صندوق chmlib (ومستخدميه) سيحتاجون إلى تثبيت Autotools.
سنحاول التخلص من هذه التبعية "المعدية" عن طريق جمع الكود "C" يدويًا ، لكن المزيد عن ذلك لاحقًا.
تحتوي ملفات lzx.h و lzx.c على تطبيق لوغاريتم ضغط LZX . بشكل عام ، سيكون من الأفضل استخدام نوع من مكتبة liblzx للحصول على تحديثات مجانًا وكل ذلك ، لكن ربما سيكون من الأسهل تجميع هذه الملفات بغباء.
يبدو أن enum_chmLib.c، enumdir_chmLib.c، extract_chmLib.c أمثلة على استخدام الدالات chm_enumerate ()، chm_enumerate_dir ()، chm_retrieve_object (). وسوف يأتي في متناول اليدين ...
يحتوي الملف test_chmLib.c على مثال آخر ، هذه المرة استخراج صفحة واحدة من ملف CHM إلى القرص.
يقوم chm_http.c بتنفيذ خادم HTTP بسيط يعرض ملف .chm في المستعرض. هذا ، ربما ، لن يكون مفيدا.
لذلك قمنا بتسوية كل ما هو موجود في البائع / CHMLib / src. هل سنجمع المكتبة؟
بصراحة ، أنها صغيرة بما يكفي لتطبيق طريقة كزة العلمية.
$ 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)
حسنا ، ربما لا تزال هناك حاجة إلى هذا LZX ...
$ clang chm_lib.c enum_chmLib.c lzx.c -o enum_chmLib
اه ... كل شيء؟
للتأكد من عمل الشفرة ، قمت بتنزيل مثال من الإنترنت:
$ 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
دعونا نرى كيف يعالج enum_chmLib ذلك:
$ ./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
يا رب ، حتى هنا مسج ¯ \ _ (ツ) _ / ¯
بناء chmlib- تميز الكلية
الآن نحن نعرف ما يكفي لاستخدام CHMLib في قفص chmlib -sys ، وهو المسؤول عن بناء المكتبة الأصلية ، وربطها مع برنامج التحويل البرمجي Rast ، وواجهة لوظائف C.
لبناء المكتبة تحتاج إلى كتابة ملف build.rs
. باستخدام صندوق cc ، سيتصل بالمترجم C ويقوم بالصداقات الأخرى حتى يعمل كل شيء معًا كما ينبغي.
نحن محظوظون لأننا نستطيع تحويل معظم الأعمال إلى سم مكعب ، ولكن في بعض الأحيان يكون الأمر أكثر صعوبة. اقرأ المزيد في وثائق البرامج النصية للتجميع .
أولاً أضف cc كتبعية لـ 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
ثم نكتب build.rs
:
تحتاج أيضًا إلى إخبار Cargo بأن روابط chmlib-sys إلى مكتبة chmlib. ثم يضمن Cargo أنه في الرسم البياني التبعية بأكمله يوجد رف واحد فقط ، اعتمادًا على المكتبة الأصلية المحددة. يؤدي ذلك إلى تجنب رسائل الخطأ الغامضة حول الأحرف المتكررة أو الاستخدام غير المقصود للمكتبات غير المتوافقة.
بعد ذلك ، نحتاج إلى الإعلان عن جميع الوظائف التي تم تصديرها بواسطة مكتبة chmlib حتى يمكن استخدامها من Rast.
ولهذا الغرض يوجد مشروع bindgen الرائع. يتم إعطاء ملف الرأس C إلى الإدخال ، ويتم إخراج الملف ذو روابط FFI لـ Rast.
$ 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; }
أوصي بشدة بقراءة دليل المستخدم Bindgen إذا كنت بحاجة إلى إصلاح شيء ما في العادم الخاص به.
في هذه المرحلة ، سيكون من المفيد كتابة اختبار الدخان الذي سيتحقق من أن كل شيء يعمل بالشكل المتوقع وأنه يمكننا بالفعل استدعاء وظائف مكتبة C الأصلية.
يقول cargo test
أن كل شيء يبدو جيدًا:
$ 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
كتابة غلاف آمن في Rust
من الناحية الفنية والتقنية ، يمكننا الآن استدعاء CHMLib من الراستا ، ولكن هذا يتطلب كومة غير آمنة . قد يكون هذا الأمر مفيدًا بالنسبة لمركبة تم اختراقها ، ولكن للنشر على الصناديق. يستحق الأمر كتابة غلاف آمن لجميع الشفرات غير الآمنة.
إذا نظرت إلى واجهة برمجة تطبيقات chmlib-sys باستخدام cargo doc --open
، يمكنك رؤية العديد من الوظائف التي تأخذ *mut ChmFile
كوسيطة أولى. هذا يشبه الكائنات والأساليب.
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 */
لنبدأ بنوع البيانات ، الذي يستدعي chm_open () في المنشئ ، و chm_close () في المدمر.
pub unsafe extern "C" fn chm_open(filename: *const c_char) -> *mut chmFile; pub unsafe extern "C" fn chm_close(h: *mut chmFile);
لتبسيط معالجة الأخطاء ، نستخدم صندوق الإرهاب هذا ، والذي يقوم بتنفيذ std::error::Error
تلقائيًا.
$ cd chmlib $ cargo add thiserror
الآن تحتاج إلى معرفة كيفية تحويل std::path::Path
إلى *const c_char
. لسوء الحظ ، هذا ليس بالأمر السهل بسبب النكات المختلفة المتوافقة .
الآن تحديد هيكل ChmFile . يقوم بتخزين مؤشر غير فارغة إلى chmlib_sys :: chmFile. إذا أرجعت chm_open () مؤشرًا خاليًا ، فهذا يعني أنها لا تستطيع فتح الملف بسبب نوع من الخطأ.
للتأكد من عدم وجود تسرب للذاكرة ، قم بإجراء اختبار بسيط تحت Valgrind . سوف يقوم بإنشاء ملف ChmFile وإطلاقه على الفور.
يقول Valgrind إنه لا توجد ذاكرة مفقودة:
$ 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)
البحث عن العناصر بالاسم
التالي في السطر هو وظيفة 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;
قد تفشل عملية البحث ، لذلك تقوم chm_resolve_object () بإرجاع رمز خطأ للإبلاغ عن النجاح أو الفشل ، وسيتم تسجيل المعلومات حول الكائن الموجود بواسطة المؤشر الذي تم تمريره إلى chmUnitInfo .
تم إنشاء النوع std::mem::MaybeUninit
فقط std::mem::MaybeUninit
مع المعلمة خارج ui .
الآن ، دعنا نترك بنية UnitInfo فارغة - هذا هو المكافئ الصدأ لهيكل chmUnitInfo C. سنضيف الحقول عندما نبدأ القراءة من ChmFile.
لاحظ أن ChmFile :: find () يقبل &mut self
، على الرغم من أن الكود الموجود في Rast لا يحتوي على تغيير صريح في الحالة. الحقيقة هي أن تطبيق C يستخدم كل أنواع fseek () للتنقل حول الملف ، وبالتالي فإن الحالة الداخلية لا تزال تتغير أثناء البحث.
دعونا نتحقق من ChmFile :: find () في الملف التجريبي الذي قمنا بتنزيله مسبقًا:
تصفية العناصر الالتفافية
يوفر CHMLib واجهة برمجة تطبيقات لعرض محتويات ملف CHM من خلال مرشح قناع بت.
خذ صندوق علامات البت المناسب للعمل مع الأقنعة والأعلام:
$ cargo add bitflags Updating 'https://github.com/rust-lang/crates.io-index' index Adding bitflags v1.2.1 to dependencies
وحدد خانات الاختيار تصفية استنادًا إلى الثوابت من chm_lib.h:
نحتاج أيضًا إلى محول extern "C"
لإغلاقات Rastovyh ، والذي يمكن تمريره إلى C في شكل مؤشر إلى دالة:
يحتوي function_wrapper
على رمز غير آمن صعب تحتاج إلى استخدامه:
- يجب أن يشير مؤشر
state
إلى مثيل الإغلاق F. - رمز الراستا تنفيذها عن طريق إغلاق يمكن أن يسبب الذعر. لا ينبغي أن يعبر الحدود بين Rast و C ، لأن الترويج المكدس بلغات مختلفة هو سلوك غير محدد. يجب اعتراض الذعر المحتمل باستخدام
std::panic::catch_unwind()
. - يتم أيضًا تخزين مؤشر إلى chmlib_sys :: chmFile مرت إلى function_wrapper في استدعاء ChmFile. خلال مدة المكالمة ، يجب عليك التأكد من أن الإغلاق فقط هو الذي يمكنه معالجة chmlib_sys :: chmFile ، وإلا فقد تحدث حالة سباق.
- يحتاج الإغلاق إلى تمرير
&mut ChmFile
، ولهذا ستحتاج إلى إنشاء كائن مؤقت على المكدس باستخدام المؤشر الموجود. ومع ذلك ، إذا كان destructor ChmFile يعمل في هذه الحالة ، فسيتم تحرير chmlib_sys :: chmFile في وقت قريب جدًا. لحل هذه المشكلة ، يوجد std::mem::ManuallyDrop
.
هذا هو كيفية استخدام ChmFile::for_each()
لتطبيق ChmFile::for_each()
:
لاحظ كيفية تفاعل المعلمة F مع دالة function_wrapper العامة. غالبًا ما تستخدم هذه التقنية عندما تحتاج إلى تمرير إغلاق الصدأ عبر FFI إلى رمز بلغة أخرى.
قراءة محتويات الملف
الوظيفة الأخيرة التي نحتاجها هي المسؤولة عن قراءة الملف فعليًا باستخدام chm_retrieve_object ().
تنفيذه هو تافهة جدا. يشبه هذا سمة std :: io :: Read النموذجية ، باستثناء إزاحة ملف صريح.
بالطبع ، سيكون من الجيد أن يكون لديك رسالة خطأ أكثر تفصيلاً من "فشل في القراءة" ، ولكن إذا حكمنا من خلال شفرة المصدر ، فإن chm_retrieve_object () لا يميز بشكل خاص بين الأخطاء:
- إرجاع 0 عند قراءة الملف إلى النهاية ؛
- بإرجاع 0 للوسائط غير الصالحة: مؤشرات فارغة أو الخروج من الحدود؛
- إرجاع −1 على الأخطاء في قراءة الملفات بواسطة النظام (ويملأ errno) ؛
- تقوم بإرجاع −1 لأخطاء إلغاء الضغط ، دون التمييز بين تلف البيانات و ، على سبيل المثال ، عدم القدرة على تخصيص ذاكرة لمخزن مؤقت مؤقت عبر malloc ().
يمكنك اختبار ChmFile :: read () باستخدام ملفات ذات محتويات معروفة:
أضف أمثلة
لقد غطينا معظم واجهات برمجة التطبيقات الخاصة بمكتبة CHMLib وكثير منها قد انتهى من ذلك ، مع الأخذ في الاعتبار أن عملية النقل ستكتمل بنجاح. ومع ذلك ، سيكون من الجيد جعل رفنا أكثر سهولة في الاستخدام. — , 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), , .
ما التالي؟
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
.