Thursday, April 24, 2008

IPC with CF on CE

With no remoting in the Compact Framework or named pipes on Windows CE there are still other ways to achieve Inter-process communication. Here is a list (feel free to tell me if I have missed any):

1. Sockets
2. Memory Mapped Files
3. Windows Messages
4. Point to Point Message Queues
5. MSMQ
6 Out-of-proc COM
7. Named Events + registry
8. Directly Share memory

1. Sockets are accessible to .NET apps via the System.Net.Sockets namespace. You could use a TcpListener (on 127.0.0.1 and some port you fancy) and a TcpClient or even get down to the Socket class which the other two aggregate. The msdn links given are good and as always search the web and newsgroup.

2. CF code for memory mapped files can be found here and an example of their use here

3. Passing windows messages between apps requires a windows handle of course. CF has the Microsoft.WindowsCE.Forms namespace not available on the desktop that includes the MessageWindow class. [This class was necessary to overcome the CF's limitation of not being able to pass delegates to pinvoke and hence achieve windows callbacks - this limitation is no longer in CF 2.0]. Learn about the MessageWindow here, here, here and here.

4. Point-to-Point Message Queues with the .NET Compact Framework

5. Pinvoking MSMQ is not easy (apparently, I haven't tried it myself) and I am aware of no sample code for that. CF 2.0 will support it via the System.Messaging namespace. For further details and a bunch of links on this subject check out these blog entries here.

6. COM interop is not supported in CF. A commercial offer is available. CF 2.0 will have some support for COM Interop but I don't know if out-of-proc servers will be supported. If you know drop me a note.

7. For simple data sharing, it is easy for an app to write data to predefined regisrty entries and signal a named event; at that point the other side can read from the registry. It looks something like this:

// SERVER PROCESS

//write data

private void cmdWrite_Click(object sender, System.EventArgs e) {

IntPtr hWnd = Win32Api.CreateEvent(IntPtr.Zero, true,

false, "YOUR_NAME_HERE");



// TODO write to reg

//e.g. with opennetcf Registry class



Win32Api.SetEvent(hWnd);

System.Threading.Thread.Sleep(500);

Win32Api.CloseHandle(hWnd);

}



// CLIENT PROCESS

private System.Threading.Thread mMonitorThread;

private bool mStayAlive;

private IntPtr mMonitorHwnd;



//read data

private void cmdRead_Click(object sender, System.EventArgs e) {

mStayAlive = true;



mMonitorHwnd = Win32Api.CreateEvent(IntPtr.Zero, true,

false, "YOUR_NAME_HERE");



mMonitorThread = new System.Threading.Thread(

new System.Threading.ThreadStart(

this.MonitorOtherProc));



mMonitorThread.Start();

}



// on background thread so make sure we don't

// touch GUI controls from here

private void MonitorOtherProc(){

while (mStayAlive){

Win32Api.WaitForSingleObject(mMonitorHwnd, -1);

if (mStayAlive == false) return;



MessageBox.Show("Got data "+

DateTime.Now.ToString(), "TODO read from reg");

// TODO read data from reg



Win32Api.ResetEvent(mMonitorHwnd);

}

}



// must call this before closing app - e.g. from Form_Closing

public void Shutdown(){

if (mMonitorThread == null) return;

mStayAlive = false;

Win32Api.SetEvent( mMonitorHwnd);

System.Threading.Thread.Sleep(500);

Win32Api.CloseHandle( mMonitorHwnd);

mMonitorThread = null;

}


8. Directly sharing memory is not advisable but we can do it. The logic is identical to case 7 with named events but instead of writing/reading from registry, we access memory directly. It looks something like this:


// BOTH CLIENT & SERVER PROCESS NEED THESE



// Returns pointer to shared memory

private IntPtr ObtainHandleToSharedMemory(){

const uint PHYS_ADDR =0x80230000;//Make sure this is not used

on your platform

const int MEM_SIZE = 10;



IntPtr hwnd =

Win32Api.VirtualAlloc(0, MEM_SIZE, Win32Api.MEM_RESERVE,

Win32Api.PAGE_READWRITE|Win32Api.PAGE_NOCACHE);

if (hwnd.ToInt32() != 0){

if (Win32Api.VirtualCopy(hwnd, PHYS_ADDR, MEM_SIZE,

(Win32Api.PAGE_READWRITE|Win32Api.PAGE_NOCACHE))

== true){

return hwnd;

}

}

MessageBox.Show(

Marshal.GetLastWin32Error().ToString(),"Failed");

return IntPtr.Zero;

}



// Define common structure/class in both client and server e.g.

private class SharedMemory{

public byte b1;

public byte b2;

public char c;

public bool flag;

public int i;



public SharedMemory(bool aFlag){

flag=aFlag;

if (aFlag){

b1=1;b2=2;c='!';i=3;

}else{

b1=0;b2=0;c=' ';i=0;

}

}



public override string ToString() {

return "b1=" + b1.ToString() + ", b2="+b2.ToString()

+ ", c=" + c + ", i=" + i.ToString();

}

}



// CLIENT

// As in previous example but instead of reading the registry

// read the following in MonitorOtherProc

IntPtr memHwnd=ObtainHandleToSharedMemory();

if (memHwnd.ToInt32() !=0 ){

SharedMemory sm=new SharedMemory(false);

Marshal.PtrToStructure(memHwnd,sm);

MessageBox.Show(sm.ToString(),sm.flag.ToString());

}



// SERVER

// As in previous example but instead of writing to registry

// do the following in cmdWrite_Click

IntPtr memHwnd=ObtainHandleToSharedMemory();

if (memHwnd.ToInt32() !=0 ){

SharedMemory sm=new SharedMemory(true);

Marshal.StructureToPtr(sm,memHwnd,false);

}

No comments: