I'm trying to read in some GLSL source from simple plain text files using the following.
I provide the sources to the constructor like so:
Shader *shader = new Shader("res/default.vert", "res/default.frag");
The constructor passes those paths on directly to my little OSUtils file that reads in plain text and returns std::string:
Shader::Shader(const std::string& vertPath, const std::string& fragPath)
{
addVertexShader(vertPath);
addFragmentShader(fragPath);
}
void Shader::addVertexShader(const std::string& path)
{
try {
const std::string shaderSrc = OSUtils::fileToBuffer(path);
compileVertexShader(shaderSrc);
} catch (int e) {
std::cout << "Error no: " << e << std::endl;
exit(1);
}
}
The addFragmentShader function is nearly identical.
The fileToBuffer function is as such:
const std::string OSUtils::fileToBuffer(const std::string& path)
{
std::cout << "Trying to load: " << path << std::endl;
std::ifstream in(path, std::ios::in | std::ios::binary);
if (in)
{
std::string contents;
in.seekg(0, std::ios::end);
contents.resize(in.tellg());
in.seekg(0, std::ios::beg);
in.read(&contents[0], contents.size());
in.close();
return(contents);
}
throw(errno);
}
This throws the error with Errno 2 (No file found) in the addVertexShader function. And here's the kicker. I have this EXACT code running in a different project. Most of this code was copypasta from that earlier project that compiles just fine.
I'm running both projects in Xcode 5. The files themselves (res/default.vert and res/default.frag) have been added to the target and are listed in the Compile Sources listing under the target's Build Phases tab. In other words, everything seems in order, but I can't figure out why it won't load the files in this project. Is there some mysterious Xcode setting I'm missing? I'd suspect the code if it wasn't 100% identical to the code in the working project. The other project also mimics the file structure with both the .vert and .frag files living in a res/ folder. The only difference is that the source files that attempt to read the files live in a src/ subdirectory. My understanding though is that I should be able to reference the files relative to the project, not the actual source files themselves. Just in case, I tried "../res/default.vert" to no avail.
Any help is appreciated. Cheers!
The answer was in fact related to the working directory. There's a custom setting under the schemes that lets you set a custom working dir. Obviously not really suitable for distribution, but that explains the discrepancy. Thanks for the help!
Related
I am doing a openGL porject and readering my shaders from .vert and .frag glsl files. I am using CMake with an extension for auto ninja file generation as well. This is my readfile code:
std::string readFile(const char *filePath) {
std::string content;
std::ifstream fileStream(filePath, std::ios::in);
if(!fileStream.is_open()) {
std::cerr << "Could not read file " << filePath << ". File does not exist." << std::endl;
return "";
}
std::string line = "";
while(!fileStream.eof()) {
std::getline(fileStream, line);
content.append(line + "\n");
}
fileStream.close();
return content;
}
I spent a long time thinking I was crazy because from my build dir I was doing ./app/NAME_OF_APP.exe but the shaders were not being applied just black. It was only when I clicked on it from within the file explorer that it worked so I cd'ed into build/app/ and called ./NAME_OF_APP.exe and boom it worked. Why is this behavior happening? does the starting dir effect the relative pathing of the actual application? seems odd to me. thanks.
If you run a program from a command line the current directory is whatever directory is current in that command prompt. If you run a program from explorer the current directory is the directory that houses the executable.
You could use a function like GetModuleFileName (with NULL as the hModule parameter) to get the path to the executable and then chop off at the last \ and then change to that directory and then the program would not depend on the directory the program is run from.
I am working on a project in which I need to read/open an .stl from any arbitrary directory. This is what I have for opening a file:
int ReadStl( std::string fname, std::vector<ReadFacet>& mesh)
{
std::ifstream ifs;
ifs.open(fname, std::ios::binary);
... stl reading and processing...
return 0;
}
As I understand it, I can pass the complete file path and name like so:
fname = "C:/Users/Luke/folder1/example.stl"
This works for the most part, but fails once I go more than 6 folders deep...
Any ideas on why this happens or what is wrong with my solution?
I am still relatively new to C++ so any input/corrections are welcomed.
I've got a method to read a vector of bools from a file:
std::vector<bool> OPCConnector::getAlarmVector() {
std::vector<bool> data;
std::ifstream DataFile(filepath);
if (DataFile) {
bool value;
while (DataFile >> value) {
data.push_back(value);
std::cout << value;
}
}
return data;
}
The filepath variable is an object property that is assigned through the constructor:
OPCConnector::OPCConnector(std::string fpth) {
filepath = fpth;
}
And in the main() function, the constructor is called:
std::vector<bool> activations;
std::string filepath = "alarmes.txt";
OPCConnector opcc = OPCConnector(filepath);
activations = opcc.getAlarmVector();
Now, I've checked what the folder of the executable is via GetModuleFileNameA(), and I made sure that the file is in the same directory and has the same name (also, I made sure that the extension isn't part of the file name, like "alarmes.txt.txt").
I debugged the first method getAlarmVector() and it never gets past the if (DataFile) condition, as if it won't find file.
I run the code using Visual Studio 2019, and nothing happens. The vector remains empty. Error is No such file or directory.
Default working directory is $(ProjectDir) and it's exactly where my file is.
Edit: I've also tried using both relative and absolute paths, none work.
Edit 2: I've also checked the directory using GetCurrentDirectory() and copied the .txt file there too, and it isn't working.
SOLUTION: Strangely enough, I deleted the file and created it again with the same name, and it worked. Thanks for the answers.
My guess: your current working directory isn't what you think it is, especially if you're running from an IDE. I know of several IDEs where the current working directory is some build directory (it varies by IDE) unless you specifically change it.
I'm fairly sure Visual Studio is one such IDE.
Here's a tiny example program I wrote;
$ cat Foo.cpp
#include <iostream>
#include <fstream>
int main(int, char **) {
std::ifstream file { "Foo.cpp" };
if (file) {
std::cout << "File opened.\n";
}
else {
std::cout << "File not opened.\n";
}
}
Compile and run it:
$ g++ --std=c++17 Foo.cpp -o Foo && Foo
File opened.
Current folder and folder-of-exe-file are different things (sometimes). Try to specify full name of file (with disk, all folders, etc.).
You can check errors of file open operation by calling
if (!DataFile) { ... }
The std::filesystem library can help you resolve file and path related issues.
#include <filesystem>
// (in some function)
std::filesystem::path filepath = "alarmes.txt";
if ( !exists(filepath) )
{
std::cout << "File path " << filepath << " at absolute location "
<< absolute(filepath) << " does not exist\n";
}
See it on Compiler Explorer
You can get an error code (and get a description of error in internet) if you use C-function fopen. If open is failed, you get the nullptr as result of fopen and errno will contain code of error.
Hello and sorry if the answer is clear to those out there. I am still fairly new to programming and ask for some guidance.
This function should write just one of the three string parameters it takes in to the txt file I have already generated. When I run the program the function seems to work fine and the cout statement shows the info is in the string and does get passes successfully. The issue is after running the program I go to check the txt file and find it is still blank.
I am using C++17 on visual studio professional 2015.
void AddNewMagicItem(const std::string & ItemKey,
const std::string & ItemDescription,
const std::string &filename)
{
const char* ItemKeyName = ItemKey.c_str();
const char* ItemDescriptionBody = ItemDescription.c_str();
const char* FileToAddItemTo = filename.c_str();
std::ofstream AddingItem(FileToAddItemTo);
std::ifstream FileCheck(FileToAddItemTo);
AddingItem.open(FileToAddItemTo, std::ios::out | std::ios::app);
if (_access(FileToAddItemTo, 0) == 0)
{
if (FileCheck.is_open())
{
AddingItem << ItemKey;
std::cout << ItemKey << std::endl;
}
}
AddingItem.close(); // not sure these are necessary
FileCheck.close(); //not sure these are necessary
}
This should print out a message onto a .txt file when you pass a string into the ItemKey parameter.
Thank you very much for your help and again please forgive me as I am also new to stackoverflow and might have made some mistakes in formatting this question or not being clear enough.
ADD ON: Thank you everyone who has answered this question and for all your help. I appreciate the help and would like to personally thank you all for your help, comments, and input on this topic. May your code compile every time and may your code reviews always be commented.
As mentioned by previous commenters/answerers, your code can be simplified by letting the destructor of the ofstream object close the file for you, and by refraining from using the c_str() conversion function.
This code seems to do what you wanted, on GCC v8 at least:
#include <string>
#include <fstream>
#include <iostream>
void AddNewMagicItem(const std::string& ItemKey,
const std::string& ItemDescription,
const std::string& fileName)
{
std::ofstream AddingItem{fileName, std::ios::app};
if (AddingItem) { // if file successfully opened
AddingItem << ItemKey;
std::cout << ItemKey << std::endl;
}
else {
std::cerr << "Could not open file " << fileName << std::endl;
}
// implicit close of AddingItem file handle here
}
int main(int argc, char* argv[])
{
std::string outputFileName{"foobar.txt"};
std::string desc{"Description"};
// use implicit conversion of "key*" C strings to std::string objects:
AddNewMagicItem("key1", desc, outputFileName);
AddNewMagicItem("key2", desc, outputFileName);
AddNewMagicItem("key3", desc, outputFileName);
return 0;
}
Main Problem
std::ofstream AddingItem(FileToAddItemTo);
opened the file. Opening it again with
AddingItem.open(FileToAddItemTo, std::ios::out | std::ios::app);
caused the stream to fail.
Solution
Move the open modes into the constructor (std::ofstream AddingItem(FileToAddItemTo, std::ios::app);) and remove the manual open.
Note that only the app open mode is needed. ofstream implies the out mode is already set.
Note: If the user does not have access to the file, the file cannot be opened. There is no need to test for this separately. I find testing for an open file followed by a call to perror or a similar target-specific call to provide details on the cause of the failure to be a useful feature.
Note that there are several different states the stream could be in and is_open is sort of off to the side. You want to check all of them to make sure an IO transaction succeeded. In this case the file is open, so if is_open is all you check, you miss the failbit. A common related bug when reading is only testing for EOF and winding up in a loop of failed reads that will never reach the end of the file (or reading past the end of the file by checking too soon).
AddingItem << ItemKey;
becomes
if (!(AddingItem << ItemKey))
{
//handle failure
}
Sometimes you will need better granularity to determine exactly what happened in order to properly handle the error. Check the state bits and possibly perror and target-specific
diagnostics as above.
Side Problem
Opening a file for simultaneous read and write with multiple fstreams is not recommended. The different streams will provide different buffered views of the same file resulting in instability.
Attempting to read and write the same file through a single ostream can be done, but it is exceptionally difficult to get right. The standard rule of thumb is read the file into memory and close the file, edit the memory, and the open the file, write the memory, close the file. Keep the in-memory copy of the file if possible so that you do not have to reread the file.
If you need to be certain a file was written correctly, write the file and then read it back, parse it, and verify that the information is correct. While verifying, do not allow the file to be written again. Don't try to multithread this.
Details
Here's a little example to show what went wrong and where.
#include <iostream>
#include <fstream>
int main()
{
std::ofstream AddingItem("test");
if (AddingItem.is_open()) // test file is open
{
std::cout << "open";
}
if (AddingItem) // test stream is writable
{
std::cout << " and writable\n";
}
else
{
std::cout << " and NOT writable\n";
}
AddingItem.open("test", std::ios::app);
if (AddingItem.is_open())
{
std::cout << "open";
}
if (AddingItem)
{
std::cout << " and writable\n";
}
else
{
std::cout << " and NOT writable\n";
}
}
Assuming the working directory is valid and the user has permissions to write to test, we will see that the program output is
open and writable
open and NOT writable
This shows that
std::ofstream AddingItem("test");
opened the file and that
AddingItem.open("test", std::ios::app);
left the file open, but put the stream in a non-writable error state to force you to deal with the potential logic error of trying to have two files open in the same stream at the same time. Basically it's saying, "I'm sorry Dave, I'm afraid I can't do that." without Undefined Behaviour or the full Hal 9000 bloodbath.
Unfortunately to get this message, you have to look at the correct error bits. In this case I looked at all of them with if (AddingItem).
As a complement of the already given question comments:
If you want to write data into a file, I do not understand why you have used a std::ifstream. Only std::ofstream is needed.
You can write data into a file this way:
const std::string file_path("../tmp_test/file_test.txt"); // path to the file
std::string content_to_write("Something\n"); // content to be written in the file
std::ofstream file_s(file_path, std::ios::app); // construct and open the ostream in appending mode
if(file_s) // if the stream is successfully open
{
file_s << content_to_write; // write data
file_s.close(); // close the file (or you can also let the file_s destructor do it for you at the end of the block)
}
else
std::cout << "Fail to open: " << file_path << std::endl; // write an error message
As you said being quite new to programming, I have explicitly commented each line to make it more understandable.
I hope it helps.
EDIT:
For more explanation, you tried to open the file 3 times (twice in writing mode and once in reading mode). This is the cause of your problems. You only need to open the file once in writing mode.
Morever, checking that the input stream is open will not tell you if the output stream is open too. Keep in mind that you open a file stream. If you want to check if it is properly open, you have to check it over the related object, not over another one.
We are working on a c++ project using CMake. In the project we are referencing some resource files like resources/file.txt (located directly inside the project folder) using the std::ifstream class. The problem now is that, while our other team members can reference these files as they use Linux, I cannot (I am using Visual Studio 2017).
How can I set up CMake or Visual Studio so I can reference these files without changing their paths? I am still a pretty big novice using CMake but I didn't find anything that worked.
EDIT:
Minimal code showing the issue:
#include <iostream>
#include <fstream>
#include <string>
std::string read_file(std::string filename)
{
std::string result = "";
std::ifstream file(filename);
if (!file.is_open())
{
std::cout << "Could not open file " << filename.c_str() << "." << std::endl;
return result;
}
std::string line;
while (getline(file, line))
result += line + '\n';
return result;
}
int main(int argc, char *argv[])
{
std::string fileContent = read_file("src/Test_File.txt");
if (fileContent.compare(""))
{
std::cout << fileContent.c_str() << std::endl;
}
return 0;
}
This program should load the "Test_File.txt" located inside the src folder with the folder structure being:
src
main.cpp
CMakeList.txt
Test_File.txt
CMakeList.txt
The problem here is that the program cannot find the Text_File.txt while the other team members don't have such issue. Absolute paths of course work but they are obviously not the way to go.
I've already tried to set the working directory using the
VS_DEBUGGER_WORKING_DIRECTORY parameter with set_target_properties(${PROJECT_NAME} PROPERTIES VS_DEBUGGER_WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") after add_executable(${PROJECT_NAME} "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp") but that does not seem to work either.
After some more tries I have got it working thanks to #Tsyvarev. As states in this question they brought up, setting "currentDir" worked. For me it didn't work before because "name" and "projectTarget" were improperly set.
I now added "currentDir": "${workspaceRoot}" after fixing the "name" and "projectTarget" values and it worked.