Windows CE 5.0 HTTPD & # 8596; .NET application

I am the most practical way to associate a Windows CE 5.0 device HTTPD web server with a .NET application that runs on the same Windows CE device

My first idea was to create an ISAPI extension that would redirect incoming HTTP requests to a .NET application. I don’t know how to do it! Could it be shared memory, COM, TCP / IP sockets?

Another way would be to implement a standalone HTTP server inside the .NET application itself and avoid using HTTPD.

Any experience or ideas?

Thank you, Chris

+4
source share
2 answers

In my opinion, based on attempts to use the built-in HTTPD server in the past, it was that the built-in server completely sucks for trying to do something useful. This is a serious headache to debug something and interact with any hardware / system device.

Since CF does not support EE hosting, the web server cannot load managed assemblies (from ISAPI or anything else). This means that your managed code must be in a separate process and for communication you need to use IPC - something like P2PMessageQueue , MemoryMappedFile , socket, etc.

Writing your own HTTPD server is also an option, but this is not a trivial task. It sounds simple, but there is a lot of interesting things when you immerse yourself in it. I know that since we made this decision several years ago in the project, and the server that we created supported a subset of ASP.NET, and we actually turned it into a commercial product , because: a) it was really useful and b) because it took him a lot of work to write. However, this gives the advantage of hosting managed code and the ability to debug in Studio instead of printf, as a managed developer expects.

+2
source

The key to running .NET code on the CE web server is loading the server dll into the .NET process. I demonstrated the concept a few years ago to demonstrate this.

Design may look a bit confusing at first glance, but it has several advantages:

  • .NET code and unmanaged ISAPI extensions can work side by side on a web server.
  • Web server features like encryption and authentication still work
  • Resources are still managed by the web server, including the thread pool
  • Efficiency likely outperforms any single process and IPC solution

Firstly, we do not need to automatically start the Windows CE web server automatically. Add this to the registry:

[HKEY_LOCAL_MACHINE\Services\HTTPD] "Flags"=dword:4 ; DEVFLAGS_NOLOAD 

While we are doing this, add another key to display '/ dotnet' to our custom ISAPI handler:

 [HKEY_LOCAL_MACHINE\Comm\HTTPD\VROOTS\/dotnet] @="\\Windows\\HttpdHostUnmanaged.dll" 

