How do you copy one stream to another using dedicated read/write threads in C++?
Let's say I have these methods (not real, but to illustrate the point) to read/write data from. These read/write functions could represent anything (network/file/USB/serial/etc).
// returns the number of bytes read
void read(char* buffer, int bufferSize, int* bytesRead);
// returns the number of bytes written
void write(char* buffer, int bufferSize, int* bytesWritten);
The solution should also be portable.
NOTE: I am aware that Windows has a FILE_FLAG_OVERLAPPED feature, but this assumes that the read/write is file IO. Remember, these read/write methods could represent anything.
Here is the solution I came up with.
Header
#pragma once
#include <stdlib.h>
#include <queue>
#include <mutex>
#include <thread>
#include <chrono>
#include <list>
#include <thread>
#define ASYNC_COPY_READ_WRITE_SUCCESS 0
struct BufferBlock;
struct ReadStream
{
// read a stream to a buffer.
// return non-zero if error occured
virtual int read(char* buffer, int bufferSize, int* bytesRead) = 0;
};
struct WriteStream
{
// write a buffer to a stream.
// return non-zero if error occured
virtual int write(char* buffer, int bufferSize, int* bytesWritten) = 0;
};
class BufferBlockManager
{
public:
BufferBlockManager(int numberOfBlocks, int bufferSize);
~BufferBlockManager();
void enqueueBlockForRead(BufferBlock* block);
void dequeueBlockForRead(BufferBlock** block);
void enqueueBlockForWrite(BufferBlock* block);
void dequeueBlockForWrite(BufferBlock** block);
void resetState();
private:
std::list<BufferBlock*> blocks;
std::queue<BufferBlock*> blocksPendingRead;
std::queue<BufferBlock*> blocksPendingWrite;
std::mutex queueLock;
std::chrono::milliseconds dequeueSleepTime;
};
void AsyncCopyStream(BufferBlockManager* bufferBlockManager, ReadStream* readStream, WriteStream* writeStream, int* readResult, int* writeResult);
CPP
#include "AsyncReadWrite.h"
struct BufferBlock
{
BufferBlock(int bufferSize) : buffer(NULL)
{
this->bufferSize = bufferSize;
this->buffer = new char[bufferSize];
this->actualSize = 0;
this->isLastBlock = false;
}
~BufferBlock()
{
this->bufferSize = 0;
free(this->buffer);
this->buffer = NULL;
this->actualSize = 0;
}
char* buffer;
int bufferSize;
int actualSize;
bool isLastBlock;
};
BufferBlockManager::BufferBlockManager(int numberOfBlocks, int bufferSize)
{
dequeueSleepTime = std::chrono::milliseconds(100);
for (int x = 0; x < numberOfBlocks; x++)
{
BufferBlock* block = new BufferBlock(bufferSize);
blocks.push_front(block);
blocksPendingRead.push(block);
}
}
BufferBlockManager::~BufferBlockManager()
{
for (std::list<BufferBlock*>::const_iterator iterator = blocks.begin(), end = blocks.end(); iterator != end; ++iterator) {
delete (*iterator);
}
}
void BufferBlockManager::enqueueBlockForRead(BufferBlock* block)
{
queueLock.lock();
block->actualSize = 0;
block->isLastBlock = false;
blocksPendingRead.push(block);
queueLock.unlock();
}
void BufferBlockManager::dequeueBlockForRead(BufferBlock** block)
{
WAITFOR:
while (blocksPendingRead.size() == 0)
std::this_thread::sleep_for(dequeueSleepTime);
queueLock.lock();
if (blocksPendingRead.size() == 0)
{
queueLock.unlock();
goto WAITFOR;
}
*block = blocksPendingRead.front();
blocksPendingRead.pop();
queueLock.unlock();
}
void BufferBlockManager::enqueueBlockForWrite(BufferBlock* block)
{
queueLock.lock();
blocksPendingWrite.push(block);
queueLock.unlock();
}
void BufferBlockManager::dequeueBlockForWrite(BufferBlock** block)
{
WAITFOR:
while (blocksPendingWrite.size() == 0)
std::this_thread::sleep_for(dequeueSleepTime);
queueLock.lock();
if (blocksPendingWrite.size() == 0)
{
queueLock.unlock();
goto WAITFOR;
}
*block = blocksPendingWrite.front();
blocksPendingWrite.pop();
queueLock.unlock();
}
void BufferBlockManager::resetState()
{
queueLock.lock();
blocksPendingRead = std::queue<BufferBlock*>();
blocksPendingWrite = std::queue<BufferBlock*>();
for (std::list<BufferBlock*>::const_iterator iterator = blocks.begin(), end = blocks.end(); iterator != end; ++iterator) {
(*iterator)->actualSize = 0;
}
queueLock.unlock();
}
struct AsyncCopyContext
{
AsyncCopyContext(BufferBlockManager* bufferBlockManager, ReadStream* readStream, WriteStream* writeStream)
{
this->bufferBlockManager = bufferBlockManager;
this->readStream = readStream;
this->writeStream = writeStream;
this->readResult = ASYNC_COPY_READ_WRITE_SUCCESS;
this->writeResult = ASYNC_COPY_READ_WRITE_SUCCESS;
}
BufferBlockManager* bufferBlockManager;
ReadStream* readStream;
WriteStream* writeStream;
int readResult;
int writeResult;
};
void ReadStreamThread(AsyncCopyContext* asyncContext)
{
int bytesRead = 0;
BufferBlock* readBuffer = NULL;
int readResult = ASYNC_COPY_READ_WRITE_SUCCESS;
while (
// as long there hasn't been any write errors
asyncContext->writeResult == ASYNC_COPY_READ_WRITE_SUCCESS
// and we haven't had an error reading yet
&& readResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
// let's deque a block to read to!
asyncContext->bufferBlockManager->dequeueBlockForRead(&readBuffer);
readResult = asyncContext->readStream->read(readBuffer->buffer, readBuffer->bufferSize, &bytesRead);
readBuffer->actualSize = bytesRead;
readBuffer->isLastBlock = bytesRead == 0;
if (readResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
// this was a valid read, go ahead and queue it for writing
asyncContext->bufferBlockManager->enqueueBlockForWrite(readBuffer);
}
else
{
// an error occured reading
asyncContext->readResult = readResult;
// since an error occured, lets queue an block to write indicatiting we are done and there are no more bytes to read
readBuffer->isLastBlock = true;
readBuffer->actualSize = 0;
asyncContext->bufferBlockManager->enqueueBlockForWrite(readBuffer);
}
if (readBuffer->isLastBlock) return;
}
}
void WriteStreamThread(AsyncCopyContext* asyncContext)
{
int bytesWritten = 0;
BufferBlock* writeBuffer = NULL;
int writeResult = ASYNC_COPY_READ_WRITE_SUCCESS;
bool isLastWriteBlock = false;
while (
// as long as there are no errors during reading
asyncContext->readResult == ASYNC_COPY_READ_WRITE_SUCCESS
// and we haven't had an error writing yet
&& writeResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
// lets dequeue a block for writing!
asyncContext->bufferBlockManager->dequeueBlockForWrite(&writeBuffer);
isLastWriteBlock = writeBuffer->isLastBlock;
if (writeBuffer->actualSize > 0)
writeResult = asyncContext->writeStream->write(writeBuffer->buffer, writeBuffer->actualSize, &bytesWritten);
if (writeResult == ASYNC_COPY_READ_WRITE_SUCCESS)
{
asyncContext->bufferBlockManager->enqueueBlockForRead(writeBuffer);
if (isLastWriteBlock) return;
}
else
{
asyncContext->writeResult = writeResult;
asyncContext->bufferBlockManager->enqueueBlockForRead(writeBuffer);
return;
}
}
}
void AsyncCopyStream(BufferBlockManager* bufferBlockManager, ReadStream* readStream, WriteStream* writeStream, int* readResult, int* writeResult)
{
AsyncCopyContext asyncContext(bufferBlockManager, readStream, writeStream);
std::thread readThread(ReadStreamThread, &asyncContext);
std::thread writeThread(WriteStreamThread, &asyncContext);
readThread.join();
writeThread.join();
*readResult = asyncContext.readResult;
*writeResult = asyncContext.writeResult;
}
Usage
#include <stdio.h>
#include <tchar.h>
#include "AsyncReadWrite.h"
struct ReadTestStream : ReadStream
{
int readCount = 0;
int read(char* buffer, int bufferSize, int* bytesRead)
{
printf("Starting read...\n");
memset(buffer, bufferSize, 0);
if (readCount == 10)
{
*bytesRead = 0;
return 0;
}
// pretend this function takes a while!
std::this_thread::sleep_for(std::chrono::milliseconds(100));
char buff[100];
sprintf_s(buff, "This is read number %d\n", readCount);
strcpy_s(buffer, sizeof(buff), buff);
*bytesRead = strlen(buffer);
readCount++;
printf("Finished read...\n");
return 0;
}
};
struct WriteTestStream : WriteStream
{
int write(char* buffer, int bufferSize, int* bytesWritten)
{
printf("Starting write...\n");
// pretend this function takes a while!
std::this_thread::sleep_for(std::chrono::milliseconds(500));
printf(buffer);
printf("Finished write...\n");
return 0;
}
};
int _tmain(int argc, _TCHAR* argv[])
{
BufferBlockManager bufferBlockManager(5, 4096);
ReadTestStream readStream;
WriteTestStream writeStream;
int readResult = 0;
int writeResult = 0;
printf("Starting copy...\n");
AsyncCopyStream(&bufferBlockManager, &readStream, &writeStream, &readResult, &writeResult);
printf("Finished copy... readResult=%d writeResult=%d \n", readResult, writeResult);
getchar();
return 0;
}
EDIT: I put my solution into a GitHub repository here. If you wish to use this code, refer to the repository since it may be more updated than this answer.
Typically, you would just have one thread for each direction that alternates between reads and writes.
I have a collection of jpeg, which must be decoded by lib jpeg, and after it, encoded by x264 (after it encoded packets are streamed via rtmp).
Code I used for decoding:
struct my_error_mgr
{
struct jpeg_error_mgr pub;
jmp_buf setjmp_buffer;
};
typedef my_error_mgr *my_error_ptr;
METHODDEF(void) my_error_exit (j_common_ptr cinfo)
{
my_error_ptr myerr = (my_error_ptr) cinfo->err;
(*cinfo->err->output_message) (cinfo);
longjmp(myerr->setjmp_buffer, 1);
}
void init_source(j_decompress_ptr ptr)
{
Q_UNUSED(ptr)
}
boolean fill_input_buffer(j_decompress_ptr ptr)
{
Q_UNUSED(ptr)
return TRUE;
}
void term_source(j_decompress_ptr ptr)
{
Q_UNUSED(ptr)
}
void skip_input_data(j_decompress_ptr ptr, long num_bytes)
{
if(num_bytes>0)
{
ptr->src->next_input_byte+=(size_t)num_bytes;
ptr->src->bytes_in_buffer-=(size_t)num_bytes;
}
}
EtherDecoder::EtherDecoder(QObject *parent):
QObject(parent)
{
}
void EtherDecoder::dataBlockReady(QByteArray data)
{
jpeg_decompress_struct decompressInfo;
jpeg_create_decompress(&decompressInfo);
my_error_mgr err;
decompressInfo.do_fancy_upsampling = FALSE;
decompressInfo.src = (jpeg_source_mgr *) (*decompressInfo.mem->alloc_small) ((j_common_ptr) &decompressInfo, JPOOL_PERMANENT, sizeof(jpeg_source_mgr));
decompressInfo.err = jpeg_std_error(&err.pub);
err.pub.error_exit = my_error_exit;
if (setjmp(err.setjmp_buffer))
{
jpeg_destroy_decompress(&decompressInfo);
return;
}
decompressInfo.src->init_source = init_source;
decompressInfo.src->resync_to_restart = jpeg_resync_to_restart;
decompressInfo.src->fill_input_buffer = fill_input_buffer;
decompressInfo.src->skip_input_data = skip_input_data;
decompressInfo.src->term_source = term_source;
decompressInfo.src->next_input_byte = reinterpret_cast<const JOCTET*>(data.data());
decompressInfo.src->bytes_in_buffer = data.size();
jpeg_read_header(&decompressInfo, TRUE);
jpeg_start_decompress(&decompressInfo);
int size = 0;
int n_samples = 0;
char *samples = new char[5242880];
char *reserv = samples;
while (decompressInfo.output_scanline < decompressInfo.output_height)
{
n_samples = jpeg_read_scanlines(&decompressInfo, (JSAMPARRAY) &samples, 1);
samples += n_samples * decompressInfo.image_width * decompressInfo.num_components;
size += n_samples * decompressInfo.image_width * decompressInfo.num_components;
}
jpeg_finish_decompress(&decompressInfo);
QByteArray output(reserv, size);
emit frameReady(output, decompressInfo.output_width, decompressInfo.output_height);
jpeg_destroy_decompress(&decompressInfo);
delete[] reserv;
}
When I emit frameReady signal, I send data to Encoder, method, where I init Encedor looks like:
bool EtherEncoder::initEncoder(unsigned int width, unsigned int height)
{
x264_param_t param;
x264_param_default_preset(¶m, "veryfast", "zerolatency");
param.i_width=width;
param.i_height=height;
param.i_frame_total=0;
param.i_csp=X264_CSP_I420;
param.i_timebase_num=1;
param.i_timebase_den=96000;
param.b_annexb=true;
param.b_repeat_headers=false;
x264_param_apply_fastfirstpass(¶m);
x264_param_apply_profile(¶m, "baseline");
_context=x264_encoder_open(¶m);
if(!_context)
return false;
int nal_count;
x264_nal_t *nals;
if(x264_encoder_headers(_context, &nals, &nal_count)<0)
{
x264_encoder_close(_context);
_context=0;
return false;
}
_extradata=QByteArray();
_width=width;
_height=height;
if(nal_count>0)
{
_extradata=QByteArray(
(const char *)nals[0].p_payload,
nals[nal_count-1].p_payload+nals[nal_count-1].i_payload-nals[0].p_payload);
}
return true;
}
And encoding method:
void EtherEncoder::onFrameReady(QByteArray data, int width, int height)
{
while(data.size()>0)
{
if(!_context && initEncoder(width, height))
{
_timestampDelta=realTimestamp();
}
if(_context)
{
x264_picture_t pic;
x264_picture_init(&pic);
pic.i_type=X264_TYPE_AUTO;
pic.i_pts=_timestampDelta*96000;
pic.img.i_csp=X264_CSP_I420;
pic.img.i_plane=3;
int planeSize = width*height;
uint8_t *p = (uint8_t*)data.data();
pic.img.plane[0]=p;
p+=planeSize;
pic.img.plane[1]=p;
p+=planeSize/4;
pic.img.plane[2]=p;
pic.img.i_stride[0]=width;
pic.img.i_stride[1]=width/2;
pic.img.i_stride[2]=width/2;
if(_forceKeyFrame)
{
pic.i_type=X264_TYPE_I;
_forceKeyFrame=false;
}
int nal_count;
x264_nal_t *nals;
int rc=x264_encoder_encode(_context, &nals, &nal_count, &pic, &pic);
if(rc>0)
{
_mutex.lock();
_packets.push_back(
Packet(
QByteArray(
(const char *)nals[0].p_payload, nals[nal_count- 1].p_payload+nals[nal_count-1].i_payload-nals[0].p_payload),
_timestampDelta/96.0,
_timestampDelta/96.0,
pic.b_keyframe));
_timestampDelta+=40;
data.clear();
_mutex.unlock();
emit onPacketReady();
}
}
}
}
Decoding and encoding proceeds without errors, at the end I get valid video stream, but, it seems that in one of this steps I set Invalid data for decoder/encoder. I get only 1/4 part of image (top-left, as I understood) and it has invalid color and come color stripes. Maybe I set invalid strides and planes when encode frame, or maybe my setting data for libjpeg decoder is incorrect.. Please ask questions about my code, I'll try to make some explanations for you. I explodes my brain.. Thank you.
I have a simple program that reads data from a PNG into a 2D array. I would like to save that data to a .RAW file so that Raw Studio or Irfanview can view the raw image that my program outputs to my_out.raw. Currently if I just write the raw binary data to the my_out.raw file, neither application can actually read the file, that is view the image. What do I need to do to the program below so that I can see the image?
The code to read the PNG files is:
// MAIN.cpp
#include "pngfilereader.h"
#include <string>
#include <vector>
#include <fstream>
int main (int argc, char *argv[])
{
PNGFileReader pngfr;
if (!pngfr.decompress_png_to_raw(std::string("/home/matt6809/Downloads"
"/City.png"))) {
std::cout << "File decompression error: " << std::endl;
} else {
std::ofstream out;
out.open("./my_out.raw", std::ios_base::out);
std::vector<std::vector<unsigned char> > data;
pngfr.get_image_data(data);
typedef std::vector<std::vector<unsigned char> >::iterator row_it;
typedef std::vector<unsigned char>::iterator col_it;
for(row_it rit= data.begin(); rit != data.end(); ++rit) {
for(col_it cit = rit->begin(); cit != rit->end(); ++cit) {
out << (*cit);
}
}
out << std::endl;
}
return 0;
}
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <png.h>
#include <iostream>
#include <vector>
#include <string>
class PNGFileReader
{
public:
PNGFileReader();
~PNGFileReader();
// Public exposed API:
bool compress_raw_to_png(uint8_t data, int size);
bool decompress_png_to_raw(const std::string &path);
// Getters
long unsigned int get_image_width();
long unsigned int get_image_height();
void get_image_data(std::vector<std::vector<unsigned char> > &data);
private:
// Helper functions:
bool read_png(const std::string &path);
bool create_png_structs(FILE *fp);
bool free_data();
bool alloc_data();
// Member variables:
png_structp m_pPNG;
png_infop m_pPNGInfo;
png_infop m_pPNGEndInfo;
png_bytepp m_Data;
long unsigned int m_ImageWidth;
long unsigned int m_ImageHeight;
// Enums
enum PNGBOOL {NOT_PNG, PNG};
enum PNGERRORS {ERROR, SUCCESS};
};
#include "pngfilereader.h"
#include <stdexcept>
PNGFileReader::PNGFileReader() :
m_pPNG(NULL),
m_pPNGInfo(NULL),
m_pPNGEndInfo(NULL),
m_Data(NULL),
m_ImageWidth(0),
m_ImageHeight(0)
{
}
PNGFileReader::~PNGFileReader()
{
for (unsigned long int i = 0; i < m_ImageHeight; ++i) {
if (m_Data[i]) {
delete m_Data[i];
m_Data[i] = NULL;
}
}
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
}
// Public Exposed API
bool PNGFileReader::compress_raw_to_png(uint8_t m_Data, int size)
{
return PNGFileReader::SUCCESS;
}
bool PNGFileReader::decompress_png_to_raw(const std::string &path)
{
return read_png(path);
}
// Getters
long unsigned int PNGFileReader::get_image_width()
{
return m_ImageWidth;
}
long unsigned int PNGFileReader::get_image_height()
{
return m_ImageHeight;
}
void PNGFileReader::get_image_data(
std::vector<std::vector<unsigned char> > &data)
{
for (unsigned long int i = 0; i < m_ImageHeight; ++i) {
std::vector<unsigned char> v;
data.push_back(v);
for (unsigned long int j = 0; j < m_ImageWidth; ++j) {
std::vector<unsigned char> *vp = &data[i];
vp->push_back(m_Data[i][j]);
}
}
}
// Private Methods
bool PNGFileReader::read_png(const std::string &path)
{
/*
* Open up the file to read (path) in binary mode
* first so that if anything goes wrong with libpng
* we won't have much to undo
*/
const char *c_path = path.c_str();
FILE *fp = fopen(c_path, "rb");
if (!fp)
return PNGFileReader::ERROR;
/*
* Read the first BYTES_TO_READ bytes from file
* then determine if it is a png file or
* not. If png_sig_cmp == 0 all is okay
*/
enum {BYTES_TO_READ = 8};
unsigned char sig[BYTES_TO_READ];
if (!fread(sig, 1, BYTES_TO_READ, fp)) {
fclose(fp);
return PNGFileReader::ERROR;
}
bool is_png = !png_sig_cmp(sig, 0, BYTES_TO_READ);
if (!is_png) {
fclose(fp);
return PNGFileReader::ERROR;
}
if (!this->create_png_structs(fp)) {
fclose(fp);
return PNGFileReader::ERROR;
}
/*
* For error handling purposes. Set a long pointer
* back to this function to handle all error related
* to file IO
*/
if (setjmp(png_jmpbuf(m_pPNG)))
{
png_destroy_read_struct(&m_pPNG, &m_pPNGInfo, &m_pPNGEndInfo);
fclose(fp);
return PNGFileReader::ERROR;
}
/*
* Set up the input code for FILE openend in binary mode,
* and tell libpng we have already read BYTES_TO_READ btyes from
* signature
*/
png_init_io(m_pPNG, fp);
png_set_sig_bytes(m_pPNG, BYTES_TO_READ);
/*
* Using the lowlevel interface to lib png ...
*/
png_read_info(m_pPNG, m_pPNGInfo);
m_ImageHeight = png_get_image_height(m_pPNG, m_pPNGInfo);
m_ImageWidth = png_get_rowbytes(m_pPNG, m_pPNGInfo);
this->alloc_data();
png_read_image(m_pPNG, m_Data);
png_read_end(m_pPNG, NULL);
png_destroy_read_struct(&m_pPNG, &m_pPNGInfo, &m_pPNGEndInfo);
fclose(fp);
return PNGFileReader::SUCCESS;
}
bool PNGFileReader::create_png_structs(FILE *fp)
{
/*
* Create the pointer to main libpng struct, as well as
* two info structs to maintain information after, and
* prior to all operations on png m_Data. Only necessary
* to release resource after function succeeds.
*/
m_pPNG = png_create_read_struct(PNG_LIBPNG_VER_STRING, (png_voidp)NULL,
NULL, NULL);
if (!m_pPNG)
{
fclose(fp);
return PNGFileReader::ERROR;
}
m_pPNGInfo = png_create_info_struct(m_pPNG);
if (!m_pPNGInfo)
{
png_destroy_read_struct(&m_pPNG, (png_infopp)NULL,(png_infopp)NULL);
fclose(fp);
return PNGFileReader::ERROR;
}
m_pPNGEndInfo = png_create_info_struct(m_pPNG);
if (!m_pPNGEndInfo)
{
png_destroy_read_struct(&m_pPNG, &m_pPNGInfo, (png_infopp)NULL);
fclose(fp);
return PNGFileReader::ERROR;
}
return PNGFileReader::SUCCESS;
}
bool PNGFileReader::free_data()
{
if (m_ImageHeight == 0 || m_ImageWidth == 0)
return PNGFileReader::ERROR;
for (unsigned long int i = 0; i < m_ImageHeight; ++i) {
if (m_Data[i]) {
delete m_Data[i];
m_Data[i] = NULL;
}
}
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
return PNGFileReader::SUCCESS;
}
bool PNGFileReader::alloc_data()
{
if (m_ImageHeight == 0 || m_ImageWidth == 0)
return PNGFileReader::ERROR;
if (m_Data != NULL)
this->free_data();
m_Data = new png_bytep[m_ImageHeight]();
for (unsigned long int i = 0; i < m_ImageHeight; ++i) {
m_Data[i] = NULL;
}
try {
for (unsigned long int i = 0; i < m_ImageHeight; ++i) {
m_Data[i] = new png_byte[m_ImageWidth];
}
}
catch (std::bad_alloc e) {
for (unsigned long int i = 0; i < m_ImageHeight; ++i) {
if (m_Data[i]) {
delete m_Data[i];
m_Data[i] = NULL;
}
}
if (m_Data) {
delete m_Data;
m_Data = NULL;
}
throw e;
}
return PNGFileReader::SUCCESS;
}
A "raw" file that is intended to be used with a camera-image processing program like Raw Studio and Irfraview is not a raw-binary dump of the image-data with no header. Instead the "raw" moniker refers to the fact that the image has a minimal amount of image-processing applied in-camera. For instance, the image-data may still be a single-channel monochrome image from the camera's bayer-pattern CFA, or no white-balance, color-matrix, etc. has been applied, etc. Either way, the image-data is still formatted in a standard binary image file format complete with a header, data-packing method, etc. Examples include formats such as Adobe's DNG file format (which is based on TIFF), or proprietary formats from camera manufacturer's themselves such as Canon's CR2, Nikon's NEF, etc.
So if you want these raw-file processing programs to read your "raw" file image data, you'll have to read-up on the binary data specifications the raw-file formats they support, and then re-format the original PNG image-data correctly.
For the life of me I can't figure out why this is not working. Basically, I created the pipe with the inherit bit set to true, and created two child processes, and used the STARTUPINFO structure to set the input and output handles as one would need, but the pipe seems broken (the second process writes no output to the console, even though output is expected)
I know the problem does not lie in my test program (BitTwiddler.exe) because I performed the same operation using CMD.exe, and everything works as expected.
Below is a minimal reproduction of what I have. What have I done incorrectly?
#include "windows.h"
int main()
{
PROCESS_INFORMATION piSource, piDest;
HANDLE hPipeIn, hPipeOut;
HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
STARTUPINFOW suSource, suDest;
ZeroMemory(&suSource, sizeof(suSource));
ZeroMemory(&suDest, sizeof(suDest));
suSource.cb = suDest.cb = sizeof(STARTUPINFOW);
suSource.dwFlags = suDest.dwFlags = STARTF_USESTDHANDLES;
SECURITY_ATTRIBUTES sa;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
if (CreatePipe(&hPipeIn, &hPipeOut, &sa, 0) == 0)
{
return GetLastError();
}
suSource.hStdInput = hIn;
suSource.hStdError = suSource.hStdOutput = hPipeIn;
suDest.hStdInput = hPipeOut;
suDest.hStdError = suDest.hStdOutput = hOut;
std::wstring cmdLineA(L"BitTwiddler 1"), cmdLineB(L"BitTwiddler 0");
cmdLineA.push_back(0); cmdLineB.push_back(0);
if (CreateProcessW(0, &cmdLineA[0], 0, 0, TRUE, 0, 0, 0, &suSource, &piSource) == 0)
{
return GetLastError();
}
CloseHandle(piSource.hThread);
if (CreateProcessW(0, &cmdLineB[0], 0, 0, TRUE, 0, 0, 0, &suDest, &piDest) == 0)
{
return GetLastError();
}
CloseHandle(piDest.hThread);
HANDLE hArray[2];
hArray[0] = piSource.hProcess;
hArray[1] = piDest.hProcess;
WaitForMultipleObjects(2, hArray, TRUE, INFINITE);
CloseHandle(hArray[0]);
CloseHandle(hArray[1]);
return 0;
}
(In case anyone's interested, BitTwiddler is:
#include <windows.h>
#include <sstream>
#include <iostream>
#include <string>
int main(int argc, char *argv[])
{
std::size_t opt = 0;
argc--; argv++;
if (argc == 0)
{
return 0;
}
else
{
std::istringstream converter(*argv);
converter >> opt;
}
switch(opt)
{
case 0:
{
std::wstring currentLine;
while(std::getline(std::wcin, currentLine))
{
std::wcout << "Got somepin: " << currentLine << std::endl;
}
}
break;
case 1:
for (;;)
{
std::wcout << L"Hello World!" << std::endl;
Sleep(1000);
}
break;
case 2:
return -1;
default:
std::wcout << "Unknown option.";
return 0;
}
return 0;
}
), but I really don't think that matters.
You misplaced the read and write ends :)
CreatePipe has the prototype
BOOL CreatePipe(
PHANDLE hReadPipe, // can only read from this
PHANDLE hWritePipe, // can only write to this
LPSECURITY_ATTRIBUTES lpPipeAttributes,
DWORD nSize
);
You can't call ReadFile (or in your case std::getline) from a Write-only handle, and vice versa. If you replaced your std::getline calls with a simple ReadFile call, you'd get an ACCESS_DENIED error, confirming this fact, since your STD_INPUT_HANDLE in the child process was not opened for GENERIC_READ.
The fix is as follows:
suSource.hStdError = suSource.hStdOutput = hPipeOut; // must be the write pipe!
suDest.hStdInput = hPipeIn; // must be the read pipe.
Perhaps the names you assigned are confusing. If you called them as per the formal parameters, the error would be clearer:
suSource.hStdError = suSource.hStdOutput = hReadPipe; // clearly wrong.
suDest.hStdInput = hWritePipe; // as above -- expects a read-handle.
I upvoted your question as it is a very important one. Since I learnt Windows programming from FamTrinli's demos years ago, CreateProcess has been one of the harddest functions of the Win32 API. If not, the harddest.
CreateProcess has alot of arguments. Each argument requires careful reading of the documentation to properly use. Alot of arguments are complicated structures; such as SECURITY_ATTRIBUTES and STARTUPINFO. The names for the HANDLE(s) in STARTUPINFO structure can be confusing. As you were confused. The steps that are involved in using CreateProcess. whether or not you need to immediately close the handles(nb: you don't). And the actual communication between the processes can cause deadlocks.
After using CreateProcess for years I did an extensive research and testing of the API. I then designed a library that encapsulates all that knowledge. This library is apart of my Jav/win32 library. This library is similar to Java's Process class.
I am not able to post every last bit of source code here. If you are interested in the source code I can email it to you. Keeping Cpp a goto for programming.
Jav/win32/Process.h
#ifndef JAV_WIN32_PROCESS_HPP
#define JAV_WIN32_PROCESS_HPP
#include <Jav/File.h>
#include <Jav/string/cstring.h>
#include <Jav/error/error.h>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/container/flat_map.hpp>
#include <sstream>
#include <Jav/win32/win32.h>
namespace Jav { namespace win32 {
/** class Process makes handling win32 CreateProcess a bit easier.
Parent Process refers to the process that is calling CreateProcess.
Child Process refers to the process being created.
Note: CreateProcess uses the parent process's CurrentDirectory and
EnvironmentVariable when deducing relative filepaths used in AppName and CommandLine String.
Thus I have no idea the purpose of passing a current directory and environment to the CreateProcess.
Note: Rather than using Process::setCurrentDir and Process::addEnv and Process::addParentEnv; which
seems to make no sense at all as CreateProcess doesn't use these settings to deduce relative filepaths
passed to CreateProcess; instead use Process::addPathArg to pass absolute filepaths on the commandline.
Note: Using Process::addEnv or Process::addParentEnv will temporarily adjust parent process Environment,
and pass the adjusted Environment to the child process as well. This allows you to set Path variable
so that CreateProcess deduces relative paths. However using Process::addPath is probably better.
Note: Asynchronous read and write functions are not implemented.
Probably synchronous read and writes are good enough.
To properly read and write the child process, be sure to know when it makes read and write request.
If you sync read a process that never writes to stdout or stderror your program will deadlock.
If you write to a process that didn't read stdinput that makes no sense. You are just filling up the buffer.
Also ensure to read out data written by child process so that the buffer is not clogged up causing deadlock.
*/
class ProcessA
{
public:
enum { INVALID_EXIT_CODE = -1 };
enum { AUTO_KILL=1, WAIT=2, DEBUG=4, ADD_PARENT_ENV=8 };
struct Builder
{
struct ICompare { bool operator()(const std::string &l,const std::string &r)const; };
using EnvList = boost::container::flat_map<std::string,std::ostringstream,ICompare>;
std::ostringstream cmd;
EnvList env_list;
Jav::cstring app_name;
Jav::cstring current_dir;
STARTUPINFOA si = {sizeof(STARTUPINFOA)};
};
public:
ProcessA(const char *cmd=NULL,const char *current_dir=NULL,const char *app_name=NULL);
~ProcessA();
public:
bool launch();
int stop();
int wait(uint time=INFINITE);
void pause();
void resume();
bool isOpen();
bool isValid() { return info.hProcess; }
size_t getMsgSize();
size_t getErrorMsgSize();
size_t read(void*,size_t);
size_t readError(void*,size_t);
size_t write(const void*,size_t);
size_t write(const char*);
bool read_async(void*,size_t);
bool readError_async(void*,size_t);
bool write_async(const void*,size_t);
bool write_async(const char*);
/** Set up process for creation */
void setAppName(const char*);
void setCurrentDir(const char*);
void addArg(const char*);
void addQuotedArg(const char*);
void addPathArg(const char *parent_dir,const char *child_name);
void addEnv(const char *var,const char *val);
bool addParentEnv();
template<class ...ARGS>
void addArgEx(const char*,ARGS ...rest);
void addArgEx(const char*);
void setConsoleTitle(const char*);
void setConsoleWidth(int);
void setConsoleHeight(int);
void setConsoleTextAndFillColor(int);
void setWindowVisibility(int); //SW_SHOW, SW_HIDE, etc
void setWindowXpos(int);
void setWindowYpos(int);
void setWindowWidth(int);
void setWindowHeight(int);
void setWaitTime(uint);
void setParentMode(uint);
void addParentMode(uint);
void removeParentMode(uint);
Jav::cstring toString();
Jav::cstring getError() { return error; }
private:
Jav::cstring buildEnvironment();
Jav::cstring getCombinedEnvVar(const std::string &name);
private:
PROCESS_INFORMATION info = {};
Jav::rFile m_read_end;
Jav::rFile m_error_end;
Jav::wFile m_write_end;
OVERLAPPED async_struct;
uint flags = 0;
uint waitTime = INFINITE;
Builder *builder;
Jav::Error error = "";
};
Jav::cstring getEnvVarA(const char *name);
Jav::cstring FindFileExtExeA(const char *ext);
Jav::cstring FindFileNameExeA(const char *fname);
void setEnvironmentStringsA(const char *env);
class ProcessW
{
public:
enum { INVALID_EXIT_CODE = -1 };
enum { AUTO_KILL=1, WAIT=2, DEBUG=4, ADD_PARENT_ENV=8 };
struct Builder
{
struct ICompare { bool operator()(const std::wstring &l,const std::wstring &r)const; };
using EnvList = boost::container::flat_map<std::wstring,std::wostringstream,ICompare>;
std::wostringstream cmd;
EnvList env_list;
Jav::cstringw app_name;
Jav::cstringw current_dir;
STARTUPINFOW si = {sizeof(STARTUPINFOW)};
};
public:
ProcessW(const wchar_t *cmd=NULL,const wchar_t *current_dir=NULL,const wchar_t *app_name=NULL);
~ProcessW();
public:
bool launch();
int stop();
int wait(uint time=INFINITE);
void pause();
void resume();
bool isOpen();
bool isValid() { return info.hProcess; }
size_t getMsgSize();
size_t getErrorMsgSize();
size_t read(void*,size_t);
size_t readError(void*,size_t);
size_t write(const void*,size_t);
size_t write(const wchar_t*);
size_t write(const char*);
size_t read_async(void*,size_t);
size_t readError_async(void*,size_t);
size_t write_async(const void*,size_t);
size_t write_async(const wchar_t*);
size_t write_async(const char*);
void setAppName(const wchar_t*);
void setCurrentDir(const wchar_t*);
void addArg(const wchar_t*);
void addQuotedArg(const wchar_t*);
void addPathArg(const wchar_t *parent_dir,const wchar_t *child_name);
void addEnv(const wchar_t *var,const wchar_t *val);
bool addParentEnv();
void setAppName(const char*);
void setCurrentDir(const char*);
void addArg(const char*);
void addQuotedArg(const char*);
void addPathArg(const char *parent_dir,const char *child_name);
void addEnv(const char *var,const char *val);
template<class ...ARGS>
void addArgEx(const wchar_t*,ARGS ...rest);
template<class ...ARGS>
void addArgEx(const char*,ARGS ...rest);
void addArgEx(const wchar_t*);
void addArgEx(const char*);
void setConsoleTitle(const wchar_t*);
void setConsoleTitle(const char*);
void setConsoleWidth(int);
void setConsoleHeight(int);
void setConsoleTextAndFillColor(int);
void setWindowVisibility(int);
void setWindowXpos(int);
void setWindowYpos(int);
void setWindowWidth(int);
void setWindowHeight(int);
void setWaitTime(uint);
void setParentMode(uint);
void addParentMode(uint);
void removeParentMode(uint);
Jav::cstringw toString();
Jav::cstring getError() { return error; }
private:
Jav::cstringw buildEnvironment();
Jav::cstringw getCombinedEnvVar(const std::wstring &name);\
Jav::cstringw towstring(const char *s);
private:
PROCESS_INFORMATION info = {};
Jav::rFile m_read_end;
Jav::rFile m_error_end;
Jav::wFile m_write_end;
OVERLAPPED async_struct;
uint flags = 0;
uint waitTime = INFINITE;
Builder *builder;
Jav::Error error = "";
};
Jav::cstringw getEnvVarW(const wchar_t *name);
Jav::cstringw findFileExtExeW(const wchar_t *ext);
Jav::cstringw findFileNameExeW(const wchar_t *fname);
void setEnvironmentStringsW(const wchar_t *env);
///________________///___________
template<class ...ARGS>
void ProcessA::addArgEx(const char *first,ARGS ...rest)
{
if(builder->cmd.tellp()) builder->cmd << ' ';
builder->cmd << first;
addArgEx(rest...);
}
inline void ProcessA::addArgEx(const char *s)
{
builder->cmd << ' ' << s;
}
template<class ...ARGS>
void ProcessW::addArgEx(const wchar_t *first,ARGS ...rest)
{
if(builder->cmd.tellp()) builder->cmd << ' ';
builder->cmd << first;
addArgEx(rest...);
}
template<class ...ARGS>
void ProcessW::addArgEx(const char *first,ARGS ...rest)
{
if(builder->cmd.tellp()) builder->cmd << ' ';
builder->cmd << towstring(first);
addArgEx(rest...);
}
inline void ProcessW::addArgEx(const wchar_t *s)
{
builder->cmd << ' ' << s;
}
inline void ProcessW::addArgEx(const char *s)
{
builder->cmd << ' ' << towstring(s);
}
}}
#endif // JAV_WIN32_PROCESS_HPP
src/Jav/win32/Process.cpp
#include <Jav/error/error.h>
#include <Jav/string.h>
#include <Jav/win32/Process.h>
#include <Jav/win32/debug.h>
#include <Jav/win32/registry.h>
#include <shlwapi.h>
namespace Jav { namespace win32 {
ProcessA::ProcessA(const char *cmd,const char *current_dir,const char *app_name)
: builder(new Builder())
{
if(cmd) builder->cmd << cmd;
builder->app_name = app_name;
builder->current_dir = current_dir;
}
ProcessA::~ProcessA()
{
stop();
delete builder;
}
bool ProcessA::launch()
{
Jav::rFile child_read_end;
Jav::wFile child_error_end;
Jav::wFile child_write_end;
if(!Jav::createPipe(m_read_end,child_write_end)) throw InternalError("InternalError: Failed to create pipe\n");
//Jav::createPipe(m_error_end,child_error_end);
if(!Jav::createPipe(child_read_end,m_write_end)) throw InternalError("InternalError: Failed to create pipe\n");
builder->si.dwFlags |= STARTF_USESTDHANDLES;
builder->si.hStdInput = (HANDLE)child_read_end;
builder->si.hStdOutput = (HANDLE)child_write_end;
builder->si.hStdError = (HANDLE)child_write_end;
auto env = buildEnvironment();
auto cmd = builder->cmd.str();
if(flags & ADD_PARENT_ENV)
{
auto parent_env = GetEnvironmentStringsA();
for(auto &elem : builder->env_list)
{
auto val = getCombinedEnvVar(elem.first);
SetEnvironmentVariableA(elem.first.c_str(),val);
}
auto child_env = GetEnvironmentStringsA();
if( !CreateProcessA(builder->app_name,&cmd[0],NULL,NULL,true,0,child_env,builder->current_dir,&builder->si,&info) )
{
error = Jav::DefferedError("Error: Failed to create process\nCause: %s",Jav::win32::errorString().str());
return false;
}
setEnvironmentStringsA(parent_env);
FreeEnvironmentStringsA(child_env);
FreeEnvironmentStringsA(parent_env);
}
else
{
if( !CreateProcessA(builder->app_name,&cmd[0],NULL,NULL,true,0,env,builder->current_dir,&builder->si,&info) )
{
error = Jav::DefferedError("Error: Failed to create process\nCause: %s",Jav::win32::errorString().str());
return false;
}
}
if( !(flags & DEBUG) )
{
delete builder;
builder = NULL;
}
/*
if( WaitForInputIdle(info.hProcess,INFINITE) == WAIT_FAILED)
{
error = Jav::DefferedError("Error: Failed to wait for input idle\n");
return false;
}
if( WaitForSingleObject(info.hProcess,INFINITE) == WAIT_FAILED)
{
error = Jav::InternalError("InternalError: Failed to wait for object\n");
return false;
}
*/
return true;
}
int ProcessA::wait(uint time)
{
DWORD exitCode = INVALID_EXIT_CODE;
if(!info.hProcess) return exitCode;
WaitForSingleObject(info.hProcess,waitTime);
if(!GetExitCodeProcess(info.hProcess,&exitCode)) error = Jav::InternalError("InternalError: %s: Failed to get exit code\n",__func__);
if(flags & AUTO_KILL && exitCode == STILL_ACTIVE) TerminateProcess(info.hProcess,1);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
info = {};
return exitCode;
}
int ProcessA::stop()
{
DWORD exitCode = INVALID_EXIT_CODE;
if(!info.hProcess) return exitCode;
if(flags & WAIT) WaitForSingleObject(info.hProcess,waitTime);
if(!GetExitCodeProcess(info.hProcess,&exitCode)) error = Jav::InternalError("InternalError: %s: Failed to get exit code\n",__func__);
if(flags & AUTO_KILL && exitCode == STILL_ACTIVE) TerminateProcess(info.hProcess,1);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
info = {};
return exitCode;
}
void ProcessA::pause()
{
SuspendThread(info.hThread);
}
void ProcessA::resume()
{
ResumeThread(info.hThread);
}
bool ProcessA::isOpen()
{
if(!info.hProcess) { return false; }
/*
DWORD exitCode;
if(!GetExitCodeProcess(info.hProcess,&exitCode))
throw Jav::InternalError("InternalError: Failed to GetExitCodeProcess\n");
return exitCode == STILL_ACTIVE;
*/
switch(WaitForSingleObject(info.hProcess,0))
{
case WAIT_TIMEOUT: return true;
case WAIT_FAILED: throw Jav::Error("Error: Failed to wait for object\n");
case WAIT_ABANDONED: throw Jav::Error("Error: wait abondoned\n");
case WAIT_OBJECT_0: rep("WAIT_OBJECT_0"); return false;
default: throw Jav::Error("Error: Invalid Error Code, %s\n",__func__);
}
}
size_t ProcessA::getMsgSize()
{
DWORD avail;
if(!PeekNamedPipe(m_read_end,NULL,0,NULL,&avail,NULL))
{
error = Jav::Error("Error: Failed to peekNamedPipe\nCause: %s\n",errorString().str());
return 0;
}
return avail;
}
size_t ProcessA::getErrorMsgSize()
{
DWORD avail;
if(!PeekNamedPipe(m_error_end,NULL,0,NULL,&avail,NULL))
{
error = Jav::Error("Error: Failed to peekNamedPipe\nCause: %s\n",errorString().str());
return 0;
}
return avail;
}
size_t ProcessA::read(void *buf,size_t sz)
{
return m_read_end.read(buf,sz);
}
size_t ProcessA::readError(void *buf,size_t sz)
{
return m_error_end.read(buf,sz);
}
size_t ProcessA::write(const void *data,size_t sz)
{
return m_write_end.write(data,sz);
}
size_t ProcessA::write(const char *data)
{
return m_write_end.write(data);
}
void ProcessA::setAppName(const char *name)
{
builder->app_name = name;
}
void ProcessA::setCurrentDir(const char *dir)
{
builder->current_dir = dir;
}
void ProcessA::addArg(const char *arg)
{
if(builder->cmd.tellp()) builder->cmd << ' ';
builder->cmd << arg;
}
void ProcessA::addQuotedArg(const char *arg)
{
if(builder->cmd.tellp()) builder->cmd << ' ';
builder->cmd << '\"' << arg << '\"';
}
void ProcessA::addPathArg(const char *parent_dir,const char *child_name)
{
if(builder->cmd.tellp()) builder->cmd << ' ';
builder->cmd << '\"'<< parent_dir << '\\' << child_name << '\"';
}
void ProcessA::addEnv(const char *var,const char *val)
{
auto &stream = builder->env_list[var];
llong pos = stream.tellp();
pos == 0 ? stream << val : stream << ';' << val;
}
/*
void ProcessA::addEnv(const char *var,const char *val)
{
auto &stream = builder->env_list[var];
llong pos = stream.tellp();
if(pos == 0) stream << '\"' << val << '\"';
else
{
stream.seekp(pos-1);
stream << ';' << val << '\"';
}
}
*/
bool ProcessA::addParentEnv()
{
flags |= ADD_PARENT_ENV;
}
/*
bool ProcessA::addParentEnv()
{
LPCH parent_env = GetEnvironmentStringsA();
if(parent_env == NULL)
{
error = Jav::Error("Error: GetEnvironmentStrings failed\n");
FreeEnvironmentStringsA(parent_env);
return false;
}
for(auto it = Jav::MultiStringIterator(parent_env); it.has(); it.next())
{
auto var_end = Jav::nextPosOf('=',it.get()).it;
if(!var_end)
{
error = Jav::Error("Error: Invalid EnvironmentString\n");
FreeEnvironmentStringsA(parent_env);
return false;
}
Jav::cstring var(it.get(),var_end++);
addEnv(var,var_end);
}
FreeEnvironmentStringsA(parent_env);
return true;
}
*/
void ProcessA::setConsoleTitle(const char *title)
{
builder->si.lpTitle = (char*)title;
}
void ProcessA::setConsoleWidth(int w)
{
builder->si.dwFlags |= STARTF_USECOUNTCHARS;
builder->si.dwXCountChars = w;
}
void ProcessA::setConsoleHeight(int h)
{
builder->si.dwFlags |= STARTF_USECOUNTCHARS;
builder->si.dwYCountChars = h;
}
void ProcessA::setConsoleTextAndFillColor(int color)
{
builder->si.dwFlags |= STARTF_USEFILLATTRIBUTE;
builder->si.dwFillAttribute = color;
}
void ProcessA::setWindowVisibility(int visibility)
{
builder->si.dwFlags |= STARTF_USESHOWWINDOW;
builder->si.wShowWindow = visibility;
}
void ProcessA::setWindowXpos(int x)
{
builder->si.dwFlags |= STARTF_USEPOSITION;
builder->si.dwX = x;
}
void ProcessA::setWindowYpos(int y)
{
builder->si.dwFlags |= STARTF_USEPOSITION;
builder->si.dwY = y;
}
void ProcessA::setWindowWidth(int w)
{
builder->si.dwFlags |= STARTF_USESIZE;
builder->si.dwXSize = w;
}
void ProcessA::setWindowHeight(int h)
{
builder->si.dwFlags |= STARTF_USESIZE;
builder->si.dwYSize = h;
}
void ProcessA::setWaitTime(uint time)
{
waitTime = time;
}
void ProcessA::setParentMode(uint mode)
{
flags = mode;
}
void ProcessA::addParentMode(uint mode)
{
Jav::bitOn(flags,mode);
}
void ProcessA::removeParentMode(uint mode)
{
Jav::bitOff(flags,mode);
}
Jav::cstring ProcessA::toString()
{
if(!builder) return "NOTHING TO DEBUG";
std::ostringstream s;
auto env = buildEnvironment();
s << "AppName: " << builder->app_name << '\n';
if(env) s << "Env: " << Jav::MultiString{env} << '\n';
else s << "Env: " << "NO ENVIRONMENT" << '\n';
s << "CurrentDir: " << builder->current_dir << '\n';
s << "cmd: " << builder->cmd.str() << '\n';
return s.str().c_str();
}
Jav::cstring ProcessA::buildEnvironment()
{
if(!builder->env_list.size()) return NULL;
std::string env;
for(auto &elem : builder->env_list)
{
env += elem.first + '=' + elem.second.str() + '\0';
}
env += '\0';
return Jav::cstring(&env[0],&env[env.size()]);
}
Jav::cstring ProcessA::getCombinedEnvVar(const std::string &name)
{
Jav::cstring parent_val;
std::string child_val;
auto elem = builder->env_list.find(name);
if(elem != builder->env_list.end()) child_val = elem->second.str();
SetLastError(0);
auto sz = GetEnvironmentVariableA(name.c_str(),NULL,0);
if(sz == 0)
{
parent_val = GetLastError() ? NULL : "";
return child_val.empty() ? parent_val : Jav::cstring(child_val.c_str());
}
GetEnvironmentVariableA(name.c_str(),parent_val,sz);
if(child_val.empty()) return parent_val;
Jav::cstring val( strlen(parent_val + child_val.size() + 2) );
sprintf(val,"%s;%s",parent_val.str(),child_val.c_str());
return val;
}
bool ProcessA::Builder::ICompare::operator()(const std::string &l,const std::string &r)const
{
return boost::ilexicographical_compare<std::string,std::string>(l,r);
}
Jav::cstring getEnvVarA(const char *name)
{
SetLastError(0);
auto sz = GetEnvironmentVariableA(name,NULL,0);
if(sz == 0) return GetLastError() ? NULL : "";
Jav::cstring val(sz);
GetEnvironmentVariableA(name,val,sz);
return val;
}
Jav::cstring FindFileNameExe(const char *fname)
{
Jav::cstring exe_name(MAX_PATH);
auto e = (int)FindExecutableA(fname,NULL,exe_name);
if(e <= 32) throw Jav::Error("Error: unable to find association\n");
return exe_name;
}
/*
Jav::cstring FindFileExtExe(const char *ext)
{
Jav::cstring exe_name;
Jav::win32::RegistryKeyReader key(".cpp",HKEY_CLASSES_ROOT);
if(!key || !key.getString(NULL,exe_name))
throw Jav::DefferedError("Error: No program Associated with file");
return exe_name;
}
*/
/* Note uses AssocQueryString which doesn't seem to be compatible on winxp*/
Jav::cstring FindFileExtExe(const char *ext)
{
DWORD sz=0;
auto hr = AssocQueryStringA(ASSOCF_INIT_IGNOREUNKNOWN,ASSOCSTR_EXECUTABLE,ext,NULL,NULL,&sz);
if( hr != S_FALSE)
throw Jav::InternalError("Error Failed to obtain extension association name length\n");
Jav::cstring exe_name(sz);
hr = AssocQueryStringA(ASSOCF_INIT_IGNOREUNKNOWN,ASSOCSTR_EXECUTABLE,ext,NULL,exe_name,&sz);
if( hr != S_OK)
throw Jav::InternalError("Error Failed to obtain extension association\n");
return exe_name;
}
void setEnvironmentStringsA(const char *env)
{
if(env == NULL) throw Jav::Error("Error: environment string is null\n");
for(auto it = Jav::MultiStringIterator(env); it.has(); )
{
auto begin = it.get();
auto end = it.next('\0');
auto var_end = Jav::nextPosOf('=',{begin,end});
if(!var_end) throw Jav::Error("Error: Invalid EnvironmentString\n");
Jav::cstring var(begin,var_end.it++);
SetEnvironmentVariableA(var,var_end);
}
}
ProcessW::ProcessW(const wchar_t *cmd,const wchar_t *current_dir,const wchar_t *app_name)
: builder(new Builder())
{
if(cmd) builder->cmd << cmd;
builder->app_name = app_name;
builder->current_dir = current_dir;
}
ProcessW::~ProcessW()
{
stop();
delete builder;
}
bool ProcessW::launch()
{
Jav::rFile child_read_end;
Jav::wFile child_error_end;
Jav::wFile child_write_end;
if(!Jav::createPipe(m_read_end,child_write_end)) throw InternalError("InternalError: Failed to create pipe\n");
//Jav::createPipe(m_error_end,child_error_end);
if(!Jav::createPipe(child_read_end,m_write_end)) throw InternalError("InternalError: Failed to create pipe\n");
builder->si.dwFlags |= STARTF_USESTDHANDLES;
builder->si.hStdInput = (HANDLE)child_read_end;
builder->si.hStdOutput = (HANDLE)child_write_end;
builder->si.hStdError = (HANDLE)child_write_end;
auto env = buildEnvironment();
auto cmd = builder->cmd.str();
if(flags & ADD_PARENT_ENV)
{
auto parent_env = GetEnvironmentStringsW();
for(auto &elem : builder->env_list)
{
auto val = getCombinedEnvVar(elem.first);
SetEnvironmentVariableW(elem.first.c_str(),val);
}
auto child_env = GetEnvironmentStringsW();
if( !CreateProcessW(builder->app_name,&cmd[0],NULL,NULL,false,0,child_env,builder->current_dir,&builder->si,&info) )
{
error = Jav::DefferedError("Error: Failed to create process\nCause: %s",Jav::win32::errorString().str());
return false;
}
setEnvironmentStringsW(parent_env);
FreeEnvironmentStringsW(child_env);
FreeEnvironmentStringsW(parent_env);
}
else
{
if( !CreateProcessW(builder->app_name,&cmd[0],NULL,NULL,false,0,env,builder->current_dir,&builder->si,&info) )
{
error = Jav::DefferedError("Error: Failed to create process\nCause: %s",Jav::win32::errorString().str());
return false;
}
}
if( !(flags & DEBUG) )
{
delete builder;
builder = NULL;
}
return true;
}
int ProcessW::wait(uint time)
{
DWORD exitCode = INVALID_EXIT_CODE;
if(!info.hProcess) return exitCode;
WaitForSingleObject(info.hProcess,waitTime);
if(!GetExitCodeProcess(info.hProcess,&exitCode)) error = Jav::InternalError("InternalError: %s: Failed to get exit code\n",__func__);
if(flags & AUTO_KILL && exitCode == STILL_ACTIVE) TerminateProcess(info.hProcess,1);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
info = {};
return exitCode;
}
int ProcessW::stop()
{
DWORD exitCode = INVALID_EXIT_CODE;
if(!info.hProcess) return exitCode;
if(flags & WAIT) WaitForSingleObject(info.hProcess,waitTime);
if(!GetExitCodeProcess(info.hProcess,&exitCode)) error = Jav::InternalError("InternalError: %s: Failed to get exit code\n",__func__);
if(flags & AUTO_KILL && exitCode == STILL_ACTIVE) TerminateProcess(info.hProcess,1);
CloseHandle(info.hThread);
CloseHandle(info.hProcess);
info = {};
return exitCode;
}
void ProcessW::pause()
{
SuspendThread(info.hThread);
}
void ProcessW::resume()
{
ResumeThread(info.hThread);
}
bool ProcessW::isOpen()
{
if(!info.hProcess) return false;
DWORD exitCode;
if(!GetExitCodeProcess(info.hProcess,&exitCode))
throw Jav::InternalError("InternalError: Failed to GetExitCodeProcess\n");
return exitCode == STILL_ACTIVE;
}
size_t ProcessW::getMsgSize()
{
DWORD avail;
if(!PeekNamedPipe(m_read_end,NULL,0,NULL,&avail,NULL))
{
error = Jav::Error("Error: Failed to peekNamedPipe\nCause: %s\n",errorString().str());
return 0;
}
return avail;
}
size_t ProcessW::getErrorMsgSize()
{
DWORD avail;
if(!PeekNamedPipe(m_error_end,NULL,0,NULL,&avail,NULL))
{
error = Jav::Error("Error: Failed to peekNamedPipe\nCause: %s\n",errorString().str());
return 0;
}
return avail;
}
size_t ProcessW::read(void *buf,size_t sz)
{
return m_read_end.read(buf,sz);
}
...
...
...
I have a 2884765579 bytes file. This is double checked with this function, that returns that number:
size_t GetSize() {
const size_t current_position = mFile.tellg();
mFile.seekg(0, std::ios::end);
const size_t ret = mFile.tellg();
mFile.seekg(current_position);
return ret;
}
I then do:
mFile.seekg(pos, std::ios::beg);
// pos = 2883426827, which is < than the file size, 2884765579
This sets the failbit. errno is not changed. What steps can I take to troubleshoot this?
I am absolutely sure that:
The file size is really 2884765579
pos is really 2884765579
The failbit is not set before .seekg()
The failbit is set right after .seekg() and no other calls are made in between
The file is opened with the binary flag
EDIT: in case someone runs into the same problem.. Use this code I wrote (works on windows only) and many less headaches for you:
class BinaryIFile
{
public:
BinaryIFile(const string& path) : mPath(path), mFileSize(0) {
mFile = open(path.c_str(), O_RDONLY | O_BINARY);
if (mFile == -1)
FATAL(format("Cannot open %s: %s") % path.c_str() % strerror(errno));
}
~BinaryIFile() {
if (mFile != -1)
close(mFile);
}
string GetPath() const { return mPath; }
int64 GetSize() {
if (mFileSize)
return mFileSize;
const int64 current_position = _telli64(mFile);
_lseeki64(mFile, 0, SEEK_END);
mFileSize = _telli64(mFile);
_lseeki64(mFile, current_position, SEEK_SET);
return mFileSize;
}
int64 Read64() { return _Read<int64>(); }
int32 Read32() { return _Read<int32>(); }
int16 Read16() { return _Read<int16>(); }
int8 Read8() { return _Read<int8>(); }
float ReadFloat() { return _Read<float>(); }
double ReadDouble() { return _Read<double>(); }
void Skip(int64 bytes) { _lseeki64(mFile, bytes, SEEK_CUR); }
void Seek(int64 pos) { _lseeki64(mFile, pos, SEEK_SET); }
int64 Tell() { return _telli64(mFile); }
template <class T>
T Read() { return _Read<T>(); }
void Read(char *to, size_t size) {
const int ret = read(mFile, (void *)to, size);
if ((int)size != ret)
FATAL(format("Read error: attempted to read %d bytes, read() returned %d, errno: %s [we are at offset %d, file size is %d]") % size % ret % strerror(errno) % Tell() % GetSize());
}
template <class T>
BinaryIFile& operator>>(T& val) { val = _Read<T>(); return *this; }
private:
const string mPath;
int mFile;
int64 mFileSize;
template <class T>
T _Read() { T ret; if (sizeof(ret) != read(mFile, (void *)&ret, sizeof(ret))) FATAL("Read error"); return ret; }
};
You can seekg before a given position, so pos is signed. Try it with files of size 0x7fffffff and 0x80ffffff and see if the latter triggers the problem, that's my guess.