Reading SDL_RWops from a std::istream - c++

I'm quite surprised that Google didn't find a solution. I'm searching for a solution that allows SDL_RWops to be used with std::istream. SDL_RWops is the alternative mechanism for reading/writing data in SDL.
Any links to sites that tackle the problem?
An obvious solution would be to pre-read enough data to memory and then use SDL_RWFromMem. However, that has the downside that I'd need to know the filesize beforehand.
Seems like the problem could somehow be solved by "overriding" SDL_RWops functions...

I feel bad answering my own question, but it preocupied me for some time, and this is the solution I came up with:
int istream_seek( struct SDL_RWops *context, int offset, int whence)
{
std::istream* stream = (std::istream*) context->hidden.unknown.data1;
if ( whence == SEEK_SET )
stream->seekg ( offset, std::ios::beg );
else if ( whence == SEEK_CUR )
stream->seekg ( offset, std::ios::cur );
else if ( whence == SEEK_END )
stream->seekg ( offset, std::ios::end );
return stream->fail() ? -1 : stream->tellg();
}
int istream_read(SDL_RWops *context, void *ptr, int size, int maxnum)
{
if ( size == 0 ) return -1;
std::istream* stream = (std::istream*) context->hidden.unknown.data1;
stream->read( (char*)ptr, size * maxnum );
return stream->bad() ? -1 : stream->gcount() / size;
}
int istream_close( SDL_RWops *context )
{
if ( context ) {
SDL_FreeRW( context );
}
return 0;
}
SDL_RWops *SDL_RWFromIStream( std::istream& stream )
{
SDL_RWops *rwops;
rwops = SDL_AllocRW();
if ( rwops != NULL )
{
rwops->seek = istream_seek;
rwops->read = istream_read;
rwops->write = NULL;
rwops->close = istream_close;
rwops->hidden.unknown.data1 = &stream;
}
return rwops;
}
Works under the assumptions that istream's are never freed by SDL (and that they live through the operation). Also only istream support is in, a separate function would be done for ostream -- I know I could pass iostream, but that would not allow passing an istream to the conversion function :/.
Any tips on errors or upgrades welcome.

If you're trying to get an SDL_RWops struct from an istream, you could do it by reading the whole istream into memory and then using SDL_RWFromMem to get a struct to represent it.
Following is a quick example; note that it's unsafe, as no sanity checks are done. For example, if the file's size is 0, accessing buffer[0] may throw an exception or assert in debug builds.
// Open a bitmap
std::ifstream bitmap("bitmap.bmp");
// Find the bitmap file's size
bitmap.seekg(0, std::ios_base::end);
std::istream::pos_tye fileSize = bitmap.tellg();
bitmap.seekg(0);
// Allocate a buffer to store the file in
std::vector<unsigned char> buffer(fileSize);
// Copy the istream into the buffer
std::copy(std::istreambuf_iterator<unsigned char>(bitmap), std::istreambuf_iterator<unsigned char>(), buffer.begin());
// Get an SDL_RWops struct for the file
SDL_RWops* rw = SDL_RWFromMem(&buffer[0], buffer.size());
// Do stuff with the SDL_RWops struct

Related

Weird seek behaviour in C and C++ [duplicate]

