嵌入并动态加载二进制资源及一个bug的解决
这次做的一个project需要对一个XML文件进行解析,为了保证文件的格式是正确的,于是在进行解析(parsing)之前需要先用schema验证一下,大致的代码如下(C++):
1: try
2: {
3: HRESULT hr;
4:
5: hr = pProductListXml.CreateInstance(__uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER);
6: if (FAILED(hr))
7: return hr;
8:
9: MSXML2::IXMLDOMDocument2Ptr pSchemaDoc;
10: hr = pSchemaDoc.CreateInstance(__uuidof(MSXML2::DOMDocument60), NULL, CLSCTX_INPROC_SERVER);
11: if (FAILED(hr))
12: return hr;
13:
14: // TODO: initialize the schema doc
15:
16: MSXML2::IXMLDOMSchemaCollection2Ptr pSchemaColl;
17: hr = pSchemaColl.CreateInstance(__uuidof(MSXML2::XMLSchemaCache60), NULL, CLSCTX_INPROC_SERVER);
18: if (FAILED(hr))
19: return hr;
20:
21: hr = pSchemaColl->add(L"", pSchemaDoc.GetInterfacePtr());
22: if (FAILED(hr))
23: return hr;
24:
25: pProductListXml->schemas = pSchemaColl.GetInterfacePtr();
26: pProductListXml->async = VARIANT_FALSE;
27: pProductListXml->validateOnParse = VARIANT_FALSE;
28: pProductListXml->resolveExternals = VARIANT_FALSE;
29:
30: if (VARIANT_TRUE != pProductListXml->load(productListFile))
31: return HRESULT_FROM_WIN32(ERROR_XML_PARSE_ERROR);
32:
33: MSXML2::IXMLDOMParseErrorPtr pError = pProductListXml->validate();
34:
35: if (S_OK != pError->errorCode)
36: return HRESULT_FROM_WIN32(ERROR_XML_PARSE_ERROR);
37: }
38: catch (_com_error e)
39: {
40: return e.Error();
41: }
看起来有点繁复,主要是因为是用C++写的关系。进行schema验证的也就是:
MSXML2::IXMLDOMParseErrorPtr pError = pProductListXml->validate();
这一句。
在处理如何初始化schema文件的时候,遇到了点问题。因为schema文件本质上也是一个XML文件,所以我打算将schema文件当做一个二进制资源加入到工程中,然后在运行时将其作为一个字符串直接从内存中加载进来,然后调用pSchemaDoc->loadXML就可以了。所以上面的
14: // TODO: initialize the schema doc
展开实现后就是这样的
1: _bstr_t schemaXml = GetProductsSchemaXml();
2:
3: if (_bstr_t(L"") == schemaXml || VARIANT_TRUE != pSchemaDoc->loadXML(schemaXml))
4: {
5: return HRESULT_FROM_WIN32(ERROR_XML_PARSE_ERROR);
6: }
没问题吧?
最后就是如何实现GetProductsSchemaXml()了,这个帮助函数的代码如下:
1: _bstr_t GetProductsSchemaXml()
2: {
3: HMODULE hDll = ::GetModuleHandle(NAME_OF_THIS_DLL);
4: if (NULL == hDll)
5: return _bstr_t(L"");
6:
7: HRSRC hResource = ::FindResource(hDll, MAKEINTRESOURCE(IDR_PRODUCTS_SCHEMA), L"BIN");
8: if (NULL == hResource)
9: return _bstr_t(L"");
10:
11: HGLOBAL hSchema = ::LoadResource(hDll, hResource);
12: if (NULL == hSchema)
13: return _bstr_t(L"");
14:
15: WCHAR* pSchemaXml = (WCHAR*)::LockResource(hSchema);
16: if (NULL == pSchemaXml)
17: return _bstr_t(L"");
18:
19: DWORD size = ::SizeofResource(hDll, hResource);
20:
21: return _bstr_t(CStringW(pSchemaXml, size));
22: }
嗯,思路是很简单的,取得带有该二进制资源的DLL的句柄,然后从DLL里加载资源,最后将该资源作为一个字符串返回。
XML schema就是一个xsd文件,我们可以通过资源编辑器来将它嵌入到编译成的DLL中:
请注意我把资源的类型定名为"BIN”,这个其实是无所谓的,你爱叫什么都可以,只是记得这里的类型要和FindResource的第三个参数一致(参看上面的代码:::FindResource(.., .., L”BIN”) )。
另外请注意因为我的代码全部是Unicode的(仔细的朋友应该注意到了诸如:L”…” 以及 "WCHAR*” 这类明显的Unicode代码的特征),所以products.xsd这个文件也是以Unicode格式存盘的:
好了,到此为止工作基本上就完成了。
------------------------------------------------------------------------ 自以为工作已经结束的分割线 ---------------------------------------------------------------------------
但是,在实际测试的时候,pSchemaDoc->loadXML却一直返回VARIANT_FALSE这个失败的结果。怎么回事呢。
开动debugger来检视pSchemaXml(或者hResource,或者hSchema,这三者在这里其实都指向同一个内存地址,当然从语义上它们是不同的)指向的内存区,觉得内容看起来是对的呀:
调试了很久,一直到打开内存观察器(从菜单的Debug->Windows->Memory那里打开)我才意识到问题所在:
大家注意到红色框中的内容了么?也还记得刚才我说因为程序是Unicode的,所以我把products.xsd也以Unicode编码存盘的么?这就是问题所在了。因为UTF-16编码(UTF-16是Unicode的一种)文件的开头都有这个两字节的文件头(2-byte header),主要是用来区分UTF-16和UTF-8的,但就是这个“自动”多加出来的两个字节,使得pSchemaDoc->loadXML无法正确理解字符串(因为完全无法理解开头的FFFE这两个字节),所以当然就失败了。
另外顺便说一句:FFFE这个2 byte header也叫Byte Order Mark,也就是用来标记文件是low-endian的还是big-endian的。FFFE指的是low-endian,而FEFF指的是big-endian,有兴趣的同学可以试试在notepad里制定用Unicode big endian来存盘,然后再用任何十六进制编辑器看看开头的两个字节为何。
事情到这里也有了解决方案了,我只要在实际取得pSchemaXml的时候在LockResource返回的内存地址上再加2个bytes就行了。
可是,最后我发现其实还有更简单的方法,因为各种的混乱,我自己疏忽了。
事实上,你是可以用一个单字节的字符串(也就是ANSI编码的字符串)来初始化一个CStringW的对象的,所以根本就没有必要特意将products.xsd存为Unicode,所以最后我直接将products.xsd以ANSI编码存盘,然后GetProdutsSchemaXml这么写就可以了:
1: _bstr_t GetProductsSchemaXml()
2: {
3: HMODULE hDll = ::GetModuleHandle(NAME_OF_THIS_DLL);
4: if (NULL == hDll)
5: return _bstr_t(L"");
6:
7: HRSRC hResource = ::FindResource(hDll, MAKEINTRESOURCE(IDR_PRODUCTS_SCHEMA), L"BIN");
8: if (NULL == hResource)
9: return _bstr_t(L"");
10:
11: HGLOBAL hSchema = ::LoadResource(hDll, hResource);
12: if (NULL == hSchema)
13: return _bstr_t(L"");
14:
15: // the products.xsd resource is stored in ANSI encoding, so we cast the pointer to char* instead of WCHAR*
16: char* pSchemaXml = (char*)::LockResource(hSchema);
17: if (NULL == pSchemaXml)
18: return _bstr_t(L"");
19:
20: DWORD size = ::SizeofResource(hDll, hResource);
21:
22: return _bstr_t(CStringW(pSchemaXml, size));
23: }
总之不算什么了不起的软件技巧啦,只是所有这些小东西加起来可能还有点意思,因此写下来和大家分享。
posted on 2009-09-09 16:51:37 by demonfox 评论(0) 阅读(3169)