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

Started by
11 comments, last by mynameisnafe 9 years, 9 months ago

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?

Thanks in advance

Advertisement
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

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

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?

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) );

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 smile.png

Crap - I just added the extra null terminator.


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

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

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 smile.png 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?! ohmy.png

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);

This topic is closed to new replies.

Advertisement