Hi everyone
I am trying to communicate with a process constantly.
What i need:
I create the process, which is an external and non-UE4 based application
Through pipes i communicate with it constantly(as long as the game runs), which involves sending and receiving messages.
FMonitoredProcess can actually be used after adding necessery functionality such as WritePipe. But i dont think my computer can handle building the source code.
Than i decided to use FPlatformProcess but i couldn’t find a function to send message.
So i tried to write a class which further extends FPlatformProcess. I confronted many problems and since i can’t find enough information i feel like i am suffocating. I have learned many things from try and see.
Then when i was trying to write some code i saw this class with the help of auto-completion, FPlatformNamedPipe and the most painful thing is it doesnt even have a page at documentation
I didn’t know anything about inter-process communication before this. So using FMonitoredProcess as a start point, i tried to create mine. But there are some problems i can’t solve and some questions i have:
1-) The process is terminated about one second later. Happens when i attach WritePipe to process, no idea why.
2-) [DONE] How can i write through pipe so that the program will read it
2-b) Need to implement WriteToPipe for other platforms
3-) [NO IDEA] If i can use FPlatformNamedPipe with FPlatformProcess, how can i do that
This is what i have done till now
FInteractiveProcess header — Updated 2 times the header and source
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#pragma once
#include "Runtime/Core/Public/GenericPlatform/GenericPlatformProcess.h"
#include "Runtime/Core/Public/Misc/Timespan.h"
/**
* Declares a delegate that is executed when a interactive process completed.
*
* The first parameter is the process return code.
*/
DECLARE_DELEGATE_OneParam(FOnInteractiveProcessCompleted, int32)
/**
* Declares a delegate that is executed when a interactive process produces output.
*
* The first parameter is the produced output.
*/
DECLARE_DELEGATE_OneParam(FOnInteractiveProcessOutput, FString)
/**
* Implements an external process that can be interacted.
*/
class CHESSGAME_API FInteractiveProcess
: public FRunnable
{
public:
/**
* Creates a new interactive process.
*
* @param InURL The URL of the executable to launch.
* @param InParams The command line parameters.
* @param InHidden Whether the window of the process should be hidden.
*/
FInteractiveProcess(const FString& InURL, const FString& InParams, bool InHidden);
/** Destructor. */
~FInteractiveProcess();
/**
* Cancels the process.
*
* @param InKillTree Whether to kill the entire process tree when canceling this process.
*/
void Cancel(bool InKillTree = false)
{
bCanceling = true;
bKillTree = InKillTree;
}
/**
* Gets the duration of time that the task has been running.
*
* @return Time duration.
*/
FTimespan GetDuration() const;
/**
* Checks whether the process is still running.
*
* @return true if the process is running, false otherwise.
*/
bool IsRunning() const
{
return (Thread != nullptr);
}
/**
* Launches the process
*
* @return True if succeed
*/
bool Launch();
/**
* Returns a delegate that is executed when the process has been canceled.
*
* @return The delegate.
*/
FSimpleDelegate& OnCanceled()
{
return CanceledDelegate;
}
/**
* Returns a delegate that is executed if the process is terminated without user wanting
*
* @return The delegate
*/
FSimpleDelegate& OnTerminated()
{
return TerminatedDelegate;
}
/**
* Returns a delegate that is executed when a interactive process completed.
*
* @return The delegate.
*/
FOnInteractiveProcessCompleted& OnCompleted()
{
return CompletedDelegate;
}
/**
* Returns a delegate that is executed when a interactive process produces output.
*
* @return The delegate.
*/
FOnInteractiveProcessOutput& OnOutput()
{
return OutputDelegate;
}
/**
* Sends the message when process is ready
*
* @param Message to be send
*/
void WriteWhenReady(const FString& Message);
/**
* Returns the return code from the exited process
*
* @return Process return code
*/
int GetReturnCode() const
{
return ReturnCode;
}
// FRunnable interface
virtual bool Init() override
{
return true;
}
virtual uint32 Run() override;
virtual void Stop() override
{
Cancel();
}
virtual void Exit() override { }
protected:
/**
* Processes the given output string.
*
* @param Output The output string to process.
*/
void ProcessOutput(const FString& Output);
// Write message to process through pipe
bool WriteToPipe();
private:
// Whether the process is being canceled. */
bool bCanceling : 1;
// Whether the window of the process should be hidden. */
bool bHidden : 1;
// Whether to kill the entire process tree when cancelling this process. */
bool bKillTree : 1;
// Holds the URL of the executable to launch. */
FString URL;
// Holds the command line parameters. */
FString Params;
// Holds the handle to the process. */
FProcHandle ProcessHandle;
// Holds the read pipe. */
void* ReadPipe;
// Holds the write pipe. */
void* WritePipe;
// Holds the monitoring thread object. */
FRunnableThread* Thread;
// Holds the return code. */
int ReturnCode;
// Holds the time at which the process started. */
FDateTime StartTime;
// Holds the time at which the process ended. */
FDateTime EndTime;
// Message to be written to pipe when ready
FString MessageToProcess;
// Holds a delegate that is executed when the process has been canceled. */
FSimpleDelegate CanceledDelegate;
// Holds a delegate that is executed when the process has been canceled. */
FSimpleDelegate TerminatedDelegate;
// Holds a delegate that is executed when a interactive process completed. */
FOnInteractiveProcessCompleted CompletedDelegate;
// Holds a delegate that is executed when a interactive process produces output. */
FOnInteractiveProcessOutput OutputDelegate;
};
FInteractiveProcess source
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
#include "ChessGame.h"
#include "FInteractiveProcess.h"
#include "Runtime/Core/Public/Misc/Paths.h"
FInteractiveProcess::FInteractiveProcess(const FString& InURL, const FString& InParams, bool InHidden)
: bCanceling(false)
, bHidden(InHidden)
, bKillTree(false)
, URL(InURL)
, Params(InParams)
, ReadPipe(nullptr)
, WritePipe(nullptr)
, Thread(nullptr)
, ReturnCode(0)
, StartTime(0)
, EndTime(0)
{ }
FInteractiveProcess::~FInteractiveProcess()
{
if (IsRunning())
{
Cancel(true);
Thread->WaitForCompletion();
delete Thread;
Thread = nullptr;
}
}
FTimespan FInteractiveProcess::GetDuration() const
{
if (IsRunning())
{
return (FDateTime::UtcNow() - StartTime);
}
return (EndTime - StartTime);
}
bool FInteractiveProcess::Launch()
{
if (IsRunning())
{
return false;
}
if (!FPlatformProcess::CreatePipe(ReadPipe, WritePipe))
{
return false;
}
ProcessHandle = FPlatformProcess::CreateProc(*URL, *Params, false, bHidden, bHidden, nullptr, 0, nullptr, WritePipe);
if (!ProcessHandle.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Failed to launch"));
return false;
}
// Creating name for the process
static uint32 tempInteractiveProcessIndex = 0;
ThreadName = FString::Printf(TEXT("FInteractiveProcess %d"), tempInteractiveProcessIndex);
tempInteractiveProcessIndex++;
Thread = FRunnableThread::Create(this, *ThreadName);
return true;
}
void FInteractiveProcess::ProcessOutput(const FString& Output)
{
TArray<FString> LogLines;
Output.ParseIntoArray(&LogLines, TEXT("
"), false);
for (int32 LogIndex = 0; LogIndex < LogLines.Num(); ++LogIndex)
{
OutputDelegate.ExecuteIfBound(LogLines[LogIndex]);
}
}
bool FInteractiveProcess::WriteToPipe()
{
// If there is not a message
if (MessageToProcess.Len() == 0)
{
return false;
}
if (!IsRunning())
{
return false;
}
if (!ProcessHandle.IsValid())
{
UE_LOG(LogTemp, Warning, TEXT("Process handle is not valid"));
return false;
}
// Convert tempInput to UTF8CHAR
uint32 BytesAvailable = MessageToProcess.Len();
UTF8CHAR* Buffer = new UTF8CHAR[BytesAvailable + 1];
if (!FString::ToBlob(MessageToProcess, Buffer, BytesAvailable))
{
UE_LOG(LogTemp, Warning, TEXT("Failed to convert UTF8CHAR"));
return false;
}
// Empty original FString
MessageToProcess.Empty(0);
// Write pipe UTF8CHAR
uint32 BytesWritten = 0;
// @todo This doesn't works on any OS other than Windows
if (!WriteFile(WritePipe, Buffer, BytesAvailable, (::DWORD*)&BytesWritten, nullptr))
{
UE_LOG(LogTemp, Warning, TEXT("Failed to write"));
return false;
}
if (BytesAvailable > BytesWritten)
{
UE_LOG(LogTemp, Warning, TEXT("Failed to write all of the message"));
}
else
{
UE_LOG(LogTemp, Log, TEXT("Succesfully write through pipe"));
}
return true;
}
void FInteractiveProcess::WriteWhenReady(const FString& Message)
{
MessageToProcess = Message;
}
// FRunnable interface
uint32 FInteractiveProcess::Run()
{
// control and interact with the process
StartTime = FDateTime::UtcNow();
{
bool IsProcRunning = false;
do
{
// 1 millisecond sleep is a problem on Windows platform, dont change this
FPlatformProcess::Sleep(0.0);
// Read pipe
ProcessOutput(FPlatformProcess::ReadPipe(ReadPipe));
// Write pipe
WriteToPipe();
// If wanted to stop program
if (bCanceling)
{
// close pipes
FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
ReadPipe = WritePipe = nullptr;
FPlatformProcess::TerminateProc(ProcessHandle, bKillTree);
CanceledDelegate.ExecuteIfBound();
// get completion status
if (!FPlatformProcess::GetProcReturnCode(ProcessHandle, &ReturnCode))
{
ReturnCode = -1;
}
CompletedDelegate.ExecuteIfBound(ReturnCode);
}
IsProcRunning = FPlatformProcess::IsProcRunning(ProcessHandle);
} while (IsProcRunning);
}
EndTime = FDateTime::UtcNow();
UE_LOG(LogTemp, Warning, TEXT("%s terminated"), *ThreadName);
// Means the process terminated without user wanting
if (!bCanceling)
{
TerminatedDelegate.ExecuteIfBound();
}
return 0;
}