编码相关的编程实验

关于字符模式应用

字符模式(命令行模式)应用:

  1. [可选] 从标准输入读取(stdin)
  2. 工作
  3. [可选] 写数据到标准输出(stdout)或标准输出(stderr)

字符模式应用程序通过,console 或 terminal application 和end-user 用户交流。console 转换用户输入(键盘,鼠标,触屏,pen 等),并将其发送到一个字符模式application 的stdin。一个console 可能会显示一个字符模式application 的text output 在一个用户的屏幕上。
windows 中,console 是内置的,并提供丰富的操作API,借此,字符模式app 可以与用户交互。

输入和输出方法

有两种console I/O 的模式,使用哪种取决于application 的复杂程度及需求。高层的方法支持简单的字符流I/O ,但它限制了对于console 的输入输出缓冲的访问。低层的方法需要开发人员写更多的代码,并从更大范围的函数集中做选择,但它给乐application 更大的灵活性。

application 可以使用文件I/O 函数,ReadFile 和 WriteFile ,以及Console 函数 ReadConsole 和 WriteConsole ,来执行高层的I/O ,这些函数提供了对于console 的 输入输出缓冲的间接访问。高层输入函数过滤并处理console 的输入缓冲中的数据,并返回一个字符流格式的输入,丢弃鼠标和缓冲区大小重置输入。类似的,高层输出函数将字符流写入到屏幕缓冲中,并显示在当前光标所在的位置。一个application 通过控制一个console 的I/O 模式来控制这些函数的工作方式。

低层的I/O 函数提供了对于console 的输入和输出缓冲的直接的方法,使得application 访问鼠标和缓冲区-大小重置输入事件,以及扩展的键盘输入事件。低层输出函数使得一个application 读或写屏幕缓冲区中的特定数量的连续字符单元格,或者在屏幕缓冲区中的指定位置读取或写入字符单元的矩形块。console 的输入模式影响低层输入,它决定了application 是否将输入表和缓冲区-重置大小 事件放到输入缓冲中。一个console 的输出模式不会影响低层的输出方法。

高层和低层I/O 方法不是互相独立的,一个application 可以使用这些函数的任意的组合。

console code pages

一个code page 是256 个字符代码到单个字符的映射。不同的代码页包括不同的指定字符,通常是针对一种语言或一组语言定制的。

跟每个console 绑定的,有两个code page:输入和输出。一个console 使用它的输入code page 将键盘输出转换到对应的字符值,使用output code page 来将各种输出函数写的各种字符值转换为显示在console 窗口上的字符图像。一个application 可以使用SetConsoleCP 和 GetConsoleCP 设置和获取console 的input code page ,output code page:SetConsoleOutputCP 和 GetConsoleOutputCP。

本地计算机上可以使用的code page 的ID 存储在如下注册表:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Nls\CodePage

Console 控制处理函数

每个console 进程有自己的control handler 函数列表,当进程接收到CTRL+C,CTRL+BREAK或CTRL+CLOSE信号,系统会调用这些函数。初始,每个进程的control handlers 列表仅拥有一个默认的handler 函数,其调用ExitProcess。console process 可以通过调用SetConsoleCtrlHandler 函数添加或删除额外的HandlerRoutine 函数。这个函数不影响其他进程的这个列表。当一个console process 接收到任何control signals,它调用最新注册的 handler functions ,直到其中一个handlers 返回了TRUE。如果没有handlers 返回TRUE,将调用默认的handler。

函数的dwCtrlType 参数表示了当前是哪种control signal,返回值标识此signal 是否被处理。

例子:https://docs.microsoft.com/en-us/windows/console/registering-a-control-handler-function

console 别名

Console 别名用来映射一个源字符串到目标字符串。

console buffer 安全性和访问权限

调用CreateFile 或 CreateConsoleScreenBuffer 时,可以指定自己需要的安全描述符。详情看相关函数即可

console application 问题

