mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-13 00:04:19 +00:00
Changed case of examples folder name.
This commit is contained in:
parent
46547bf8d6
commit
a626425764
2262 changed files with 0 additions and 0 deletions
|
|
@ -0,0 +1,264 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
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 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();
|
||||
}
|
||||
|
||||
static bool isPingMessage (const MemoryBlock& m) noexcept
|
||||
{
|
||||
return memcmp (m.getData(), pingMessage, specialMessageSize) == 0;
|
||||
}
|
||||
|
||||
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 (MemoryBlock (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 || ! isPingMessage (m))
|
||||
owner.handleMessageFromSlave (m);
|
||||
}
|
||||
|
||||
ChildProcessMaster& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (Connection)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
ChildProcessMaster::ChildProcessMaster() {}
|
||||
|
||||
ChildProcessMaster::~ChildProcessMaster()
|
||||
{
|
||||
if (connection != nullptr)
|
||||
{
|
||||
sendMessageToSlave (MemoryBlock (killMessage, specialMessageSize));
|
||||
connection->disconnect();
|
||||
connection = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
connection = nullptr;
|
||||
jassert (childProcess.kill());
|
||||
|
||||
const String pipeName ("p" + String::toHexString (Random().nextInt64()));
|
||||
|
||||
StringArray args;
|
||||
args.add (executable.getFullPathName());
|
||||
args.add (getCommandLinePrefix (commandLineUniqueID) + pipeName);
|
||||
|
||||
if (childProcess.start (args))
|
||||
{
|
||||
connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs);
|
||||
|
||||
if (connection->isConnected())
|
||||
{
|
||||
sendMessageToSlave (MemoryBlock (startMessage, specialMessageSize));
|
||||
return true;
|
||||
}
|
||||
|
||||
connection = nullptr;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
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 (m.getSize() == specialMessageSize)
|
||||
{
|
||||
if (isPingMessage (m))
|
||||
return;
|
||||
|
||||
if (memcmp (m.getData(), killMessage, specialMessageSize) == 0)
|
||||
{
|
||||
triggerConnectionLostMessage();
|
||||
return;
|
||||
}
|
||||
|
||||
if (memcmp (m.getData(), startMessage, specialMessageSize) == 0)
|
||||
{
|
||||
owner.handleConnectionMade();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
String prefix (getCommandLinePrefix (commandLineUniqueID));
|
||||
|
||||
if (commandLine.trim().startsWith (prefix))
|
||||
{
|
||||
String pipeName (commandLine.fromFirstOccurrenceOf (prefix, false, false)
|
||||
.upToFirstOccurrenceOf (" ", false, false).trim());
|
||||
|
||||
if (pipeName.isNotEmpty())
|
||||
{
|
||||
connection = new Connection (*this, pipeName, timeoutMs <= 0 ? defaultTimeoutMs : timeoutMs);
|
||||
|
||||
if (! connection->isConnected())
|
||||
connection = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
return connection != nullptr;
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED
|
||||
#define JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
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>;
|
||||
ScopedPointer<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
|
||||
*/
|
||||
class JUCE_API ChildProcessMaster
|
||||
{
|
||||
public:
|
||||
/** Creates an uninitialised master process object.
|
||||
Use launchSlaveProcess to launch and connect to a child process.
|
||||
*/
|
||||
ChildProcessMaster();
|
||||
|
||||
/** Destructor. */
|
||||
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.
|
||||
*/
|
||||
bool launchSlaveProcess (const File& executableToLaunch,
|
||||
const String& commandLineUniqueID,
|
||||
int timeoutMs = 0);
|
||||
|
||||
/** 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:
|
||||
ChildProcess childProcess;
|
||||
|
||||
struct Connection;
|
||||
friend struct Connection;
|
||||
friend struct ContainerDeletePolicy<Connection>;
|
||||
ScopedPointer<Connection> connection;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ChildProcessMaster)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_CONNECTEDCHILDPROCESS_H_INCLUDED
|
||||
|
|
@ -0,0 +1,365 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
struct InterprocessConnection::ConnectionThread : public Thread
|
||||
{
|
||||
ConnectionThread (InterprocessConnection& c) : Thread ("JUCE IPC"), owner (c) {}
|
||||
|
||||
void run() override { owner.runThread(); }
|
||||
|
||||
private:
|
||||
InterprocessConnection& owner;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ConnectionThread)
|
||||
};
|
||||
|
||||
//==============================================================================
|
||||
InterprocessConnection::InterprocessConnection (const bool callbacksOnMessageThread,
|
||||
const uint32 magicMessageHeaderNumber)
|
||||
: callbackConnectionState (false),
|
||||
useMessageThread (callbacksOnMessageThread),
|
||||
magicMessageHeader (magicMessageHeaderNumber),
|
||||
pipeReceiveMessageTimeout (-1)
|
||||
{
|
||||
thread = new ConnectionThread (*this);
|
||||
}
|
||||
|
||||
InterprocessConnection::~InterprocessConnection()
|
||||
{
|
||||
callbackConnectionState = false;
|
||||
disconnect();
|
||||
masterReference.clear();
|
||||
thread = nullptr;
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnection::connectToSocket (const String& hostName,
|
||||
const int portNumber,
|
||||
const int timeOutMillisecs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
const ScopedLock sl (pipeAndSocketLock);
|
||||
socket = new StreamingSocket();
|
||||
|
||||
if (socket->connect (hostName, portNumber, timeOutMillisecs))
|
||||
{
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
socket = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool InterprocessConnection::connectToPipe (const String& pipeName, const int timeoutMs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
ScopedPointer<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, const int timeoutMs)
|
||||
{
|
||||
disconnect();
|
||||
|
||||
ScopedPointer<NamedPipe> newPipe (new NamedPipe());
|
||||
|
||||
if (newPipe->createNewPipe (pipeName))
|
||||
{
|
||||
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 = nullptr;
|
||||
pipe = nullptr;
|
||||
}
|
||||
|
||||
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 String();
|
||||
|
||||
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 = newSocket;
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
}
|
||||
|
||||
void InterprocessConnection::initialiseWithPipe (NamedPipe* newPipe)
|
||||
{
|
||||
jassert (socket == nullptr && pipe == nullptr);
|
||||
pipe = newPipe;
|
||||
connectionMadeInt();
|
||||
thread->startThread();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
struct ConnectionStateMessage : public MessageManager::MessageBase
|
||||
{
|
||||
ConnectionStateMessage (InterprocessConnection* ipc, bool connected) noexcept
|
||||
: owner (ipc), connectionMade (connected)
|
||||
{}
|
||||
|
||||
void messageCallback() override
|
||||
{
|
||||
if (InterprocessConnection* const ipc = owner)
|
||||
{
|
||||
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 (InterprocessConnection* const ipc = owner)
|
||||
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);
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnection::readNextMessageInt()
|
||||
{
|
||||
uint32 messageHeader[2];
|
||||
const int bytes = socket != nullptr ? socket->read (messageHeader, sizeof (messageHeader), true)
|
||||
: pipe ->read (messageHeader, sizeof (messageHeader), -1);
|
||||
|
||||
if (bytes == sizeof (messageHeader)
|
||||
&& ByteOrder::swapIfBigEndian (messageHeader[0]) == magicMessageHeader)
|
||||
{
|
||||
int 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;
|
||||
|
||||
const int numThisTime = jmin (bytesInMessage, 65536);
|
||||
void* const data = addBytesToPointer (messageData.getData(), bytesRead);
|
||||
|
||||
const int bytesIn = socket != nullptr ? socket->read (data, numThisTime, true)
|
||||
: pipe ->read (data, numThisTime, -1);
|
||||
|
||||
if (bytesIn <= 0)
|
||||
break;
|
||||
|
||||
bytesRead += bytesIn;
|
||||
bytesInMessage -= bytesIn;
|
||||
}
|
||||
|
||||
if (bytesRead >= 0)
|
||||
deliverDataInt (messageData);
|
||||
}
|
||||
}
|
||||
else if (bytes < 0)
|
||||
{
|
||||
if (socket != nullptr)
|
||||
deletePipeAndSocket();
|
||||
|
||||
connectionLostInt();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void InterprocessConnection::runThread()
|
||||
{
|
||||
while (! thread->threadShouldExit())
|
||||
{
|
||||
if (socket != nullptr)
|
||||
{
|
||||
const int ready = socket->waitUntilReady (true, 0);
|
||||
|
||||
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() || ! readNextMessageInt())
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_INTERPROCESSCONNECTION_H_INCLUDED
|
||||
#define JUCE_INTERPROCESSCONNECTION_H_INCLUDED
|
||||
|
||||
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
|
||||
*/
|
||||
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.
|
||||
@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);
|
||||
|
||||
/** 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; }
|
||||
|
||||
/** Returns the pipe that this connection is using (or nullptr if it uses a socket). */
|
||||
NamedPipe* getPipe() const noexcept { return pipe; }
|
||||
|
||||
/** 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:
|
||||
//==============================================================================
|
||||
WeakReference<InterprocessConnection>::Master masterReference;
|
||||
friend class WeakReference<InterprocessConnection>;
|
||||
CriticalSection pipeAndSocketLock;
|
||||
ScopedPointer <StreamingSocket> socket;
|
||||
ScopedPointer <NamedPipe> pipe;
|
||||
bool callbackConnectionState;
|
||||
const bool useMessageThread;
|
||||
const uint32 magicMessageHeader;
|
||||
int pipeReceiveMessageTimeout;
|
||||
|
||||
friend class InterprocessConnectionServer;
|
||||
void initialiseWithSocket (StreamingSocket*);
|
||||
void initialiseWithPipe (NamedPipe*);
|
||||
void deletePipeAndSocket();
|
||||
void connectionMadeInt();
|
||||
void connectionLostInt();
|
||||
void deliverDataInt (const MemoryBlock&);
|
||||
bool readNextMessageInt();
|
||||
|
||||
struct ConnectionThread;
|
||||
friend struct ConnectionThread;
|
||||
friend struct ContainerDeletePolicy<ConnectionThread>;
|
||||
ScopedPointer<ConnectionThread> thread;
|
||||
void runThread();
|
||||
int writeData (void*, int);
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnection)
|
||||
};
|
||||
|
||||
#endif // JUCE_INTERPROCESSCONNECTION_H_INCLUDED
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
InterprocessConnectionServer::InterprocessConnectionServer()
|
||||
: Thread ("Juce IPC server")
|
||||
{
|
||||
}
|
||||
|
||||
InterprocessConnectionServer::~InterprocessConnectionServer()
|
||||
{
|
||||
stop();
|
||||
}
|
||||
|
||||
//==============================================================================
|
||||
bool InterprocessConnectionServer::beginWaitingForSocket (const int portNumber)
|
||||
{
|
||||
stop();
|
||||
|
||||
socket = new StreamingSocket();
|
||||
|
||||
if (socket->createListener (portNumber))
|
||||
{
|
||||
startThread();
|
||||
return true;
|
||||
}
|
||||
|
||||
socket = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
void InterprocessConnectionServer::stop()
|
||||
{
|
||||
signalThreadShouldExit();
|
||||
|
||||
if (socket != nullptr)
|
||||
socket->close();
|
||||
|
||||
stopThread (4000);
|
||||
socket = nullptr;
|
||||
}
|
||||
|
||||
void InterprocessConnectionServer::run()
|
||||
{
|
||||
while ((! threadShouldExit()) && socket != nullptr)
|
||||
{
|
||||
ScopedPointer<StreamingSocket> clientSocket (socket->waitForNextConnection());
|
||||
|
||||
if (clientSocket != nullptr)
|
||||
if (InterprocessConnection* newConnection = createConnectionObject())
|
||||
newConnection->initialiseWithSocket (clientSocket.release());
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
==============================================================================
|
||||
|
||||
This file is part of the JUCE library.
|
||||
Copyright (c) 2013 - Raw Material Software Ltd.
|
||||
|
||||
Permission is granted to use this software under the terms of either:
|
||||
a) the GPL v2 (or any later version)
|
||||
b) the Affero GPL v3
|
||||
|
||||
Details of these licenses can be found at: www.gnu.org/licenses
|
||||
|
||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
To release a closed-source product which uses JUCE, commercial licenses are
|
||||
available: visit www.juce.com for more information.
|
||||
|
||||
==============================================================================
|
||||
*/
|
||||
|
||||
#ifndef JUCE_INTERPROCESSCONNECTIONSERVER_H_INCLUDED
|
||||
#define JUCE_INTERPROCESSCONNECTIONSERVER_H_INCLUDED
|
||||
|
||||
|
||||
//==============================================================================
|
||||
/**
|
||||
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
|
||||
*/
|
||||
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, in 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.
|
||||
|
||||
@see createConnectionObject, stop
|
||||
*/
|
||||
bool beginWaitingForSocket (int portNumber);
|
||||
|
||||
/** Terminates the listener thread, if it's active.
|
||||
|
||||
@see beginWaitingForSocket
|
||||
*/
|
||||
void stop();
|
||||
|
||||
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:
|
||||
//==============================================================================
|
||||
ScopedPointer <StreamingSocket> socket;
|
||||
|
||||
void run() override;
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InterprocessConnectionServer)
|
||||
};
|
||||
|
||||
|
||||
#endif // JUCE_INTERPROCESSCONNECTIONSERVER_H_INCLUDED
|
||||
Loading…
Add table
Add a link
Reference in a new issue