【问题标题】:Converting LPCTSTR to LPWSTR将 LPCTSTR 转换为 LPWSTR
【发布时间】:2016-06-07 21:00:52
【问题描述】:

使用 MFC 和 Unicode 构建

我想更改 listctrl 的列标题文本,为此我必须将 LPCTSTR 转换为 LPWSTR。我现在要做的是

void CSPCListViewCtrl::SetHeaderText(long lCol, LPCTSTR lColText)
{
    CListCtrl& ListCtrl = GetListCtrl();

    LV_COLUMN lvc;
    ::ZeroMemory((void *)&lvc, sizeof(LVCOLUMN));
    lvc.mask |= LVCF_TEXT;

    ListCtrl.GetColumn(lCol, &lvc);
    lvc.pszText = const_cast<LPWSTR>(lColText);

    ListCtrl.SetColumn(lCol, &lvc);
}

它似乎有效,但 const_cast 对我来说看起来有些奇怪和错误,所以我尝试了类似的东西

USES_CONVERSION;
lvc.pszText = CT2W(lColText);

这似乎在发布构建中有效,但在调试构建中产生垃圾所以我想知道正确的方法是什么?

【问题讨论】:

  • LV_COLUMN (msdn.microsoft.com/en-us/library/windows/desktop/…) 中没有 LPWSTR。我假设 LPTSTR 参数传递的字符串在某种程度上是临时的。您应该考虑传递给 pszText 成员的字符串的所有权和生命周期。
  • 好吧,我的错。 LV_COLUMN::pszText 是一个 LPTSTR(在我的 unicode 案例中编译为一个 LPWSTR)。所以你的意思是 const_casting 到 LPTSTR 是正确的方法吗?是的,传递的参数是临时的,但我认为 SetColumn 会复制,不是吗?
  • 虽然const_cast 可以在所有现有版本的列表控件中工作,但它肯定不是必须的。想象一下,在下一个版本中,他们将增强列标题,使其具有左右对齐的部分,并选择使用strtok() 之类的东西来解析pszText。如果您的字符串实际上是常量,这可能会导致访问冲突。底线是 - 您必须创建该文本的非常量副本;下面的答案中建议了一种方法。
  • @VladFeinstein:这不正确。 pszText 被声明为非常量的事实是一个人工制品。 LV_COLUMN 结构用于设置和读取列信息。读取时,指针必须指向一个非常量缓冲区;写入时,它指定一个指向常量的指针。 C 没有规定可以根据它是用作输入参数还是输出参数来修改结构成员的常量。 const_cast 实际上是安全的,并且可以使用(带有适当的注释)。 Microsoft 不会以会破坏 98% 的软件的方式改变 API 的行为。

标签: visual-c++ unicode mfc


【解决方案1】:

TL;DR:调用CListCtrl::SetColumn 时使用const_cast&lt;LPTSTR&gt;(lColText) 是安全的。

但是为什么LVCOLUMN structurepszText 成员声明为非常量? LVCOLUMN 结构用于设置和检索信息。检索列的文本时,您需要传入一个可修改的缓冲区(和长度参数)。另一方面,当设置列文本时,系统使用 pszText 成员并在内部存储一个副本。它不会尝试写入它。这也是documented,即使非常微妙:

cchTextMax
pszText 成员指向的缓冲区的 TCHAR 大小。 如果结构没有接收到有关列的信息,则忽略此成员。

这是 Windows API 中的常见模式,其中相同的结构同时用作输入和输出参数。解决此问题的唯一选择是为这些结构引入const-ified 版本。当 30 年前发明 Windows API 时,这被认为没有必要或有用。此外,它会使通用模式(读取-更新-写入)更加繁琐且容易出错,因为必须在不相关的类型之间手动复制数据。


既然您知道,在您描述的场景中使用const_cast 是安全的,您可能想知道使用它是否是个好主意。这取决于许多因素,例如
  • 您能否提出有用的评论来简明扼要地解释一下,为什么这样可以?类似于// Totally safe, it's just that M$ sucks 的东西可能不会削减它。
  • 您的团队中的所有成员是否都了解其中的含义,或者这是否会在堆损坏调试会话中出现红鲱鱼?
  • 您是否有允许使用const_casts 的编码指南?
  • 您是否使用不会对const_casts 产生误报的静态代码分析工具?

如果您发现您无法满意地回答所有这些问题,您可以考虑实施(技术上不必要的)手动复制(并交换const_cast 以进行异常处理):

void CSPCListViewCtrl::SetHeaderText(long lCol, LPCTSTR lColText) {
    CListCtrl& ListCtrl = GetListCtrl();

    LVCOLUMN lvc = {0};
    lvc.mask |= LVCF_TEXT;

    // Create modifiable copy of lColText
    size_t colTextLength = _tcslen(lColText);
    std::vector<TCHAR> buffer(colTextLength + 1);
    std::copy(lColText, lColText + colTextLength + 1, buffer.data());
    lvc.pszText = buffer.data();

    ListCtrl.SetColumn(lCol, &lvc);
}


关于您使用字符串编码的另一个注意事项:您将Generic-Text Mappings 与显式Unicode (UTF-16LE) 编码混合使用。为了增加一致性,您应该将 lColText 参数更改为类型LPCWSTR,并使用LVCOLUMNW 结构(以及如果您决定采用该方法,则使用const_cast&lt;LPWSTR&gt;)。不幸的是,在使用 MFC 时,您将不得不在调用任何类成员时使用通用文本映射。但至少在字符编码不匹配的情况下你会得到一个编译器错误。

【讨论】:

    【解决方案2】:

    你可以使用CString::GetBuffer()

    void SetHeaderText(long lCol, LPCTSTR lColText)
    {
        LV_COLUMN lvc;
        ::ZeroMemory((void *)&lvc, sizeof(LVCOLUMN));
        lvc.mask |= LVCF_TEXT;
    
        list.GetColumn(lCol, &lvc);
    
        CString str = lColText;
        lvc.pszText = str.GetBuffer();
    
        list.SetColumn(lCol, &lvc);
    
        str.ReleaseBuffer();
        //ReleaseBuffer is option in this case because 
        //"str" is local variable and is destroyed before being used again*
    }
    
    
    SetHeaderText(0, L"text");
    

    在 UNICODE 中,LPTSTR 只是 LPWSTR(或简称为 wchar_t*

    如果由于某种原因您有 ANSI 文本,那么您可以使用 CString 进行转换

    CStringA ansi = "Text";
    CStringW wide = CStringW(ansi);
    
    SetHeaderText(0, wide);
    

    另请参阅
    CString::GetBuffer()

    【讨论】:

    • 关于你的代码注释:str被使用后不会被销毁吗?
    • @harper 我的意思是在“再次”使用之前说,我不知道如何正确解释,我应该不管它。我添加了文档链接。
    猜你喜欢
    • 2017-11-10
    • 2018-06-02
    • 2011-10-15
    • 2012-08-12
    • 1970-01-01
    • 2019-02-01
    • 1970-01-01
    • 2012-12-14
    • 2012-09-09
    相关资源
    最近更新 更多