• Create Account

## C++ Multi-Byte - prepare string of extensions for OpenFileDialog

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

12 replies to this topic

### #1mynameisnafe  Members

Posted 01 July 2014 - 01:04 PM

HI all,

I'm having some trouble figuring out how to do this one.

Assimp gives me a string, containing all the file extensions it can handle.

I found some code to tokenize strings on SO and wrapped it in a function.

The format of the string that is used to initialise the accepted extensions list for the OPENFILENAME structure is as follows:

L"All\0*.*\0Text\*.txt\0"

And so I'm trying to recreate that programatically. Which is a nightmare, as I'm trying to stick '\0' in the middle of a string.

// Get a vector of extensions supported by Assimp ( assimp exts string, tokenise by ';' )
std::vector<std::string> exts = Scatterbrain::Split(extsAccepted.c_str(), ';');

//create a string with a bunch of '\0' s in it, if possible
std::string extsList="";
for(size_t i = 0; i < exts.size(); i++)
extsList.append(exts[i] + '\0');

// contains the first extension.
CString pwszExts(extsList.c_str());

// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));

ofn.lpstrFilter = (LPCWSTR)pwszExts; // This one
...

// Display the Open dialog box.
if( GetOpenFileName(&ofn) == TRUE )
{
...


Okay - I see one bug already: the dialog is going to say 'extension_a extension_b', instead of 'ext_name_a ext_a', but I'm happy to add blank spaces for names.

how is the generation of a string

L"Hello\0World\0How\0Are\0You\0'

done?

Posted 01 July 2014 - 01:11 PM

Using a vector<char> will work for that. Or wchar_t in Unicode.
"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

### #3fastcall22  Moderators

Posted 01 July 2014 - 01:24 PM

how is the generation of a string

L"Hello\0World\0How\0Are\0You\0'

done?

Well, in your case, it would just be replacing ';' with '\0'.

So, maybe something like this:
std::wstring ext;
ext.reserve(str.length()+1);

std::transform(str.begin(),str.end(),back_inserter(ext),[](char t) -> wchar_t {
return t == ';' ? 0 : t;
});
ext.push_back(0); // (This is necessary to indicate where the array of strings end)

wchar_t* buf = ext.c_str(); // An extra null terminator should be appended here.

// str = "Hello;World;How;Are;You"
// buf = L"Hello\0World\0How\0Are\0You\0\0"

EDIT:
Code sample here: http://ideone.com/wqrckP

Edited by fastcall22, 01 July 2014 - 03:00 PM.

zlib: eJzVVLsSAiEQ6/1qCwoK i7PxA/2S2zMOZljYB1TO ZG7OhUtiduH9egZQCJH9 KcJyo4Wq9t0/RXkKmjx+ cgU4FIMWHhKCU+o/Nx2R LEPgQWLtnfcErbiEl0u4 0UrMghhZewgYcptoEF42 YMj+Z1kg+bVvqxhyo17h nUf+h4b2W4bR4XO01TJ7 qFNzA7jjbxyL71Avh6Tv odnFk4hnxxAf4w6496Kd OgH7/RxC

### #4mynameisnafe  Members

Posted 01 July 2014 - 02:44 PM

What is this lambda magic?

[](char t)->wchar_t

Is that it? It's not the replacing that I'm struggling with, it's the char ->wchar_t.. and there is the char->wchar_t

I'm going to try basically an empty lambda, meanwhile, whats with the extra push_back(0)'s? are those important (i.e. the real null terminator of the returned exts wstring?

### #5fastcall22  Moderators

Posted 01 July 2014 - 02:58 PM

According to the documentation for OPENFILENAME, lpstrFilter must be terminated by two NULL characters, as I had suspected. However, according to std::string::c_str, there should already be a null terminator appended to its contents. Therefore, the second push_back(0) is not needed. I will modify my previous post.

As for the lambda, [](char t)->wchar_t{}, it reads as: A lambda that takes one parameter, captures no variables in its parent scope(s), and returns a wchar_t. The equivalent C++03 code would look something like:
struct ReplaceAndWiden {
char to_find;
wchar_t to_replace;

ReplaceAndWiden(char f, wchar_t r) : to_find(f), to_replace(r) {
}

wchar_t operator() (char t) const {
return t == to_find ? to_replace : (wchar_t)t;
}
};

std::transform( str.begin(), str.end(), back_inserter(ext), ReplaceAndWiden(';',0) );


zlib: eJzVVLsSAiEQ6/1qCwoK i7PxA/2S2zMOZljYB1TO ZG7OhUtiduH9egZQCJH9 KcJyo4Wq9t0/RXkKmjx+ cgU4FIMWHhKCU+o/Nx2R LEPgQWLtnfcErbiEl0u4 0UrMghhZewgYcptoEF42 YMj+Z1kg+bVvqxhyo17h nUf+h4b2W4bR4XO01TJ7 qFNzA7jjbxyL71Avh6Tv odnFk4hnxxAf4w6496Kd OgH7/RxC

### #6mynameisnafe  Members

Posted 01 July 2014 - 03:01 PM

I bailed on my split fn, all that CString jazz.. now I have this beautiful code:


std::string App::DoOpenFileDialog(std::string extsAccepted)
{
std::string fName;
OPENFILENAME ofn;       // common dialog box structure
char szFile[260];       // buffer for file name

std::wstring wextsList;
std::transform(extsAccepted.begin(), extsAccepted.end(), back_inserter(wextsList), [](char t)->wchar_t {
return t==';' ? '\0' : t;
});

// Initialize OPENFILENAME
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = m_hWnd;
ofn.lpstrFile = (LPWSTR)szFile;
ofn.lpstrFile[0] = '\0';
ofn.nMaxFile = sizeof(szFile);
ofn.lpstrFilter = (LPCWSTR)wextsList.c_str();
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;

// Display the Open dialog box.

if( GetOpenFileName(&ofn) == TRUE )
{


..Problem is, it's missing every other file format - this is something I saw before while straying through the realms of doing it a char at a time in a for loop, and also while using a CString..

PS: D'oh! I've got Unicode set in my project settings (VS 2012), so that should make things easier?
PPS: I just saw your reply - I've used lambdas before, just appreciating the sly-as-hell widening, just implicit. Must use lambdas much more.Loving that one, using it, thank you

Crap - I just added the extra null terminator.

Edited by mynameisnafe, 01 July 2014 - 03:21 PM.

### #7Bregma  Members

Posted 01 July 2014 - 03:15 PM

std::transform(str.begin(),str.end(),back_inserter(ext),[](char t) -> wchar_t { return t == ';' ? 0 : t; });

I don't think that's actually going to do the right thing if there's anything other than US ASCII in the original string. A simple integral promotion from a multibyte UTF-8 string into Microsoft's proprietary UCS-2 wide-character Unicode variant is a fail.

Of course, if you're restricting your somain to US ASCII, you're fine.

Stephen M. Webb
Professional Free Software Developer

### #8fastcall22  Moderators

Posted 01 July 2014 - 03:25 PM

Bregma is correct, Assimp's aiString is UTF-8. Therefore, a temporary buffer and mbstowcs are needed for proper UTF-8 handling.

Edited by fastcall22, 01 July 2014 - 03:26 PM.

zlib: eJzVVLsSAiEQ6/1qCwoK i7PxA/2S2zMOZljYB1TO ZG7OhUtiduH9egZQCJH9 KcJyo4Wq9t0/RXkKmjx+ cgU4FIMWHhKCU+o/Nx2R LEPgQWLtnfcErbiEl0u4 0UrMghhZewgYcptoEF42 YMj+Z1kg+bVvqxhyo17h nUf+h4b2W4bR4XO01TJ7 qFNzA7jjbxyL71Avh6Tv odnFk4hnxxAf4w6496Kd OgH7/RxC

### #9mynameisnafe  Members

Posted 01 July 2014 - 03:45 PM

Okay..

Everything seems to be working with the lambda. The reason I have half the file formats visible in the dialog is to do with the OPENFILENAME and the GetOpenFileName() function

http://msdn.microsoft.com/en-gb/library/windows/desktop/ms646829(v=vs.85).aspx#open_file

I think I need a name for each format.. "Model" will do.

Hello people above std::string seems to work fine - I think (it appears) Assimp provides an overload - pretty sure I've seen it documented.

std::string GLFactory::GetAssimpExtsList()
{
std::string exts;
Assimp::Importer importer;
importer.GetExtensionList(exts);
return exts;
}

I don't think that's actually going to do the right thing if there's anything other than US ASCII in the original string.

Indeed. I'm happy to assume Assimp don't bother with language packs. PS That all sounds like a lot of effort - have you seen that lambda?!

Edited by mynameisnafe, 01 July 2014 - 03:46 PM.

### #10wack  Members

Posted 01 July 2014 - 03:48 PM

Bregma is correct, Assimp's aiString is UTF-8. Therefore, a temporary buffer and mbstowcs are needed for proper UTF-8 handling.

That will probably not be a correct conversion either, as mbstowcs expects the multibyte string to be in the system native multibyte encoding, which on Windows probably never is UTF-8.

You probably want something like this for a correct conversion:

#include <codecvt>
...
std::wstring_convert<std::codecvt_utf8<wchar_t>> wc;
std::wstring converted_str = wc.from_bytes(utf8str);

### #11Chris_F  Members

Posted 01 July 2014 - 03:49 PM

std::transform(str.begin(),str.end(),back_inserter(ext),[](char t) -> wchar_t { return t == ';' ? 0 : t; });

I don't think that's actually going to do the right thing if there's anything other than US ASCII in the original string. A simple integral promotion from a multibyte UTF-8 string into Microsoft's proprietary UCS-2 wide-character Unicode variant is a fail.

Of course, if you're restricting your somain to US ASCII, you're fine.

Microsoft hasn't used UCS-2 since Windows 2000. All modern versions of Windows use UTF-16 which is a multibyte encoding the same as UTF-8. Converting a UTF-8 string to UTF-16 is not going to make it easier to process.

http://www.utf8everywhere.org/

Edited by Chris_F, 01 July 2014 - 03:53 PM.

### #12L. Spiro  Members

Posted 01 July 2014 - 04:27 PM

If you are going to hard-code an L"" string, you are required to use the W version of OPENFILENAME (OPENFILENAMEW) and GetOpenFileName (GetOpenFileNameW).

Never mix-and-match "", _T( "" ), and L"" strings.

The solution is to convert from UTF-8 to wide-character strings via MultiByteToWideChar() (passing CP_UTF8) and use OPENFILENAMEW and GetOpenFileNameW() to open the file.

L. Spiro

Edited by L. Spiro, 01 July 2014 - 04:33 PM.

### #13mynameisnafe  Members

Posted 03 July 2014 - 11:39 AM

L. Spiro replies to my thread? Honoured!

I shall give it a try in 5 mins and let you guys know what happened..

Old topic!

Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.