Allegro 5: trouble storing bitmaps in a std map - c++

I started creating a game in Visual Studio 2017 in C++ using Allegro 5. In order to make it easier to manage images, I created an ImageLoader class which would load and store all active images, and destroy them as necessary. It does so using a map which matches filenames to the corresponding images.
Currently my main() code looks like this:
int main(){
if (!al_init()) {
al_show_native_message_box(NULL, NULL, NULL, "Could not intialize allegro 5.", NULL, NULL);
return -1;
}
ALLEGRO_DISPLAY *display = al_create_display(DEFAULT_SCREEN_WIDTH, DEFAULT_SCREEN_HEIGHT);
al_set_window_title(display, "Game title");
// make usre the display was created correctly
if (!display) {
al_show_native_message_box(display, "Title", "settings", "Could not create Allegro window.", NULL, NULL);
}
// intialize fonts, primitives, keyboard,etc.
al_init_font_addon();
al_init_ttf_addon();
al_init_primitives_addon();
al_install_keyboard();
if(!al_init_image_addon()) {
al_show_native_message_box(display, "Error", "Error", "Failed to initialize al_init_image_addon!",
NULL, ALLEGRO_MESSAGEBOX_ERROR);
return -1;}
ImageLoader image_loader;
The ImageLoader then loads an image for the player:
ALLEGRO_BITMAP *image = al_load_bitmap(filename);
if (image == NULL) {
std::cout << filename << std::endl;
std::cout << "loader failed to load image" << std::endl;
}
image_map[filename] = image;
When I test this part, it seems to work, as the image is not null. Note that image_map is declared like so in ImageLoader.h:
std::map<const char *, ALLEGRO_BITMAP*> image_map;
The problem arises in my main game loop when I then try to get the image from the ImageLoader:
for (GameImage image : imageList) {
ALLEGRO_BITMAP *draw_image = image_loader.get_current_image(image);
if (draw_image == NULL) {
//TODO: error handling
std::cout << "no image" << std::endl;
}
else
al_draw_bitmap(draw_image, image.get_x(), image.get_y(), NULL);
}
My check always shows that draw_image is null. Here is the code for get_current_image:
ALLEGRO_BITMAP * ImageLoader::get_current_image(GameImage image)
{
return image_map[image.get_image_filename()];
}
I have tested to make sure I'm using the same filename here as when I load the image and store it in the map by checking if (image_map.find(image.get_image_filename()) == image_map.end()), but even though this returns false the ALLEGRO_BITMAP pointer is still null. I have tried making the map store bitmaps instead of pointers, but this gives me an error when I try to add an element to the map since I'm not allowed to modify map values like this. Why might these pointers become null in between the time where I set them and the time where I retrieve them? I'm also open to other suggestions for how I should store my bitmaps.
EDIT: I revised my project, changing the instances of filenames as char arrays to std::strings. The image map is now declared as std::map<std::string, ALLEGRO_BITMAP*> image_map in ImageLoader.h, and the filename stored by GameImage is now std::string image_filename. load_image in ImageLoader.cpp now looks like this:
ALLEGRO_BITMAP *image = al_load_bitmap(filename.c_str());
if (image == NULL) {
std::cout << filename << std::endl;
std::cout << "loader failed to load image" << std::endl;
}
image_map[filename] = image;
Finally, get_current_image is still the same:
ALLEGRO_BITMAP * ImageLoader::get_current_image(GameImage image)
{
return image_map[image.get_image_filename()];
}
However, the same problem still persists. I have also checked the size of the image map and it stays at 1 throughout the duration of my program, with the filename for the image I insert as the key and the value starting as a non-null pointer to an bitmap and appearing to become null at some point.
EDIT 2:
After changing the char arrays to strings and fixing get_current_image() so that it no longer adds to the map if the searched filename is not found, I discovered that I had also made a mistake when loading the images in a line I'd forgotten to include when I initially posted the question:
current_screen.load_images(image_loader);
As it turned out, I had written load_images() like so:
void MainGameScreen::load_images(ImageLoader loader)
...meaning that the calls the function made to loader weren't actually applying to the ImageLoader I'd passed in. I changed it to:
void MainGameScreen::load_images(ImageLoader& loader)
...and now everything works fine.

Your problem is that using const char * as your key means that map is going to perform a direct address comparison, i.e. "Is the address of the string equal to the address of the string I'm holding in memory". This is almost certainly NOT the way you want to perform string comparisons.
There's actually two solutions to this problem, depending on your use-case. The first is to simply change const char * to std::string, which is the simplest solution and what you should have done by default.
std::map<std::string, ALLEGRO_BITMAP*> image_map;
And, broadly speaking, anywhere that you are using a string, you should either be using std::string or std::string const&. There's no reason to use anything else.
...UNLESS you're concerned about performance. And if you're writing a game, performance is almost certainly something you care about, which brings us to the second solution. Having to do lots of lookups to a map is going to invoke a large number of comparisons, and while this isn't normally a huge issue, it is here because each comparison is a full-blown string-based equality check.
The solution is, when you load images, issue each image a unique ID (like an int64_t or uint64_t), and assign those values to the GameImage object instead of the filename or pathname. Then, when you do lookups, use that ID to perform the lookup.
It's up to you. Swapping out const char * with std::string is almost certainly going to fix the logical errors in your code and make it work the way you expect. The rest is more of an optimization problem if you discover that having to do all those string comparisons is slowing down your program too dramatically.
EDIT:
Your new problem is that std::map's operator[] function automatically inserts a default value (in this case, nullptr) if it fails to find any pre-existing image with the name requested. The code you want looks more like this:
ALLEGRO_BITMAP * ImageLoader::get_current_image(GameImage image)
{
auto it = image_map.find(image.get_image_filename());
if(it == image_map.end()) return nullptr;
else return it->second;
//return image_map[image.get_image_filename()];
}
This way, failure to find an image won't trick any debugging tools you use into thinking that a valid (null) value is stored at that location.
You could also simplify it to this if you wanted to use the built-in exception facilities instead:
ALLEGRO_BITMAP * ImageLoader::get_current_image(GameImage image)
{
//Will throw an exception if nothing is found
return image_map.at(image.get_image_filename());
}

Related

Wierd access violation error message when trying to use an openFile dialog window

I am still kinda bad in c++ so pls dont mind my bad code or my missing knowledge. The project is about chosing a file and the paste it in the console for the user to read and i thought the best way would be using a dialog window (and i get more practice using the winapi).
Here my code for the window:
OPENFILENAMEA NameOfFile;
ZeroMemory(&NameOfFile, sizeof(NameOfFile));
NameOfFile.nFileOffset = 1;
char szFile[260];
NameOfFile.lpstrFile[0] = '\0';
NameOfFile.lpstrFile = szFile;
NameOfFile.nMaxFile = 4096;
NameOfFile.Flags = OFN_ALLOWMULTISELECT;
if (GetOpenFileName(&NameOfFile)) {
cout << "opened";
}
Now the wierd thing. The Programm crashes with the error "-1073741819". Google said its an access violation of smth (no clue what exactly it means).
When i comment out the ZeroMemory function i got a linker and compiler error that NameOfFile is apparently not initialized??? (but if its not commented it compiles normally?!)
There are several issues in your code:
You need to set the lStructSize field of your OPENFILENAMEA variable.
You need to swap the 2 lines setting NameOfFile.lpstrFile, so that the pointer that you access (in order to write the zero termination) will point to a valid buffer.
NameOfFile.nMaxFile should be set to the size of the filename pointer by lpstrFile, i.e. the sizeof(szFile).
In order to set the file names filter, you can set lpstrFilter and nFilterIndex. The example below enabled to select either all or txt files.
Fixed version:
#include <Windows.h>
#include <iostream>
int main()
{
OPENFILENAMEA NameOfFile;
ZeroMemory(&NameOfFile, sizeof(NameOfFile));
NameOfFile.lStructSize = sizeof(NameOfFile); // Item 1 above
char szFile[260];
NameOfFile.lpstrFile = szFile; // Item 2 above
NameOfFile.lpstrFile[0] = '\0'; // Item 2 above
NameOfFile.nMaxFile = sizeof(szFile); // Item 3 above
NameOfFile.Flags = OFN_ALLOWMULTISELECT;
NameOfFile.lpstrFilter = "All\0*.*\0Text\0*.TXT\0"; // Item 4 above
NameOfFile.nFilterIndex = 1; // Item 4 above
if (GetOpenFileNameA(&NameOfFile))
{
std::cout << "opened " << szFile << std::endl;
}
}
For additional info you can see the example in the MS documentation here under the "Opening a File" section.
You can also have a look at the OPENFILENAMEA structure for the list of fields you can potentially set for the API.
A side note: better to avoid using namespace std - see here Why is "using namespace std;" considered bad practice?.
So in the end its just the fault of one single line of code.
NameOfFile.lpstrFile[0] = '\0';
it was the fault for the whole thing and it was my mistake for pasting it from another stack overflow post without knowing what it does (it had an explanation that i didnt really understand)
just deleting it fixed the problem
here the code i use from now on. I fixed the problem with LPWSTR trough making szFile a TCHAR and converting it into LPWSTR in FILE. Not the best but enoght for me right now.
LPWSTR FILE{};
OPENFILENAME NameOfFile;
ZeroMemory(&NameOfFile, sizeof(NameOfFile));
NameOfFile.lStructSize = sizeof(NameOfFile);
TCHAR szFile[MAX_PATH];
NameOfFile.lpstrFile = szFile;
NameOfFile.nMaxFile = sizeof(szFile);
NameOfFile.Flags = OFN_ALLOWMULTISELECT;
if (GetOpenFileName(&NameOfFile))
{
FILE = (LPWSTR)szFile;
}
For everyone that wants to paste this, don´t forget the file filters. Otherwise you wont be able to see any files if the windows opens for you. More of that in the documentation

std::cout re-printing everything currently in the console, each time it is used

For some reason, every time I use std::cout, the entire content (sort of, difficult to explain) of the console is re-printed, unless I << endl;. To provide some context, I am using glfw to back my Window class, which has higher level std::function callbacks. My compiler is MinGW 3.21, using what pieces of C++11 MinGW 3.21 actually implements. What is going on?
void Window::setTextCallback(std::function<void(char text)> callback) {
textCallback = callback;
auto onText = [](GLFWwindow* window, unsigned int text, int mods) {
Window* win = reinterpret_cast<Window*>(glfwGetWindowUserPointer(window));
win->textCallback(static_cast<char>(text));
};
glfwSetCharModsCallback(window, onText);
}
And then in main.cpp...
Window w;
w.setTextCallback([](char text){
cout << text;
}
When the window is open, lets say I type "asdf". The output is "aasasdasdf". In slow motion, it goes: "a", "aas", "aasasd", aasasdasdf".
However, if I change main.cpp to:
Window w;
w.setTextCallback([](char text){
cout << text << endl;
}
The output is:
"a
s
d
f"
As expected.
No other threads are using cout and I know that because I don't have any other threads. This behavior does not happen elsewhere.
cout is a buffer. So each time you << to it, you're just adding text and it's holding on. endl is a way to flush the buffer. If you want to print only the last bit of text you push into it, you need to start with it empty. Try this post and then I'm sure your googling can finish any questions you might have about this.

ADO Recordset Field Value to C++ vector/array (capture pointer value)

I am trying to query an SQL Server (Express) through Visual C++ (express) and store the resulting dataset into a C++ vector (array would be great as well). To that end I researched the ADO library and found plenty of help on MSDN. In short, reference the msado15.dll library and use those features (especially the ADO Record Binding, which requires icrsint.h). In short, I have been able to query the database and display the field values with printf(); but am stumbling when I try to load the field values into a vector.
I originally tried loading values by casting everything as char* (due to despair after many type conversion errors) only to find the end result was a vector of pointers that all pointed to the same memory address. Next (and this is the code provided below) I attempted to assign the value of the memory location but am ending up with a vector of the first character of the memory location only. In short, I need help understanding how to pass the entire value stored by the Recordset Field Value (rs.symbol) pointer (at the time it is passed to the vector) instead of just the first character? In this circumstance the values returned from SQL are strings.
#include "stdafx.h"
#import "msado15.dll" no_namespace rename("EOF", "EndOfFile")
#include "iostream"
#include <icrsint.h>
#include <vector>
int j;
_COM_SMARTPTR_TYPEDEF(IADORecordBinding, __uuidof(IADORecordBinding));
inline void TESTHR(HRESULT _hr) { if FAILED(_hr) _com_issue_error(_hr); }
class CCustomRs : public CADORecordBinding {
BEGIN_ADO_BINDING(CCustomRs)
ADO_VARIABLE_LENGTH_ENTRY2(1, adVarChar, symbol, sizeof(symbol), symbolStatus, false)
END_ADO_BINDING()
public:
CHAR symbol[6];
ULONG symbolStatus;
};
int main() {
::CoInitialize(NULL);
std::vector<char> tickers;
try {
char sym;
_RecordsetPtr pRs("ADODB.Recordset");
CCustomRs rs;
IADORecordBindingPtr picRs(pRs);
pRs->Open(L"SELECT symbol From Test", L"driver={sql server};SERVER=(local);Database=Securities;Trusted_Connection=Yes;",
adOpenForwardOnly, adLockReadOnly, adCmdText);
TESTHR(picRs->BindToRecordset(&rs));
while (!pRs->EndOfFile) {
// Process data in the CCustomRs C++ instance variables.
//Try to load field value into a vector
printf("Name = %s\n",
(rs.symbolStatus == adFldOK ? rs.symbol: "<Error>"));
//This is likely where my mistake is
sym = *rs.symbol;//only seems to store the first character at the pointer's address
// Move to the next row of the Recordset. Fields in the new row will
// automatically be placed in the CCustomRs C++ instance variables.
//Try to load field value into a vector
tickers.push_back (sym); //I can redefine everything as char*, but I end up with an array of a single memory location...
pRs->MoveNext();
}
}
catch (_com_error &e) {
printf("Error:\n");
printf("Code = %08lx\n", e.Error());
printf("Meaning = %s\n", e.ErrorMessage());
printf("Source = %s\n", (LPCSTR)e.Source());
printf("Description = %s\n", (LPCSTR)e.Description());
}
::CoUninitialize();
//This is me running tests to ensure the data passes as expected, which it doesn't
std::cin.get();
std::cout << "the vector contains: " << tickers.size() << '\n';
std::cin.get();
j = 0;
while (j < tickers.size()) {
std::cout << j << ' ' << tickers.size() << ' ' << tickers[j] << '\n';
j++;
}
std::cin.get();
}
Thank you for any guidance you can provide.
A std::vector<char*> does not work because the same buffer is used for all the records. So when pRs->MoveNext() is called, the new content is loaded into the buffer, overwriting the previous content.
You need to make a copy of the content.
I would suggest using a std::vector<std::string>:
std::vector<std::string> tickers;
...
tickers.push_back(std::string(rs.symbol));
Why you did not use std::string instead of std::vector?
To add characters use one of these member functions:
basic_string& append( const CharT* s ); - for cstrings,
basic_string& append( const CharT* s,size_type count ); - otherwise.
Read more at: http://en.cppreference.com/w/cpp/string/basic_string/append.
If you want a line breaks, simply append '\n' where you want it.

C++ SendMessage loop stop and end of line

Here is my dilemma. I have a program that uses SendMessage to get text from a chat program. Now when I did this in Visual Basic I just put the SendMessage in a loop and whenever the chat was updated so would my output.
This is not the case with C++. When I put the SendMessage in a loop it loops forever. Say the Chat is something like:
Apples
Bananas
Cheerios
Now lets say I run my program it finds the text and starts looping. Now in Visual Basic it would keep looping until it hit Cheerios and it would stop and wait until someone in the chat typed something else.
With C++ it would output:
Apples
Bananas
Cheerios
Apples
Bananas
Cheerios
...
And go on forever. Is there a way to stop this? I was thinking along the terms of EOF but that wouldn't help because as soon as it hit the last line it would go out of the loop and wouldn't pick up anything else that people typed in chat.
Here is the portion of the code that gets the text. Now not that I do have an loop that won't end until I set bLoop to True.
cout << " + found RichEdit20W window at: " << hwndRichEdit20W << endl << endl;
cout << "- get text " << endl;
bool bLoop = false;
int textLen = (int)SendMessage(hwndRichEdit20W, WM_GETTEXTLENGTH, 0, 0);
while (bLoop == false)
{
const int MAXSIZE = 32678;
wchar_t szBuf[MAXSIZE];
SendMessage(hwndRichEdit20W, WM_GETTEXT, (WPARAM)textLen, (LPARAM)szBuf);
wcout << szBuf;
}
Thanks for any help!
VB CODE UPDATE
This is how I did it in my VB program. This is for a game console and not a chat window but it should be the same concept right?
Note Do While PARENThwnd <> IntPtr.Zero The handle is never 0 so it will be an infinite loop.
Private Sub bwConsole_DoWork(ByVal sender As System.Object, ByVal e As System.ComponentModel.DoWorkEventArgs) Handles bwConsole.DoWork
Dim PARENThwnd As IntPtr
Dim CHILDhwnd As IntPtr
PARENThwnd = FindWindow(Nothing, "ET Console")
If PARENThwnd = IntPtr.Zero Then
txtConsole.Text = "ET Console is not availble."
Else
Do While PARENThwnd <> IntPtr.Zero
CHILDhwnd = GetDlgItem(PARENThwnd, 100)
Dim Handle As IntPtr = Marshal.AllocHGlobal(32767)
Dim NumText As Integer = CInt(SendMessage(CType(CHILDhwnd, IntPtr), WM_GETTEXT, CType(32767 \ Marshal.SystemDefaultCharSize, IntPtr), Handle))
Dim Text As String = Marshal.PtrToStringAuto(Handle)
txtConsole.Text = Text
Marshal.FreeHGlobal(Handle)
txtConsole.SelectionStart = txtConsole.TextLength
txtConsole.ScrollToCaret()
rtxtDefinition.Text = ""
Call GetDefinitions(txtConsole.Text)
Loop
End If
End Sub
Also will not be able to answer any questions until later tonight. Off to work.
You never update your bLoop variable, causing the loop to run infinitely. If you want to break your loop, implement a loop termination condition that changes the value of bLoop.
A condensed version of your code does the following:
bool bLoop = false;
while ( bLoop == false ) {
// This loop never terminates, unless bLoop is set to true
}
It is somewhat hard to understand, why you are looping to begin with. I suppose you hope the SendMessage call would somehow guess that you don't want it to run, until some condition is met. There is no such magic built into functions.
To respond to text changes you should choose a different solution altogether: An event-based model. The supported way to implement this is through UI Automation.
The first thing you need to understand is that the two versions of the code are doing very different things with the contents of the chat window. The VisualBasic version is taking that text and stuffing it into a text control. This has the effect of replacing the existing text with the new text from the chat window so you never see duplicates. The C++ version outputs the text to the output stream (console) each time it retrieves it. Unlike the text control used in the VB version the output stream appends the text rather than replaces it.
To get around this you need to keep tract of the previous text retrieved from the chat window and only output the difference. This may mean appending new text from the chat or outputting the entire string. The code below maintains a string buffer and manages appending or replacing the history of the chat. It also creates a new string containing only the differences from the last change allowing you to easily deal with updates.
class ChatHistory
{
std::wstring history;
public:
std::wstring update(const std::wstring& newText)
{
const std::wstring::size_type historySize = history.size();
if(newText.compare(0, historySize, history.c_str()) == 0)
{
history.append(newText.c_str() + historySize, newText.size() - historySize);
return history.c_str() + historySize;
}
else
{
history = newText;
}
return newText;
}
};
Below are the necessary changes to your code to use it. I haven't had a chance to test it with your exact set up but it should work.
ChatHistory history;
while (bLoop == false)
{
const int MAXSIZE = 32678;
wchar_t szBuf[MAXSIZE];
SendMessage(hwndRichEdit20W, WM_GETTEXT, (WPARAM)textLen, (LPARAM)szBuf);
std::wcout << history.update(szBuf);
}
In both VBA and C++ that structure will cause an infinite loop.
It sounds like you want an infinite loop, but don't want an infinite print out of the contents of the buffer.
As #IInspectable has pointed out (thanks!) sendmessage will copy the contents of the chat program into the buffer at every iteration of the loop.
So, either purge the chat window or let the program know what content is new and needs to be printed.
I'm on a linux box at the moment, so can't test this very easily. Please let me know if the following works...
#include <cwchar> // so we can use wcschr function
const int MAXSIZE = 32678; // moved outside of the loop
wchar_t szBuf[MAXSIZE]; // moved outside of the loop
szBuf[0] = L'\0'; // set sentinel (wide NULL!)
wchar_t* end_printed_message = szBuf;
//pointer to the end of the content that has been printed
while (bLoop == false)
{
SendMessage(hwndRichEdit20W, WM_GETTEXT, (WPARAM)textLen, (LPARAM)szBuf);
wchar_t* end_message_buffer = wcschr(szBuf, L'\0'); // find end of the string using wchar version of strchr()
if(end_printed_message != end_message_buffer){ // if the buffer contains something new
wcout << end_printed_message; // print out the new stuff (start at old end)
end_printed_message = end_message_buffer; // update the pointer to the end of the content
}
}
I don't know what headers you've already included, hence the use of 'ol faithful <cstring>
As an aside (yes, I will still bang on about this) mixed use of (w)cout is generally bad and may cause problems.

Listing Folders in a Directory C+

I am aware that this may well be a duplicate. However, I am struggling to actually get a working answer.
What I am trying to do is list all of the folders in the working directory. Below is some code that I have adapted from the MS website (http://msdn.microsoft.com/en-us/library/windows/desktop/aa365200(v=vs.85).aspx)
This gives the output:
Filname:52428
I have checked the folder - and there are three folders that I am wanting to list 'Vidoe' 'John' 'David' I am not sure as to why it is printing out the result above.
I do not want to use Boost - nor to download any third party plugings.
int main(int argc, char** argv)
{
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA ffd;
//The Directory where the .exe is run from.
hFind = FindFirstFile(TEXT(".\\Players\\*"), &ffd);
do
{
Sleep(1000);
bool isDirectory = ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY;
if(isDirectory)
{
cout << "DirectoryName: " << *ffd.cFileName << endl;
}
else
{
cout << "FileName: " << *ffd.cFileName << endl;
}
}while(FindNextFile(hFind, &ffd) != 0);
FindClose(hFind);
}
EDIT:
I do not have a specific way that I want to do this, all I am wanting to do is output the Folders in the directory - I do not care how it is done.
In …
*ffd.cFileName
remove the *.
Also remove the call to Sleep.
Also remove the silly TEXT macro call, use wide string literals like L"blah".
Oh I forgot, also replace the do loop with a while loop (or for loop), because it's not sure that the FindFirstFile call will succeed.
Oh, and important, for the debug output use wcout, not cout. The latter doesn't know anything about output of Unicode strings. But wcout can handle them.
The output you're getting,
52428
appears to be wchar_t value 0xCCCC, treated as an integer by cout, which value indicates uninitialized storage, which implies that the FindFirstFile call failed.
So, also be sure about the current directory when you run the program. A good idea is to run it from the command line. Then you're sure.