NPAPI向Javascript传输中文的问题(二)
在上一篇文章《NPAPI向Javascript传输中文的问题(一)》(http://www.cnblogs.com/wliu6V/p/3245684.html)中,我们可以在NPAPI内部新建一字符串,并将其传到Javascript中进行显示。但后来发现仍有一些其他问题:当Javascript将字符串传递到NPAPI插件时,若NPAPI插件用上一篇文章所介绍的方式进行输出的话,就会出现一些编码问题。
问题当然是出在我们自己写的encodeURI方法上。因为这个encodeURI是从网上抄来的,当时也没太仔细研究。这个encodeURI方法中有这么一句:
MultiByteToWideChar(CP_ACP, NULL, Str, -1, Bufw, needSize);
与这么一句:
WideCharToMultiByte(CP_UTF8, NULL, Bufw, -1, Bufc, needSize, NULL, NULL);
当时刚看到这两句的时候感觉很不解,为什么要将传入的字符串先从MultiByte转到WideChar,然后再从WideChar转回MultiByte。但稍一留意,发现这两个方法的第一个参数是不同的。既然第二句话参数是UTF8,那么第一句话的参数想必是ANSI编码了。上网查了一下ANSI编码转为UTF8编码的方法,果然是走的这么一个步骤。所以这个encodeURI方法自然就是先将ANSI编码的字符串转为UTF8编码,然后再进行%.2X,将其转为十六进制。
知道了这个方法是如何对字符串进行处理的,我们就不难发现刚才的问题所在了。在NPAPI插件内部用的是ANSI编码,因此必须要有这么一步,将其转为UTF8然后发给Javascript。但是从Javascript那里得到的字符串应该已经是UTF8编码的了,如果再用同样的encodeURI执行一遍的话,自然就会出现问题。因此我们应该跳过encodeURI前面那些ANSI转UTF8的步骤,直接进行%.2X,将其转为十六进制即可。因此我们需要先对字符串进行判断,根据其编码方式采用不同的处理方式。
判断字符串编码的方法也是在网上扒来的,来自《判断字符串是否UTF8编码》(http://it.114study.com/ncre/article411272.html) 并有小幅修改(修改后的方法见文末)。这个方法可以判断传入的字符串是否为UTF8格式的。然后我们在encodeURI的时候先调用这个方法对字符串进行判断,若是的话就直接编码为16进制,若否的话就先从ANSI转为UTF8。其他的部分与之前的encodeURI保持相似即可。
上篇文章说过,encodeURI之后还要将”%”替换为”%25”,才能正常的通过NPN_GetURL方法传给Javascript代码。之前是单独写了一个ReplaceAll方法来进行此步骤,后来想了想,针对此插件的特定用途,我们可以直接把这个替换的过程整合到encodeURI中。也就是将原先的”%%%.2X”改成”%%25%.2X”即可,这样输出的结果就是 “%”+”25”+16进制编码 了。
最终我们得到的方法如下所示(需包含<stdio.h>与<malloc.h>):
1 /******************************************************************************* 2 * encodeURI 3 * 来自 http://zhidao.baidu.com/question/113953665.html 并有修改。 4 * 以C的方式实现了Javascript中的encodeURI方法。 5 * 其结果可以被Javascript通过DecodeURIComponent解码。 6 * 能够自动识别传入的参数是否为UTF8编码,并采取不同的encode方式。 7 *******************************************************************************/ 8 char* CScriptPluginObject::encodeURI(const char *Str) { 9 10 if(isEncodeUTF8(Str)) { 11 char* RTV = (char*)malloc(strlen(Str) *5 +1); 12 memset(RTV, 0x0, strlen(Str)); 13 unsigned char *pWork = (unsigned char *)Str; 14 while(*pWork != 0x0) { 15 sprintf(RTV + strlen(RTV), "%%25%.2X", *pWork); 16 pWork++; 17 } 18 return RTV; 19 20 } else { 21 22 // 先将ANSI转成UTF8 23 wchar_t *Bufw = NULL; 24 char *Bufc = NULL; 25 long needSize = MultiByteToWideChar(CP_ACP, 26 NULL, 27 Str, 28 -1, 29 NULL, 30 0); 31 if ( 0 == needSize ) goto ERROR_HANDLE; 32 33 Bufw = new wchar_t[needSize]; 34 if ( NULL == Bufw ) goto ERROR_HANDLE; 35 36 memset(Bufw,0x0,needSize*2); 37 MultiByteToWideChar(CP_ACP, 38 NULL, 39 Str, 40 -1, 41 Bufw, 42 needSize); 43 44 45 needSize = WideCharToMultiByte(CP_UTF8, 46 NULL, 47 Bufw, 48 -1, 49 NULL, 50 0, 51 NULL, 52 NULL); 53 if ( 0 == needSize ) goto ERROR_HANDLE; 54 55 Bufc = new char[needSize]; 56 if ( NULL == Bufc ) goto ERROR_HANDLE; 57 58 memset(Bufc,0x0,needSize); 59 WideCharToMultiByte(CP_UTF8, 60 NULL, 61 Bufw, 62 -1, 63 Bufc, 64 needSize, 65 NULL, 66 NULL); 67 68 // 进行转码 69 70 char* RTV = (char*)malloc(needSize *5 +1); // 一个字符转码变成五个字符 71 memset(RTV,0x0,(needSize *5 +1)); 72 unsigned char *pWork = (unsigned char *)Bufc; 73 if ( strlen(Bufc) > 5120 ) { goto ERROR_HANDLE; } 74 75 while( *pWork != 0x0 ) { 76 sprintf(RTV+strlen(RTV),"%%25%.2X",*pWork); 77 pWork++; 78 } 79 80 if ( NULL != Bufw ) { 81 delete [] Bufw; 82 Bufw = NULL; 83 } 84 if ( NULL != Bufc ) { 85 delete [] Bufc; 86 Bufc = NULL; 87 } 88 return RTV; 89 90 ERROR_HANDLE: 91 if ( NULL != Bufw ) { 92 delete [] Bufw; 93 Bufw = NULL; 94 } 95 if ( NULL != Bufc ) { 96 delete [] Bufc; 97 Bufc = NULL; 98 } 99 return NULL; 100 } 101 }
修改后的 isEncodeUTF8() 方法如下:
1 /******************************************************************************* 2 * isEncodeUTF8 3 * 来自 http://it.114study.com/ncre/article411272.html 并有小幅修改。 4 * 可以判断一个字符串是UTF8还是ANSI编码。 5 * 通常来说,在插件内部写的一般是ANSI编码,从JS代码传来的一般是UTF8编码。 6 *******************************************************************************/ 7 bool CScriptPluginObject::isEncodeUTF8(const char* str) { 8 int i; 9 DWORD nBytes = 0; // UTF8使用1~6个字节进行编码,而ASCII只用一个字节 10 UCHAR ch; 11 BOOL bAllAscii = true; // 若全部字符都是ASCII,则说明不是UTF-8 12 13 for(i = 0; i < strlen(str); i++) { 14 ch = *(str + i); 15 if( (ch & 0x80) != 0) // 判断是否为ASCII编码。如果不是,则有可能是UTF8。ASCII用7位编码,但使用一个字节存。最高位标记为0,即0xxxxxxx 16 bAllAscii = FALSE; 17 if (nBytes == 0) { // 若不是ASCII码,则应该是多字节符。计算字节数 18 if (ch >= 0x80) { 19 if ( (ch >= 0xFC) && (ch <= 0xFD) ) 20 nBytes = 6; 21 else if (ch >= 0xF8) 22 nBytes = 5; 23 else if (ch >= 0xF0) 24 nBytes = 4; 25 else if (ch >= 0xE0) 26 nBytes = 3; 27 else if (ch >= 0xC0) 28 nBytes = 2; 29 else 30 return false; 31 nBytes--; 32 } 33 } else { // nBytes != 0 多字节符的非首字节,应为10xxxxxx 34 if ( (ch & 0xC0) != 0x80) 35 return false; 36 nBytes--; 37 } 38 } 39 40 if (nBytes > 0) // 违反规则 41 return false; 42 43 if (bAllAscii) // 若全部为ASCII,则说明不是UTF8 44 return false; 45 46 return true; 47 }
调用时如下所示:
1 /***************************************************************************************** 2 * popJavascriptAlert 3 * 将传入的CString以Alert的形式弹出在浏览器窗口。 4 * 会先对传入的字符串进行一系列处理 5 *****************************************************************************************/ 6 void CScriptPluginObject::popJavascriptAlert(const char* str){ 7 char* tmpStr = encodeURI(str); 8 char* prevStr = "javascript:alert(decodeURIComponent('"; 9 char* postStr = "'));"; 10 int mlen = strlen(prevStr) + strlen(tmpStr) + strlen(postStr) + 1; 11 char *command = (char*)malloc(mlen); 12 memset(command, 0, mlen); 13 sprintf(command, "%s%s%s", prevStr, tmpStr, postStr); 14 15 NPN_GetURL(m_npp, command, "_self"); 16 }
以上。
PS. 今天突然发现前一篇文章被另一个网站给抄过去了。第一次自己写了东西被其他网站抄袭,感觉颇是心情愉快。话说那个网站还巧妙地把大部分词语都替换为近义词了,读起来颇为费劲= =