1

Trying to add a custom font to a C++ MFC application using the following code:

void CMFCApplication1View::OnInitialUpdate()
{
    CFormView::OnInitialUpdate();

    // ...

    // dynamic path 
    std::string test = std::string("Oswald.ttf");
    std::string path = this->ExePath() + test;
    std::wstring widestr = std::wstring(path.begin(), path.end());
    const wchar_t* widecstr = widestr.c_str();

    // hardcoded path
    std::string s = R"(D:\DEV\C\CTests\MFCApplication1\Debug\Oswald.ttf)";
    std::wstring r = std::wstring(s.begin(), s.end());
    const wchar_t* t = r.c_str();

    Gdiplus::PrivateFontCollection m_fontcollection;
    Gdiplus::Status nResults = m_fontcollection.AddFontFile(t); // access violation exception thrown here

    // ...
}

std::string CMFCApplication1View::ExePath() {
    char buffer[MAX_PATH];
    GetModuleFileNameA(NULL, buffer, MAX_PATH);
    std::string::size_type pos = std::string(buffer).find_last_of("\\/");
    return std::string(buffer).substr(0, pos + 1);
}

I'm new to C++ (coming from C#). The code is throwing an access violation exception when AddFontFile is called, both with hardcoded and dynamic version, but the value of the path is correct.

Am I missing something obvious?


Update

This is what I did now, based on the comments. It seems to be working and there is no more conversion stuff, but as I said, it will take a while until I am sure I am doing this right.

void CMFCApplication1View::OnInitialUpdate()
{
    CFormView::OnInitialUpdate();

    // ...

    // font path 
    // const wchar_t* path = (this->ExePath() + std::wstring(L"Oswald.ttf")).c_str();
    const wchar_t* path = &(this->ExePath() + std::wstring(L"Oswald.ttf"))[0];

    // Gdiplus init
    ULONG_PTR gdiplusToken; 
    Gdiplus::GdiplusStartupInput gdiplusStartupInput;
    Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);

    // Gdiplus font
    Gdiplus::PrivateFontCollection m_fontcollection;
    Gdiplus::Status nResults = m_fontcollection.AddFontFile(path);

    // ...
}

std::wstring CMFCApplication1View::ExePath() {
    wchar_t buffer[MAX_PATH];
    GetModuleFileNameW(NULL, buffer, MAX_PATH);
    std::wstring::size_type pos = (std::wstring(buffer)).find_last_of(L"\\/");
    return std::wstring(buffer).substr(0, pos + 1);
}

The conversions above using c_str() or assigning &wstring[0] are based on this answer: https://stackoverflow.com/a/16113660/2983568

evilmandarine
  • 4,241
  • 4
  • 17
  • 40
  • 2
    Have you initalized GDI+? – Michael Chourdakis Jul 04 '19 at 16:58
  • 5
    As Michael Chourdakis said, we don't see where you initialized GDI+. Like :`Gdiplus::GdiplusStartupInput gdiplusStartupInput; ULONG_PTR gdiplusToken; Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);` – Castorix Jul 04 '19 at 18:20
  • 2
    Propably unrelated, but the implicit `char` to `wchar_t` casts are strange. Why not just write `std::wstring r = LR"(D:\DEV\C\CTests\MFCApplication1\Debug\Oswald.ttf)";` for instance? Similarly in `ExePath()` you should call `GetModuleFileNameW` and return `std::wstring`. Use `wchar_t` and `wstring` consistently instead of randomly mixing up `std::string` and `std::wstring`. The OS uses only Unicode internally, so you are forcing it to use legacy code to convert between ANSI and Unicode (which might not even succeed at all)! – zett42 Jul 04 '19 at 21:03
  • MichaelChourdakis: that was it. Castorix: worked, thanks! I had no idea that was required. If one of you want to post an answer with that initialization code I'll accept it. zett42: because there is a huge bunch of stuff I need to assimilate before I master string/char manipulation in cpp. Will try to do what you say, thank you for the comment! – evilmandarine Jul 05 '19 at 07:52
  • `widestr.c_str()` returns a non-owning pointer. Make sure you understand the consequences with respect to object lifetime. The attempt to convert a narrow character string to a wide character string (`std::wstring(s.begin(), s.end())`) doesn't convert. It randomly trashes data. Appears to work, frequently, but gets totally not fun, once your paths contain non-ASCII characters. You'll be debugging this for hours. Either use wide character strings throughout, or perform *proper* conversion (e.g. by using MFC/ATL conversion macros). – IInspectable Jul 05 '19 at 10:04
  • @IInspectable Thank you. I updated the answer using wide characters because I don't know how to use the macros (A2W for example?). Also I "think" I understand the difference between using c_str() and assigning the address with "&" as in the edit, but I don't know what is the impact on the object's lifetime. This is an order of magnitude more difficult than a managed language like C#, sorry. – evilmandarine Jul 05 '19 at 11:58
  • Note that `@` is needed to ping @MichaelChourdakis. Also `PathRemoveFileSpec(buffer)` will change `buffer` to folder name (without the trailing `'\'`). You can convert that `std::wstring` (or `CString`) and return it. – Barmak Shemirani Jul 05 '19 at 21:32
  • Since @Castorix posted the code I'm fine to let him post the answer. – Michael Chourdakis Jul 05 '19 at 21:57

0 Answers0