wxWidgets源码分析 - wxString

Posted psbec

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了wxWidgets源码分析 - wxString相关的知识,希望对你有一定的参考价值。

目录

wxString

wxString的中文字符支持

中文字符的编码格式如下:

汉字 GBK 区位码 UTF-8 UTF-16
D6 D0 54 48 E4 B8 AD 4E 2D
CE C4 46 36 E6 96 87 65 87

不同操作系统的默认内码

Windows系统(默认GBK):41  42  d6  d0  ce  c4
Linux系统(默认UTF-8):41  42  e4  b8  ad  e6  96  87

Windows

wxStrubg在window系统中使用了UTF-16编码格式。但是在windows系统中,默认的文件的编码格式为GBK格式,考虑到这点,wxString在对赋值取值时,对字符编码进行了从GBK到UTF-16编码的转换,这也就要求源码必须是GBK格式的!!,否则显示出来的就是乱码,当然还有另外一条路,就是使用wxConv类告诉wxString当前的字符编码是什么格式的。

源码文件格式为GBK时的测试,可以发现只有不进行转换的才可以显示。

测试函数:wxString("AB中文",wxConvUTF8)
输出:空

测试函数:wxString("AB中文")
输出:41 42 4E2D 6587 

测试函数:_("AB中文")
输出:41 42 4E2D 6587 

源码文件格式为UTF-8时的测试,可以发现只有转换的才可以显示,其他的都显示乱码。

测试函数:wxString("AB中文",wxConvUTF8)
输出:41 42 4E2D 6587 

测试函数:wxString("AB中文")
输出:41 42 6D93 E15F 6783 

测试函数:_("AB中文")
输出:41 42 6D93 E15F 6783 

Linux Unicode

编译wxWidgets 3.0 时指定了“--enable-unicode”选项,指定此选项后wxString的内部编码使用的是UTF-16格式进行编码(注:即使不指定--enable-unicode选项,wxWidgets依旧使用unicode格式保存数据,Windows系统中使用UTF-16,Linux&MAC系统中使用UTF-32)

测试函数:wxString("AB中文",wxConvUTF8)
输出:41 42 4E2D 6587 

测试函数:wxString("AB中文")
输出:空

测试函数:_("AB中文")
输出:空

(源码文件格式为UTF-8,如果源文件为非UTF-8格式,则都输出错误)

可以看出,Linux系统中文件格式是 UTF-8 格式的,转换后,wxString内部是UTF-32格式的,如果要在Linux中显示中文,则需要使用wxConvUTF8进行转换

Linux UTF-8

编译wxWidgets 3.0 时指定了“--enable-utf8 --enable-utf8only”选项,这样在wxString内部编码时就直接使用UTF-8格式了,增加 “--enable-utf8only”选项后,则禁止了字符之间的转换,在效率上有大幅的提升,同时编写代码也容易多了:

测试函数:wxString("AB中文",wxConvUTF8)
输出:41 42 4E2D 6587 

测试函数:wxString("AB中文")
输出:41 42 4E2D 6587 

测试函数:_("AB中文")
输出:41 42 4E2D 6587 

(源码文件格式为UTF-8,如果源文件为非UTF-8格式,则都输出错误)

注意到,上面三种格式的数据都能正常输出。

总结

建议后续在写wxWidgets程序时,界面中涉及到的语言全部是使用英语,这样不管文件是GBK编码也好,是UTF-8编码也好,都能够编译、显示正常。

如果需要支持多过语言,建议使用wxWidgets提供的wxLocale解决方案。

wxString与通用字符串的转换

wxString对象的创建

除了wxString的构造函数外,可以通过以下几种方式来生成wxString对象:

静态方法 说明
wxString::FromAscii() 通过ASCII码的字符串来创建wxString对象,如果字符串中含有大于等于0x80的字符串,wxString会报错,比如
wxString str = wxString::FromAscii("ABC中文");
在运行时如果开启了debug模式就会收到alert信息,同时显示该字符串如果显示出来是乱码;
wxString::FromUTF8() 通过UTF-8字符串来创建对象,比如:
wxString test = wxString::FromUTF8("xF0x90x8Cx80");
构造函数+wxMBConv对象 wxMBConv对象默认值为wxConvLibc

将wxString对象转换为其他类型数据

wxString 提供了多种方法,可以将数据转换为你需要的数据;

c_str()

原型:

wxCStrData wxString::c_str() const;

本函数用于将wxString对象转换为 const char* 或者 const wchar_t* 格式的数据,以下为使用方法:

