Consider I have this kind of class in process 1. And run process2 whose code is written in completely different file.
class A{
public:
int data;
A()
int f();
}
I want to make instance a
of class A
in process1, send it to process2, and run a.f()
in process2. I studied about IPC mechanism including POSIX shared memory or message queue and read many examples, but the most of examples only expalin how to send single data like integer, or struct without member function.
It is definitely possible to do it in shared memory.
Take for example the following class:
struct A {
A(int initial_value) {
internal_value = initial_value;
}
void write(int value) {
internal_value = value;
}
int read() const {
return internal_value;
}
void watch(int value) {
while (internal_value == value) usleep(1000);
}
std::atomic<int> internal_value;
};
The class uses an atomic counter inside that will be shared between two processes, both reading and writing from it.
Let's first create some shared memory space for it.
int mapsize = getpagesize();
int fid = ::open("/tmp/shared.dat", O_CREAT | O_RDWR, S_IRWXU | S_IRWXG);
ftruncate(fid, mapsize);
void* ptr = ::mmap(nullptr, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fid, 0);
Notice I am not checking the return values for brevity but you should do in your code.
Now with this memory space allocated, let's initialize an object of class "A" inside that memory with placement new.
A* a = new (ptr) A(0); // placement new
Then we fork another process to simulate the IPC mechanism working
pid_t pid = fork();
For the parent process code, we loop from 0 to 10 first waiting for it to be different from zero then 2, 4 etc. Then in the end we wait for the child to finish.
if (pid != 0) {
for (int j = 0; j < 10; j += 2) {
printf("-->parent %d\n", j);
a->watch(j);
a->write(j + 2);
}
printf("Finishing parent\n");
int status;
wait(&status);
For the child process, we write 1 and wait for 1, then write 3 and wait for 3 and so on like the parent but with odd numbers.
} else {
for (int j = 1; j < 10; j += 2) {
printf("-->child %d\n", j);
a->write(j);
a->watch(j);
}
printf("Finishing child\n");
}
In the end of both we unmap the memory and close the file
::munmap(ptr, mapsize);
::close(fid);
It prints:
-->parent 0
-->child 1
-->parent 2
-->child 3
-->parent 4
-->child 5
-->parent 6
-->child 7
-->parent 8
-->child 9
Finishing parent
Finishing child
Compiler Explorer link: https://godbolt.org/z/5orPaEhPM
If you need two independent processes running side by side you just need to be careful with how you create them, synchronize them.
Start the same way opening a file and memory mapping it
int mapsize = getpagesize();
const char* filename = "/tmp/shared.dat";
int fid = ::open(filename, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG);
void* ptr = ::mmap(nullptr, mapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fid, 0);
Notice that at this point if you write or read from the pointer ptr
you will get a segfault.
Then you need to lock the file
while (flock(fid, LOCK_EX) != 0) {
usleep(10000); // sleep a bit
}
Then you need to check the file. If the size is zero, it is uninitialized. If the file is already initialized, you do not use placement new, you just cast the raw pointer.
A* a;
if (getfilesize(fid) == 0) {
ftruncate(fid, mapsize);
a = new (ptr) A(0);
} else {
a = reinterpret_cast<A*>(ptr);
}
After that you read your value and increment it while the file is still locked.
int value = a->read();
value += 1;
a->write(value);
Then you can unlock the file
flock(fid, LOCK_UN); // unlock
Then it's just the same drill of waiting, incrementing and looping, only a tad different.
// Change 5 times
for (int j = 0; j < 5; ++j) {
printf("-->proc %d %d\n", getpid(), value);
a->watch(value);
value += 2;
a->write(value);
}
In the end, just unmap and close as before
::munmap(ptr, mapsize);
::close(fid);
The whole code is here: https://godbolt.org/z/zK3WKWqj4