挑战Oracle OLEDB Data Provider在西文数据库上乱码问题!

    根据Oracle的人说WE8ISO8859P1是最大的字符集,所以这里采用的就是它。但是用ADO来操作这种数据库有点头痛,MSDAORA可以正确读写汉字,但是不能操作BLOB字段,OraOLEDB可以操作BLOB字段,但是写汉字会乱码。这个问题困扰我们三年多了,最近再次发起冲击终于找到一个变通的办法了。
     WE8ISO8859P1是一个单字节的西文字符集,为什么可以用来保存汉字呢?主要是因为WE8ISO8859P1用完了一个字节的所有码点,这样一个GBK编码的汉字由两个合法的WE8ISO8859P1字符组成,这就是什么可以用来保存汉字的原因。也因为它用完所有码点,所以它可以是任何字符集的超集。因为客户端和服务器端的字符集都是WE8ISO8859P1,所以在Oracle Net层面不需要字符集的转换,也不会有信息丢失的问题。汉字保存在数据库中编码是GBK。SQL Plus等OCI程序直接把GBK编码发送到服务器端。OLEDB程序因为COM的原因,所以出现数据丢失,但是微软的Provider是对的。
      COM中字符串都是Unicode编码的BSTR,程序必须把SQL语句转换成Unicode才能调用COM组件的方法,组件内部又要转换一次,不同的Provider转换的方式不一样。具体如下:
OraOLEDB:
GBK-->Unicode-->WE8ISO8859P1------->WE8ISO8859P1(服务器端)
MSDAORA:
GBK-->Unicode-->GBK------>WE8ISO8859P1(服务器端)
两种实现都没有错,Oracle根据客户端NLS_LANG的设置来决定如何转换。Microsoft根据操作系统的字符集来转换,谁叫他们一个是做数据库,一个是搞操作系统的呢。其实也与我们超常规使用数据库也有些关系,用本来不支持中文的字符集来存储中文,有些难为Oracle了。
       经过验证的确是那么回事“中国”的GBK为(D6D0 B9FA),用SQL Plus写入数据库,然后用PL/SQL Developer查看16进制的值的确如此(还可以用SQL函数RAWTOHEX()查看)。而用OraOLEDB写入的为汉字“靠”(BFBF),BF在WE8ISO8859P1中为INVERTED QUESTION MARK一个加粗的问号,每个汉字变成一个问号,连续两个就是一个靠字。主要是因为OraOLEDB 把Unicode的汉字转为WE8ISO8859P1时,汉字是无效字符,于是统统用?表示。而微软默认转回操作系统字符集GBK,所以没有问题。
       现在问题找出来了,怎么解决呢?一开始我想到的方案是重写一个Data Provider或者对OraOLEDB的DLL做些小的手术,直到今天中午吃饭时我还在考虑怎么写OLEDB Data Provider。吃饭时我给同事介绍我的最新进展,原因找出来了,但是解决方案还没有。吃着吃着,我想到一个办法,既然原因是一个汉字对应不上一个西文字符,但是半个汉字可以存储,我可以在转Unicode前把汉字拆分为两半,不用MultiByteToWideChar转,因为它太老实,不会作弊。比如,中国作弊后的Unicode字符串为(00D6 00D0 00B9 00FA),算法实现也非常简单:
STDMETHODIMP COLEDBUtil::prepareWE8ISO8859P1(BSTR  * bstrSrc, VARIANT_BOOL  * bRet)
{
    
// TODO: Add your implementation code here
    int buffer_len = WideCharToMultiByte(CP_ACP, 0*bstrSrc, wcslen(*bstrSrc),
        NULL, 
000);
    
char *strBuffer = new char[buffer_len+1];
    
int retVal = WideCharToMultiByte(CP_ACP, 0*bstrSrc, wcslen(*bstrSrc),
        strBuffer, buffer_len, 
00);
    
if(retVal == 0)
    
{
        
*bRet = VARIANT_FALSE;
        delete [] strBuffer;
        
return E_FAIL;
    }

    
else
        strBuffer[buffer_len] 
= '';

    
//Convert it to Unicode manually
    const unsigned char *str;
    OLECHAR 
*wcsBuffer;
    OLECHAR 
*pWC;

    SysFreeString(
*bstrSrc);
    wcsBuffer 
= new OLECHAR[strlen(strBuffer)+1];
    pWC 
= wcsBuffer;
    
for(str = (const unsigned char *)strBuffer; *str != ''; str++)
    
{
        
*pWC = *str;
        pWC
++;
    }

    
*pWC = L'';
    
    
//*bstrSrc = bstr.copy();
    *bstrSrc = ::SysAllocString(wcsBuffer);
    
*bRet = VARIANT_TRUE;

    delete [] wcsBuffer;
    delete [] strBuffer;
    
return S_OK;
}
VB中的代码如下:
Dim  conn  As   New  ADODB.Connection
Dim  oraUtil  As   New  ORACLEUTILLib.OLEDBUtil
Dim  strSQL  As   String
    
conn.Open 
" Provider=OraOLEDB.Oracle.1;Data Source=QDESTATE;User ID=landuse;Password=landuse "
    
strSQL 
=   " insert into a values ('中国') "
Debug.Print oraUtil.prepareWE8ISO8859P1(strSQL)
conn.Execute strSQL
没有造成太大的额外负担,效果不错。也许还有更好的办法,但是现在能想到的也就这个了,而且的确能够解决问题。困扰我们三年多了,一直找不到很好的解决方案,我花了好几天看文档,没有直接的答案,但是加深了我对Oracle多语言支持的理解。其实我一开始死死盯着Unicode看,后来突然发现与Unicode一点关系都没有,主要是最大的字符集在误导我。Oracle上真正最大的字符集是AL32UTF16,AL32UTF8四个字节的补充字符不支持。
       解决了这个问题,似乎我很厉害,其实不是那么回事。我之所以能够解决这个问题,主要还是我比较闲,才有大量的时间在网上泡着,看文档什么的,我们领导也比较宽松,不管我。如果我的另外两个同事也有这么多的时间,同样也可以解决这个问题。他们实在太忙了,经常加班,尤其是我们领导,实在太忙不可能钻得很深,能够到他那个深度和广度,实在是非常的不容易。去年我当了几个月的研发部经理,我比他懒多了,真是惭愧!
       同时我也在想我的价值何在,开出10k、15k的工资,别人会问我为什么值这个价钱。如何提高自身的价值,一个人的精力是有限的,要把有限的精力花在最值得花的地方。还有非常多的东西想学习,但我必须作出选择,我没有那么多的时间。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值