I did a sample project to read a file into a buffer.
When I use the tellg() function it gives me a larger value than the
read function is actually read from the file. I think that there is a bug.
here is my code:
EDIT:
void read_file (const char* name, int *size , char*& buffer)
{
ifstream file;
file.open(name,ios::in|ios::binary);
*size = 0;
if (file.is_open())
{
// get length of file
file.seekg(0,std::ios_base::end);
int length = *size = file.tellg();
file.seekg(0,std::ios_base::beg);
// allocate buffer in size of file
buffer = new char[length];
// read
file.read(buffer,length);
cout << file.gcount() << endl;
}
file.close();
}
main:
void main()
{
int size = 0;
char* buffer = NULL;
read_file("File.txt",&size,buffer);
for (int i = 0; i < size; i++)
cout << buffer[i];
cout << endl;
}
tellg does not report the size of the file, nor the offset
from the beginning in bytes. It reports a token value which can
later be used to seek to the same place, and nothing more.
(It's not even guaranteed that you can convert the type to an
integral type.)
At least according to the language specification: in practice,
on Unix systems, the value returned will be the offset in bytes
from the beginning of the file, and under Windows, it will be
the offset from the beginning of the file for files opened in
binary mode. For Windows (and most non-Unix systems), in text
mode, there is no direct and immediate mapping between what
tellg returns and the number of bytes you must read to get to
that position. Under Windows, all you can really count on is
that the value will be no less than the number of bytes you have
to read (and in most real cases, won't be too much greater,
although it can be up to two times more).
If it is important to know exactly how many bytes you can read,
the only way of reliably doing so is by reading. You should be
able to do this with something like:
#include <limits>
file.ignore( std::numeric_limits<std::streamsize>::max() );
std::streamsize length = file.gcount();
file.clear(); // Since ignore will have set eof.
file.seekg( 0, std::ios_base::beg );
Finally, two other remarks concerning your code:
First, the line:
*buffer = new char[length];
shouldn't compile: you have declared buffer to be a char*,
so *buffer has type char, and is not a pointer. Given what
you seem to be doing, you probably want to declare buffer as
a char**. But a much better solution would be to declare it
as a std::vector<char>& or a std::string&. (That way, you
don't have to return the size as well, and you won't leak memory
if there is an exception.)
Second, the loop condition at the end is wrong. If you really
want to read one character at a time,
while ( file.get( buffer[i] ) ) {
++ i;
}
should do the trick. A better solution would probably be to
read blocks of data:
while ( file.read( buffer + i, N ) || file.gcount() != 0 ) {
i += file.gcount();
}
or even:
file.read( buffer, size );
size = file.gcount();
EDIT: I just noticed a third error: if you fail to open the
file, you don't tell the caller. At the very least, you should
set the size to 0 (but some sort of more precise error
handling is probably better).
In C++17 there are std::filesystem file_size methods and functions, so that can streamline the whole task.
std::filesystem::file_size - cppreference.com
std::filesystem::directory_entry::file_size - cppreference.com
With those functions/methods there's a chance not to open a file, but read cached data (especially with the std::filesystem::directory_entry::file_size method)
Those functions also require only directory read permissions and not file read permission (as tellg() does)
void read_file (int *size, char* name,char* buffer)
*buffer = new char[length];
These lines do look like a bug: you create an char array and save to buffer[0] char. Then you read a file to buffer, which is still uninitialized.
You need to pass buffer by pointer:
void read_file (int *size, char* name,char** buffer)
*buffer = new char[length];
Or by reference, which is the c++ way and is less error prone:
void read_file (int *size, char* name,char*& buffer)
buffer = new char[length];
...
fseek(fptr, 0L, SEEK_END);
filesz = ftell(fptr);
will do the file if file opened through fopen
using ifstream,
in.seekg(0,ifstream::end);
dilesz = in.tellg();
would do similar

How to read and write a ppm file?

I try to read a ppm file aand create a new one identical. But when I open them with GIMP2 the images are not the same.
Where is the problem with my code ?
int main()
{
FILE *in, *out;
in = fopen("parrots.ppm","r");
if( in == NULL )
{
std::cout<<"Error.\n";
return 0;
}
unsigned char *buffer = NULL;
long size = 0;
fseek(in, 0, 2);
size = ftell(in);
fseek(in, 0, 0);
buffer = new unsigned char[size];
if( buffer == NULL )
{
std::cout<<"Error\n";
return 0;
}
if( fread(buffer, size, 1, in) < 0 )
{
std::cout<<"Error.\n";
return 0 ;
}
out = fopen("out.ppm","w");
if( in == NULL )
{
std::cout<<"Error.\n";
return 0;
}
if( fwrite(buffer, size, 1, out) < 0 )
{
std::cout<<"Error.\n";
return 0;
}
delete[] buffer;
fcloseall();
return 0;
}
Before that I read the ppm file in a structure and when I wrote it I get the same image but the green was more intense than in the original picture. Then I tried this simple reading and writing but I get the same result.
int main()
Missing includes.
FILE *in, *out;
C style I/O in a C++ program, why? Also, declare at point of initialization, close to first use.
in = fopen("parrots.ppm","r");
This is opening the file in text mode, which is most certainly not what you want. Use "rb" for mode.
unsigned char *buffer = NULL;
Declare at point of initialization, close to first use.
fseek(in, 0, 2);
You are supposed to use SEEK_END, which is not guaranteed to be defined as 2.
fseek(in, 0, 0);
See above, for SEEK_SET not guaranteed to be defined as 0.
buffer = new unsigned char[size];
if( buffer == NULL )
By default, new will not return a NULL pointer, but throw a std::bad_alloc exception. (With overallocation being the norm on most current operating systems, checking for NULL would not protect you from out-of-memory even with malloc(), but good to see you got into the habit of checking anyway.)
C++11 brought us smart pointers. Use them. They are an excellent tool to avoid memory leaks (one of the very few weaknesses of C++).
if( fread(buffer, size, 1, in) < 0 )
Successful use of fread should return the number of objects written, which should be checked to be equal the third parameter (!= 1), not < 0.
out = fopen("out.ppm","w");
Text mode again, you want "wb" here.
if( fwrite(buffer, size, 1, out) < 0 )
See the note about the fread return value above. Same applies here.
fcloseall();
Not a standard function. Use fclose( in ); and fclose( out );.
A C++11-ified solution (omitting the error checking for brevity) would look somewhat like this:
#include <iostream>
#include <fstream>
#include <memory>
int main()
{
std::ifstream in( "parrots.ppm", std::ios::binary );
std::ofstream out( "out.ppm", std::ios::binary );
in.seekg( 0, std::ios::end );
auto size = in.tellg();
in.seekg( 0 );
std::unique_ptr< char[] > buffer( new char[ size ] );
in.read( buffer.get(), size );
out.write( buffer.get(), size );
in.close();
out.close();
return 0;
}
Of course, a smart solution would do an actual filesystem copy, either through Boost.Filesystem or the standard functionality (experimental at the point of this writing).

