2

First off: I know there are similar topics for C++, but I am curious about standard C, and I don't believe my problem is related to previous problems.

I am trying to implement Unicode support for a simple program which just asks the user to select a directory through a folder browser and then passes it to another program (only got to the first part). But when attempting to write the received path to a file, it results in a 0-byte file. And when printing it out using wprintf_s, non-ASCII characters come out as question marks. I don't believe there is any undefined behavior or anything as I've double checked documentation. So what am I doing wrong?

The code currently looks like this (just the bare minimum for strict test conditions):

#define UNICODE
#define _UNICODE

#include <windows.h>
#include <shlobj.h>
#include <stdio.h>

int main()
{
    BROWSEINFOW bi = { 0 };
    LPITEMIDLIST pidl;
    wchar_t path[MAX_PATH];
    pidl = SHBrowseForFolderW(&bi);
    SHGetPathFromIDListW(pidl, path);
    wprintf_s(L"%s\n", path);
    return 0;
}

The above code prints it regularly. When attempting to write to a file instead, I replace the wprintf_s call with this (having declared FILE *f first of course):

if(_wfopen_s(&f, L"C:\\test.txt", L"w"))
{
    fwprintf_s(f, L"%s\n", path)
    fclose(f);
}

However, I have also tried using fwrite with both w and wb mode, but all methods results in an empty file.

user966939
  • 692
  • 8
  • 27
  • Try ` _setmode(_fileno(stdout), _O_U16TEXT);` http://stackoverflow.com/a/36527398/4603670 – Barmak Shemirani Jun 02 '16 at 20:20
  • @BarmakShemirani Huh. That works for stdout, but I'm still getting empty output for actual files? Any ideas? – user966939 Jun 02 '16 at 20:31
  • The problem is that the terminal application or the app that shows the output is probably not correctly configured. – Jean-Baptiste Yunès Jun 02 '16 at 20:33
  • Whenever an I/O function fails to work as expected, good idea to check the return value of the function. – chux - Reinstate Monica Jun 02 '16 at 20:47
  • @chux again, I have checked the documentation - double-checked in fact. `wprintf_s` only takes a format string and respective parameters, like the regular function. See for yourself. But the problem is solved for stdout anyway. The problem that remains is writing to actual files. – user966939 Jun 02 '16 at 20:53
  • What compiler and library are you using. There are differences between MSVCRT and others in this area. – M.M Jun 02 '16 at 20:57
  • @M.M I'm using *Microsoft Visual C++ 2010 Express* for everything. – user966939 Jun 02 '16 at 21:09
  • Unrelated to your question, but you need to initialize COM on the calling thread (see [CoInitializeEx](https://msdn.microsoft.com/en-us/library/windows/desktop/ms695279.aspx)). – IInspectable Jun 02 '16 at 21:14
  • @user966939 My mistake - was thinking of `wscanf_s()`. Comment deleted. – chux - Reinstate Monica Jun 02 '16 at 21:46
  • You've specified the character set (Unicode) but not the encoding. UTF-16 is one possibility but UTF-8 is arguably better (and perhaps more common) for files. – Tom Blodget Jun 02 '16 at 23:16
  • @TomBlodget yep, I just wasn't aware it needed to be specified I guess. I assumed wide chars on Windows was all UTF-16 of some kind since there wasn't much of a way to specify it in most function arguments... except for files/streams I guess. But I agree, UTF-8 should really be standard everywhere these days... – user966939 Jun 02 '16 at 23:26

1 Answers1

2

You need _O_U16TEXT for console output, and "UTF-16LE" for file output.

Also, _wfopen_s returns zero when successful according to MS documentation:

Return Value Zero if successful; an error code on failure. See errno, _doserrno, _sys_errlist, and _sys_nerr for more information about these error codes.

You should make sure return value is zero

if (0 == _wfopen_s(&f, filename, L"w, ccs=UTF-16LE")){
    //isokay ...
}

or check if f is non-NULL. For example:

#include <stdio.h>
#include <io.h> //for _setmode
#include <fcntl.h> //for _O_U16TEXT

int main()
{
    _setmode(_fileno(stdout), _O_U16TEXT);
    const wchar_t *buf = L"ελληνική";
    wprintf(L"%s\n", buf);

    FILE *f = NULL;
    _wfopen_s(&f, L"C:\\test\\test.txt", L"w, ccs=UTF-16LE");
    if (f)
    {
        fwprintf_s(f, L"%s\n", buf);
        fclose(f);
    }

    return 0;
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • That did it! As for checking the handle, I assumed that, since `_wfopen_s` returns non-zero on errors (like failing to create a handle?), it would suffice to check for that... It's more or less how they do it in the MSDN example code at least (only, they assign the error to a variable). – user966939 Jun 02 '16 at 21:07
  • 3
    @user966939: The tooltip for the upvote-arrow reads *"This answer is useful"*. It always strikes me as odd when someone accepts an answer, without also voting on it. – IInspectable Jun 02 '16 at 21:13
  • @IInspectable Well, I just noticed he updated his answer. I now realized (despite having referred to the return value of `_wfopen_s` myself in my previous comment) that I was checking for success against a NON-zero number in the code, when success is actually 0 of course. I thought Barmak was falsely accusing me of having made an error by checking the handle with the help of the function's return value. So of course he also deserves a point. I don't think one thing necessarily implies the other, though. – user966939 Jun 02 '16 at 22:32