使用LoadLibrary显式加载动态链接库以及所需要注意的编码问题(涉及宽字符窄字符)

使用LoadLibrary显式加载动态链接库以及所需要注意的编码问题(涉及宽字符窄字符),第1张

关于LoadLibrary、宽字符、窄字符以及编码方式

在使用LoadLibrary中遇到了一些字符问题,就将字符以及编码进行了一个整理

一、编码方式

先来看看编码方式,例如当点击Qt的seleclass="superseo">ct encoding的时候,会有多个编码方式以供选择,例如:UTF8、GBK、ANSI\UNICODE、GB2312等,这些编码方式其实是随着计算机的发展而发展的,先看看编码方式的发展:

1.编码方式的发展

先从ASCII看起,ASCII是用来表示英文字符的一种编码规范。最开始的计算机使用8个可以开合的晶体管来组合成不同的状态,这也就是8位(一个字节),而每个ASCII字符占用1个字节,因此,ASCII编码可以表示的最大字符数是255(00H—FFH)。其实,英文字符并没有那么多,一般只用前128个(00H—7FH,最高位为0),其中包括了控制字符、数字、大小写字母和其它一些符号。而最高位为1的另128个字符(80H—FFH)被称为“扩展ASCII”,也成为扩展字符集,一般用来存放英文的制表符、部分音标字符等等的一些其它符号。这种字符编码规则显然用来处理英文没有什么问题。但是面对中文、阿拉伯文等复杂的文字,255个字符显然不够用。

​ 于是,各个国家纷纷制定了自己的文字编码规范,其中中文的文字编码规范叫做“GB2312—80”,它是和ASCII兼容的一种编码规范,其实就是利用扩展ASCII没有真正标准化这一点,把一个中文字符用两个扩展ASCII字符来表示,以区分ASCII码部分。我们规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。

但是这个方法有问题,最大的问题就是中文的文字编码和扩展ASCII码有重叠。而很多软件利用扩展ASCII码的英文制表符来画表格,这样的软件用到中文系统中,这些表格就会被误认作中文字符,出现乱码。另外中国的汉字很多,这些码位还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。但是后面考虑到少数民族的文字等,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。这样的编码标准被称为: “DBCS”(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。因此会有这样一个说法:“一个汉字算两个英文字符”。

另外,由于各国和各地区都有自己的文字编码规则,它们互相冲突,这给各国和各地区交换信息带来了很大的麻烦。要真正解决这个问题,不能从扩展ASCII的角度入手,而必须有一个全新的编码系统,这个系统要可以将中文、法文、德文……等等所有的文字统一起来考虑,为每一个文字都分配一个单独的编码。于是,Unicode诞生了。Unicode也是一种字符编码方法,它占用两个字节(0000H—FFFFH),容纳65536个字符,这完全可以容纳全世界所有语言文字的编码。
在Unicode里,所有的字符被一视同仁,汉字不再使用“两个扩展ASCII”,而是使用“1个Unicode”,也就是说,所有的文字都按一个字符来处理,它们都有一个唯一的Unicode码。即一个汉字不再是相当于两个字符了,而是一个。

​ UNICODE 来到时,一起到来的还有计算机网络的兴起,UNICODE 如何在网络上传输也是一个必须考虑的问题,于是面向传输的众多 UTF(UCS Transfer Format)标准出现了,顾名思义,UTF8就是每次8个位传输数据,而UTF16就是每次16个位,只不过为了传输时的可靠性,从UNICODE到UTF时并不是直接的对应,而是要过一些算法和规则来转换。

​ 在网络里传递信息时有一个很重要的问题,就是对于数据高低位的解读方式,一些计算机是采用低位先发送的方法,例如我们PC机采用的 INTEL 架构,而另一些是采用高位先发送的方式,在网络中交换数据时,为了核对双方对于高低位的认识是否是一致的,采用了一种很简便的方法,就是在文本流的开始时向对方发送一个标志符——如果之后的文本是高位在位,那就发送"FEFF",反之,则发送"FFFE"。不信你可以用二进制方式打开一个UTF-X格式的文件,看看开头两个字节是不是这两个字节?

讲到这里,我们再顺便说说一个很著名的奇怪现象:当你在 windows 的记事本里新建一个文件,输入"联通"两个字之后,保存,关闭,然后再次打开,你会发现这两个字已经消失了,代之的是几个乱码!呵呵,有人说这就是联通之所以拼不过移动的原因。

其实这是因为GB2312编码与UTF8编码产生了编码冲撞的原因。

从网上引来一段从UNICODE到UTF8的转换规则:

Unicode

UTF-8

0000 - 007F

0xxxxxxx

0080 - 07FF

110xxxxx 10xxxxxx

0800 - FFFF

1110xxxx 10xxxxxx 10xxxxxx

例如"汉"字的Unicode编码是6C49。6C49在0800-FFFF之间,所以要用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将6C49写成二进制是:0110 1100 0100 1001,将这个比特流按三字节模板的分段方法分为0110 110001 001001,依次代替模板中的x,得到:1110-0110 10-110001 10-001001,即E6 B1 89,这就是其UTF8的编码。

​ 在数据库里,有n前缀的字串类型就是UNICODE类型,这种类型中,固定用两个字节来表示一个字符,无论这个字符是汉字还是英文字母,或是别的什么。如果要测试"abc汉字"这个串的长度,在没有n前缀的数据类型里,这个字串是7个字符的长度,因为一个汉字相当于两个字符。而在有n前缀的数据类型里,同样的测试串长度的函数将会告诉你是5个字符,因为一个汉字就是一个字符。

2. ASCII码

上面也提到了ASCII码,ASCII码一共规定了128个字符的编码,比如空格“SPACE”是32(二进制00100000),大写的字母A是65(二进制01000001)。这128个符号(包括32个不能打印出来的控制符号),只占用了一个字节的后面7位,最前面的1位统一规定为0。

3.非ASCII编码

英语用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é的编码为130(二进制10000010)。这样一来,这些欧洲国家使用的编码体系,可以表示最多256个符号。

但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é,在希伯来语编码中却代表了字母Gimel (ג),在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0—127表示的符号是一样的,不一样的只是128—255的这一段。

至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。

中文编码的问题需要专文讨论,这篇笔记不涉及。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。

4.Unicode

Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母Ain,U+0041表示英语的大写字母A,U+4E25表示汉字“严”。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。

5. Unicode的问题

需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。

比如,汉字“严”的unicode是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。

这里就有两个严重的问题,第一个问题是,如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。

它们造成的结果是:1)出现了unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示unicode。2)unicode在很长一段时间内无法推广,直到互联网的出现。

