已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也
四、关于字符串、字符集那些事

和现代Windows操作系统一样,COM也使用UTF-16 LE字符集的16位wchar_t字符串(文档中称之为LPCOLESTRLPCWSTR)。C/C++对wchar_t字符串也有支持,比如C的wcslen、wcscpy等函数、C++的wstring字符串对象等,WinAPI也有lstrlenW、lstrcpyW等函数可以处理它们。

由于字符串都是基于wchar_t的,因此COM和WinAPI之间可以正常交换字符串。所以在Win32程序中使用COM并不需要进行转码。但是对于C/C++的控制台输入输出函数(如scanf、printf)来说,使用wchar_t却容易出问题。由于它们是基于char的,wchar_t需要在内部转换成char才能正常输出。输出不正常的主要原因是VC运行库默认总是使用"C"这种区域设定(即将Unicode的00-FF段直接对应到单字节,大于FF的直接丢弃)进行转码。解决这个问题通常有三个方法:

第一个方法是使用setlocale(LC_ALL, ".OCP");locale::global(locale(".OCP"));重置一下区域设定。但是C语言中对于宽字符的输入输出,需要使用特殊的函数,其中许多带有下划线(如_getws_s),不太美观。并且不支持UTF-8(setlocale(LC_ALL, ".65001")返回NULL)。

第二个方法是不使用VC运行时的转码机制,改用WinAPI转码。Windows有两个WinAPI可以实现wchar_t和char之间的转码:MultiByteToWideChar和WideCharToMultiByte。它们不受VC运行库的限制,但是使用较为麻烦,这里不考虑。

第三个方法是使用ATL为我们定义了两个伪函数CW2ACA2W。它们其实是转码中间层,我们调用的是它的构造函数。由于它们实际上也是调用的WinAPI转码,因此同样不用受VC运行库的限制。CW2A的作用是将wchar_t字符串(Wide)转换为临时char字符串(ANSI),而CA2W则是将char字符串(ANSI)转换为临时wchar_t字符串(Wide)。使用它们要包含atlbase.h

CW2A和CA2W的使用方法很简单:

1.将char字符串传入CA2W,可得到临时wchar_t字符串(用于COM调用)
wcscpy(my_wide_string, CA2W("我是普通char字符串")); // 使用C式wchar_t[]字符串保存
wstring my_wide_string2 = CA2W("我是普通char字符串"); // 使用C++式wstring字符串保存
wstring my_wide_string3 = CA2W("我是普通char字符串", 950); // 使用C++式wstring字符串保存,char使用Big5字符集(950)
hr = pISpVoice->Speak(CA2W("我是普通char字符串"), SPF_DEFAULT, NULL); // 在COM中直接使用

2.将wchar_t字符串传入CW2A,可得到临时char字符串(用于C/C++的输入输出)
strcpy(my_ansi_string, CW2A(L"我是wchar_t字符串")); // 使用C式char[]字符串保存
string my_ansi_string2 = CW2A(L"我是wchar_t字符串"); // 使用C++式string字符串保存
string my_ansi_string3 = CW2A(L"我是wchar_t字符串", 65001); // 使用C++式string字符串保存,char使用UTF-8字符集(65001)
printf("字符串:%s\n", (char*)CW2A(L"我是wchar_t字符串", 1)); // 输出字符串,OEM代码页,这里要手动转换为(char *)