8-bit console 函数使用OEM code page,所有其他的函数默认使用ANSI code page。这意味着,console 函数返回的strings,其他函数可能无法正确处理,反之亦然。例如,如果FindFirstFileA 返回一个包含特定的扩展的ANSI 字符,WriteConsoleA 函数将无法正确显示该string。

console application 最好的长期的解决方法是使用unicode。除此之外,console application 应该使用SetFileApisToOEM 函数,这个函数改变相关文件函数,它们将使用OEM 字符集而不是ANSI 字符集。

wiki 中关于code pages 的描述

  • windows code pages 是字符集,或code page 集,(其他操作系统上称为,字符编码)。当windows 中实现了 unicode,windows code pages 渐渐被取而代之了,尽管它们仍然在windows 和 其它平台上受支持。
  • 有两组code pages,OEM 和 ANSI

ANSI code page

ANSI code page(也称windows code page),windows 系统中,使用GUI 的原生非 unicode application 使用。
ANSI windows code page,特使是 1252 code page(默认情况下,在Microsoft Windows的旧版组件中使用英语和其他一些西方语言(其他语言使用不同的默认编码)),据说它们是基于提交给或打算提交给 ANSI(American National Standards Institute 美国国家标准协会)的草案建立的,因此叫这个名字。但,ANSI 和 ISO 没有这些code page 的任何标准。

OEM code page

OME code page,由win32 console application 和 虚拟 DOS 使用,可以被认为是DOS 和原始 IBM PC架构的延续。实现了一套单独的code page,不仅是因为兼容性,还因为VGA(和后代)硬件的字体建议将线条图字符的编码与code page 437 兼容。大多数OEM 代码页共享许多代码点,特别是对于非字母字符,使用CP437 的第二个(非ANSI)的一半。

实验

    char szBuffer[] = "汉";
    wchar_t wzBuffer[] = L"汉";
    TCHAR tzBuffer[] = TEXT("汉");
    

在这里插入图片描述
在这里插入图片描述
以上两个图,是分别将,字符集设置为unicode 和 multibyte,它们没有区别

编程中,设置字符集(multibyte、unicode 多字节 unicode)到底是设置了什么?

https://docs.microsoft.com/en-us/cpp/atl-mfc-shared/unicode-and-multibyte-character-set-mbcs-support?view=vs-2017