Now create a .NET exe called HttpdHostProc.exe from the following source code:

 using System; using System.Runtime.InteropServices; using System.Text; class HttpdHostProc { static void Main(string[] args) { GetExtensionVersionDelegate pInit = new GetExtensionVersionDelegate(GetExtensionVersion); TerminateExtensionDelegate pDeinit = new TerminateExtensionDelegate(TerminateExtension); HttpExtensionProcDelegate pProc = new HttpExtensionProcDelegate(HttpExtensionProc); Init(pInit, pDeinit, pProc); int state = SERVICE_INIT_STOPPED | SERVICE_NET_ADDR_CHANGE_THREAD; int context = HTP_Init(state); HTP_IOControl(context, IOCTL_SERVICE_REGISTER_SOCKADDR, IntPtr.Zero, 0, IntPtr.Zero, 0, IntPtr.Zero); HTP_IOControl(context, IOCTL_SERVICE_STARTED, IntPtr.Zero, 0, IntPtr.Zero, 0, IntPtr.Zero); RunHttpd(context, 80); } static int GetExtensionVersion(IntPtr pVer) { OutputDebugString("GetExtensionVersion from .NET\r\n"); return 1; } static int TerminateExtension(int dwFlags) { OutputDebugString("TerminateExtension from .NET\r\n"); return 1; } static int HttpExtensionProc(IntPtr pECB) { OutputDebugString("HttpExtensionProc from .NET\r\n"); var response = "<html><head></head><body><p>Hello .NET!</p></body></html>"; var bytes = Encoding.UTF8.GetBytes(response); var length = bytes.Length; var unmanagedbuffer = Marshal.AllocHGlobal(length); Marshal.Copy(bytes, 0, unmanagedbuffer, length); var retval = WriteClient(pECB, unmanagedbuffer, ref length); Marshal.FreeHGlobal(unmanagedbuffer); return retval; } delegate int GetExtensionVersionDelegate(IntPtr pVer); delegate int TerminateExtensionDelegate(int dwFlags); delegate int HttpExtensionProcDelegate(IntPtr pECB); [DllImport("HttpdHostUnmanaged.dll", SetLastError = true)] extern static void Init( GetExtensionVersionDelegate pInit, TerminateExtensionDelegate pDeinit, HttpExtensionProcDelegate pProc ); [DllImport("HttpdHostUnmanaged.dll", SetLastError = true)] extern static int RunHttpd(int context, int port); [DllImport("HttpdHostUnmanaged.dll", SetLastError = true)] extern static int WriteClient(IntPtr pECB, IntPtr Buffer, ref int lpdwBytes); [DllImport("coredll.dll")] extern static void OutputDebugString(string msg); [DllImport("httpd.dll", SetLastError = true)] extern static int HTP_Init(int dwData); [DllImport("httpd.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] extern static bool HTP_IOControl(int dwData, int dwCode, IntPtr pBufIn, int dwLenIn, IntPtr pBufOut, int dwLenOut, IntPtr pdwActualOut); const int IOCTL_SERVICE_STARTED = 0x01040038; const int IOCTL_SERVICE_REGISTER_SOCKADDR = 0x0104002c; const int SERVICE_INIT_STOPPED = 0x00000001; const int SERVICE_NET_ADDR_CHANGE_THREAD = 0x00000008; } 

A few comments:

  • The main function loads and initializes our unmanaged dll, which will act as a step between the managed and unmanaged code.
  • Then it initializes and starts the web server, calling its function HTP_Init, and then a couple of ioctls
  • Finally, it calls RunHttpd in our unmanaged dll, which will accept incoming requests and redirect them to the web server.

The three functions below - GetExtensionVersion, TerminateExtension, HttpExtensionProc - should look familiar if you've ever done any ISAPI programs. If not, all you really need to know is that incoming requests are handled by HttpExtensionProc.

Going to an unmanaged dll, HttpdHostUnmanaged.dll:

 #include <winsock2.h> #include <httpext.h> typedef BOOL (* pfHTP_IOControl)(DWORD dwData, DWORD dwCode, PBYTE pBufIn, DWORD dwLenIn, PBYTE pBufOut, DWORD dwLenOut, PDWORD pdwActualOut); typedef BOOL (* PFN_WRITE_CLIENT)(HCONN ConnID, LPVOID Buffer, LPDWORD lpdwBytes, DWORD dwReserved); static PFN_GETEXTENSIONVERSION g_pInit; static PFN_TERMINATEEXTENSION g_pDeinit; static PFN_HTTPEXTENSIONPROC g_pProc; BOOL APIENTRY DllMain(HANDLE, DWORD, LPVOID) { return TRUE; } extern "C" void Init( PFN_GETEXTENSIONVERSION pInit, PFN_TERMINATEEXTENSION pDeinit, PFN_HTTPEXTENSIONPROC pProc) { // Store pointers to .NET delegates g_pInit = pInit; g_pDeinit = pDeinit; g_pProc = pProc; } extern "C" BOOL WINAPI GetExtensionVersion(HSE_VERSION_INFO *pVer) { pVer->dwExtensionVersion = HSE_VERSION; strncpy(pVer->lpszExtensionDesc, "HttpdHostUnmanaged", HSE_MAX_EXT_DLL_NAME_LEN); // Call .NET GetExtensionVersion return g_pInit(pVer); } extern "C" BOOL WINAPI TerminateExtension(DWORD dwFlags) { // Call .NET TerminateExtension return g_pDeinit(dwFlags); } extern "C" DWORD WINAPI HttpExtensionProc(EXTENSION_CONTROL_BLOCK *pECB) { // Call .NET HttpExtensionProc return g_pProc(pECB); } extern "C" DWORD WINAPI WriteClient(EXTENSION_CONTROL_BLOCK *pECB, LPVOID Buffer, LPDWORD lpdwBytes) { return pECB->WriteClient(pECB->ConnID, Buffer, lpdwBytes, 0); } extern "C" int WINAPI RunHttpd(DWORD context, int port) { // Load web server and start accepting connections. // When a connection comes in, // pass it to httpd using IOCTL_SERVICE_CONNECTION. HMODULE hDll = LoadLibrary(L"httpd.dll"); if(!hDll) { return -1; } pfHTP_IOControl Ioctl = (pfHTP_IOControl)GetProcAddress(hDll, L"HTP_IOControl"); if(!Ioctl) { FreeLibrary(hDll); return -2; } WSADATA Data; int status = WSAStartup(MAKEWORD(1, 1), &Data); if(status != 0) { FreeLibrary(hDll); return status; } SOCKET s = socket(PF_INET, SOCK_STREAM, 0); if(s == INVALID_SOCKET) { status = WSAGetLastError(); goto exit; } SOCKADDR_IN sAddr; memset(&sAddr, 0, sizeof(sAddr)); sAddr.sin_port = htons(port); sAddr.sin_family = AF_INET; sAddr.sin_addr.s_addr = htonl(INADDR_ANY); if(bind(s, (LPSOCKADDR)&sAddr, sizeof(sAddr)) == SOCKET_ERROR) { status = WSAGetLastError(); goto exit; } if(listen(s, SOMAXCONN) == SOCKET_ERROR) { status = WSAGetLastError(); goto exit; } for(;;) { SOCKADDR_IN addr; int cbAddr = sizeof(addr); SOCKET conn = accept(s, (LPSOCKADDR)&addr, &cbAddr); if(conn == INVALID_SOCKET) { status = WSAGetLastError(); goto exit; } DWORD IOCTL_SERVICE_CONNECTION = 0x01040034; Ioctl(context, IOCTL_SERVICE_CONNECTION, (PBYTE)&conn, sizeof(conn), NULL, 0, NULL); } exit: FreeLibrary(hDll); if(s != INVALID_SOCKET) { closesocket(s); } WSACleanup(); return status; } 

There are a number of not-so-interesting features that redirect calls to and from .NET.

As mentioned above, the RunHttpd function simply accepts incoming connections and passes them to the web server for processing using another ioctl.

To verify all this, run HttpdHostProc.exe on the device, then open http://<ipaddr>/dotnet in a browser. The CE device should respond with a bit of HTML containing the message "Hello.NET!"

This code runs on Windows Embedded Compact 7.0 with the .NET Compact Framework 3.5, but is likely to work with other versions.

I built an unmanaged dll against Pocket PC 2003 SDK, as this is what I accidentally installed. Probably any Windows CE SDK will do, but you may have to configure the compiler options, for example, I had to build with / GS - (buffer security checks are disabled) for PPC2003.

It is tempting to implement the RunHttpd function in .NET, but be warned that there are several possible problems with this:

  • In my testing, the Handle property on the .NET CF socket returned a pseudo descriptor that did not work with socket APIs
  • The socket lifetime will be controlled by the .NET runtime, which makes it difficult to transfer ownership of the socket to the web server.

If you don't mind compiling with / unsafe, performance can probably be improved a bit by passing fixed buffers to WriteClient rather than copying all the response data to an unmanaged buffer in HttpExtensionProc.

The EXTENSION_CONTROL_BLOCK structure contains a number of useful fields and functions that, obviously, should be included in the full implementation.

EDIT

Just to clarify how requests are handled:

  • Incoming requests are received at RunHttpd, which redirects them to the web server using ioctl
  • According to the introduction of vroot in the registry that we installed earlier, the web server calls HttpdHostUnmanaged.dll to process requests for / dotnet
  • If this is the first request for / dotnet, the web server starts by invoking an unmanaged version of GetExtensionVersion in HttpdHostUnmanaged.dll. The unmanaged GetExtensionVersion returns back to the .NET version of GetExtensionVersion. GetExtensionVersion is a convenient place to initialize any necessary resources, since it is called only once (the corresponding cleaning function is TerminateExtension, which is called when / if the web server decides to unload HttpdHostUnmanaged.dll)
  • The web server then calls the unmanaged HttpExtensionProc
  • Unmanaged HttpExtensionProc accesses the .NET version of HttpExtensionProc
  • Managed HttpExtensionProc generates a response and calls an unmanaged WriteClient to deliver to its client
+3
source

All Articles