如何将LPWSTR转换为带有UTF-8编码的char*

3
我正在使用Qt开发跨平台项目。在Windows上,我想通过命令行传递一些Unicode字符(例如包含中文字符的文件路径)作为参数启动应用程序。然后使用这些参数来创建一个QCoreApplication
由于某些原因,我需要使用CommandLineToArgvW获取参数列表,如下所示:
LPWSTR * argvW = CommandLineToArgvW( GetCommandLineW(), &argc );

我了解在现代Windows操作系统上,LPWSTR实际上是wchar_t*,它是16位并使用UTF-16编码。
但如果我想初始化QCoreApplication,只能使用char*而不能使用wchar_t *QCoreApplication 那么问题来了:如何安全地将CommandLineToArgvW()函数返回的LPWSTR转换为char*,同时不丢失UNICODE编码(例如,汉字仍然是汉字)?
我尝试了许多不同的方法都没有成功:
1:
    std::string const argvString = boost::locale::conv::utf_to_utf<char>( argvW[0] )

2:

    int res;
    char buf[0x400];
    char* pbuf = buf;
    boost::shared_ptr<char[]> shared_pbuf;

    res = WideCharToMultiByte(CP_UTF8, 0, pcs, -1, buf, sizeof(buf), NULL, NULL);

3:首先将其转换为QString,然后再转换为UTF-8。

ETID: 问题已解决。对于这三种方法,UTF-16宽字符到UTF-8 char的转换实际上都可以正常工作,没有问题。在Visual Studio中,为了正确查看调试中的UTF-8字符串,需要在所监视的变量名后面添加s8格式说明符(参见:https://msdn.microsoft.com/en-us/library/75w45ekt.aspx)。这是我忽略的部分,让我认为我的字符串转换有问题。

实际上,这里的真正问题是当调用QCoreApplication.arguments()时,返回的QString是由QString::fromLocal8Bit()构造的,当命令行参数包含Unicode字符时,会引发编码问题。解决方法是,在Windows上每次需要检索命令行参数时,总是调用Windows APICommandLineToArgvW(),并将16位的UTF-16 wchar_t *(或LPWSTR)转换为8位的UTF-8 char *(通过上述三种方法之一)。


1
根据Qt文档,Qt会自动为您使用CommandLineToArgvW除非您将修改后的参数传递给QCoreApplication构造函数。它没有说明什么是“修改后的”,但大概意思是对于普通代码只是盲目地转发main参数,但如果有任何差异,则遵循客户端代码的意愿。请参见http://doc.qt.io/qt-5/qcoreapplication.html#arguments。 - Cheers and hth. - Alf
可能是Windows unicode commandline argv的重复问题。 - Dan Korn
3
在Windows下,WideCharToMultiByte(CP_UTF8, ...)是规范的方式。你说它“失败”了。那么返回值是什么?然后是GetLastError()返回值是什么? - dxiv
@NicolBolas,我可以通过使用传递的参数作为文件路径加载文件来确保字符仍然是我想要的。如果文件正确加载,则字符串被成功转换并传递。 - Wayee
叹气。关于qtmain_win.cpp没有使用CommandLineToArgvW来处理Win32的问题,你发现得很好。经过更仔细的阅读后,看起来Qt会将参数转换为本地的8位字符集,无论如何。你可以将修改后的参数传递给构造函数,但它们必须是系统本地编码。我对为什么会这样感到有些惊讶,尽管我怀疑这是出于历史原因。QGuiApplication可能会修改这种行为并读取Unicode字符串,但是在QPA的混乱中导航似乎是不可攻破的。我已经删除了我的答案,因为它没有帮助。 - jonspaceharper
显示剩余2条评论
2个回答

2

Qt内部包装了int main(),在执行您的代码之前提取和解析Unicode命令行参数(通过CommandLineToArgvW)。生成的已解析数据被转换为本地UTF-8格式作为相当于QString::toLocal8Bit()char **argv

使用QCoreApplication::arguments()来获取Unicode args。此外,文档中还有一个有用的提示:

在Windows上,只有在构造函数传递了修改后的argv / argc参数时,列表才是从argc和argv参数构建的。在这种情况下,可能会出现编码问题。


那么您的建议是每当涉及到Unicode时,就不应使用Windows API CommandLineToArgvW()来检索argv参数并将它们传递给QCoreApplication吗?那正确的方式是什么? - Wayee
@Wayee 请看我的更新。只需调用QCoreApplication::arguments()来获取您所需的数据。 - jonspaceharper
通过调用 QCoreApplication::arguments() 返回的 QStringList 实际上是您在构造 QApplication 时传递的参数。我的问题是如何传递正确的 Unicode 字符串来构造 QApplication。明确一点,在 MAC OS 上,调用 QApplication(0,nullptr) 将足以获取控制台参数。但是在 Windows 上这样做将只会发送空的参数列表来构造 QApplication。 - Wayee
也许我之前没有表达清楚。我正在寻找的是在Windows上检索Unicode参数并使用这些参数构建QApplication的合适方法。一个具体的例子是支持通过拖动文件到我的应用程序并释放鼠标来加载文件的行为。然后,文件路径作为启动我的应用程序的参数通过命令行传递。一旦文件路径包含非ANSI字符,问题就会出现。@Jon - Wayee
由于arguments()函数正是您所需的,因此我已经取消了对Qt包装main()的信息的答案。 - jonspaceharper

2
您应该能够使用 QString 的函数。例如:
QString str = QString::fromUtf16((const ushort*)argvW[0]);
::MessageBoxW(0, (const wchar_t*)str.utf16(), 0, 0);

使用WideCharToMultiByte函数时,输出缓冲区和输出缓冲区长度都应该传入0。这样可以得到需要的输出缓冲区字符数。例如:

const wchar_t* wbuf = argvW[0];
int len = WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, 0, 0, 0, 0);

