fix macOS build (following Projucer changes made in Windows, which removed /Applications/JUCE/modules from its headers). move JUCE headers under source control, so that Windows and macOS can both build against same version of JUCE. remove AUv3 target (I think it's an iOS thing, so it will never work with this macOS fluidsynth dylib).
This commit is contained in:
267
modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp
Normal file
267
modules/juce_events/interprocess/juce_ConnectedChildProcess.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
enum { magicMastSlaveConnectionHeader = 0x712baf04 };
|
||||
|
||||
static const char* startMessage = "__ipc_st";
|
||||
static const char* killMessage = "__ipc_k_";
|
||||
static const char* pingMessage = "__ipc_p_";
|
||||
enum { specialMessageSize = 8, defaultTimeoutMs = 8000 };
|
||||
|
||||
static inline bool isMessageType (const MemoryBlock& mb, const char* messageType) noexcept
|
||||
{
|
||||
return mb.matches (messageType, (size_t) specialMessageSize);
|
||||
}
|
||||
|
||||
static String getCommandLinePrefix (const String& commandLineUniqueID)
|
||||
{
|
||||
return "--" + commandLineUniqueID + ":";
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
// This thread sends and receives ping messages every second, so that it
|
||||
// can find out if the other process has stopped running.
|
||||
struct ChildProcessPingThread : public Thread,
|
||||
private AsyncUpdater
|
||||
{
|
||||
ChildProcessPingThread (int timeout) : Thread ("IPC ping"), timeoutMs (timeout)
|
||||
{
|
||||
pingReceived();
|
||||
}
|
||||
|
||||
void pingReceived() noexcept { countdown = timeoutMs / 1000 + 1; }
|
||||
void triggerConnectionLostMessage() { triggerAsyncUpdate(); }
|
||||
|
||||
virtual bool sendPingMessage (const MemoryBlock&) = 0;
|
||||
virtual void pingFailed() = 0;
|
||||
|
||||
int timeoutMs;
|
||||
|
||||
private:
|
||||
Atomic<int> countdown;
|
||||
|
||||
void handleAsyncUpdate() override { pingFailed(); }
|
||||
|
||||
void run() override
|
||||
{
|
||||
while (! threadShouldExit())
|
||||
{
|
||||
if (--countdown <= 0 || ! sendPingMessage ({ pingMessage, specialMessageSize }))
|
||||
{
|
||||
triggerConnectionLostMessage();
|
||||
break;
|
||||
}
|
||||
|
||||
wait (1000);
|
||||
}
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessPingThread)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
struct ChildProcessMaster::Connection : public InterprocessConnection,
|
||||
private ChildProcessPingThread
|
||||
{
|
||||
Connection (ChildProcessMaster& m, const String& pipeName, int timeout)
|
||||
: InterprocessConnection (false, magicMastSlaveConnectionHeader),
|
||||
ChildProcessPingThread (timeout),
|
||||
owner (m)
|
||||
{
|
||||
if (createPipe (pipeName, timeoutMs))
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
void connectionMade() override {}
|
||||
void connectionLost() override { owner.handleConnectionLost(); }
|
||||
|
||||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToSlave (m); }
|
||||
void pingFailed() override { connectionLost(); }
|
||||
|
||||
void messageReceived (const MemoryBlock& m) override
|
||||
{
|
||||
pingReceived();
|
||||
|
||||
if (m.getSize() != specialMessageSize || ! isMessageType (m, pingMessage))
|
||||
owner.handleMessageFromSlave (m);
|
||||
}
|
||||
|
||||
ChildProcessMaster& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ChildProcessMaster::ChildProcessMaster() {}
|
||||
|
||||
ChildProcessMaster::~ChildProcessMaster()
|
||||
{
|
||||
killSlaveProcess();
|
||||
}
|
||||
|
||||
void ChildProcessMaster::handleConnectionLost() {}
|
||||
|
||||
bool ChildProcessMaster::sendMessageToSlave (const MemoryBlock& mb)
|
||||
{
|
||||
if (connection != nullptr)
|
||||
return connection->sendMessage (mb);
|
||||
|
||||
jassertfalse; // this can only be used when the connection is active!
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChildProcessMaster::launchSlaveProcess (const File& executable, const String& commandLineUniqueID,
|
||||
int timeoutMs, int streamFlags)
|
||||
{
|
||||
killSlaveProcess();
|
||||
|
||||
auto pipeName = "p" + String::toHexString (Random().nextInt64());
|
||||
|
||||
StringArray args;
|
||||
args.add (executable.getFullPathName());
|
||||
args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName);
|
||||
|
||||
childProcess.reset (new ChildProcess());
|
||||
|
||||
if (childProcess->start (args, streamFlags))
|
||||
{
|
||||
connection.reset (new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs));
|
||||
|
||||
if (connection->isConnected())
|
||||
{
|
||||
sendMessageToSlave ({ startMessage, specialMessageSize });
|
||||
return true;
|
||||
}
|
||||
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void ChildProcessMaster::killSlaveProcess()
|
||||
{
|
||||
if (connection != nullptr)
|
||||
{
|
||||
sendMessageToSlave ({ killMessage, specialMessageSize });
|
||||
connection->disconnect();
|
||||
connection.reset();
|
||||
}
|
||||
|
||||
childProcess.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ChildProcessSlave::Connection : public InterprocessConnection,
|
||||
private ChildProcessPingThread
|
||||
{
|
||||
Connection (ChildProcessSlave& p, const String& pipeName, int timeout)
|
||||
: InterprocessConnection (false, magicMastSlaveConnectionHeader),
|
||||
ChildProcessPingThread (timeout),
|
||||
owner (p)
|
||||
{
|
||||
connectToPipe (pipeName, timeoutMs);
|
||||
startThread (4);
|
||||
}
|
||||
|
||||
~Connection()
|
||||
{
|
||||
stopThread (10000);
|
||||
}
|
||||
|
||||
private:
|
||||
ChildProcessSlave& owner;
|
||||
|
||||
void connectionMade() override {}
|
||||
void connectionLost() override { owner.handleConnectionLost(); }
|
||||
|
||||
bool sendPingMessage (const MemoryBlock& m) override { return owner.sendMessageToMaster (m); }
|
||||
void pingFailed() override { connectionLost(); }
|
||||
|
||||
void messageReceived (const MemoryBlock& m) override
|
||||
{
|
||||
pingReceived();
|
||||
|
||||
if (isMessageType (m, pingMessage))
|
||||
return;
|
||||
|
||||
if (isMessageType (m, killMessage))
|
||||
return triggerConnectionLostMessage();
|
||||
|
||||
if (isMessageType (m, startMessage))
|
||||
return owner.handleConnectionMade();
|
||||
|
||||
owner.handleMessageFromMaster (m);
|
||||
}
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ChildProcessSlave::ChildProcessSlave() {}
|
||||
ChildProcessSlave::~ChildProcessSlave() {}
|
||||
|
||||
void ChildProcessSlave::handleConnectionMade() {}
|
||||
void ChildProcessSlave::handleConnectionLost() {}
|
||||
|
||||
bool ChildProcessSlave::sendMessageToMaster (const MemoryBlock& mb)
|
||||
{
|
||||
if (connection != nullptr)
|
||||
return connection->sendMessage (mb);
|
||||
|
||||
jassertfalse; // this can only be used when the connection is active!
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ChildProcessSlave::initialiseFromCommandLine (const String& commandLine,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs)
|
||||
{
|
||||
auto prefix = getCommandLinePrefix (commandLineUniqueID);
|
||||
|
||||
if (commandLine.trim().startsWith (prefix))
|
||||
{
|
||||
auto pipeName = commandLine.fromFirstOccurrenceOf (prefix, false, false)
|
||||
.upToFirstOccurrenceOf (" ", false, false).trim();
|
||||
|
||||
if (pipeName.isNotEmpty())
|
||||
{
|
||||
connection.reset (new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs));
|
||||
|
||||
if (! connection->isConnected())
|
||||
connection.reset();
|
||||
}
|
||||
}
|
||||
|
||||
return connection != nullptr;
|
||||
}
|
||||
|
||||
} // namespace juce
|
204
modules/juce_events/interprocess/juce_ConnectedChildProcess.h
Normal file
204
modules/juce_events/interprocess/juce_ConnectedChildProcess.h
Normal file
@ -0,0 +1,204 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Acts as the slave end of a master/slave pair of connected processes.
|
||||
|
||||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app
|
||||
to spawn a child process, and to manage a 2-way messaging connection to control it.
|
||||
|
||||
To use the system, you need to create subclasses of both ChildProcessSlave and
|
||||
ChildProcessMaster. To instantiate the ChildProcessSlave object, you must
|
||||
add some code to your main() or JUCEApplication::initialise() function that
|
||||
calls the initialiseFromCommandLine() method to check the app's command-line
|
||||
parameters to see whether it's being launched as a child process. If this returns
|
||||
true then the slave process can be allowed to run, and its handleMessageFromMaster()
|
||||
method will be called whenever a message arrives.
|
||||
|
||||
The juce demo app has a good example of this class in action.
|
||||
|
||||
@see ChildProcessMaster, InterprocessConnection, ChildProcess
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ChildProcessSlave
|
||||
{
|
||||
public:
|
||||
/** Creates a non-connected slave process.
|
||||
Use initialiseFromCommandLine to connect to a master process.
|
||||
*/
|
||||
ChildProcessSlave();
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~ChildProcessSlave();
|
||||
|
||||
/** This checks some command-line parameters to see whether they were generated by
|
||||
ChildProcessMaster::launchSlaveProcess(), and if so, connects to that master process.
|
||||
|
||||
In an exe that can be used as a child process, you should add some code to your
|
||||
main() or JUCEApplication::initialise() that calls this method.
|
||||
|
||||
The commandLineUniqueID should be a short alphanumeric identifier (no spaces!)
|
||||
that matches the string passed to ChildProcessMaster::launchSlaveProcess().
|
||||
|
||||
The timeoutMs parameter lets you specify how long the child process is allowed
|
||||
to run without receiving a ping from the master before the master is considered to
|
||||
have died, and handleConnectionLost() will be called. Passing <= 0 for this timeout
|
||||
makes it use a default value.
|
||||
|
||||
Returns true if the command-line matches and the connection is made successfully.
|
||||
*/
|
||||
bool initialiseFromCommandLine (const String& commandLine,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs = 0);
|
||||
|
||||
//==============================================================================
|
||||
/** This will be called to deliver messages from the master process.
|
||||
The call will probably be made on a background thread, so be careful with your
|
||||
thread-safety! You may want to respond by sending back a message with
|
||||
sendMessageToMaster()
|
||||
*/
|
||||
virtual void handleMessageFromMaster (const MemoryBlock&) = 0;
|
||||
|
||||
/** This will be called when the master process finishes connecting to this slave.
|
||||
The call will probably be made on a background thread, so be careful with your thread-safety!
|
||||
*/
|
||||
virtual void handleConnectionMade();
|
||||
|
||||
/** This will be called when the connection to the master process is lost.
|
||||
The call may be made from any thread (including the message thread).
|
||||
Typically, if your process only exists to act as a slave, you should probably exit
|
||||
when this happens.
|
||||
*/
|
||||
virtual void handleConnectionLost();
|
||||
|
||||
/** Tries to send a message to the master process.
|
||||
This returns true if the message was sent, but doesn't check that it actually gets
|
||||
delivered at the other end. If successful, the data will emerge in a call to your
|
||||
ChildProcessMaster::handleMessageFromSlave().
|
||||
*/
|
||||
bool sendMessageToMaster (const MemoryBlock&);
|
||||
|
||||
private:
|
||||
struct Connection;
|
||||
friend struct Connection;
|
||||
friend struct ContainerDeletePolicy<Connection>;
|
||||
std::unique_ptr<Connection> connection;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessSlave)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Acts as the master in a master/slave pair of connected processes.
|
||||
|
||||
The ChildProcessSlave and ChildProcessMaster classes make it easy for an app
|
||||
to spawn a child process, and to manage a 2-way messaging connection to control it.
|
||||
|
||||
To use the system, you need to create subclasses of both ChildProcessSlave and
|
||||
ChildProcessMaster. When you want your master process to launch the slave, you
|
||||
just call launchSlaveProcess(), and it'll attempt to launch the executable that
|
||||
you specify (which may be the same exe), and assuming it has been set-up to
|
||||
correctly parse the command-line parameters (see ChildProcessSlave) then a
|
||||
two-way connection will be created.
|
||||
|
||||
The juce demo app has a good example of this class in action.
|
||||
|
||||
@see ChildProcessSlave, InterprocessConnection, ChildProcess
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API ChildProcessMaster
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised master process object.
|
||||
Use launchSlaveProcess to launch and connect to a child process.
|
||||
*/
|
||||
ChildProcessMaster();
|
||||
|
||||
/** Destructor.
|
||||
Note that the destructor calls killSlaveProcess(), but doesn't wait for
|
||||
the child process to finish terminating.
|
||||
*/
|
||||
virtual ~ChildProcessMaster();
|
||||
|
||||
/** Attempts to launch and connect to a slave process.
|
||||
This will start the given executable, passing it a special command-line
|
||||
parameter based around the commandLineUniqueID string, which must be a
|
||||
short alphanumeric string (no spaces!) that identifies your app. The exe
|
||||
that gets launched must respond by calling ChildProcessSlave::initialiseFromCommandLine()
|
||||
in its startup code, and must use a matching ID to commandLineUniqueID.
|
||||
|
||||
The timeoutMs parameter lets you specify how long the child process is allowed
|
||||
to go without sending a ping before it is considered to have died and
|
||||
handleConnectionLost() will be called. Passing <= 0 for this timeout makes
|
||||
it use a default value.
|
||||
|
||||
If this all works, the method returns true, and you can begin sending and
|
||||
receiving messages with the slave process.
|
||||
|
||||
If a child process is already running, this will call killSlaveProcess() and
|
||||
start a new one.
|
||||
*/
|
||||
bool launchSlaveProcess (const File& executableToLaunch,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs = 0,
|
||||
int streamFlags = ChildProcess::wantStdOut | ChildProcess::wantStdErr);
|
||||
|
||||
/** Sends a kill message to the slave, and disconnects from it.
|
||||
Note that this won't wait for it to terminate.
|
||||
*/
|
||||
void killSlaveProcess();
|
||||
|
||||
/** This will be called to deliver a message from the slave process.
|
||||
The call will probably be made on a background thread, so be careful with your thread-safety!
|
||||
*/
|
||||
virtual void handleMessageFromSlave (const MemoryBlock&) = 0;
|
||||
|
||||
/** This will be called when the slave process dies or is somehow disconnected.
|
||||
The call will probably be made on a background thread, so be careful with your thread-safety!
|
||||
*/
|
||||
virtual void handleConnectionLost();
|
||||
|
||||
/** Attempts to send a message to the slave process.
|
||||
This returns true if the message was dispatched, but doesn't check that it actually
|
||||
gets delivered at the other end. If successful, the data will emerge in a call to
|
||||
your ChildProcessSlave::handleMessageFromMaster().
|
||||
*/
|
||||
bool sendMessageToSlave (const MemoryBlock&);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ChildProcess> childProcess;
|
||||
|
||||
struct Connection;
|
||||
friend struct Connection;
|
||||
friend struct ContainerDeletePolicy<Connection>;
|
||||
std::unique_ptr<Connection> connection;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster)
|
||||
};
|
||||
|
||||
} // namespace juce
|
371
modules/juce_events/interprocess/juce_InterprocessConnection.cpp
Normal file
371
modules/juce_events/interprocess/juce_InterprocessConnection.cpp
Normal file
@ -0,0 +1,371 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
struct InterprocessConnection::ConnectionThread : public Thread
|
||||
{
|
||||
ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {}
|
||||
void run() override { owner.runThread(); }
|
||||
|
||||
InterprocessConnection& owner;
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
InterprocessConnection::InterprocessConnection (bool callbacksOnMessageThread, uint32 magicMessageHeaderNumber)
|
||||
: useMessageThread (callbacksOnMessageThread),
|
||||
magicMessageHeader (magicMessageHeaderNumber)
|
||||
{
|
||||
thread.reset (new ConnectionThread (*this));
|
||||
}
|
||||
|
||||
InterprocessConnection::~InterprocessConnection()
|
||||
{
|
||||
callbackConnectionState = false;
|
||||
disconnect();
|
||||
masterReference.clear();
|
||||
thread.reset();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnection::connectToSocket (const String& hostName,
|
||||
int portNumber, int timeOutMillisecs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
socket.reset (new StreamingSocket());
|
||||
|
||||
if (socket->connect (hostName, portNumber, timeOutMillisecs))
|
||||
{
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
socket.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::connectToPipe (const String& pipeName, int timeoutMs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
std::unique_ptr<NamedPipe> newPipe (new NamedPipe());
|
||||
|
||||
if (newPipe->openExisting (pipeName))
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
pipeReceiveMessageTimeout = timeoutMs;
|
||||
initialiseWithPipe (newPipe.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::createPipe (const String& pipeName, int timeoutMs, bool mustNotExist)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
std::unique_ptr<NamedPipe> newPipe (new NamedPipe());
|
||||
|
||||
if (newPipe->createNewPipe (pipeName, mustNotExist))
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
pipeReceiveMessageTimeout = timeoutMs;
|
||||
initialiseWithPipe (newPipe.release());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnection::disconnect()
|
||||
{
|
||||
thread->signalThreadShouldExit();
|
||||
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
if (socket != nullptr) socket->close();
|
||||
if (pipe != nullptr) pipe->close();
|
||||
}
|
||||
|
||||
thread->stopThread (4000);
|
||||
deletePipeAndSocket();
|
||||
connectionLostInt();
|
||||
}
|
||||
|
||||
void InterprocessConnection::deletePipeAndSocket()
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
socket.reset();
|
||||
pipe.reset();
|
||||
}
|
||||
|
||||
bool InterprocessConnection::isConnected() const
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
|
||||
return ((socket != nullptr && socket->isConnected())
|
||||
|| (pipe != nullptr && pipe->isOpen()))
|
||||
&& thread->isThreadRunning();
|
||||
}
|
||||
|
||||
String InterprocessConnection::getConnectedHostName() const
|
||||
{
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
|
||||
if (pipe == nullptr && socket == nullptr)
|
||||
return {};
|
||||
|
||||
if (socket != nullptr && ! socket->isLocal())
|
||||
return socket->getHostName();
|
||||
}
|
||||
|
||||
return IPAddress::local().toString();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnection::sendMessage (const MemoryBlock& message)
|
||||
{
|
||||
uint32 messageHeader[2] = { ByteOrder::swapIfBigEndian (magicMessageHeader),
|
||||
ByteOrder::swapIfBigEndian ((uint32) message.getSize()) };
|
||||
|
||||
MemoryBlock messageData (sizeof (messageHeader) + message.getSize());
|
||||
messageData.copyFrom (messageHeader, 0, sizeof (messageHeader));
|
||||
messageData.copyFrom (message.getData(), sizeof (messageHeader), message.getSize());
|
||||
|
||||
return writeData (messageData.getData(), (int) messageData.getSize()) == (int) messageData.getSize();
|
||||
}
|
||||
|
||||
int InterprocessConnection::writeData (void* data, int dataSize)
|
||||
{
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
|
||||
if (socket != nullptr)
|
||||
return socket->write (data, dataSize);
|
||||
|
||||
if (pipe != nullptr)
|
||||
return pipe->write (data, dataSize, pipeReceiveMessageTimeout);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
void InterprocessConnection::initialiseWithSocket (StreamingSocket* newSocket)
|
||||
{
|
||||
jassert (socket == nullptr && pipe == nullptr);
|
||||
socket.reset (newSocket);
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
}
|
||||
|
||||
void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe)
|
||||
{
|
||||
jassert (socket == nullptr && pipe == nullptr);
|
||||
pipe.reset (newPipe);
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ConnectionStateMessage : public MessageManager::MessageBase
|
||||
{
|
||||
ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept
|
||||
: owner (ipc), connectionMade (connected)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (auto* ipc = owner.get())
|
||||
{
|
||||
if (connectionMade)
|
||||
ipc->connectionMade();
|
||||
else
|
||||
ipc->connectionLost();
|
||||
}
|
||||
}
|
||||
|
||||
WeakReference<InterprocessConnection> owner;
|
||||
bool connectionMade;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionStateMessage)
|
||||
};
|
||||
|
||||
void InterprocessConnection::connectionMadeInt()
|
||||
{
|
||||
if (! callbackConnectionState)
|
||||
{
|
||||
callbackConnectionState = true;
|
||||
|
||||
if (useMessageThread)
|
||||
(new ConnectionStateMessage (this, true))->post();
|
||||
else
|
||||
connectionMade();
|
||||
}
|
||||
}
|
||||
|
||||
void InterprocessConnection::connectionLostInt()
|
||||
{
|
||||
if (callbackConnectionState)
|
||||
{
|
||||
callbackConnectionState = false;
|
||||
|
||||
if (useMessageThread)
|
||||
(new ConnectionStateMessage (this, false))->post();
|
||||
else
|
||||
connectionLost();
|
||||
}
|
||||
}
|
||||
|
||||
struct DataDeliveryMessage : public Message
|
||||
{
|
||||
DataDeliveryMessage (InterprocessConnection* ipc, const MemoryBlock& d)
|
||||
: owner (ipc), data (d)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (auto* ipc = owner.get())
|
||||
ipc->messageReceived (data);
|
||||
}
|
||||
|
||||
WeakReference<InterprocessConnection> owner;
|
||||
MemoryBlock data;
|
||||
};
|
||||
|
||||
void InterprocessConnection::deliverDataInt (const MemoryBlock& data)
|
||||
{
|
||||
jassert (callbackConnectionState);
|
||||
|
||||
if (useMessageThread)
|
||||
(new DataDeliveryMessage (this, data))->post();
|
||||
else
|
||||
messageReceived (data);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
int InterprocessConnection::readData (void* data, int num)
|
||||
{
|
||||
if (socket != nullptr)
|
||||
return socket->read (data, num, true);
|
||||
|
||||
if (pipe != nullptr)
|
||||
return pipe->read (data, num, pipeReceiveMessageTimeout);
|
||||
|
||||
jassertfalse;
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::readNextMessage()
|
||||
{
|
||||
uint32 messageHeader[2];
|
||||
auto bytes = readData (messageHeader, sizeof (messageHeader));
|
||||
|
||||
if (bytes == sizeof (messageHeader)
|
||||
&& ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader)
|
||||
{
|
||||
auto bytesInMessage = (int) ByteOrder::swapIfBigEndian (messageHeader[1]);
|
||||
|
||||
if (bytesInMessage > 0)
|
||||
{
|
||||
MemoryBlock messageData ((size_t) bytesInMessage, true);
|
||||
int bytesRead = 0;
|
||||
|
||||
while (bytesInMessage > 0)
|
||||
{
|
||||
if (thread->threadShouldExit())
|
||||
return false;
|
||||
|
||||
auto numThisTime = jmin (bytesInMessage, 65536);
|
||||
auto bytesIn = readData (addBytesToPointer (messageData.getData(), bytesRead), numThisTime);
|
||||
|
||||
if (bytesIn <= 0)
|
||||
break;
|
||||
|
||||
bytesRead += bytesIn;
|
||||
bytesInMessage -= bytesIn;
|
||||
}
|
||||
|
||||
if (bytesRead >= 0)
|
||||
deliverDataInt (messageData);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (bytes < 0)
|
||||
{
|
||||
if (socket != nullptr)
|
||||
deletePipeAndSocket();
|
||||
|
||||
connectionLostInt();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnection::runThread()
|
||||
{
|
||||
while (! thread->threadShouldExit())
|
||||
{
|
||||
if (socket != nullptr)
|
||||
{
|
||||
auto ready = socket->waitUntilReady (true, 100);
|
||||
|
||||
if (ready < 0)
|
||||
{
|
||||
deletePipeAndSocket();
|
||||
connectionLostInt();
|
||||
break;
|
||||
}
|
||||
|
||||
if (ready == 0)
|
||||
{
|
||||
thread->wait (1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (pipe != nullptr)
|
||||
{
|
||||
if (! pipe->isOpen())
|
||||
{
|
||||
deletePipeAndSocket();
|
||||
connectionLostInt();
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (thread->threadShouldExit() || ! readNextMessage())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
210
modules/juce_events/interprocess/juce_InterprocessConnection.h
Normal file
210
modules/juce_events/interprocess/juce_InterprocessConnection.h
Normal file
@ -0,0 +1,210 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
class InterprocessConnectionServer;
|
||||
class MemoryBlock;
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
Manages a simple two-way messaging connection to another process, using either
|
||||
a socket or a named pipe as the transport medium.
|
||||
|
||||
To connect to a waiting socket or an open pipe, use the connectToSocket() or
|
||||
connectToPipe() methods. If this succeeds, messages can be sent to the other end,
|
||||
and incoming messages will result in a callback via the messageReceived()
|
||||
method.
|
||||
|
||||
To open a pipe and wait for another client to connect to it, use the createPipe()
|
||||
method.
|
||||
|
||||
To act as a socket server and create connections for one or more client, see the
|
||||
InterprocessConnectionServer class.
|
||||
|
||||
@see InterprocessConnectionServer, Socket, NamedPipe
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API InterprocessConnection
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates a connection.
|
||||
|
||||
Connections are created manually, connecting them with the connectToSocket()
|
||||
or connectToPipe() methods, or they are created automatically by a InterprocessConnectionServer
|
||||
when a client wants to connect.
|
||||
|
||||
@param callbacksOnMessageThread if true, callbacks to the connectionMade(),
|
||||
connectionLost() and messageReceived() methods will
|
||||
always be made using the message thread; if false,
|
||||
these will be called immediately on the connection's
|
||||
own thread.
|
||||
@param magicMessageHeaderNumber a magic number to use in the header to check the
|
||||
validity of the data blocks being sent and received. This
|
||||
can be any number, but the sender and receiver must obviously
|
||||
use matching values or they won't recognise each other.
|
||||
*/
|
||||
InterprocessConnection (bool callbacksOnMessageThread = true,
|
||||
uint32 magicMessageHeaderNumber = 0xf2b49e2c);
|
||||
|
||||
/** Destructor. */
|
||||
virtual ~InterprocessConnection();
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to connect this object to a socket.
|
||||
|
||||
For this to work, the machine on the other end needs to have a InterprocessConnectionServer
|
||||
object waiting to receive client connections on this port number.
|
||||
|
||||
@param hostName the host computer, either a network address or name
|
||||
@param portNumber the socket port number to try to connect to
|
||||
@param timeOutMillisecs how long to keep trying before giving up
|
||||
@returns true if the connection is established successfully
|
||||
@see Socket
|
||||
*/
|
||||
bool connectToSocket (const String& hostName,
|
||||
int portNumber,
|
||||
int timeOutMillisecs);
|
||||
|
||||
/** Tries to connect the object to an existing named pipe.
|
||||
|
||||
For this to work, another process on the same computer must already have opened
|
||||
an InterprocessConnection object and used createPipe() to create a pipe for this
|
||||
to connect to.
|
||||
|
||||
@param pipeName the name to use for the pipe - this should be unique to your app
|
||||
@param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing
|
||||
to the pipe, or -1 for an infinite timeout.
|
||||
@returns true if it connects successfully.
|
||||
@see createPipe, NamedPipe
|
||||
*/
|
||||
bool connectToPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs);
|
||||
|
||||
/** Tries to create a new pipe for other processes to connect to.
|
||||
|
||||
This creates a pipe with the given name, so that other processes can use
|
||||
connectToPipe() to connect to the other end.
|
||||
|
||||
@param pipeName the name to use for the pipe - this should be unique to your app
|
||||
@param pipeReceiveMessageTimeoutMs a timeout length to be used when reading or writing
|
||||
to the pipe, or -1 for an infinite timeout
|
||||
@param mustNotExist if set to true, the method will fail if the pipe already exists
|
||||
@returns true if the pipe was created, or false if it fails (e.g. if another process is
|
||||
already using using the pipe)
|
||||
*/
|
||||
bool createPipe (const String& pipeName, int pipeReceiveMessageTimeoutMs, bool mustNotExist = false);
|
||||
|
||||
/** Disconnects and closes any currently-open sockets or pipes. */
|
||||
void disconnect();
|
||||
|
||||
/** True if a socket or pipe is currently active. */
|
||||
bool isConnected() const;
|
||||
|
||||
/** Returns the socket that this connection is using (or nullptr if it uses a pipe). */
|
||||
StreamingSocket* getSocket() const noexcept { return socket.get(); }
|
||||
|
||||
/** Returns the pipe that this connection is using (or nullptr if it uses a socket). */
|
||||
NamedPipe* getPipe() const noexcept { return pipe.get(); }
|
||||
|
||||
/** Returns the name of the machine at the other end of this connection.
|
||||
This may return an empty string if the name is unknown.
|
||||
*/
|
||||
String getConnectedHostName() const;
|
||||
|
||||
//==============================================================================
|
||||
/** Tries to send a message to the other end of this connection.
|
||||
|
||||
This will fail if it's not connected, or if there's some kind of write error. If
|
||||
it succeeds, the connection object at the other end will receive the message by
|
||||
a callback to its messageReceived() method.
|
||||
|
||||
@see messageReceived
|
||||
*/
|
||||
bool sendMessage (const MemoryBlock& message);
|
||||
|
||||
//==============================================================================
|
||||
/** Called when the connection is first connected.
|
||||
|
||||
If the connection was created with the callbacksOnMessageThread flag set, then
|
||||
this will be called on the message thread; otherwise it will be called on a server
|
||||
thread.
|
||||
*/
|
||||
virtual void connectionMade() = 0;
|
||||
|
||||
/** Called when the connection is broken.
|
||||
|
||||
If the connection was created with the callbacksOnMessageThread flag set, then
|
||||
this will be called on the message thread; otherwise it will be called on a server
|
||||
thread.
|
||||
*/
|
||||
virtual void connectionLost() = 0;
|
||||
|
||||
/** Called when a message arrives.
|
||||
|
||||
When the object at the other end of this connection sends us a message with sendMessage(),
|
||||
this callback is used to deliver it to us.
|
||||
|
||||
If the connection was created with the callbacksOnMessageThread flag set, then
|
||||
this will be called on the message thread; otherwise it will be called on a server
|
||||
thread.
|
||||
|
||||
@see sendMessage
|
||||
*/
|
||||
virtual void messageReceived (const MemoryBlock& message) = 0;
|
||||
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
CriticalSection pipeAndSocketLock;
|
||||
std::unique_ptr<StreamingSocket> socket;
|
||||
std::unique_ptr<NamedPipe> pipe;
|
||||
bool callbackConnectionState = false;
|
||||
const bool useMessageThread;
|
||||
const uint32 magicMessageHeader;
|
||||
int pipeReceiveMessageTimeout = -1;
|
||||
|
||||
friend class InterprocessConnectionServer;
|
||||
void initialiseWithSocket (StreamingSocket*);
|
||||
void initialiseWithPipe (NamedPipe*);
|
||||
void deletePipeAndSocket();
|
||||
void connectionMadeInt();
|
||||
void connectionLostInt();
|
||||
void deliverDataInt (const MemoryBlock&);
|
||||
bool readNextMessage();
|
||||
int readData (void*, int);
|
||||
|
||||
struct ConnectionThread;
|
||||
friend struct ConnectionThread;
|
||||
friend struct ContainerDeletePolicy<ConnectionThread>;
|
||||
std::unique_ptr<ConnectionThread> thread;
|
||||
void runThread();
|
||||
int writeData (void*, int);
|
||||
|
||||
JUCE_DECLARE_WEAK_REFERENCEABLE (InterprocessConnection)
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection)
|
||||
};
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,80 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
InterprocessConnectionServer::InterprocessConnectionServer() : Thread ("JUCE IPC server")
|
||||
{
|
||||
}
|
||||
|
||||
InterprocessConnectionServer::~InterprocessConnectionServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber, const String& bindAddress)
|
||||
{
|
||||
stop();
|
||||
|
||||
socket.reset (new StreamingSocket());
|
||||
|
||||
if (socket->createListener (portNumber, bindAddress))
|
||||
{
|
||||
startThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
socket.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnectionServer::stop()
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
|
||||
if (socket != nullptr)
|
||||
socket->close();
|
||||
|
||||
stopThread (4000);
|
||||
socket.reset();
|
||||
}
|
||||
|
||||
int InterprocessConnectionServer::getBoundPort() const noexcept
|
||||
{
|
||||
return (socket == nullptr) ? -1 : socket->getBoundPort();
|
||||
}
|
||||
|
||||
void InterprocessConnectionServer::run()
|
||||
{
|
||||
while ((! threadShouldExit()) && socket != nullptr)
|
||||
{
|
||||
std::unique_ptr<StreamingSocket> clientSocket (socket->waitForNextConnection());
|
||||
|
||||
if (clientSocket != nullptr)
|
||||
if (auto* newConnection = createConnectionObject())
|
||||
newConnection->initialiseWithSocket (clientSocket.release());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace juce
|
@ -0,0 +1,106 @@
|
||||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2017 - ROLI Ltd.
|
||||
|
||||
JUCE is an open source library subject to commercial or open-source
|
||||
licensing.
|
||||
|
||||
The code included in this file is provided under the terms of the ISC license
|
||||
http://www.isc.org/downloads/software-support-policy/isc-license. Permission
|
||||
To use, copy, modify, and/or distribute this software for any purpose with or
|
||||
without fee is hereby granted provided that the above copyright notice and
|
||||
this permission notice appear in all copies.
|
||||
|
||||
JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
|
||||
EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
|
||||
DISCLAIMED.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
namespace juce
|
||||
{
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
An object that waits for client sockets to connect to a port on this host, and
|
||||
creates InterprocessConnection objects for each one.
|
||||
|
||||
To use this, create a class derived from it which implements the createConnectionObject()
|
||||
method, so that it creates suitable connection objects for each client that tries
|
||||
to connect.
|
||||
|
||||
@see InterprocessConnection
|
||||
|
||||
@tags{Events}
|
||||
*/
|
||||
class JUCE_API InterprocessConnectionServer : private Thread
|
||||
{
|
||||
public:
|
||||
//==============================================================================
|
||||
/** Creates an uninitialised server object.
|
||||
*/
|
||||
InterprocessConnectionServer();
|
||||
|
||||
/** Destructor. */
|
||||
~InterprocessConnectionServer();
|
||||
|
||||
//==============================================================================
|
||||
/** Starts an internal thread which listens on the given port number.
|
||||
|
||||
While this is running, if another process tries to connect with the
|
||||
InterprocessConnection::connectToSocket() method, this object will call
|
||||
createConnectionObject() to create a connection to that client.
|
||||
|
||||
Use stop() to stop the thread running.
|
||||
|
||||
@param portNumber The port on which the server will receive
|
||||
connections
|
||||
@param bindAddress The address on which the server will listen
|
||||
for connections. An empty string indicates
|
||||
that it should listen on all addresses
|
||||
assigned to this machine.
|
||||
|
||||
@see createConnectionObject, stop
|
||||
*/
|
||||
bool beginWaitingForSocket (int portNumber, const String& bindAddress = String());
|
||||
|
||||
/** Terminates the listener thread, if it's active.
|
||||
|
||||
@see beginWaitingForSocket
|
||||
*/
|
||||
void stop();
|
||||
|
||||
/** Returns the local port number to which this server is currently bound.
|
||||
|
||||
This is useful if you need to know to which port the OS has actually bound your
|
||||
socket when calling beginWaitingForSocket with a port number of zero.
|
||||
|
||||
Returns -1 if the function fails.
|
||||
*/
|
||||
int getBoundPort() const noexcept;
|
||||
|
||||
protected:
|
||||
/** Creates a suitable connection object for a client process that wants to
|
||||
connect to this one.
|
||||
|
||||
This will be called by the listener thread when a client process tries
|
||||
to connect, and must return a new InterprocessConnection object that will
|
||||
then run as this end of the connection.
|
||||
|
||||
@see InterprocessConnection
|
||||
*/
|
||||
virtual InterprocessConnection* createConnectionObject() = 0;
|
||||
|
||||
private:
|
||||
//==============================================================================
|
||||
std::unique_ptr<StreamingSocket> socket;
|
||||
|
||||
void run() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnectionServer)
|
||||
};
|
||||
|
||||
} // namespace juce
|
Reference in New Issue
Block a user