CW2A和CA2W有个可选参数,可指定代码页,其中有一些系统指定的特殊代码页:
0 当前ANSI代码页CP_ACP(记事本编码)(简体中文936,繁体中文950,英文1252)(默认值)
1 当前OEM代码页CP_OEMCP(命令提示符编码)(简体中文936,繁体中文950,英文437)
65000 UTF-7代码页CP_UTF7(不常用)
65001 UTF-8代码页CP_UTF8(常用国际编码

有些文章可能提到可以用wchar_t *或char *变量来接收传过来的字符串,这是错误的。C++中通过直接调用构造函数创建的对象,生命期仅限本条语句。CA2W和CW2A对于128个字符以内的字符串是分配在栈上的,即使析构了字符串有时还可以从栈中读出。但是对于特别长的字符串则会分配在堆上,用char *或wchar_t *接收的只是地址而已,CW2A或CA2W的析构函数已经调用,为字符串所分配的空间已释放,再访问它肯定会崩溃。

在这里我们实现一个可以交互式阅读文本的程序,在命令提示符(OEM代码页)输入语句,即可让Windows的语音引擎为我们读出来。

首先我们要使用gets_s读入字符串(使用起来比gets差不多,但能正确判断缓冲区大小):
char strinput[200] = "";
gets_s(strinput); // 从控制台读入字符串

然后我们将pISpVoice->Speak第一个参数L"电脑在说话!"替换为CA2W(strinput, 1)即可:
hr = pISpVoice->Speak(CA2W(strinput, 1), SPF_DEFAULT, NULL);
ValidateHR(hr);

为了能够重复输入,我们将上述两个过程套进while(true)循环里,并判断是否输入了exit,若有则退出:
if (strcmpi(strinput, "exit") == 0) break; // 如果输入了exit则退出

最终代码如下。现在,你可以让系统读出你输入的任意字符串了。[s::lol]
<code class="lang-cpp">#include <stdio.h>
#include <stdlib.h>
                                                                                     
#include <windows.h> // 使用COM组件需要包含windows.h
#include <sapi.h> // 使用ISpVoice语音组件需要包含sapi.h
#include <atlbase.h> // 使用CComPtr<t>和CA2W需要包含atlbase.h
                                                                                     
#define ValidateHR(hr) \
    if (FAILED(hr)) { \
        printf("HRESULT错误:%p,在%s第%d行\n", hr, __FILE__, __LINE__); \
        exit(1); \
    }
                                                                                     
                                                                                     
int main()
{
                                                                                     
    HRESULT hr = CoInitialize(0); // 初始化COM环境,参数保留,必须为0
    ValidateHR(hr);
                                                                                     
    {
        // 建立ISpVoice接口实例(组件ID为CLSID_SpVoice)
        CComPtr<ispvoice> pISpVoice;
        hr = pISpVoice.CoCreateInstance(CLSID_SpVoice); // 省略了后两个参数:NULL, CLSCTX_ALL
        ValidateHR(hr);
                                                                                     
        while (true)
        {
            char strinput[200] = "";
            gets_s(strinput); // 从控制台读入字符串(OEM代码页)
                                                                                     
            if (strcmpi(strinput, "exit") == 0) // 如果输入了exit则退出
                break;
                                                                                     
            // 调用ISpVoice的Speak成员函数(使用CA2W转码)
            hr = pISpVoice->Speak(CA2W(strinput, 1), SPF_DEFAULT, NULL);
            ValidateHR(hr);
        }
                                                                                     
    } // pISpVoice在作用域边界会自动释放
                                                                                         
    CoUninitialize(); // 卸载COM环境
    return 0;
}</ispvoice></t></atlbase.h></sapi.h></windows.h></stdlib.h></stdio.h></code>
文号 / 789660

千古风流
名片发私信
学术分 4
总主题 466 帖总回复 2942 楼拥有证书:进士 学者 笔友
注册于 2009-05-30 21:22最后登录 2019-01-31 17:16
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步

个人简介

暂未填写
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

插入资源
全部
图片
视频
音频
附件
全部
未使用
已使用
正在上传
空空如也~
上传中..{{f.progress}}%
处理中..
上传失败,点击重试
等待中...
{{f.name}}
空空如也~
(视频){{r.oname}}
{{selectedResourcesId.indexOf(r.rid) + 1}}
处理中..
处理失败
插入表情
我的表情
共享表情
Emoji
上传
注意事项
最大尺寸100px,超过会被压缩。为保证效果,建议上传前自行处理。
建议上传自己DIY的表情,严禁上传侵权内容。
点击重试等待上传{{s.progress}}%处理中...已上传,正在处理中
空空如也~
处理中...
处理失败
加载中...
草稿箱
加载中...
此处只插入正文,如果要使用草稿中的其余内容,请点击继续创作。
{{fromNow(d.toc)}}
{{getDraftInfo(d)}}
标题:{{d.t}}
内容:{{d.c}}
继续创作
删除插入插入
插入公式
评论控制
加载中...
文号:{{pid}}
加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}
ID: {{user.uid}}