为了支持,那些拥有庞大的字符集的语言(日语,中文),MFC 支持两种处理大字符集的方法:

  • unicode wchar_t 基于宽字符 和UTF-16 字符编码
  • multibyte character sets(MBCS),char,基于单或多字节 character 和 以特定于语言环境的字符集编码的字符串。(windows 中的字符集编码的概念,就是code page 的概念,因此下面的操作中我们要额外关心code page,包括输入code page 和 输出 code page

这里的字符集设置其实就是选择性的定义了两个宏:
多字节:_MBCS
UNICODE:_UNICODE
此时将影响,TCHAR 的定义(间接影响到CString的类型)、默认的函数类型(A函数还是W 函数)。
新开发的应用程序尽量使用UNICODE,以支持国际化操作。

wchar_t 与 gbk2312

  • 注意,我本机设置的系统默认字符集为英文
  • 使用的是C 语言
  • 上面说了,使用unicode 和 多字节的影响范围,下面的代码没有使用到相关函数,因此结果总是一样的
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<tchar.h>
#include<windows.h>
#include<locale.h>
#include<string.h>
void printCh(LPCSTR strPre,LPCSTR sz)
{
    int iIndex = 0;
    printf("%s\r\n", strPre);
    while (sz[iIndex] != '\0')
    {
        printf("%2X ", (UCHAR)sz[iIndex++]);
    }
    printf("\r\n");
}
void printWh(LPCSTR strPre,LPCWSTR wz)
{
    int iIndex = 0;
    printf("%s\r\n", strPre);
    while (wz[iIndex] != L'\0')
    {
        printf("%2X ", (UCHAR)((BYTE*)wz)[2 * iIndex]);
        printf("%2X ", (UCHAR)((BYTE*)wz)[2 * iIndex + 1]);
        iIndex++;
    }
    printf("\r\n");
}
int main()
{
    char * szBuffer = (LPSTR)malloc(0x1000);
    WCHAR* wzBuffer = (LPWSTR)malloc(0x1000);
    char insideSZ[] = "中文";
    WCHAR insideWZ[] = L"中文";
    printf("%d\r\n", GetACP());
    if (szBuffer == NULL)
    {
        printf("%error ,malloc func error:%d\r\n", GetLastError());
        return 0;
    }
    if (wzBuffer == NULL)
    {
        free(szBuffer);
        szBuffer = NULL;
        printf("%error ,malloc func error:%d\r\n", GetLastError());
        return 0;
    }
    printf("%d\r\n", GetACP());
    printf("please input some chinese\r\n");
    scanf("%s", szBuffer);
    printf("there is your input: %s\r\n", szBuffer);
    printf("there is inside :%s\r\n", insideSZ);
    printf("gbk byte:\r\n");
    printCh("input:",szBuffer);

    printCh("inside:",insideSZ);
    printf("please input some chinese(UTF-8)\r\n");
    
    wscanf(L"%s", wzBuffer);
    wprintf(L"there is your input: %s\r\n", wzBuffer);
    wprintf(L"there is your inside :%s\r\n", insideWZ);

    printf("utf8 byte:\r\n");
    printWh("input",wzBuffer);
    printWh("inside",insideWZ);
  // 清理操作
}

结构如下:
在这里插入图片描述

我们看到,刚开始输出的936 表示,当前的code page 为936
https://docs.microsoft.com/en-us/windows/desktop/intl/code-page-identifiers
在这里插入图片描述

证明我们的Windows ANSI code page,即,默认的窗口code page 为gbk2312。因此我们才能有机会进行中文的输入输出操作。尽管我本机的默认语言是英语--------这里的默认英语,是后续手动修改的,系统安装的时候,还是选择的中文。后面我们会在真正的英文系统上进行测试

之后,以char 存储的“中文”,的十六进制表示即GBK2312 中对应的十六进制,而从console 输入进来并存储到char 的“中文”与此相同。
这里有几个问题

  • 默认的源码文件的保存格式
  • console 的输入的code page
  • console 的输出的code page
  • *A 函数,如何适配不同的系统?

新建一个源码文件的时候,使用的默认保存编码,看来是ANSI,因此,我们以二进制的形式查看代码文件的时候,会发现其字符串, 直接传递给*A 函数后,可以正确的解析----这里有一个问题,当,我们在中文环境下,在源码中,使用了中文字符串,这些字符串,默认,在中文环境可以正常显示;当放到其他语言环境,如果不手动修改输出的code page ,应该是无法正常显示的,下面来实验

int main()
{
    char szBuffer[] = "中文";
    printf("%s\r\n", szBuffer);
    system("pause");
    return 0;
}

在这里插入图片描述
修改输出的code page:

    char szBuffer[] = "中文";
    printf("%d\r\n", SetConsoleOutputCP(936));
    printf("%s\r\n", szBuffer);
    system("pause");

在这里插入图片描述
上述结果证明了我们的猜想,但需要注意的是:
刚开始这个程序也没有正确的显示,因为,控制台没有对应的字体来显示我们要求的中文:
在这里插入图片描述
这是修改后的,英文版系统,控制台属性中,默认的字体可不是这个。修改前如下:
在这里插入图片描述
当控制台的字体中都没有这个字体的时候,无法正确的显示中文。原因很简单:字体可以理解成,字符编码与显示的字符的对应关系。当这个对应关系中,都没有中文的时候,当然无法显示。
修改方法:
在这里插入图片描述
修改后,需要重启才能生效。这里我们改为,中文简体时,才能在控制台正确的输出汉字。

以,wchar 存储的内部“中文”,使用的是UTF-16 编码,而从console 传进来后,由wchar 存储的“中文”,使用的显然是,使用2 字节表示原来GBK 中1字节表示的内容,wchar_t 存储gbk2312 的结果就是这样。而再次向屏幕输出它们的时候,wprintf 可以正常输出wchar 存储的,gbk2312,而UTF-16无法正常显示。

当有国际化的需求,显示其它国家的语言的时候,如何做?

上面我们为了输出中文,将output code page 修改为 936,如果想适应所有的语言,不如直接将其修改为utf-8。一劳永逸,适应性完美。只不过,结合后面,字体的问题,当目标系统上没有要显示的语言的字体,无论如何是显示不出来的。下面,我们使用utf-8 输出中文.我们本机是ansi—gbk2312,如果想输出为utf-8.应该要先转换为wchar–>utf-8。
因此实际编程的时候,我们推荐,直接使用wchar,兼容性和效率都有了。

    int pre = GetConsoleOutputCP();
    printf("%d\r\n", pre);
    printf("%d\r\n", SetConsoleOutputCP(CP_UTF8));
    printf("%d\r\n", GetConsoleOutputCP());
    printf("%d\r\n", GetACP());
    WCHAR wzChar[] = L"中文";
    char szBuffer[0x100] = {0};
    WideCharToMultiByte(CP_UTF8, 0, wzChar, _countof(wzChar), szBuffer, 0x100, NULL, NULL);
    printf("s %s\r\n", szBuffer);//正常
    printf("S %S\r\n", szBuffer);
    wprintf(L"wprintf %s\r\n", szBuffer);
    wprintf(L"wprintf %S\r\n", szBuffer);//正常---->这种方法更推荐,因为,这样L""中的内容也能全部正常显示了。

    printf("s %s\r\n", wzChar);
    printf("S %S\r\n", wzChar);
    wprintf(L"wprintf %s\r\n", wzChar);
    wprintf(L"wprintf %S\r\n", wzChar);

    printf("%d\r\n", SetConsoleOutputCP(pre));
    printf("%d\r\n", GetConsoleOutputCP());
    system("pause");

在这里插入图片描述

UNICODE to gbk2312

上一篇文章中介绍到了一个函数,

int
WINAPI
WideCharToMultiByte(
    _In_ UINT CodePage,
    _In_ DWORD dwFlags,
    _In_NLS_string_(cchWideChar) LPCWCH lpWideCharStr,
    _In_ int cchWideChar,
    _Out_writes_bytes_to_opt_(cbMultiByte, return) LPSTR lpMultiByteStr,
    _In_ int cbMultiByte,
    _In_opt_ LPCCH lpDefaultChar,
    _Out_opt_ LPBOOL lpUsedDefaultChar
    );

即,宽字符向多字节字符的转换:我们尝试使用:

    iLength = WideCharToMultiByte(CP_ACP, 0, insideWZ, -1, NULL, 0, NULL, NULL);
    memset(szBuffer, 0, 0x100);
    iLength = WideCharToMultiByte(CP_ACP, 0, insideWZ, -1, szBuffer, iLength, NULL, NULL);

    printCh("after WideCharToMultiByte",szBuffer);
    printf("szBuffer: %s\r\n", szBuffer);

得到结果如下:
在这里插入图片描述

我们成功将宽字符,即utf-16,转换为了gbk2312。

setlocale 如何影响中文的输入输出

当我们在程序起始,调用_tsetlocale 并设置,语言环境为 中文,事情变得有点不同:

int main()
{
    char * szBuffer = (LPSTR)malloc(0x1000);
    WCHAR* wzBuffer = (LPWSTR)malloc(0x1000);
    char insideSZ[] = "中文";
    WCHAR insideWZ[] = L"中文";
    int iLength = 0;
    _tprintf(TEXT("%s\r\n"), _tsetlocale(LC_CTYPE, TEXT("zh-CN")));
    if (szBuffer == NULL)
    {
        printf("%error ,malloc func error:%d\r\n", GetLastError());
        return 0;
    }
    if (wzBuffer == NULL)
    {
        free(szBuffer);
        szBuffer = NULL;
        printf("%error ,malloc func error:%d\r\n", GetLastError());
        return 0;
    }
    printf("%d\r\n", GetACP());
    printf("please input some chinese\r\n");
    scanf("%s", szBuffer);
    printf("there is your input: %s\r\n", szBuffer);
    printf("there is inside :%s\r\n", insideSZ);
    printf("gbk byte:\r\n");
    printCh("input:",szBuffer);
    printCh("inside:",insideSZ);
    printf("please input some chinese(UTF-8)\r\n");

    wscanf(L"%s", wzBuffer);
    wprintf(L"there is your input: %s\r\n", wzBuffer);
    wprintf(L"there is your inside :%s\r\n", insideWZ);

    printf("utf8 byte:\r\n");
    printWh("input",wzBuffer);
    printWh("inside",insideWZ);
	return 0;
}

在这里插入图片描述

使用wscanf 输入到 wchar_t 时,使用的utf16 存储,且,utf16 和 gbk2312 可同时正常显示。且,GetACP() 在_tsetlocale 之后调用,code page 依然是936。什么情况?

在这里插入图片描述

由此我们知道,在执行wprintf 的时候,会自动执行宽字节到,multibyte 的转换。(我没把WideCharToMultiByte 的调用展示出来,单很明显,我们能看到,这里生成了gbk 的字符,因此,确实进行了转换)。
上述过程的代码为:

    iLength = WideCharToMultiByte(20936, 0, insideWZ, -1, NULL, 0, NULL, NULL);// 这一步单纯的想得到该函数的地址,好下断点
    wprintf(L"there is your inside :%s\r\n", insideWZ);

下面这步,每输出一个字符会中一次断点,我是在输出了“中”字后,开始单步调试,最终看到writefile 字样,猜测是,console 的输出方式,就在那里尝试拿了下参数,最终证明猜测是正确的。各位看官如果有什么问题,欢迎交流,可能上面的过程不是足够详细。(当然,早知道, 这个,wprintf 函数,直接输出“中文“二字,无需其它多余的动作)。

代码文件的保存格式是什么?

在这里插入图片描述
显然,是,ANSI 格式(这里是GBK2312)。文章开始的,IDA 中,”中文“对应了不同的编码,是因为,编译器对其进行了处理。
我们脱离这个思考一下,冷静一下。
源码就是文本文件,编码肯定是统一的。

生成的程序中,”字符串“,就是一块儿内存,或者叫字节流,可以通过函数进行解析、转换等操作,因此可以按需存在,硬编码字符串可以这样静态的看到它们编码的不同,当程序运行起来,各种编码的输入输出,当然会产生更多不同的编码数据格式。

utf-8

上面的代码一直有个问题,其中的utf-8 并不是真正的utf-8。UTF-8 的数据长什么样?
我们将源文件copy 一份,然后,使用notepad 保存为utf-8 格式,然后,查看其二进制数据:
在这里插入图片描述
众所周知,utf-8 中,中文,每个字符,对应,三个字节。

国际化的软件,如何支持用户选择自己的语言?

#include<iostream>
#include<tchar.h>
#include<windows.h>
#include<string>

using namespace std;

int main()
{
    cout << _tsetlocale(LC_CTYPE, TEXT("zh-CN")) << endl;
    std::string aStr;

    cin >> aStr;
    std::wstring wStr(aStr.begin(), aStr.end());
    std::u16string u16(wStr.begin(), wStr.end());
    std::u32string u32(wStr.begin(),wStr.end());
    auto pa = aStr.data();
    auto pw = wStr.data();
    auto pu16 = u16.data();
    auto pu32 = u32.data();

    system("pause");
    return 0;
}

16进制数据如下:

d6 d0 ce c4 00											gbk
2d 4e 87 65 00 00										utf16
2d 4e 87 65 00 00 										utf16
2d 4e 00 00 87 65 00 00 00 00 00 00			utf32
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值