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);

}

Using pointers or using references?

What is the reason for using pointers if you can use references to get the same job done with less code. and wouldn't less code mean faster runtime of the program, i'm pretty sure that 4 or 5 lines wouldn't make that much of a difference but i'm just curious. and if there is a reasonable use for pointers, when do you decide if you need to use a pointer or a reference?

First we define a simple class
class A
{
public;
A(int x){m_a = x;}
int m_a;
};

Now for a couple of functions..

void by_ref_func(A & a)
{
a.m_a *= 2; //easy to read;
}

void by_ptr_func(A *pa)
{
pa->m_a *= 2; // not so intuitive
}


In main..
int main()
{
A a(5);
by_ref_func(a); //simple function

A *pa = &a //not so simple
by_ptr_func(pa);
return 0;
}
Three reasons to use pointers instead of references:
1. You need to access someone else's functions that require pointers in the parameter list -- most Win322 API functions are like that.

2. You are writing a library of functions that can be called from other languages, such as C, C++, Visual Basic, etc. You will have to make the parameters available to all these languages so the pointer is the only suitable parameter type.

3. You need to interate through a string for some reason -- pointers are more useful here than references. I frankly don't know how this could be done with a reference. For example:
int foo(char buffer[])
{
char *ptr = buffer;
while(*ptr != 0)
{
// do something with the character at the current pointer location
//
// point to the next character
ptr++;
}
}

The reference is a pointer on low level, so the code runs in the same way
as the code with pointers. It will have the same speed and size.

Basically, you need pointers to scan the arrays, as mentioned above. Also,
when you have a collection of polymorphic objects - they can only be the
created with new operator and therefore you keep array (or list) of pointers
to the base class.

Calling Managed code from Native Code

Subject: Calling Managed code from Native Code

In order to support C++ customer to use ManagedLibraryCE Libray, we have to find a way to call managed code from native code.

There are two possible ways to achieve this.

  1. Mixed Code: CLR options; managed C++ and unmanaged C++.

Use managed C++ to call managed code (ManagedLibraryCE Libray). Basically to create a unmanaged C++ DLL wraper around managed DLL.

  1. COM interface method call.

ManagedLibraryCE Libray COM visible and serves as host, then unmanaged C++ DLL wrapper serves as client.

Mixed Code:

  1. Settings:

Project -> … Properties:

Common Properties->Reference: add ManagedLibraryCE.dll

Configuration Properties->General: Project defaults->Common Language Run Time Support: Common Language Run Time Support (/clr)

  1. Code

#using "ManagedLibraryCE.dll"

using namespace ManagedLibraryCE;

ManagedLibraryObj a;

a.Method();

Problem: there is no “/clr” support for Compact Framework (CF). Managed C++ is not the recommended language for smart device development and Microsoft did not and will not support it. C# and VB.NET are the strongly recommenced languages for CF related development.

Reference:

  1. http://msdn2.microsoft.com/en-us/library/x0w2664k(VS.80).aspx
  2. http://forum.soft32.com/pda/Managed-2005-ftopict56624.html

COM interface method call:

  1. Make managed DLL COM visible

Settings:

Project->…Properties: Build: check “Register for COM interop”.

Code:

namespace ManagedDLL

{

public interface ICalculator

{

int Add(int n1, int n2);

}

public class ManagedClass:ICalculator

{

public int Add(int n1, int n2)

{return n1 + n2;}

}

}

Strong name and ComVisible:

To create a strong name for your class library, type the following command at the Visual Studio .NET command prompt: sn.exe -k MyKeyFile.SNK

Copy the MyKeyFile.SNK file to your project folder.

Double-click the AssemblyInfo.cs file to open the file in Solution Explorer.

Replace the following lines of code in the AssemblyInfo.cs file

               [assembly: ComVisible(false)]
               [assembly: AssemblyDelaySign(false)]
               [assembly: AssemblyKeyFile("")]

with the following.

               [assembly: ComVisible(true)] 
               [assembly: AssemblyDelaySign(false)] 
               [assembly: AssemblyKeyFile("..\\..\\MyKeyFile.SNK")]
  1. Calling the Managed DLL from Native C++ Code

Code:

#import "C:\My Projects\Prac\CallingManagedFromUnmanaged\COM\PC\ManagedDLL\ManagedDLL\bin\Debug\ManagedDLL.tlb" raw_interfaces_only

using namespace ManagedDLL;

//initialize COM

HRESULT hr = CoInitialize(NULL);

//create the interface pointer

ICalculatorPtr pICalc(__uuidof(ManagedClass));

long lResult = 0;

//call the Add method

pICalc->Add(5, 10, &lResult);

CString aa;

aa.Format(L"%d", lResult);

MessageBox(aa, L"", MB_OK);

//UnInitized COM

CoUninitialize();

Reference:

  1. http://support.microsoft.com/kb/828736
  2. http://msdn.microsoft.com/library/default.asp?url=/library/en-us/csref/html/vcwlkCOMInteropPart2CServerTutorial.asp
  1. Problem:

For Compact Framework (CF), there is no registration or activation support to make this work.

“There is limited Com Callable Wrappers (CCW) support in CF 2.0. However, there is no registration or activation support in CF 2.0.

What this means is that a managed application can pass managed interfaces to native code, and the native code can treat these interfaces as COM Interface Pointers to make calls back into the managed code. Additionally, CF 2.0 supports passing managed delegates to native code as function pointers which native code can used to call back into managed code. However, there is no support for a purely native app to call into managed code. All pointers need to be passed from managed code to native code before any calls can be made back into managed code.”

---Mike Hall, Microsoft CF team (http://blogs.msdn.com/mikehall/archive/2005/01/26/360936.aspx )

Reference:

a. http://blogs.msdn.com/mikehall/archive/2005/01/26/360936.aspx
b. http://blogs.msdn.com/netcfteam/archive/2005/07/24/442612.aspx