Ключом к запуску кода .NET на веб-сервере CE является загрузка dll сервера в процесс .NET.Несколько лет назад я проверил концепцию концепции, чтобы продемонстрировать это.
На первый взгляд дизайн может показаться немного запутанным, но имеет несколько преимуществ:
- .NET-код и неуправляемыйРасширения ISAPI могут работать бок о бок на веб-сервере
- Функции веб-сервера, такие как шифрование и аутентификация, все еще работают
- Ресурсы по-прежнему управляются веб-сервером, включая пул потоков
- По производительности, вероятно, превосходит любое решение, основанное на отдельных процессах и IPC
Во-первых, нам нужно запретить Windows CE автоматически запускать веб-сервер.Добавьте это в реестр:
[HKEY_LOCAL_MACHINE\Services\HTTPD]
"Flags"=dword:4 ; DEVFLAGS_NOLOAD
Пока мы это делаем, добавьте еще один ключ для сопоставления '/ dotnet' в наш пользовательский обработчик ISAPI:
[HKEY_LOCAL_MACHINE\Comm\HTTPD\VROOTS\/dotnet]
@="\\Windows\\HttpdHostUnmanaged.dll"
Теперь создайте.NET exe называется HttpdHostProc.exe из следующего исходного кода:
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;
}
Несколько комментариев:
- Функция Main загружает и инициализирует нашу неуправляемую dll, которая будет действовать как пошаговыйкамень между управляемым и неуправляемым кодом
- Затем он инициализирует и запускает веб-сервер, вызывая его функцию HTP_Init, а затем пару ioctls
- Наконец, он вызывает RunHttpd в нашем неуправляемом dll, который будетпринимать входящие запросы и пересылать их на веб-сервер.
Три функции далее - GetExtensionVersion, TerminateExtension, HttpExtensionProc - должны выглядеть знакомо, если вы когда-либо занимались программированием ISAPI.Если нет, все, что вам действительно нужно знать, это то, что входящие запросы обрабатываются HttpExtensionProc.
Переход к неуправляемой 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;
}
Есть ряд неТам очень интересные функции, которые пересылают вызовы в .NET.
Как упоминалось выше, функция RunHttpd просто принимает входящие соединения и передает их на веб-сервер для обработки с помощью другого ioctl.
Чтобы проверить все это, запустите HttpdHostProc.exe на устройстве, затем откройте http://<ipaddr>/dotnet
в браузере.Устройство CE должно ответить небольшим HTML-кодом, содержащим сообщение «Hello .NET!».
Этот код работает в Windows Embedded Compact 7.0 с .NET Compact Framework 3.5, но, вероятно, будет работать и в других версиях..
Я собрал неуправляемую dll для Pocket PC 2003 SDK, так как я установил именно это.Вероятно, подойдет любой Windows CE SDK, но вам, возможно, придется изменить настройки компилятора, например, мне пришлось собирать с / GS- (проверки безопасности буфера отключены) для PPC2003.
Соблазнительно реализовать функцию RunHttpdв .NET, но имейте в виду, что есть несколько потенциальных проблем:
- В моем тестировании свойство Handle в сокете .NET CF возвратило своего рода псевдо-дескриптор, которыйне работал с собственными API сокетов
- Время жизни сокета будет управляться средой выполнения .NET, что особенно затруднит передачу владения сокетом веб-серверу
Если вы не возражаете против компиляции с / unsafe, производительность, возможно, можно немного улучшить, передав фиксированные буферы в WriteClient, а не копируя все данные ответов в неуправляемый буфер в HttpExtensionProc.
Структура EXTENSION_CONTROL_BLOCK содержит числополезных полей и функций, которые, очевидно, должны быть включеныг в полной реализации.
РЕДАКТИРОВАТЬ
Просто чтобы уточнить, как обрабатываются запросы:
- Входящие запросы принимаются в RunHttpd, которыйперенаправляет их на веб-сервер с помощью ioctl
- В соответствии с записью vroot в реестре, которую мы настроили ранее, веб-сервер вызывает HttpdHostUnmanaged.dll для обработки запросов на / dotnet
- Если это первый запрос для / dotnet, веб-сервер запускается с вызова неуправляемой версии GetExtensionVersion в HttpdHostUnmanaged.dll. Неуправляемый GetExtensionVersion вызывает обратно .NET-версию GetExtensionVersion. GetExtensionVersion - это удобное место для инициализации любых необходимых ресурсов, так как он вызывается только один раз (соответствующая функция очистки - TerminateExtension, которая вызывается, когда / если веб-сервер решает выгрузить HttpdHostUnmanaged.dll)
- Затем веб-сервер вызывает неуправляемый HttpExtensionProc
- Неуправляемый HttpExtensionProc вызывает обратно .NET-версию HttpExtensionProc
- Управляемый HttpExtensionProc генерирует ответ и вызывает неуправляемый WriteClient для доставки его клиенту