前一篇介绍了写的一个VC工程打包工具,转换打包好之后用QuickCHM编译成CHM帮助文件,但是如果以后又下载了
或者自己又写了一些程序,想把它们也加入到先前的CHM文件之中怎么办呢?总不能每次都把以前转换好的工程重新用
QuickCHM编译一遍,那样太麻烦了。于是想在网上找一个CHM合并工具,找了很久只在VckBase上找到一个Magic CHM Merge
的公具说是可以合并chm,但是不知道是因为我下载的是绿色版还是其他什么原因,这个工具老是用不起来,要么总是提示我
选择的文件不在一个工程文件夹内,要么就是合并中文CHM时莫名其妙的退出,有时候运气好能合并成功(郁闷了一两天)。
于是没办法自己用vc++写了一个CHM合并工具,它能将已经编译好的CHM合并到一个CHM文件中。和Magic CHM Merge
一样,这种合并其实并不是真正意义上的把多个CHM的内容合并到一个chm中,而只是把多个CHM文件中的主题的链接加入
到一个chm中,这样只要打开编译好的那个CHM帮助文件就能访问所有的内容。就像MSDN一样,用这个程序你也能打造自己的
MSDN。如果要真正的把多个CHM中的内容合并到一个CHM中,则需要先把所有需要合并的chm反编译,然后把反编译得到的文
件一起同一编译成一个CHM,这样太费时间也没必要。
工程源代码下载地址:http://download.csdn.net/source/2246858
程序运行图:
这个程序实际是利用Microsoft发布的CHM制作工具HTML Help Workshop来合并chm的。所以需要根据用户选择的要合并的CHM
生成相应的hhp工程文件和hhc主题列表文件,然后用HTML Help Workshop的控制台程序hhc.exe来编译生成合并的CHM文件(
hhc.exe运行时需要一个dll文件: itcc.dll,只要是安装了HTML Help Workshop都会有,本程序已经自带)。先说一下hhp工程文件
和hhc主题列表文件的格式:
hhp文件(HTML Help Workshop工程文件)内容:
[OPTIONS]
Compatibility=1.1 or later //编译器版本
Compiled file=VcKbase.chm //编译后的chm文件名
Contents file=vckbase.hhc //主题列表文件名
Default Font=宋体,11,134 //左边面板的字体
Default Window=Main
Default topic=hello.htm //主页
Display compile progress=Yes
Enhanced decompilation=Yes
Full-text search=Yes //全文搜索
Index file=Vckbase.hhk //索引文件,可以没有
Language=0x804 中文(中国) //语言
Title=VcKbase合订本 //编译后的chm文件的标题
[WINDOWS]
Main="VcKbase合订本","vckbase.hhc",,"hello.htm",,,,,,0x21420,180,0x186e,[80,60,720,540],0x10B0000,,,,2,,0
[MERGE FILES] //要合并的chm文件列表
vckbase01.chm
vckbase02.chm
vckbase03.chm
vckbase04.chm
[INFOTYPES]
hhc文件(主题类表文件)内容格式:
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
<HTML>
<HEAD>
<meta name="GENERATOR" content="Microsoft® HTML Help Workshop 4.1">
<!-- Sitemap 1.0 -->
</HEAD>
<BODY>
<OBJECT type="text/site properties">
<param name="FrameName" value="right">
<param name="ImageType" value="Book">
<param name="ExWindow Styles" value="0x800025">
</OBJECT>
<UL>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase01">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase01.chm::/Toc1.hhc"> //toc1.hhc是主题列表文件,已经编译压缩到vckbase01.chm中
</OBJECT>
</LI>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase02">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase02.chm::/toc.hhc">
</OBJECT>
</LI>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase03">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase03.chm::/toc.hhc">
</OBJECT>
</LI>
<LI><OBJECT type="text/sitemap">
<param name="Name" value="vckbase04">
</OBJECT>
<OBJECT type="text/sitemap">
<param name="Merge" value="vckbase04.chm::/toc.hhc">
</OBJECT>
</LI>
</UL>
</BODY>
</HTML>
通过以上这两个文件的例子不难看出,只要按照上面的格式生成了相应的hhp和hhc文件,然后调用hhc.exe就能生成合并的chm了,估计
magic Merge CHM的原理也是如此。但是有一个难点是如何知道已经编译在chm中的主题文件名,例如上面hhc文件里的一句:
vckbase01.chm::/Toc1.hhc,只有知道Toc1.hhc这个文件名才能正确链接合并vckbase01.chm。某个chm中包含的hhc文件到底叫什么,
是看不出来的,只有从vckbase01.chm文件中查找该文件名。通过分析了七八个chm文件,发现每个CHM文件从第0xe6这个字节开始往后
会有一段类似于配置的字符串,这段字符串里面就有我们需要的hhc文件名,只要从0xe6字节开始查找就能找到内部包含的hhc文件名(想了
半天也只想到这个笨办法)。后来又分析了几个中文chm的情况,发现这些字符串是UTF-8编码:
CString CMergeCHMDlg::ReadHHCName(CString strPath)
{
char path[255];
WCharToAChar(strPath.GetBuffer(),path,255);
FILE* fr=fopen(path,"rb");
if(!fr)
{
return _T("读取错误!");
}
fseek(fr,0xe6,SEEK_SET);
while(!feof(fr))
{
char c;
fread(&c,sizeof(char),1,fr);
if(c=='.')
{
fread(&c,sizeof(char),1,fr);
if(c=='h'||c=='H')
{
fread(&c,sizeof(char),1,fr);
if(c=='h'||c=='H')
{
fread(&c,sizeof(char),1,fr);
if(c=='c'||c=='C')
{
BYTE be;
fread(&be,sizeof(BYTE),1,fr);
if(be==0x01)
{
fseek(fr,-1,SEEK_CUR);
long pos=ftell(fr);
fseek(fr,-5,SEEK_CUR);
fread(&c,sizeof(char),1,fr);
while(c!='/' && 0xe6<=ftell(fr))
{
fseek(fr,-2,SEEK_CUR);
fread(&c,sizeof(char),1,fr);
}
fseek(fr,-2,SEEK_CUR);
fread(&be,sizeof(BYTE),1,fr);
if(be<=0x20)
{
fseek(fr,1,SEEK_CUR);
long len=pos-ftell(fr);
char* hhc=(char*)calloc(len+4,sizeof(char));
fread(hhc,sizeof(char),len,fr);
wchar_t* whhc=(wchar_t*)calloc(len+4,sizeof(wchar_t));
DWORD flag=MultiByteToWideChar(CP_UTF8, 0, hhc, -1, NULL, 0);
MultiByteToWideChar (CP_UTF8, 0, hhc, -1, whhc, len+4);
CString str(whhc);
free(hhc);
free(whhc);
fclose(fr);
return str;
}
else
{
fseek(fr,pos,SEEK_SET);
}
}
else
fseek(fr,-4,SEEK_CUR);
}
else
fseek(fr,-3,SEEK_CUR);
}
else
fseek(fr,-2,SEEK_CUR);
}
else
fseek(fr,-1,SEEK_CUR);
}
}
fclose(fr);
return _T("读取失败");
}
找到了文件名,其他的一切都好办了,接下来生成相应的hhc工程文件和hhc主题列表,调用hhc.exe编译就好了:
#include <locale>//头文件
CString CMergeCHMDlg::CreateHHPFile()
{
int n=m_strSavePath.ReverseFind('//');
CString strFolder=m_strSavePath.Left(n);
CString strPath=strFolder+_T("//")+m_saveName+_T(".hhp");
CStdioFile fileW;
fileW.Open(strPath,CFile::modeCreate | CFile::modeWrite);
char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) );
setlocale( LC_CTYPE, "chs" );//设定
fileW.WriteString(_T("[OPTIONS]/r/n"));
fileW.WriteString(_T("Compatibility=1.1 or later/r/n"));
fileW.WriteString(_T("Compiled file=")+m_saveName+_T(".chm/r/n"));
fileW.WriteString(_T("Contents file=")+m_saveName+_T(".hhc/r/n"));
fileW.WriteString(_T("Default Font=宋体,10,134/r/n"));
fileW.WriteString(_T("Default Window=Main/r/n"));
fileW.WriteString(_T("Default topic=")+m_strTopic+_T("/r/n"));
fileW.WriteString(_T("Display compile progress=Yes/r/n"));
fileW.WriteString(_T("Enhanced decompilation=Yes/r/n"));
fileW.WriteString(_T("Full-text search=Yes/r/n"));
fileW.WriteString(_T("Language=0x804 中文(中国)/r/n"));
fileW.WriteString(_T("Title=")+m_strTitle+_T("/r/n/r/n"));
fileW.WriteString(_T("[WINDOWS]/r/n"));
fileW.WriteString(_T("Main=/"")+m_strTitle+_T("/",/"")+m_saveName+_T(".hhc/", ,/"")+m_strTopic+_T("/",/"")+m_strTopic+_T("/",,,,,0x21420,250,0x10304E,[80,60,1000,700],0x10B0000,,,,0,,0/r/n/r/n"));
fileW.WriteString(_T("[MERGE FILES]/r/n"));
int ncount=m_List.GetItemCount();
for(int i=0;i<ncount;i++)
{
CString strText=m_List.GetItemText(i,2);
int nsp=strText.ReverseFind('//');
CString strName=strText.Right(strText.GetLength()-nsp-1);
fileW.WriteString(strName+_T("/r/n"));
}
fileW.WriteString(_T("/r/n[INFOTYPES]"));
setlocale( LC_CTYPE, old_locale );
free( old_locale );//还原区域设定
fileW.Close();
return strPath;
}
CString CMergeCHMDlg::CreateHHCFile() //生成合并chm的hhc文件
{
int n=m_strSavePath.ReverseFind('//');
CString strFolder=m_strSavePath.Left(n);
CString strPath=strFolder+_T("//")+m_saveName+_T(".hhc");
CStdioFile fileW;
fileW.Open(strPath,CFile::modeCreate | CFile::modeWrite);
char* old_locale = _strdup( setlocale(LC_CTYPE,NULL) );
setlocale( LC_CTYPE, "chs" );//设定
fileW.WriteString(_T("<!DOCTYPE HTML PUBLIC /"-//IETF//DTD HTML//EN/">/r/n"));
fileW.WriteString(_T("<HTML>/r/n<HEAD>/r/n"));
fileW.WriteString(_T("<meta name=/"GENERATOR/" content=/"Microsoft® HTML Help Workshop 4.1/">/r/n"));
fileW.WriteString(_T("<!-- Sitemap 1.0 -->/r/n</HEAD>/r/n<BODY>/r/n<OBJECT type=/"text/site properties/">/r/n"));
fileW.WriteString(_T("<param name=/"FrameName/" value=/"right/">/r/n"));
fileW.WriteString(_T("<param name=/"ImageType/" value=/"Book/">/r/n"));
fileW.WriteString(_T("<param name=/"ExWindow Styles/" value=/"0x800025/">/r/n</OBJECT>/r/n<UL>/r/n/r/n"));
int ncount=m_List.GetItemCount();
for(int i=0;i<ncount;i++)
{
fileW.WriteString(_T("<LI><OBJECT type=/"text/sitemap/">/r/n"));
CString title=m_List.GetItemText(i,0);
CString File=m_List.GetItemText(i,2);
CString hhc=m_List.GetItemText(i,3);
int nsp=File.ReverseFind('//');
File=File.Right(File.GetLength()-nsp-1);
fileW.WriteString(_T("<param name=/"Name/" value=/"")+title+_T("/">/r/n</OBJECT>/r/n<OBJECT type=/"text/sitemap/">/r/n"));
fileW.WriteString(_T("<param name=/"Merge/" value=/"")+File+_T(":://")+hhc+_T("/">/r/n</OBJECT>/r/n</LI>/r/n/r/n"));
}
fileW.WriteString(_T("</UL>/r/n</BODY>/r/n</HTML>/r/n"));
setlocale( LC_CTYPE, old_locale );
free( old_locale );//还原区域设定
fileW.Close();
return strPath;
}
void CMergeCHMDlg::OnBnClickedMakemerge() //点击“编译合并”按钮的消息函数
{
// TODO: 在此添加控件通知处理程序代码
UpdateData(TRUE);
if(m_List.GetItemCount()<=0)
{
AfxMessageBox(_T("请先添加要合并的帮助文件!"));
OnBnClickedAdd();
return;
}
if(m_strTitle.IsEmpty())
{
AfxMessageBox(_T("请先填写合并后的标题!"));
return ;
}
if(m_strSavePath.IsEmpty())
{
AfxMessageBox(_T("请先选择合并后保存的位置!"));
return ;
}
if(m_strTopic.IsEmpty())
{
AfxMessageBox(_T("请先设定主页文件!"));
return;
}
if(!IsInOneDirectory())
{
//MessageBox(_T("需要合并的所有子文件和合并以后的帮助文件必须在同一文件夹内,请检查!"),_T("提示"),MB_ICONWARNING|MB_OK);
return;
}
CString strhhp=CreateHHPFile();
CString strhhc=CreateHHCFile();
CString cmdline=m_CurrentFolder+_T("//hhc.exe /"")+strhhp+_T("/""); //启动控制台程序的命令行
STARTUPINFO si;
GetStartupInfo(&si);
si.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
si.wShowWindow=SW_HIDE;
PROCESS_INFORMATION ProcessInformation;
int ret=CreateProcess(NULL, cmdline.GetBuffer(),NULL,NULL,1,0,NULL,NULL, &si,&ProcessInformation);
CDialog* pDlg=new CDialog;
pDlg->Create(IDD_TIPSDLG,this);
pDlg->ShowWindow(SW_SHOW);
WaitForSingleObject(ProcessInformation.hProcess,INFINITE);
CloseHandle(ProcessInformation.hProcess);
DeleteFile(strhhp.GetBuffer());
DeleteFile(strhhc.GetBuffer());
pDlg->DestroyWindow();
delete pDlg;
AfxMessageBox(_T("编译完成!"));
}
另外告诉某些想合并“VCKBase知识库在线杂志”所有chm的朋友,合并时把vckbase20.chm去掉,那个chm帮助文件本身就是
有问题的,所以会导致合并后的chm也会出现问题
可能有某些地方没有考虑到,程序存在某些bug,如果哪位朋友发现了,还请留言告知,谢谢……
vc写的chm合并工具(mergechm)
最新推荐文章于 2015-09-15 20:59:48 发布