[英]How am I allowed to workaround DOS functions that used strings containing accented characters (ASCII to UTF-8)?
我在编写SW时想使用80年代初编写的旧C代码。 这段代码对字符串做了一些转换。 它还使用当时在ASCII表(代码大于127)中编码的重音字符(DOS)。
现在,新系统使用UTF-8编码,因此旧代码无法正常工作。 我正在使用Linux(Ubuntu 17 / gcc gcc(Ubuntu 7.2.0-8ubuntu3)7.2.0)。
我正在寻找一种解决方法,使我能够进行尽可能少的更改。 我已经开始做一些测试来分析出现的问题。 我做了两个main
:一个使用char *
字符串和char
元素,另一个使用wchar_t *
字符串和wchar_t
元素。 两者均无法正常工作。
例如,第一个(使用char *
和char
)需要一种变通方法,当strchr
识别多字节代码时,它不能以正确的方式打印( printf
)多字节char,althoug可以正确地打印char *
。 此外,还会产生许多与使用多字节字符有关的警告。
第二个(使用wchar_t *
和char *
)运行,但是不能正确打印多字节字符,它们显示为“?” 当它们分别打印为wchar_t和wchar_t *(字符串)时。
MAIN1:
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
/* http://clc-wiki.net/wiki/strchr
* standard C implementation
*/
char *_strchr(const char *s, int c);
char *_strchr(const char *s, int c)
{
while (*s != (char)c)
if (!*s++)
return 0;
return (char *)s;
}
int main()
{
char * p1 = NULL;
const char * t1 = "Sergio è un Italiano e andò via!";
printf("Text --> %s\n\n",t1);
for(size_t i=0;i<strlen(t1);i++) {
printf("%02X %c|",(uint8_t)t1[i],t1[i]);
}
puts("\n");
puts("Searching ò");
/*warning: multi-character character constant [-Wmultichar]
p1 = strchr(t1,'ò');
^~~~
*/
p1 = strchr(t1,'ò');
printf("%s\n",p1-1); // -1 needs to correct the position
/*warning: multi-character character constant [-Wmultichar]
p1 = _strchr(t1,'ò');
^~~~
*/
p1 = _strchr(t1,'ò');
printf("%s\n",p1-1); // -1 needs to correct the position
puts("");
puts("Searching è");
/*warning: multi-character character constant [-Wmultichar]
p1 = strchr(t1,'è');
^~~~
*/
p1 = strchr(t1,'è');
printf("%s\n",p1-1); // -1 needs to correct the position
/*warning: multi-character character constant [-Wmultichar]
p1 = _strchr(t1,'è');
^~~~
*/
p1 = _strchr(t1,'è');
printf("%s\n",p1-1); // -1 needs to correct the position
puts("");
/*warning: multi-character character constant [-Wmultichar]
printf("%c %c %08X %08X\n",'è','ò','è','ò');
^~~~
printf("%c %c %08X %08X\n",'è','ò','è','ò');
^~~~
printf("%c %c %08X %08X\n",'è','ò','è','ò');
^~~~
printf("%c %c %08X %08X\n",'è','ò','è','ò');
^~~~
*/
printf("%c %c %08X %08X\n",'è','ò','è','ò');
/*multi-character character constant [-Wmultichar]
printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
^~~~
printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
^~~~
printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
^~~~
printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
^~~~
*/
printf("%c %c %08X %08X\n",'è','ò',(uint8_t)'è',(uint8_t)'ò');
puts("");
return 0;
}
输出:
MAIN2:
#include <stdio.h>
#include <string.h>
#include <wchar.h>
#include <inttypes.h>
#define wputs(s) wprintf(s"\n")
/* https://opensource.apple.com/source/Libc/Libc-498.1.1/string/wcschr-fbsd.c
* FBSD C implementation
*/
wchar_t * _wcschr(const wchar_t *s, wchar_t c);
wchar_t * _wcschr(const wchar_t *s, wchar_t c)
{
while (*s != c && *s != L'\0')
s++;
if (*s == c)
return ((wchar_t *)s);
return (NULL);
}
int main()
{
wchar_t * p1 = NULL;
const wchar_t * t1 = L"Sergio è un Italiano e andò via!";
const wchar_t * f0 = L"%02X %c|";
const wchar_t * f1 = L"Text --> %ls\n\n";
const wchar_t * f2 = L"%ls\n";
uint8_t * p = (uint8_t *)t1;
wprintf(f1,t1);
for(size_t i=0;;i++) {
uint8_t c=*(p+i);
wprintf(f0,c,(c<' ')?'.':(c>127)?'*':c);
if ( c=='!' )
break;
}
wputs(L"\n");
wputs(L"Searching ò");
p1 = wcschr(t1,L'ò');
wprintf(f2,p1);
p1 = _wcschr(t1,L'ò');
wprintf(f2,p1);
wputs(L"---");
wputs(L"Searching è");
p1 = wcschr(t1,L'è');
wprintf(f2,p1);
p1 = _wcschr(t1,L'è');
wprintf(f2,p1);
wputs(L"");
wprintf(L"%lc %lc %08X %08X\n",L'è',L'ò',L'è',L'ò');
wprintf(L"%lc %lc %08X %08X\n",L'è',L'ò',(uint8_t)L'è',(uint8_t)L'ò');
wputs(L"");
return 0;
}
输出:
如果要使用宽字符I / O,则需要本地化程序。 这并不困难,只需调用setlocale()
以及可选的fwide()
即可查看用户区域设置是否支持所需流上的宽I / O。
在main()
,在任何输入/输出之前运行
if (!setlocale(LC_ALL, "")) {
/* Current locale is not supported
by the C library; abort. */
}
就像注释中所说的,这告诉您的C库,该程序可以识别语言环境,并且应该按照用户设置的语言环境规则进行设置和准备。 有关更多信息,请参见man 7语言环境 。 本质上,C库不会自动选择用户设置的当前语言环境,而是使用默认的C / POSIX语言环境。 此命令告诉C库尝试并符合当前设置的语言环境。
在POSIX C中,每个FILE
句柄都有一个方向 ,可以使用fwide()
来查询和设置该方向 (但仅在对其进行读取或写入之前fwide()
。 注意,它是文件句柄的属性,而不是文件本身。 并且它仅确定C库是使用面向字节的(正常/窄)还是宽字符函数来读取和写入流。 如果未调用它,则C库会尝试根据用于访问流的第一个读/写功能( 如果已设置语言环境)自动执行此操作。 但是,使用例如
if (fwide(stdout, 1) <= 0) {
/* The C library does not support wide-character
orientation for standard output in this locale.
Abort.
*/
}
在设置语言环境之后,意味着您可以针对该特定流检测C库是否不支持用户语言环境或用户语言环境根本不支持宽字符; 并终止程序。 (最好总是告诉用户结果将是垃圾,而不是默默地尽力而为,可能会使用户数据乱码。毕竟,用户始终可以使用其他工具;而默默地使用户数据乱码表示此特定工具简直是不可信赖的:一文不值。)
您不得混用wprintf()
和printf()
; 也不fwprintf()
和fprintf()
移至同一流。 它要么失败(不打印任何内容),要么混淆C库,要么产生乱码。 同样,您不得在同一流上混合使用fgetc()
和fgetwc()
。 简而言之,您不得在同一流上混合面向字节或面向宽字符的函数。
这并不意味着您不能将面向字节(或多字节)的字符串打印到面向宽字符的流,反之亦然; 恰恰相反。 它在逻辑上起作用, %s
和%c
始终引用面向字节的字符串或字符, %ls
和%lc
引用宽字符串或字符。 例如,如果您有
const wchar_t *ws = L"Hello";
const char *s = "world!";
您可以使用以下命令将它们都打印到面向字节的标准输出中
printf("%ls, %s\n", ws, s);
或使用以下命令生成面向宽字符的标准输出
wprintf(L"%ls, %s\n", ws, s);
这基本上是POSIX C库中的一个限制:必须将面向字节的函数用于字节流,而将宽字符的函数用于宽字符流。 刚开始时可能会觉得很奇怪,但是如果您考虑一下,这是非常简单明了的规则。
让我们看一个与您的程序大致相似的示例程序。 扩展为使用任何换行符惯例(CR,LF,CRLF,LFCR)从标准输入逐行读取(无限长)字符串:
#define _POSIX_C_SOURCE 200809L
#include <stdlib.h>
#include <locale.h>
#include <wchar.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
/* Function to read a wide-character line,
using any newline convention, skipping embedded NUL bytes (L'\0'),
and dynamically reallocating the buffer as needed.
If *lineptr==NULL and *sizeptr==0, the buffer is dynamically allocated.
Returns the number of wide characters read.
If an error occurs, returns zero, with errno set.
At end of input, returns zero, with errno zero.
*/
size_t wide_line(wchar_t **lineptr, size_t *sizeptr, FILE *in)
{
wchar_t *line;
size_t size, used = 0;
wint_t wc;
if (!lineptr || !sizeptr) {
errno = EINVAL;
return 0;
}
if (ferror(in)) {
errno = EIO;
return 0;
}
if (*sizeptr) {
line = *lineptr;
size = *sizeptr;
} else {
*lineptr = line = NULL;
*sizeptr = size = 0;
}
while (1) {
if (used + 3 >= size) {
/* Conservative dynamic memory reallocation policy. */
if (used < 126)
size = 128;
else
if (used < 2097152)
size = (used * 3) / 2;
else
size = (used | 1048575) + 1048579;
/* Check for size overflow. */
if (used + 2 >= size) {
errno = ENOMEM;
return 0;
}
line = realloc(line, size * sizeof line[0]);
if (!line) {
errno = ENOMEM;
return 0;
}
*lineptr = line;
*sizeptr = size;
}
wc = fgetwc(in);
if (wc == WEOF) {
line[used] = L'\0';
errno = 0;
return used;
} else
if (wc == L'\n') {
line[used++] = L'\n';
wc = fgetwc(in);
if (wc == L'\r')
line[used++] = L'\r';
else
if (wc != WEOF)
ungetwc(wc, in);
line[used] = L'\0';
errno = 0;
return used;
} else
if (wc == L'\r') {
line[used++] = L'\r';
wc = fgetwc(in);
if (wc == L'\n')
line[used++] = L'\n';
else
if (wc != WEOF)
ungetwc(wc, in);
line[used] = L'\0';
errno = 0;
return used;
} else
if (wc != L'\0')
line[used++] = wc;
}
}
/* Returns a dynamically allocated wide string,
with contents from a multibyte string. */
wchar_t *dup_mbstowcs(const char *src)
{
if (src && *src) {
wchar_t *dst;
size_t len, check;
len = mbstowcs(NULL, src, 0);
if (len == (size_t)-1) {
errno = EILSEQ;
return NULL;
}
dst = malloc((len + 1) * sizeof *dst);
if (!dst) {
errno = ENOMEM;
return NULL;
}
check = mbstowcs(dst, src, len + 1);
if (check != len) {
free(dst);
errno = EILSEQ;
return NULL;
}
/* Be paranoid, and ensure the string is terminated. */
dst[len] = L'\0';
return dst;
} else {
wchar_t *empty;
empty = malloc(sizeof *empty);
if (!empty) {
errno = ENOMEM;
return NULL;
}
*empty = L'\0';
return empty;
}
}
int main(int argc, char *argv[])
{
wchar_t **argw;
wchar_t *line = NULL;
size_t size = 0;
size_t len;
int arg;
if (!setlocale(LC_ALL, "")) {
fprintf(stderr, "Current locale is unsupported.\n");
return EXIT_FAILURE;
}
if (fwide(stdin, 1) <= 0) {
fprintf(stderr, "Standard input does not support wide characters.\n");
return EXIT_FAILURE;
}
if (fwide(stdout, 1) <= 0) {
fprintf(stderr, "Standard output does not support wide characters.\n");
return EXIT_FAILURE;
}
if (argc < 2) {
fprintf(stderr, "\n");
fprintf(stderr, "Usage: %s WIDE-CHARACTER [ WIDE-CHARACTER ... ]\n", argv[0]);
fprintf(stderr, "\n");
fprintf(stderr, "This program will look for the first instance of each wide character\n");
fprintf(stderr, "in each line of input.\n");
return EXIT_SUCCESS;
}
/* Convert command-line arguments to wide character strings. */
argw = malloc((size_t)(argc + 1) * sizeof *argw);
if (!argw) {
fprintf(stderr, "Out of memory.\n");
return EXIT_FAILURE;
}
for (arg = 0; arg < argc; arg++) {
argw[arg] = dup_mbstowcs(argv[arg]);
if (!argw[arg]) {
fprintf(stderr, "Error converting argv[%d]: %s.\n", arg, strerror(errno));
return EXIT_FAILURE;
}
}
argw[argc] = NULL;
while (1) {
len = wide_line(&line, &size, stdin);
if (!len) {
if (errno) {
fprintf(stderr, "Error reading standard input: %s.\n", strerror(errno));
return EXIT_FAILURE;
} else
if (ferror(stdin)) {
fprintf(stderr, "Error reading standard input.\n");
return EXIT_FAILURE;
}
/* It was just an end of file, no error. */
break;
}
for (arg = 1; arg < argc; arg++)
if (argw[arg][0] != L'\0') {
wchar_t *pos = wcschr(line, argw[arg][0]);
if (pos) {
size_t i = (size_t)(pos - line);
fputws(line, stdout);
wprintf(L"%*lc\n", (int)(i + 1), argw[arg][0]);
}
}
}
/* Because we are exiting the program,
we don't *need* to free the line buffer we used.
However, this is completely safe,
and this is the way you should free the buffer. */
free(line);
line = NULL;
size = 0;
return EXIT_SUCCESS;
}
由于POSIX尚未对getline()
的宽字符版本进行标准化,因此我们将自己的变体实现为wide_line()
。 它支持所有四个换行符约定,并返回size_t
; 如果发生错误,则为0
( errno
)。
由于支持通用换行符,因此wide_line
不太适合交互式输入,因为它往往是一个字符“晚期”。 (对于行缓冲输入,随着端子的增加,这意味着晚整整行。)
我包含了wide_line()
实现,因为它或非常类似的东西可以解决读取在各种系统上编写的宽输入文件时的大多数问题。
当需要命令行参数作为宽字符串时, dup_mbstowcs()
函数最有用。 它只是简单地转换为动态分配的缓冲区。 本质上, argw[]
是argv[]
数组的宽字符副本。
除了这两个函数以及用于创建argw[]
数组的代码外,根本没有多少代码。 (以后可以随意在自己的项目中使用这些功能或整个代码;我认为这些代码在Public Domain中 。)
如果将以上内容另存为example.c
,则可以使用例如eg进行编译
gcc -Wall -O2 example.c -o example
如果您运行例如
printf 'Sergio è un Italiano e andò via!\n' | ./example 'o' 'ò' 'è'
输出将是
Sergio è un Italiano e andò via!
o
Sergio è un Italiano e andò via!
ò
Sergio è un Italiano e andò via!
è
缩进“技巧”是,如果i
是要在其上打印宽字符的位置,则(i+1)
是该逻辑字段的宽度。 当我们在打印规范中使用*
作为宽度字段时,宽度是从要打印的实际参数之前的int
参数读取的。
您需要在预期的字符编码之间进行转换。 假设旧系统需要一些Windows代码页,而新代码则需要UTF-8。 然后,要从新内容中调用旧功能,您需要:
如果您想从旧版本中调用新的UTF-8代码,则需要进行反向跳舞。
编辑:请注意,您的旧系统不可能一直期望纯ASCII,因为ASCII是7位编码,而UTF-8明确地向后兼容。 所以,你的第一个任务是纠正你的什么被实际使用的编码的理解。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.