IPDL/Low level setup
This guide goes through the steps of launching a child process and hooking the created socket into IPDL code. This is not intended to be read casually, and thus is short on friendly explanation.
The low-level setup described here is what went into creating the IPDL test harness, used e.g. here: IPDL/Five minute example. The code lives in ipc/test-harness
.
TODO: this guide was copied from an early draft is not current or correct. FIXME
Contents
- 1 IPDL specification for testing
- 2 Hook the IPDL file into our build system
- 3 Create the C++ implementation stubs
- 4 Hook the C++ stubs into your build configuration
- 5 Create the subprocess class
- 6 Create the child process's main thread
- 7 Get the subprocess to launch
- 8 Make the TestParent/TestChild do stuff
- 9 Run the test-harness
IPDL specification for testing
Put this in the file ipc/test-harness/Test.ipdl
.
namespace mozilla { namespace test { sync protocol Test { both: sync Ping() returns (int status); parent: GetValue(String key); GetValues(StringArray keys); sync SetValue(String key, String val) returns (bool ok); child: TellValue(String key, String val); TellValues(StringArray keys, StringArray vals); state START: recv Ping goto START; send Ping goto START; recv SetValue goto HAVE_VALUES; state HAVE_VALUES: recv Ping goto HAVE_VALUES; send Ping goto HAVE_VALUES; recv SetValue goto HAVE_VALUES; recv GetValue goto TELLING_VALUE; recv GetValues goto TELLING_VALUES; state TELLING_VALUE: send TellValue goto HAVE_VALUES; state TELLING_VALUES: send TellValues goto HAVE_VALUES; }; } // namespace test } // namespace mozilla
Hook the IPDL file into our build system
- Add
ipc/test-harness
to the IPDLDIRS variable inipc/ipdl/Makefile.in
- Create the file
ipc/test-harness/ipdl.mk
and add the following text to it.
IPDLSRCS = \ Test.ipdl \ $(NULL)
Create the C++ implementation stubs
- Run
ipc/test-harness$ python ../ipdl.py -d /tmp Test.ipdl
- Open
/tmp/mozilla/test/TestProtocolParent.h
. Look for the text "Skeleton implementation of abstract actor class."- copy the "Header file contents" into the file
ipc/test-harness/TestParent.h
- make the TestParent constructor and destructor public
- copy the "C++ file contents" into
ipc/test-harness/TestParent.cpp
- globally replace the text
ActorImpl
withTestParent
in both files. - set up namespaces as you wish. The checked-in example puts TestParent in the mozilla::test namespace.
- copy the "Header file contents" into the file
- Repeat the above step for
TestProtocolChild.h
andTestChild.(h, cpp)
Hook the C++ stubs into your build configuration
This is beyond the scope of this guide. See ipc/test-harness/Makefile.in
for an example. Note that if you create a new library to store your code, you will need to add it to toolkit/library/libxul-config.mk
It's a good idea to check now that everything compiles.
Create the subprocess class
This class is instantiated in the main process, and launches and tracks the actual child process.
Create the files ipc/test-harness/TestProcessParent.h
and ipc/test-harness/TestProcessParent.cpp
with the following content.
// TestProcessParent.h #ifndef mozilla_test_TestProcessParent_h #define mozilla_test_TestProcessParent_h 1 #include "mozilla/ipc/GeckoChildProcessHost.h" namespace mozilla { namespace test { class TestProcessParent : mozilla::ipc::GeckoChildProcessHost { public: TestProcessParent(); ~TestProcessParent(); /** * Asynchronously launch the plugin process. */ // Could override parent Launch, but don't need to here //bool Launch(); private: DISALLOW_EVIL_CONSTRUCTORS(TestProcessParent); }; } // namespace plugins } // namespace mozilla #endif // ifndef mozilla_test_TestProcessParent_h
// TestProcessParent.cpp #include "mozilla/test/TestProcessParent.h" using mozilla::ipc::GeckoChildProcessHost; namespace mozilla { namespace test { TestProcessParent::TestProcessParent() : GeckoChildProcessHost(GeckoChildProcess_TestHarness) { } TestProcessParent::~TestProcessParent() { } } // namespace test } // namespace mozilla
Now open nsXULAppAPI.h
and add the new value GeckoChildProcess_TestHarness
to the enumeration GeckoChildProcessType
. (Yes, this sucks. Sorry.)
Hook this new code into your build system and verify that everything still compiles.
Create the child process's main thread
The child process's main thread will hook up the TestChild actor to an IPC channel.
Create the files ipc/test-harness/TestThreadChild.h
and ipc/test-harness/TestThreadChild.cpp
with the following content.
// TestThreadChild.h #ifndef mozilla_test_TestThreadChild_h #define mozilla_test_TestThreadChild_h #include "mozilla/ipc/GeckoThread.h" #include "mozilla/test/TestChild.h" namespace mozilla { namespace test { class TestThreadChild : public mozilla::ipc::GeckoThread { public: TestThreadChild(); ~TestThreadChild(); protected: virtual void Init(); virtual void CleanUp(); private: TestChild mChild; }; } // namespace test } // namespace mozilla #endif // ifndef mozilla_test_TestThreadChild_h
// TestThreadChild.cpp #include "mozilla/test/TestThreadChild.h" using mozilla::test::TestThreadChild; using mozilla::ipc::GeckoThread; TestThreadChild::TestThreadChild() : GeckoThread() { } TestThreadChild::~TestThreadChild() { } void TestThreadChild::Init() { GeckoThread::Init(); mChild.Open(channel(), owner_loop()); } void TestThreadChild::CleanUp() { GeckoThread::CleanUp(); mChild.Close(); }
Hook these into your build and check that everything still compiles.
Open xre/toolkit/nsEmbedFunctions.cpp
. See that
#include "mozilla/test/TestThreadChild.h" using mozilla::test::TestThreadChild;
get added to the file somewhere. Go to the function XRE_InitChildProcess
. Add the following case to the switch.
case GeckoChildProcess_TestHarness: mainThread = new TestThreadChild(); break;
(Yes, this sucks. Sorry)
Get the subprocess to launch
Here we reach a fork in the road of implementation. You can either continue to follow this guide, which will walk through creating a new, dummy, non-Firefox main process, or you can roughly follow the steps here but adapt them so that they end up launching your subprocess class from the existing Firefox process.
REMEMBER: these steps create a new, non-Firefox main process.
Create the directory ipc/test-harness/app
and see that it gets added to the TOOL_DIRS
var in your Makefile. Create the file ipc/test-harness/app/TestApp.cpp
with the following content.
#include "nsXULAppAPI.h" #if defined(XP_WIN) #include <windows.h> #include "nsWindowsWMain.cpp" #endif int main(int argc, char* argv[]) { return XRE_RunIPCTestHarness(argc, argv); }
Now re-open xpcom/build/nsXULAppAPI.h
and add the declaration
XRE_API(int, XRE_RunIPCTestHarness, (int aArgc, char* aArgv[]))
Re-open toolkit/xre/nsEmbedFunctions.cpp
. See that
#include "mozilla/test/TestProcessParent.h" #include "mozilla/test/TestParent.h" using mozilla::test::TestProcessParent; using mozilla::test::TestParent;
get added somewhere in the file. Add the following code.
//----------------------------------------------------------------------------- // TestHarness static void IPCTestHarnessMain(TestProcessParent* subprocess) { TestParent parent; parent.Open(subprocess->GetChannel()); parent.DoStuff(); } static void IPCTestHarnessLaunchSubprocess(TestProcessParent* subprocess, MessageLoop* mainLoop) { NS_ASSERTION(subprocess->Launch(), "can't launch subprocess"); mainLoop->PostTask(FROM_HERE, NewRunnableFunction(IPCTestHarnessMain, subprocess)); } static void IPCTestHarnessPostLaunchSubprocessTask(void* data) { TestProcessParent* subprocess = new TestProcessParent(); MessageLoop* ioLoop = BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO); ioLoop->PostTask(FROM_HERE, NewRunnableFunction(IPCTestHarnessLaunchSubprocess, subprocess, MessageLoop::current())); } int XRE_RunIPCTestHarness(int aArgc, char* aArgv[]) { nsresult rv = XRE_InitParentProcess( aArgc, aArgv, IPCTestHarnessPostLaunchSubprocessTask, NULL); NS_ENSURE_SUCCESS(rv, 1); return 0; }
(Sorry, this code is a little hard to follow.)
This code will not compile because TestParent::DoStuff()
has not been implemented. Everything else is set up though!
Make the TestParent/TestChild do stuff
This is up to your imagination. The following is a quick example. Edit the RecvPing()
function in ipc/test-harness/TestChild.cpp
to be
nsresult TestChild::RecvPing(int* status) { *status = 42; return NS_OK; }
and add this code to ipc/test-harness/TestParent.cpp
.
void TestParent::DoStuff() { int ping; SendPing(&ping); printf("[TestParent] child replied to ping with status code %d\n", ping); }
Suggested further exercises: change ipc/test-harness/Test.ipdl
; add new messages, change the state machines, and so on. See how the generated headers change. Edit the TestParent and TestChild to implement your new messages.
Run the test-harness
If you followed the path to creating a standalone binary, you can run the test harness by executing $objdir/dist/bin/ipctestharness
.
-->