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

4
  • If you set a breakpoint and you inspect mfilter buffer what you see? (before crash...) Commented Dec 10, 2015 at 12:45
  • Whenever I have to work with MFC/WinAPI I always use the MSism way. Fighting it is not worth the effort. Commented Dec 10, 2015 at 13:17
  • If you won't show a minimal reproducible example I doubt this is going to be productive. I can guess that you modify the string, or destroy it, and so render the pointer invalid. Commented Dec 10, 2015 at 13:19
  • And so it proves to be: stackoverflow.com/questions/34223930/… Commented Dec 11, 2015 at 12:50

3 Answers 3

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".

Sign up to request clarification or add additional context in comments.

6 Comments

Added examples. Thanks.
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 I don't know what you mean when saying that I need to define "the use of embedded NULs"
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.
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.
|
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');

9 Comments

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.
This is not the answer. It is obvious from the working examples in the question that the OP is only using ANSI functions.
It is not obvious et al. In 2015 using ASCII char-set and not using UNICODE is a crime.
I've updated the answer to describe the way to parametrize lpstrFilter
Asker has runtime error. Whether or not ANSI is a crime in 2015, you do need to address the actual issue.
|
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.

3 Comments

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.
@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?)
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.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.