5

I'm having a hard time converting a string into LPWSTR so I can use the PathStripToRoot() function.

Well for one the MSDN documation says I need LPTSTR variable (http://msdn.microsoft.com/en-us/library/windows/desktop/bb773757(v=vs.85).aspx), but Visual Studio 2013 says I need LPWSTR.

Here is a code snippet of my function:

fileStat fileCreate(const string& targetFile)
{
    fileStat filez;

    fstream file(targetFile.c_str());
    if (!file)
    {
        cout << "File does not exist" << endl;
    }

    std::ifstream in(targetFile, ios::binary | ios::ate);
    int a = in.tellg();
    cout << "File size(bytes): " << in.tellg() << endl << endl;
    file.close();


    wstring stemp = strChange(targetFile);
    LPCWSTR result = stemp.c_str();

    /* Tried the below code but that did not work
    LPWSTR ws = new wchar_t[targetFile.size() + 1]; 
    copy(targetFile.begin(), targetFile.end(), ws);
    ws[targetFile.size()] = 0;
        */

    cout<<"\n\n"<<PathStripToRoot(ws)<<"\n\n";

 ...
    filez.fileSize = a;
    return filez;
}

A lot of people have said to use MultiByteToWideChar() function but I looked at the MSDN documation and have no idea how it works. Is there an easier way than using MultiByteToWideChar() ?

Mr.C64
  • 41,637
  • 14
  • 86
  • 162
dspaces1
  • 193
  • 1
  • 3
  • 15
  • 3
    Documentation says `LPTSTR`, note the extra `T`. This means it is wide or not depending on build configuration. – crashmstr Jun 10 '14 at 14:18
  • On a side note I have a warning converting from "std::streamoff to int, possible loss of data" which is cause by int a = in.tellg() function that I want to store the file size in bytes. – dspaces1 Jun 10 '14 at 14:19
  • There's [`std::wstring_convert`](http://en.cppreference.com/w/cpp/locale/wstring_convert). – chris Jun 10 '14 at 14:22
  • 1
    @dspaces1: I've just removed the [visual-studio-2013] tag, since while you are certainly using VS2013 to develop your code, probably that's not key to the question (e.g. this is not a question on VS2013 IDE...). For sake of completeness, I've added the "2013" VS version number in your question text. Of course, feel free to undo my changes if you think your original choice is better. – Mr.C64 Jun 10 '14 at 15:31
  • @AdrianMcCarthy I looked at that post and that only helps converting to LPCWSTR which doesn't work with PathStripToRoot() – dspaces1 Jun 10 '14 at 16:12
  • @dspaces1: But it shows how to use MultiByteToWideString, which is the part you said was difficult. – Adrian McCarthy Jun 10 '14 at 16:14

5 Answers5

2

You may want to use Unicode UTF-16 strings in modern Windows applications when dealing with Win32 APIs: the std::wstring class (based on wchar_t) is OK for that with Visual C++.

Then, you can wrap the Win32 C API PathStripToRoot() in some C++ code, using convenient string classes instead of raw C-like string buffers.

Consider the following commented code as an example:

// Set Unicode mode
#define UNICODE
#define _UNICODE

// Windows SDK Headers
#include <Windows.h>    // Win32 Platform SDK
#include <Shlwapi.h>    // For PathStripToRoot()
#include <Strsafe.h>    // For StringCchCopy()

// Standard C++ Headers
#include <exception>    // For std::exception
#include <iostream>     // For console output
#include <stdexcept>    // For std::invalid_argument, std::runtime_error
#include <string>       // For std::wstring

// For using PathStripToRoot()
#pragma comment(lib, "Shlwapi.lib")


// C++ wrapper around PathStripToRoot() Win32 API
std::wstring RootFromPath(const std::wstring& path) 
{   
    // Buffer for PathStripToRoot()
    wchar_t pathBuffer[MAX_PATH];

    // Copy the input string into the buffer.
    // Beware of buffer overruns!
    HRESULT hr = ::StringCchCopy(pathBuffer,            // dest
                                 _countof(pathBuffer),  // dest size
                                 path.c_str());         // source
    if (hr == STRSAFE_E_INSUFFICIENT_BUFFER)
    {
        // Copy failed due to insufficient buffer space.
        // May accept this case or throw an exception
        // based on the context...
        // In this case, I just throw here.
        throw std::invalid_argument("RootFromPath() - Path string too long.");
    }
    if (hr != S_OK)
    {
        throw std::runtime_error("RootFromPath() - StringCchCopy failed.");
    }


    // Call the Win32 C API using the raw C buffer
    if (! ::PathStripToRoot(pathBuffer))
    {
        // No valid drive letter was found.
        // Return an empty string
        return std::wstring();
    }

    // Return a std::wstring with the buffer content
    return std::wstring(pathBuffer);
}


// Test
int main() 
{
    try
    {
        const std::wstring path = L"C:\\Path1\\Path2";
        const std::wstring root = RootFromPath(path);

        std::wcout << "The content of the path before is:\t" << path << std::endl;
        std::wcout << "RootFromPath() returned:          \t" << root << std::endl;        
    }
    catch(const std::exception& ex)
    {
        std::cerr << "\n*** ERROR: " << ex.what() << std::endl;
    }
}

Compiled from command line:

C:\Temp\CppTests>cl /EHsc /W4 /nologo TestPathStripToRoot.cpp

Output:

C:\Temp\CppTests>TestPathStripToRoot.exe
The content of the path before is:      C:\Path1\Path2
RootFromPath() returned:                C:\

On that particular point of your question:

Well for one the MSDN documation says I need LPTSTR variable, but Visual Studios says I need LPWSTR.

LPTSTR is a typedef equivalent to TCHAR*.
LPWSTR is a typedef equivalent to WCHAR*, i.e. wchar_t*.

TCHAR is a placeholder for a character type, that can be expanded to char or wchar_t, depending if you are in ANSI/MBCS or Unicode build mode.

Since VS2005, Visual Studio has been using Unicode builds as default.

So, unless you are maintaining an old legacy app that must use ANSI/MBCS, just use Unicode in modern Win32 applications. In this case, you can directly use wchar_t-based strings with Win32 APIs, without bothering with the old obsolete TCHAR-model.

Note that you can still have std::strings (which are char-based) in your code, e.g. to represent Unicode UTF-8 text. And you can convert between UTF-8 (char/std::string) and UTF-16 (wchar_t/std::wstring) at the Win32 API boundaries.

For that purpose, you can use some convenient RAII wrappers to raw Win32 MultiByteToWideChar() and WideCharToMultiByte() APIs.

Mr.C64
  • 41,637
  • 14
  • 86
  • 162
  • Links are broken. This is precisely why you should include the linked content here. – JBES Dec 13 '17 at 18:40
2

The right way to think about building a Windows application is to pretend that 8-bit strings do not exist. Otherwise, the encoding of your string will vary based on the user's language settings, and your app will not be able "global ready" because there will always be some characters not representable by the user's current settings. 8-bit strings in Win32 are legacy from the 1990s and a good Win32 app uses PWSTR everywhere. Notice for instance that on Windows CE or WinRT the "A functions" don't even exist, that should give you some hint about how Microsoft feels about the issue.

Now, in practical terms, you may be interacting with non-Windows specific code that uses 8-bit strings. IMO the best approach to use for that is to say by convention that all such strings are UTF-8, and use MultiByteToWideChar and WideCharToMultiByte to convert to and from PWSTR. Be sure to use CP_UTF8. But for Windows specific code, please do define the UNICODE and _UNICODE macros, forget that TCHAR, TSTR, *A functions and other such accidents of history exist and use PWSTR and WCHAR everywhere. Your code will be saner for it.

asveikau
  • 39,039
  • 2
  • 53
  • 68
  • So if I use this MultiByteToWideChar() I found: wstring fileInfo::strChange(const string& s) { //int len; slength = (int)s.length() + 1; len = MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, 0, 0); wchar_t* buf = new wchar_t[len]; MultiByteToWideChar(CP_ACP, 0, s.c_str(), slength, buf, len); wstring r(buf); delete[] buf; return r; } What can I do with wstring to get convert it to LPWSTR? – dspaces1 Jun 10 '14 at 15:43
  • @dspaces1 On Windows `wchar_t*` and `PWSTR` are ultimately the same thing. (Pointer to utf-16 string.) – asveikau Jun 10 '14 at 15:49
  • If I try to pass in a wchar_t* into PathStripToRoot() I get 2 errors: unresolved external symbol and an unresolved externals – dspaces1 Jun 10 '14 at 16:01
0

You can use ATL conversion macros:

cout<<"\n\n"<<PathStripToRoot(CA2T(targetFile.c_str()))<<"\n\n";
Wojtek Surowka
  • 20,535
  • 4
  • 44
  • 51
  • 1
    If his project already using ATL lib then using conversion macro is fine. Otherwise just for string conversion, uses of ATL macro conversion should not be an option. – Anil8753 Jun 10 '14 at 14:41
0

If targetFile is ASCII string, use PathStripToRootA. Here you do not need any conversion, targetFile.c_str() will work. If targetFile is UTF8 string, use MultiByteToWideChar to convert it to WideChar. Then use PathStripToRoot. Otherwise make targetFile wstring, pass it to API without any conversion.

Anil8753
  • 2,663
  • 4
  • 29
  • 40
0

Please go through Unicode Programming Summary

There are several ways to solve this.

The right approach is to enclose the string definitions with _T(). PathStripToRoot is defined as

#ifdef _UNICODE
#define PathStripToRoot PathStripToRootA
#else
#define PathStripToRoot PathStripToRootW
#endif

_T, and Windows APIs follows what you define the character set support you defined for your project under project settings. If you're taking file name as single byte string (string holds ANSI string) use PathStripToRootA. You can avoid converting file name in between to UNICODE string.

sarat
  • 10,512
  • 7
  • 43
  • 74