make the compiler not to automatically flush the buffer

Why does the below code not stop the compiler from flushing the buffer automatically?
cout.sync_with_stdio(false);
cin.tie(nullptr);
cout << "hello";
cout << "world";
int a;
cin >> a;
output:
helloworld
I'm using Visual Studio 2012 Ultimate
AFAIK, the stream can be flushed whenever the implementation likes to do so, i.e. there's no guarantee that the stream will be flushed after an insert operation. However, you could use one of these manipulators to ensure your stream gets flushed (these are the only ones I know of so if someone is aware of others, please comment):
std::endl - inserts a newline into the stream and flushes it,
std::flush - just flushes the stream,
std::(no)unitbuf - enables/disables flushing the stream after each insert operation.
The standard allows an implementation to flush any time it feels
like it, but from a quality of implementation point of view, one
really doesn't expect a flush here. You might try adding
a setbuf, telling std::cin to use a buffer you specify:
std::cout.rdbuf()->setbuf( buffer, sizeof(buffer) );
Again,the standard doesn't guarantee anything, but if this isn't
respected, I'd consider the quality bad enough to warrant a bug
report.
Finally, if worse comes to worse, you can always insert
a filtering streambuf which does the buffering you want. You
shouldn't have to, but it won't be the first time we've had to
write extra code to work around a lack of quality in compilers
or libraries. If all you're doing is straightforward output (no
seeks, or anything, something like the following should do the
trick:
class BufferingOutStreambuf : public std::streambuf
{
std::streambuf* myDest;
std::ostream* myOwner;
std::vector<char> myBuffer;
static size_t const bufferSize = 1000;
protected:
virtual int underflow( int ch )
{
return sync() == -1
? EOF
: sputc( ch );
}
virtual int sync()
{
int results = 0;
if ( pptr() != pbase() ) {
if ( myDest->sputn( pbase(), pptr() - pbase() )
!= pptr() - pbase() ) {
results = -1;
}
}
setp( &myBuffer[0], &myBuffer[0] + myBuffer.size() );
return results;
}
public:
BufferingOutStreambuf( std::streambuf* dest )
: myDest( dest )
, myOwner( NULL )
, myBuffer( bufferSize )
{
setp( &myBuffer[0], &myBuffer[0] + myBuffer.size() );
}
BufferingOutStreambuf( std::ostream& dest )
: myDest( dest.rdbuf() )
, myOwner( &dest )
, myBuffer( bufferSize )
{
setp( &myBuffer[0], &myBuffer[0] + myBuffer.size() );
myOwner->rdbuf( this );
}
~BufferingOutStreambuf()
{
if ( myOwner != NULL ) {
myOwner->rdbuf( myDest );
}
}
};
Then just do:
BufferingOutStreambuf buffer( std::cout );
as the first line in main. (One could argue that iostreams
should have been designed to work like this from the start, with
filtering streambuf for buffering and code translation. But
it wasn't, and this shouldn't be necessary with a decent
implementation.)

fread gives BadPtr when done in a new process