6.UTF-8

互联网的普及,强烈要求出现一种统一的编码方式。UTF-8就是在互联网上使用最广的一种unicode的实现方式。其他实现方式还包括UTF-16和UTF-32,不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。

UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

UTF-8的编码规则很简单,只有二条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

二、宽字符、窄字符

1.窄字符

窄字符char,char类型的变量占一个字节(byte)(也就是8个bit(比特)),能表示256个字符,那char的范围有两种

第一种(signed char):-128~127

第二种(unsigned char):0~255

2.宽字符 wchar_t

C++在wchar.h头文件中定义了最基本的宽字符类型**wchar_t:**一个宽字符通常占2个字节。在c/c++/objective c中,如果你想把一个窄字符(例如ASCII 字符)表示为宽字符通常的做法是使用wchar来取代char,例如
wchar t = ‘A’;
wchar_t * p = L"Hello!" ;
这里每一个字符都占了2个字节,并且采用了unicode编码。

这个前面多的大写的L,就是告诉编译器,这个字符串按照宽字符来存储(即一个字符占两个字节),即L是表示字符串资源为Unicode的

有的时候用_T,_T是一个是适配的宏,当#ifdef _UNICODE的时候_T就是L,没有#ifdef _UNICODE的时候,_T就是ANSI的

注意宽字符的输出一般用wcout函数

三、LoadLibrary

在利用LoadLibrary显式装载某动态库的时候:

HINSTANCE hDll = LoadLibrary("my.dll");

如果以上不能成功装在动态链接库的时候,可以尝试使用以下语句:

HINSTANCE hDll = LoadLibrary(TEXT("my.dll"));

*EXT相当于MFC中的_T()*以及L,即以下也是正确的:

HINSTANCE hDll = LoadLibrary(L"my.dll");

而当在Qt或是C++中需要传进一个QString或者String时,是不能直接给LoadLibrary使用的,当直接使用String时会提示:

error: no matching function for call to ‘LoadLibraryW’
note: expanded from macro ‘LoadLibrary’
note: candidate function not viable: no known conversion from ‘QString’ to ‘LPCWSTR’ (aka ‘const wchar_t *’) for 1st argument

也就是说LoadLibrary默认使用的也是LoadLibraryW

QString str = "my.dll";
std::string str1 = str.toStdString(); //QString转为String
LPCSTR strdll = str1.c_str();  //String转LPCSTR
HINSTANCE hDll = LoadLibraryA(strdll);  //这样就可以加载了,另外此处需要使用LoadLibraryA
//    std::string s = str.toStdString(); //QString转String
//    QString qstr2 = QString::fromStdString(s);  //String转QString

以上是将String转成LPCSTR,而LPCSTR是一种字符串数据类型,LPCSTR被定义成是一个指向以’\0’结尾的常量字符的指针。

而LPCWSTR是一个指向unicode编码字符串的32位指针,所指向的字符是wchar类型,而不是char类型,这也就是为什么上述需要用到LoadLibraryA
另外将宽字符转换成LPCWSTR的方法:

std::wstring str = L"hello";
LPCWSTR str1 = str.c_str();

将String转换成LPCSTR比较简单,而要将String转换成LPCWSTR就需要用到MultiByteToWideChar函数,比较复杂!

参考文献:

https://blog.csdn.net/zhanghuaichao/article/details/77862037
https://blog.csdn.net/u010679895/article/details/9988649

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/925742.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-16
下一篇 2022-05-16

发表评论

登录后才能评论

评论列表(0条)

保存