std::string buf(len, 0);

WideCharToMultiByte(CP_UTF8, 0, wbuf, -1, &buf[0], len,0,0);
QString utf8;
utf8 = QString::fromUtf8(buf.c_str());
::MessageBoxW(0, (const wchar_t*)utf8.utf16(), 0, 0);

相同的信息也应该在QCoreApplication::arguments中可用。例如,使用Unicode参数运行此代码并查看输出:

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    QString filename = QString::fromUtf8("ελληνική.txt");
    QFile fout(filename);
    if (fout.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        QTextStream oss(&fout);
        oss.setCodec("UTF-8");
        oss << filename << "\n";
        QStringList list = a.arguments();
        for (int i = 0; i < list.count(); i++)
            oss << list[i] << "\n";
    }
    fout.close();
    return a.exec();
}

请注意,在上面的示例中,文件名被内部转换为UTF-16格式,这是由Qt完成的。WinAPI使用的是UTF-16格式,而不是UTF-8格式。

QCoreApplication的构造函数不接受QString作为命令行参数。 - jonspaceharper
我建议使用QString进行UTF-16/UTF-8转换。CommandLineToArgvW是WinAPI,它将具有正确的内容,并且可以转换并作为UTF-8 char传递。 - Barmak Shemirani
@JonHarper那么我该如何正确地初始化QCoreApplication以接受Unicode参数呢?我尝试过其中一条评论中提到的解决方法(链接),通过将NULL传递给构造QApplication。但这些参数根本没有被考虑进去。 - Wayee
转换为UTF-8相当简单,我不知道你遇到了什么问题。我猜你可能走错了方向,试图将UTF-8放在不应该的地方。请参见更新的答案,其中提供了在Unicode中写入文件的简单示例。 - Barmak Shemirani
1
你一直在错误的方向上寻找。再次强调,Windows API不支持UTF-8编码,而是使用UTF-16编码。像CommandLineToArgvW这样的宽字符串函数返回的是UTF-16编码的字符串。你可以使用MessageBoxW(0, argvW[0], 0, 0)来显示该字符串,但不应将其转换为UTF-8编码。Qt的QString试图解决Windows和许多基于Unix的系统(使用UTF-8编码)之间的不兼容性。在Windows编程中,通常只有在导入/导出数据时(例如从文本文件或HTML输入文件中),才需要将其转换为UTF-8编码。 - Barmak Shemirani
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接