I have written a C++ Dll which has two functions, one writes a binary file to disk and and other reads that file from disk and load into memory.
//extremely simplified code looks like this
bool Utilities::WriteToBinary(wstring const fileName)
{
//lot of code
DWORD size = //get size of data to write
LPBYTE * blob = new LPBYTE[size];
WriteDataToMemoryBlob(blob, & size);
FILE * pFile;
if(0 != _wfopen_s (&pFile , fileName.c_str() , L"wb" ))
{
//do something
return false;
}
fwrite (blob, 1, size , pFile );
fclose (pFile);
delete[] blob;
return true;
}
bool Utilities::ReadDataFromDisk(wstring const fileName)
{
long fileSize = GetFileSize(fileName);
FILE * filePointer;
if(0 != _wfopen_s (&filePointer, fileName.c_str() , L"rb" ))
return false;
//read from file
LPBYTE * blobRead = new LPBYTE[fileSize];
fread (blobRead, 1, fileSize , filePointer );
fclose (filePointer);
//rest of the code...
Problem
I have created another C++ project which call these DLL methods for testing.
Problem which is driving me crazy is that when I call WriteToBinary and ReadDataFromDisk consecutively inside same program they work perfectly fine. But when I call WriteToBinary at one time and let the program exit and call ReadDataFromDisk next time and give it path of file written earlier by WriteToBinary, I get a BadPtr in blobRead after doing fread.
I have tried my best to make sure there are no shared or static data structures involved. Both methods are totally independent.
Any idea what might be causing this?
A mistake is the allocation of the array as LPBYTE is a BYTE* so the:
LPBYTE * blobRead = new LPBYTE[fileSize];
Is allocating an array of BYTE*, not an array of BYTE. Change to:
BYTE* blobRead = new BYTE[fileSize];
To avoid dynamic allocation you could use a std::vector<BYTE> instead:
std::vector<BYTE> blobRead(fileSize);

C++ Debug Assertion Error

So, I've been playing around with c++ a bit and decided to write a program that involves opening and writing to a file in binary mode. I am not too familiar with the iostream functionality of c++ (I mostly do API based programming), but I read several technical guides on the subject and wrote some code. The code is meant to open one file, read it's data to a buffer, and then convert that buffer to another format and write it to another file. The problem is that it keeps throwing a "Debug Assertion" error which apparently revolves around the invalid use of a null pointer. However, I couldn't make sense of it when I looked through the code. I probably just misused the iostream library or made a simple logic error. I need to have the separate SetMemBlock function as I plan on using the same base for formatting different output on a variety of functions. This is just my prototype. Anyways, here's my quick n' dirty class setup:
const DebugMode = true;
class A
{
public:
bool FileFunction( const char *, const char * );
protected:
bool SetMemBlock( char *, std::fstream &, std::streamoff & );
private:
std::fstream SrcFileStream;
std::fstream DestFileStream;
};
bool A::SetMemBlock( char* MemBlock, std::fstream & FileStream, std::streamoff & Size )
{
std::streamoff TempOff = 0;
//This is meant to check for a non-empty buffer and to see if the stream is valid.
if( MemBlock != 0 || !FileStream.is_open() )
return false;
TempOff = FileStream.tellg();
FileStream.seekg(0, std::ios::end);
Size = FileStream.tellg();
MemBlock = new( std::nothrow ) char[ (int) Size ];
if( MemBlock == 0 )
return false;
FileStream.seekg(0, std::ios::beg);
FileStream.read( MemBlock, (int) Size );
if( !FileStream )
return false;
FileStream.seekg(TempOff);
return true;
}
bool A::FileFunction( const char * SrcFile, const char * DestFile )
{
char * MemBlock = 0;
std::streamoff Size = 0;
SrcFileStream.open( SrcFile, std::ios::binary | std::ios::in );
DestFileStream.open( DestFile, std::ios::binary | std::ios::out );
if( !SrcFileStream.is_open() || !DestFileStream.is_open() )
return false;
if( DebugMode )
{
std::cout<<"Files opened succesfully...\nNow writing memory block..."<<std::endl;
}
if( !SetMemBlock( MemBlock, SrcFileStream, Size ) )
{
std::cout<<"An error occured when reading to memory block!"<<std::endl;
return false;
}
if( DebugMode )
{
std::cout<<"Memory block written..."<<std::endl;
}
DestFileStream.seekp( std::ios::beg );
DestFileStream.write( MemBlock, Size );
SrcFileStream.close();
DestFileStream.close();
delete[] MemBlock;
return true;
}
You're passing MemBlock to SetMemBlock by value. The function therefore just sets the value of a local copy, which has no effect on the calling function; the value of MemBlock in the calling function thus remains garbage. Using it as a pointer will probably then lead to an assertion (if you're lucky) or an out-and-out crash (if you're not.) You want to pass that argument by reference instead.
If you don't know what these terms mean, Google "pass by value" and "pass by reference". You really need to understand the difference!
Pass MemBlock by reference:
bool A::SetMemBlock( char*& MemBlock, std::fstream & FileStream, std::streamoff & Size )