const char *p = str.c_str();        // 左侧已经注明类型,c_str函数将会输出`const char*`类型
sprintf(abStr, "sprintf = %s
", (const char *)str.c_str());    // 必须加强转

跟踪代码可以看到wxCStrData是通过重载强转操作符实现的类型自动转换:

    inline const wchar_t* AsWChar() const;
    inline const char* AsChar() const;
    const unsigned char* AsUnsignedChar() const
        { return (const unsigned char *) AsChar(); }
        
    // 重载转换操作符
    operator const wchar_t*() const { return AsWChar(); }
    operator const char*() const { return AsChar(); }
    operator const unsigned char*() const { return AsUnsignedChar(); }
    operator const void*() const { return AsChar(); }

数据的输出通过的是 wxCStrData::AsChar() 函数,内部实现实际上时通过调用wxString的内部函数AsCharmb_str来实现的:

inline const char* wxCStrData::AsChar() const
{
#if wxUSE_UNICODE && !wxUSE_UTF8_LOCALE_ONLY
    const char * const p = m_str->AsChar(wxConvLibc);
    if ( !p )
        return "";
#else // !wxUSE_UNICODE || wxUSE_UTF8_LOCALE_ONLY
    const char * const p = m_str->mb_str();
#endif // wxUSE_UNICODE && !wxUSE_UTF8_LOCALE_ONLY

    return p + m_offset;
}
mb_str()

原型:

const wxCharBuffer  mb_str (const wxMBConv &conv=wxConvLibc) const;

使用方法与c_str类似:

const char *p = str.c_str();

函数的实现如下,mb_str调用AsCharBufAsCharBuf接着调用AsChar实现转换(AsChar函数后续有详细分析):

const wxScopedCharBuffer mb_str(const wxMBConv& conv = wxConvLibc) const
{
    return AsCharBuf(conv);
}
wxScopedCharBuffer AsCharBuf(const wxMBConv& conv) const
{
    ...
    if ( !AsChar(conv) )
    ...
}

我们再看下wxScopedCharBuffer的实现,它也应当同样实现了wxCStrData函数的操作符强制转换,跟踪代码:

typedef wxScopedCharTypeBuffer<char> wxScopedCharBuffer;

// 继续 wxScopedCharTypeBuffer,可以看到它也实现了操作符重载,转换为模板指定的类型
// 对于 wxScopedCharBuffer 来说就是 const char*
template <typename T>
class wxScopedCharTypeBuffer
{
public:
    typedef T CharType;
    
    const CharType *data() const { return  m_data->Get(); }
    operator const CharType *() const { return data(); }
}
utf8_str()

函数返回UTF-8字符串,可以看到,它是通过调用mb_str(wxMBConvUTF8())实现了自身的转换,输出的字符串格式为UTF-8编码的。

const wxScopedCharBuffer utf8_str() const { return mb_str(wxMBConvUTF8()); }
ToStdString()

用于将wxString对象转换称std::string对象,可以看到,最终还是通过调用mb_str函数进行转换。

std::string ToStdString() const
{
    wxScopedCharBuffer buf(mb_str());
    return std::string(buf.data(), buf.length());
}
wxString::AsChar转换的关键函数

下面针对Unicode模式的代码分析:

const char *wxString::AsChar(const wxMBConv& conv) const
{
#if wxUSE_UNICODE_UTF8
    ...
#else // wxUSE_UNICODE_WCHAR
    // 调用c_str()获取wchar_t*,由于wxString内部就是使用wchar_t*存储的
    // 所以这步调用直接获取到内部的buffer
    const wchar_t * const strWC = m_impl.c_str();
    const size_t lenWC = m_impl.length();
#endif // wxUSE_UNICODE_UTF8/wxUSE_UNICODE_WCHAR
    // 调用`conv.FromWChar`将Unicode字符转换为当天系统的MultiByte字符串
    const size_t lenMB = conv.FromWChar(NULL, 0, strWC, lenWC);
    if ( lenMB == wxCONV_FAILED )
        return NULL;

    if ( !m_convertedToChar.m_str || lenMB != m_convertedToChar.m_len )
    {
        if ( !const_cast<wxString *>(this)->m_convertedToChar.Extend(lenMB) )
            return NULL;
    }

    m_convertedToChar.m_str[lenMB] = ‘‘;
    if ( conv.FromWChar(m_convertedToChar.m_str, lenMB,
                        strWC, lenWC) == wxCONV_FAILED )
        return NULL;

    return m_convertedToChar.m_str;
}

调用 wxMBConv::FromWChar 进行字符串转换, 跟踪 wxMBConv::FromWChar 的实现,它会继续调用wxMBConv::WC2MB执行转换,可参考另外一片文章,有描述此函数的实现。

通过上述过程实现了wxString到本地字符串的转换。

字符集转换

正式代码中通过下面的方式调用(wxWidgets-3.0.2):

wxString    testStr("abc中文测试");

接着我们看下wxString内部时如何实现的:

调用wxString的构造函数:

// string.h L1241
  wxString(const char *psz)
    : m_impl(ImplStr(psz)) {}

wxString的构造函数调用ImplStr来初始化wxString的内部变量m_impl,函数定义如下:

  static wxScopedWCharBuffer ImplStr(const char* str,
                                     const wxMBConv& conv = wxConvLibc)
    { return ConvertStr(str, npos, conv).data; }

我们先看下第二个参数wxConvLibc的由来,具体实现我们后面再说:

// strconv.h L563
#define WX_DECLARE_GLOBAL_CONV(klass, name)                 extern WXDLLIMPEXP_DATA_BASE(klass*) name##Ptr;         extern WXDLLIMPEXP_BASE klass* wxGet_##name##Ptr();     inline klass& wxGet_##name()                            {                                                           if ( !name##Ptr )                                           name##Ptr = wxGet_##name##Ptr();                    return *name##Ptr;                                  }
    
// 使用WX_DECLARE_GLOBAL_CONV宏预定义转换对象,实际是声明了一个函数
// 和一个指针,实现wxGet_wxConvLibc函数返回这个指针,保证全局唯一
WX_DECLARE_GLOBAL_CONV(wxMBConv, wxConvLibc)
#define wxConvLibc wxGet_wxConvLibc()

接着 ImplStr 会调用 ConvertStr 来进行转换,函数实现如下:

// string.cpp L385
#if wxUSE_UNICODE_WCHAR
wxString::SubstrBufFromMB wxString::ConvertStr(const char *psz, size_t nLength, const wxMBConv& conv)
{
    // 调用 conv.cMB2WC 进行转换
    wxScopedWCharBuffer wcBuf(conv.cMB2WC(psz, nLength, &wcLen));
}

继续调用 wxMBConv::cMB2WC 进行字符数据的转换,我们跟踪一下源码,此函数会调用 wxMBConv::ToWChar 进行数据转换,然后返回长度:

const wxWCharBuffer
wxMBConv::cMB2WC(const char *inBuff, size_t inLen, size_t *outLen) const
{
    const size_t dstLen = ToWChar(NULL, 0, inBuff, inLen);
    //...

查看wxMBConv::ToWChar的源码可以看到,它继续调用MB2WC方法进行字符的转换,这个是个虚接口,接着我们看看这个虚接口的实现。

接着上文的 wxConvLib ,我们看看是怎么实现的。

// strconv.cpp L3417
#define WX_DEFINE_GLOBAL_CONV2(klass, impl_klass, name, ctor_args)          WXDLLIMPEXP_DATA_BASE(klass*) name##Ptr = NULL;                         WXDLLIMPEXP_BASE klass* wxGet_##name##Ptr()                             {                                                                           static impl_klass name##Obj ctor_args;                                  return &name##Obj;                                                  }                                                                       /* this ensures that all global converter objects are created */        /* by the time static initialization is done, i.e. before any */        /* thread is launched: */                                               static klass* gs_##name##instance = wxGet_##name##Ptr()

// strconv.cpp L3437
#ifdef __WINDOWS__
    WX_DEFINE_GLOBAL_CONV2(wxMBConv, wxMBConv_win32, wxConvLibc, wxEMPTY_PARAMETER_VALUE);
#elif 0 // defined(__WXOSX__)
    WX_DEFINE_GLOBAL_CONV2(wxMBConv, wxMBConv_cf, wxConvLibc,  (wxFONTENCODING_UTF8));
#else
    WX_DEFINE_GLOBAL_CONV2(wxMBConv, wxMBConvLibc, wxConvLibc, wxEMPTY_PARAMETER_VALUE);
#endif

上面的宏展开后,可以得到在windows平台上 wxConvLibc 的实现类是 wxMBConv_win32,在其他平台上实现类是 wxMBConvLibc

对于windows平台中wxMBConv_win32实现可参考 strconv.cpp 中的实现,主要实现了 MB2WC 和 WC2MB 两个接口,功能说明可以参考wxMBConv类的接口定义:

  1. MB2WC:用于实现从multibyte encoding 到 Unicode的转换,也就是GBK到UTF-16的转换;具体的实现就是调用win32API MultiByteToWideChar 函数执行字符集的转换
  2. WC2MB:与上一个相反,用于实现 Unicode 到 multibyte encoding的转换;具体的实现是调用 WideCharToMultiByte 函数执行字符集的转换。

对于Unix平台,实际上时调用的mbstowcs()wcstombs()实现的转换,这两个函数是标准C库函数,这两个函数的实现与当前系统的locale息息相关,手册上明确说明这两个函数的行为依赖 LC_CTYPE 值,至于具体的实现,可以参考locale相关的文档。




以上是关于wxWidgets源码分析 - wxString的主要内容,如果未能解决你的问题,请参考以下文章

wxWidgets源码分析 - 窗口管理

wxWidgets源码分析 - 消息处理过程

wxWidgets源码分析 - 窗口尺寸

wxWidgets源码分析 - App启动过程

wxWidgets源码分析 - 消息映射表

wxWidgets源码分析 - 窗口关闭过程