0

I'm using GetOpenFileName function from Winapi, and I'm applying filter to the select file dialog.

THIS works perfectly:

LPSTR mfilter = "Filter\0*.PDF\0";
ofn.lpstrFilter = mfilter;

if(GetOpenFileName(&ofn)){
...

enter image description here

THIS fails (dialog opens but no filters apply):

string mfilter = "Filter\0*.PDF\0";
ofn.lpstrFilter = mfilter.c_str();

if(GetOpenFileName(&ofn)){
...

enter image description here

I need to use std:string because I'm getting the file extension via parameters and this type facilitates the concatenation but I'm getting incompatibility issues...

This would be my code if it worked as expected (IT FAILS the same as previous example):

const char * ext = &(4:); //Ampersand parameter (from CA Plex) It contains "PDF"
string mfilter = "Filter\0*." + ext + "\0"; //Final string: Filter\0*.PDF\0;
ofn.lpstrFilter = mfilter.c_str();

When I use this method, I'm getting runtime exception:

string mf;
mf.append("Filter")
.append('\0')
.append("*.pdf")
.append('\0');

ofn.lpstrFilter = mf.c_str();

enter image description here

andlabs
  • 11,290
  • 1
  • 31
  • 52
ProtectedVoid
  • 1,293
  • 3
  • 17
  • 42

3 Answers3

2

With

string mfilter = "Filter\0*.PDF\0";

you are calling an std::string contructor, which terminates the string at the first \0.

The following code:

string mfilter = "Filter\0*.PDF\0";
cout << "string:" << mfilter << "   len: " << mfilter.length() << endl;

prints

string: Filter   len: 6

The string is only constructed until the first \0 terminator. Do the string is only composed of the word "Filter".

manatttta
  • 3,054
  • 4
  • 34
  • 72
  • Added examples. Thanks. – ProtectedVoid Dec 10 '15 at 12:14
  • Is the use of embedded NULs in `std::string` even defined? If it turns out that you really cannot do that with `std::string`, (to answer your question below) you will need to use good old fashioned memory allocation and `strcpy()`... – andlabs Dec 10 '15 at 13:00
  • @andlabs I don't know what you mean when saying that I need to define "the use of embedded NULs" – ProtectedVoid Dec 10 '15 at 13:05
  • You don't need to define anything, the C++ standard does. Everything I've found though indicates that yes, you can use NUL with a `std::string`. You will need to either show your full code or use a debugger. – andlabs Dec 10 '15 at 13:09
  • I'm not able to use a debugger due to I'm working on an IDE which doesn't allow it (yes, unbelievable). I don't mind showing my full code but the issue is located where I'm saying, when trying to form a "filter" to that Winapi function it doesn't understand it and does whatever it wants, quite common from Windows API. – ProtectedVoid Dec 10 '15 at 13:15
  • To construct a `std::string` from a string literal that has embedded nulls in it, you need to specify the literal's actual length to the constructor, eg: `string mfilter("Filter\0*.PDF\0", 13);` – Remy Lebeau Dec 10 '15 at 17:54
1

The GetOpenFileName function uses TCHARs, and TCHARs become WCHARs in case of UNICODE character set is used.

Here's an example:

std::wstring getOpenFileName(HWND hWnd, const std::wstring& sFilter)
{
    wchar_t buffer[MAX_PATH] = L"";

    OPENFILENAMEW ofn = {0};

    ofn.lStructSize = sizeof(ofn);
    ofn.hwndOwner = hWnd;
    ofn.lpstrFilter = sFilter.c_str();
    ofn.nFilterIndex = 1;
    ofn.lpstrFile = buffer;
    ofn.nMaxFile = MAX_PATH;
    ofn.Flags = OFN_HIDEREADONLY | OFN_FILEMUSTEXIST;

    if( !::GetOpenFileNameW( &ofn ) )
        return L"";

    return buffer;
}

If you want to parametrize lpstrFilter based on std::wstring you can just use wstring::c_str() to get LPCTSTR which is const wchar* in case of UNICODE.

IMPORTANT: The problem is that the std::wstring constructor that takes a const wchar* assumes the input is a C string. C strings are '\0' terminated and thus parsing stops when it reaches the '\0' character. To compensate for this you need to use the constructor that takes two parameters a pointer to the char array and a length. You can also use string::push_back() method to append NULLs.

std::wstring sFilter = L"PDF Files";
sFilter.push_back('\0');
sFilter.append(L"*.pdf");
sFilter.push_back('\0');
Andrew Komiagin
  • 6,446
  • 1
  • 13
  • 23
  • 1
    The only issue I've got is setting `ofn.lpstrFilter` dynamically. A file extension is passed via a parameter and I need to apply that extension filter to the dialog when it opens. As I can see you are setting the filters as a literal string and that's the way it worked for me as the first example shows, but it isn't a solution. How would you set a filter string dynamically with a parameter always keeping null-terminators? Thanks. – ProtectedVoid Dec 10 '15 at 12:54
  • This is not the answer. It is obvious from the working examples in the question that the OP is only using ANSI functions. – andlabs Dec 10 '15 at 12:59
  • It is not obvious et al. In 2015 using ASCII char-set and not using UNICODE is a crime. – Andrew Komiagin Dec 10 '15 at 13:11
  • I've updated the answer to describe the way to parametrize lpstrFilter – Andrew Komiagin Dec 10 '15 at 13:22
  • Asker has runtime error. Whether or not ANSI is a crime in 2015, you do need to address the actual issue. – David Heffernan Dec 10 '15 at 13:37
  • @David Heffernan: Totally agree. That was my mistake. I've updated the answer to address the actual issue. – Andrew Komiagin Dec 10 '15 at 13:41
  • @AndrewKomiagin Your answer worked! I was going crazy with this. Just one last thing, `std::wstring sFilter = L"PDF Files";` sets "3DF Files" to the dialog filter, I'm assuming the first character of the string is converted, also tried other string and it happens the same. I'm getting "˫PG" in "JPG" example. – ProtectedVoid Dec 10 '15 at 14:23
  • @ProtectedVoid: I guess the problem is that your project uses **MBCS** (Multi-Byte character set) and you should be using **UNICODE**. So just change project settings to use Unicode Character Set. – Andrew Komiagin Dec 10 '15 at 14:30
  • Nothing here makes sense. The only thing that makes sense is that the string is modified after `c_str()` is called, thus invalidating the pointer. – David Heffernan Dec 10 '15 at 15:38
0
string mfilter = "Filter\0*.PDF\0";

This calls a std::basic_string constructor that uses a null-terminated string. It will stop parsing the string literal at "Filter".

Try this one instead:

string mfilter( "Filter\0*.PDF", 13 ); // need double null at end

This calls a std::basic_string constructor that uses "the first count characters of character string pointed to by s. s can contain null characters."

You have to either count the characters yourself, or write wrapper code if you encounter this problem more often.


Related: std::basic_string constructors.


As for your runtime error:

string mf;
mf.append("Filter")
.append('\0')
.append("*.pdf")
.append('\0');

append() does not have an overload for a single character type. You are probably hitting the const CharT* s overload, with a null pointer.

Use either append( 1, '\0' ) or append( "", 1 ), either of which should append a null byte.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
  • `string mfilter( "Filter\0*.PDF", 12 );` needs to be `string mfilter( "Filter\0*.PDF\0", 13 );` instead, because `GetOpenFileName()` expects the filter to be **double** null terminated. `c_str()` will add one null terminator for you, but the data needs to contain the other null terminator. – Remy Lebeau Dec 10 '15 at 17:56
  • @RemyLebeau: I enjoyed being able to stay *well* clear of WinAPI in my career so far. ;) Thanks for the heads-up. But since the string literal comes with an implicit zero at the end, I don't have to add one, just up the count. (Or does `c_str()` skip the adding then?) – DevSolar Dec 10 '15 at 18:31
  • A count of 12 does not include the implicit null. You have to bump the count to 13, whether the final null is implicit or explicit. `c_str()` always includes its own null at the end of whatever the `std::string` is actually holding. – Remy Lebeau Dec 10 '15 at 18:56