From ca11fdec82318b3a681eab54d2fb9eef8dc9f15d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B6nke=20Ludwig?= Date: Sun, 11 Jun 2017 11:00:31 +0200 Subject: [PATCH] Implement WinAPI based TCP socket support. --- dub.sdl | 4 +- lib/kernel32.lib | Bin 0 -> 165888 bytes source/eventcore/drivers/winapi/driver.d | 2 +- source/eventcore/drivers/winapi/sockets.d | 528 ++++++++++++++++++++-- source/eventcore/internal/win32.d | 11 +- 5 files changed, 510 insertions(+), 35 deletions(-) create mode 100644 lib/kernel32.lib diff --git a/dub.sdl b/dub.sdl index fdc779f..dbefed8 100644 --- a/dub.sdl +++ b/dub.sdl @@ -33,13 +33,13 @@ configuration "select" { configuration "winapi-optlink" { platforms "windows-x86-dmd" versions "EventcoreWinAPIDriver" - sourceFiles "lib/ws2_32.lib" + sourceFiles "lib/ws2_32.lib" "lib/kernel32.lib" } configuration "select-optlink" { platforms "windows-x86-dmd" versions "EventcoreSelectDriver" - sourceFiles "lib/ws2_32.lib" + sourceFiles "lib/ws2_32.lib" "lib/kernel32.lib" } configuration "libasync" { diff --git a/lib/kernel32.lib b/lib/kernel32.lib new file mode 100644 index 0000000000000000000000000000000000000000..95e734f4a0aa918d0463c638ee27e28d0866c7a0 GIT binary patch literal 165888 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

eq>x5D#_RXo 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 literal 0 HcmV?d00001 diff --git a/source/eventcore/drivers/winapi/driver.d b/source/eventcore/drivers/winapi/driver.d index 97d5afe..a439525 100644 --- a/source/eventcore/drivers/winapi/driver.d +++ b/source/eventcore/drivers/winapi/driver.d @@ -53,7 +53,7 @@ final class WinAPIEventDriver : EventDriver { m_core = new WinAPIEventDriverCore(m_timers); m_events = new WinAPIEventDriverEvents(m_core); m_files = new WinAPIEventDriverFiles(m_core); - m_sockets = new WinAPIEventDriverSockets(); + m_sockets = new WinAPIEventDriverSockets(m_core); m_dns = new WinAPIEventDriverDNS(); m_watchers = new WinAPIEventDriverWatchers(m_core); } diff --git a/source/eventcore/drivers/winapi/sockets.d b/source/eventcore/drivers/winapi/sockets.d index ca83e87..d355e9b 100644 --- a/source/eventcore/drivers/winapi/sockets.d +++ b/source/eventcore/drivers/winapi/sockets.d @@ -3,66 +3,346 @@ module eventcore.drivers.winapi.sockets; version (Windows): import eventcore.driver; +import eventcore.drivers.winapi.core; import eventcore.internal.win32; +import eventcore.internal.utils : AlgebraicChoppedVector, print; import std.socket : Address; +private enum WM_USER_SOCKET = WM_USER + 1; + final class WinAPIEventDriverSockets : EventDriverSockets { @safe: /*@nogc:*/ nothrow: + private { + alias SocketVector = AlgebraicChoppedVector!(SocketSlot, StreamSocketSlot, StreamListenSocketSlot, DgramSocketSlot); + SocketVector m_sockets; + WinAPIEventDriverCore m_core; + DWORD m_tid; + HWND m_hwnd; + size_t m_waiters; + } + + this(WinAPIEventDriverCore core) + @trusted { + m_tid = GetCurrentThreadId(); + m_core = core; + + // setup socket event message window + setupWindowClass(); + m_hwnd = CreateWindowA("VibeWin32MessageWindow", "VibeWin32MessageWindow", 0, 0,0,0,0, HWND_MESSAGE,null,null,null); + SetWindowLongPtrA(m_hwnd, GWLP_USERDATA, cast(ULONG_PTR)cast(void*)this); + assert(cast(WinAPIEventDriverSockets)cast(void*)GetWindowLongPtrA(m_hwnd, GWLP_USERDATA) is this); + } + + package @property size_t waiterCount() const { return m_waiters; } + override StreamSocketFD connectStream(scope Address peer_address, scope Address bind_address, ConnectCallback on_connect) - { - assert(false, "TODO!"); + @trusted { + assert(m_tid == GetCurrentThreadId()); + + auto fd = WSASocketW(peer_address.addressFamily, SOCK_STREAM, IPPROTO_TCP, null, 0, WSA_FLAG_OVERLAPPED); + if (fd == INVALID_SOCKET) + return StreamSocketFD.invalid; + + void invalidateSocket() @nogc @trusted nothrow { closesocket(fd); fd = INVALID_SOCKET; } + + int bret; + if (bind_address !is null) + bret = bind(fd, bind_address.name, bind_address.nameLen); + if (bret != 0) { + invalidateSocket(); + on_connect(StreamSocketFD.invalid, ConnectStatus.bindFailure); + return StreamSocketFD.invalid; + } + + auto sock = adoptStreamInternal(fd); + + auto ret = .connect(fd, peer_address.name, peer_address.nameLen); + //auto ret = WSAConnect(m_socket, peer_address.name, peer_address.nameLen, null, null, null, null); + + if (ret == 0) { + m_sockets[sock].specific.state = ConnectionState.connected; + on_connect(sock, ConnectStatus.connected); + return sock; + } + + auto err = WSAGetLastError(); + if (err == WSAEWOULDBLOCK) { + with (m_sockets[sock].streamSocket) { + connectCallback = on_connect; + state = ConnectionState.connecting; + } + return sock; + } else { + clearSocketSlot(sock); + invalidateSocket(); + on_connect(StreamSocketFD.invalid, ConnectStatus.unknownError); + return StreamSocketFD.invalid; + } } override StreamSocketFD adoptStream(int socket) { - assert(false, "TODO!"); + return adoptStreamInternal(socket); + } + + private StreamSocketFD adoptStreamInternal(SOCKET socket) + { + auto fd = StreamSocketFD(socket); + if (m_sockets[fd].common.refCount) // FD already in use? + return StreamSocketFD.invalid; + + uint enable = 1; + () @trusted { ioctlsocket(socket, FIONBIO, &enable); } (); + + void setupOverlapped(ref WSAOVERLAPPEDX overlapped) @trusted @nogc nothrow { + overlapped.Internal = 0; + overlapped.InternalHigh = 0; + overlapped.Offset = 0; + overlapped.OffsetHigh = 0; + overlapped.hEvent = cast(HANDLE)cast(void*)&m_sockets[socket]; + } + + initSocketSlot(fd); + with (m_sockets[socket]) { + specific = StreamSocketSlot.init; + setupOverlapped(streamSocket.write.overlapped); + setupOverlapped(streamSocket.read.overlapped); + } + + () @trusted { WSAAsyncSelect(socket, m_hwnd, WM_USER_SOCKET, FD_READ|FD_WRITE|FD_CONNECT|FD_CLOSE); } (); + + return fd; } alias listenStream = EventDriverSockets.listenStream; override StreamListenSocketFD listenStream(scope Address bind_address, StreamListenOptions options, AcceptCallback on_accept) { - assert(false, "TODO!"); + auto fd = () @trusted { return WSASocketW(bind_address.addressFamily, SOCK_STREAM, IPPROTO_TCP, null, 0, WSA_FLAG_OVERLAPPED); } (); + if (fd == INVALID_SOCKET) + return StreamListenSocketFD.invalid; + + void invalidateSocket() @nogc @trusted nothrow { closesocket(fd); fd = INVALID_SOCKET; } + + () @trusted { + int tmp_reuse = 1; + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &tmp_reuse, tmp_reuse.sizeof) != 0) { + invalidateSocket(); + return; + } + + // FIXME: should SO_EXCLUSIVEADDRUSE be used of StreamListenOptions.reuseAddress isn't set? + + if (bind(fd, bind_address.name, bind_address.nameLen) != 0) { + invalidateSocket(); + return; + } + if (listen(fd, 128) != 0) { + invalidateSocket(); + return; + } + } (); + + if (fd == INVALID_SOCKET) + return StreamListenSocketFD.invalid; + + auto sock = cast(StreamListenSocketFD)fd; + initSocketSlot(sock); + m_sockets[sock].specific = StreamListenSocketSlot.init; + + if (on_accept) waitForConnections(sock, on_accept); + + return sock; } override void waitForConnections(StreamListenSocketFD sock, AcceptCallback on_accept) { - assert(false, "TODO!"); + assert(!m_sockets[sock].streamListen.acceptCallback); + m_sockets[sock].streamListen.acceptCallback = on_accept; + () @trusted { WSAAsyncSelect(sock, m_hwnd, WM_USER_SOCKET, FD_ACCEPT); } (); + m_core.addWaiter(); } override ConnectionState getConnectionState(StreamSocketFD sock) { - assert(false, "TODO!"); + return m_sockets[sock].streamSocket.state; } override bool getLocalAddress(SocketFD sock, scope RefAddress dst) { - assert(false, "TODO!"); + socklen_t addr_len = dst.nameLen; + if (() @trusted { return getsockname(sock, dst.name, &addr_len); } () != 0) + return false; + dst.cap(addr_len); + return true; } override bool getRemoteAddress(SocketFD sock, scope RefAddress dst) { - assert(false, "TODO!"); + socklen_t addr_len = dst.nameLen; + if (() @trusted { return getpeername(sock, dst.name, &addr_len); } () != 0) + return false; + dst.cap(addr_len); + return true; } override void setTCPNoDelay(StreamSocketFD socket, bool enable) - { - assert(false, "TODO!"); + @trusted { + BOOL eni = enable; + setsockopt(INVALID_SOCKET, IPPROTO_TCP, TCP_NODELAY, &eni, eni.sizeof); } override void setKeepAlive(StreamSocketFD socket, bool enable) - { - assert(false, "TODO!"); + @trusted { + BOOL eni = enable; + setsockopt(INVALID_SOCKET, SOL_SOCKET, SO_KEEPALIVE, &eni, eni.sizeof); } override void read(StreamSocketFD socket, ubyte[] buffer, IOMode mode, IOCallback on_read_finish) { - assert(false, "TODO!"); + WSABUF buf; + buf.len = buffer.length; + buf.buf = () @trusted { return buffer.ptr; } (); + + m_sockets[socket].streamSocket.read.buffer = buffer; + m_sockets[socket].streamSocket.read.mode = mode; + + auto ovl = mode == IOMode.immediate ? null : &m_sockets[socket].streamSocket.read.overlapped; + DWORD flags = 0; + auto ret = () @trusted { return WSARecv(socket, &buf, 1, null, &flags, ovl, &onIOReadCompleted); } (); + if (ret == SOCKET_ERROR) { + auto err = WSAGetLastError(); + if (err == WSA_IO_PENDING) { + if (mode == IOMode.immediate) { + on_read_finish(socket, IOStatus.wouldBlock, 0); + return; + } + } else { + on_read_finish(socket, IOStatus.error, 0); + return; + } + } + m_sockets[socket].streamSocket.read.callback = on_read_finish; + m_core.addWaiter(); + } + + + private static extern(System) nothrow + void onIOReadCompleted(DWORD dwError, DWORD cbTransferred, WSAOVERLAPPEDX* lpOverlapped, DWORD dwFlags) + { + auto slot = () @trusted { return cast(SocketVector.FullField*)lpOverlapped.hEvent; } (); + + if (!slot.streamSocket.read.callback) return; + + void invokeCallback(IOStatus status, size_t nsent) + @safe nothrow { + slot.common.core.removeWaiter(); + auto cb = slot.streamSocket.read.callback; + slot.streamSocket.read.callback = null; + cb(cast(StreamSocketFD)slot.common.fd, status, nsent); + } + + slot.streamSocket.read.bytesTransferred += cbTransferred; + slot.streamSocket.read.buffer = slot.streamSocket.read.buffer[cbTransferred .. $]; + + if (dwError) { + invokeCallback(IOStatus.error, 0); + return; + } + + if (slot.streamSocket.read.mode == IOMode.once || !slot.streamSocket.read.buffer.length) { + invokeCallback(IOStatus.ok, cbTransferred); + return; + } + + WSABUF buf; + buf.len = slot.streamSocket.read.buffer.length; + buf.buf = () @trusted { return cast(ubyte*)slot.streamSocket.read.buffer.ptr; } (); + auto ovl = slot.streamSocket.read.mode == IOMode.immediate ? null : &slot.streamSocket.read.overlapped; + DWORD flags = 0; + auto ret = () @trusted { return WSARecv(slot.common.fd, &buf, 1, null, &flags, ovl, &onIOReadCompleted); } (); + if (ret == SOCKET_ERROR) { + auto err = WSAGetLastError(); + if (err == WSA_IO_PENDING) { + if (slot.streamSocket.read.mode == IOMode.immediate) { + invokeCallback(IOStatus.wouldBlock, 0); + } + } else { + invokeCallback(IOStatus.error, 0); + } + } } override void write(StreamSocketFD socket, const(ubyte)[] buffer, IOMode mode, IOCallback on_write_finish) { - assert(false, "TODO!"); + WSABUF buf; + buf.len = buffer.length; + buf.buf = () @trusted { return cast(ubyte*)buffer.ptr; } (); + + m_sockets[socket].streamSocket.write.buffer = buffer; + m_sockets[socket].streamSocket.write.mode = mode; + + auto ovl = mode == IOMode.immediate ? null : &m_sockets[socket].streamSocket.write.overlapped; + auto ret = () @trusted { return WSASend(socket, &buf, 1, null, 0, ovl, &onIOWriteCompleted); } (); + if (ret == SOCKET_ERROR) { + auto err = WSAGetLastError(); + if (err == WSA_IO_PENDING) { + if (mode == IOMode.immediate) { + on_write_finish(socket, IOStatus.wouldBlock, 0); + return; + } + } else { + on_write_finish(socket, IOStatus.error, 0); + return; + } + } + m_sockets[socket].streamSocket.write.callback = on_write_finish; + m_core.addWaiter(); + } + + private static extern(System) nothrow + void onIOWriteCompleted(DWORD dwError, DWORD cbTransferred, WSAOVERLAPPEDX* lpOverlapped, DWORD dwFlags) + { + auto slot = () @trusted { return cast(SocketVector.FullField*)lpOverlapped.hEvent; } (); + + if (!slot.streamSocket.write.callback) return; + + void invokeCallback(IOStatus status, size_t nsent) + @safe nothrow { + slot.common.core.removeWaiter(); + auto cb = slot.streamSocket.write.callback; + slot.streamSocket.write.callback = null; + cb(cast(StreamSocketFD)slot.common.fd, status, nsent); + } + + slot.streamSocket.write.bytesTransferred += cbTransferred; + slot.streamSocket.write.buffer = slot.streamSocket.write.buffer[cbTransferred .. $]; + + if (dwError) { + invokeCallback(IOStatus.error, 0); + return; + } + + if (slot.streamSocket.write.mode == IOMode.once || !slot.streamSocket.write.buffer.length) { + invokeCallback(IOStatus.ok, cbTransferred); + return; + } + + WSABUF buf; + buf.len = slot.streamSocket.write.buffer.length; + buf.buf = () @trusted { return cast(ubyte*)slot.streamSocket.write.buffer.ptr; } (); + auto ovl = slot.streamSocket.write.mode == IOMode.immediate ? null : &slot.streamSocket.write.overlapped; + auto ret = () @trusted { return WSASend(slot.common.fd, &buf, 1, null, 0, ovl, &onIOWriteCompleted); } (); + if (ret == SOCKET_ERROR) { + auto err = WSAGetLastError(); + if (err == WSA_IO_PENDING) { + if (slot.streamSocket.write.mode == IOMode.immediate) { + invokeCallback(IOStatus.wouldBlock, 0); + } + } else { + invokeCallback(IOStatus.error, 0); + } + } } override void waitForData(StreamSocketFD socket, IOCallback on_data_available) @@ -72,17 +352,26 @@ final class WinAPIEventDriverSockets : EventDriverSockets { override void shutdown(StreamSocketFD socket, bool shut_read = true, bool shut_write = true) { - assert(false, "TODO!"); + () @trusted { WSASendDisconnect(socket, null); } (); + with (m_sockets[socket].streamSocket) { + state = ConnectionState.closed; + } } override void cancelRead(StreamSocketFD socket) - { - assert(false, "TODO!"); + @trusted { + if (!m_sockets[socket].streamSocket.read.callback) return; + CancelIoEx(cast(HANDLE)cast(SOCKET)socket, cast(LPOVERLAPPED)&m_sockets[socket].streamSocket.read.overlapped); + m_sockets[socket].streamSocket.read.callback = null; + m_core.removeWaiter(); } override void cancelWrite(StreamSocketFD socket) - { - assert(false, "TODO!"); + @trusted { + if (!m_sockets[socket].streamSocket.write.callback) return; + CancelIoEx(cast(HANDLE)cast(SOCKET)socket, cast(LPOVERLAPPED)&m_sockets[socket].streamSocket.write.overlapped); + m_sockets[socket].streamSocket.write.callback = null; + m_core.removeWaiter(); } override DatagramSocketFD createDatagramSocket(scope Address bind_address, scope Address target_address) @@ -125,23 +414,206 @@ final class WinAPIEventDriverSockets : EventDriverSockets { assert(false, "TODO!"); } - override void addRef(SocketFD descriptor) + override void addRef(SocketFD fd) { - assert(false, "TODO!"); + assert(m_sockets[fd].common.refCount > 0, "Adding reference to unreferenced socket FD."); + m_sockets[fd].common.refCount++; } - override bool releaseRef(SocketFD descriptor) + override bool releaseRef(SocketFD fd) { - assert(false, "TODO!"); + import taggedalgebraic : hasType; + assert(m_sockets[fd].common.refCount > 0, "Releasing reference to unreferenced socket FD."); + if (--m_sockets[fd].common.refCount == 0) { + final switch (m_sockets[fd].specific.kind) with (SocketVector.FieldType) { + case Kind.none: break; + case Kind.streamSocket: + cancelRead(cast(StreamSocketFD)fd); + cancelWrite(cast(StreamSocketFD)fd); + break; + case Kind.streamListen: + if (m_sockets[fd].streamListen.acceptCallback) + m_core.removeWaiter(); + break; + case Kind.datagramSocket: + cancelReceive(cast(DatagramSocketFD)fd); + cancelSend(cast(DatagramSocketFD)fd); + break; + } + + clearSocketSlot(fd); + () @trusted { closesocket(fd); } (); + return false; + } + return true; } - protected override void* rawUserData(StreamSocketFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy) @system - { - assert(false, "TODO!"); + final protected override void* rawUserData(StreamSocketFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy) + @system { + return rawUserDataImpl(descriptor, size, initialize, destroy); } - protected override void* rawUserData(DatagramSocketFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy) @system + final protected override void* rawUserData(DatagramSocketFD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy) + @system { + return rawUserDataImpl(descriptor, size, initialize, destroy); + } + + private void* rawUserDataImpl(FD descriptor, size_t size, DataInitializer initialize, DataInitializer destroy) + @system { + SocketSlot* fds = &m_sockets[descriptor].common; + assert(fds.userDataDestructor is null || fds.userDataDestructor is destroy, + "Requesting user data with differing type (destructor)."); + assert(size <= SocketSlot.userData.length, "Requested user data is too large."); + if (size > SocketSlot.userData.length) assert(false); + if (!fds.userDataDestructor) { + initialize(fds.userData.ptr); + fds.userDataDestructor = destroy; + } + return m_sockets[descriptor].common.userData.ptr; + } + + private void initSocketSlot(SocketFD fd) { - assert(false, "TODO!"); + m_sockets[fd.value].common.refCount = 1; + m_sockets[fd.value].common.fd = fd; + m_sockets[fd.value].common.core = m_core; + } + + package void clearSocketSlot(FD fd) + { + auto slot = () @trusted { return &m_sockets[fd]; } (); + if (slot.common.userDataDestructor) + () @trusted { slot.common.userDataDestructor(slot.common.userData.ptr); } (); + *slot = m_sockets.FullField.init; + } + + private static nothrow extern(System) + LRESULT onMessage(HWND wnd, UINT msg, WPARAM wparam, LPARAM lparam) + { + auto driver = () @trusted { return cast(WinAPIEventDriverSockets)cast(void*)GetWindowLongPtrA(wnd, GWLP_USERDATA); } (); + switch(msg){ + default: break; + case WM_USER_SOCKET: + SOCKET sock = cast(SOCKET)wparam; + auto evt = () @trusted { return LOWORD(lparam); } (); + auto err = () @trusted { return HIWORD(lparam); } (); + auto slot = () @trusted { return &driver.m_sockets[sock]; } (); + final switch (slot.specific.kind) with (SocketVector.FieldType) { + case Kind.none: break; + case Kind.streamSocket: + switch (evt) { + default: break; + case FD_CONNECT: + auto cb = slot.streamSocket.connectCallback; + slot.streamSocket.connectCallback = null; + if (err) { + slot.streamSocket.state = ConnectionState.closed; + cb(cast(StreamSocketFD)sock, ConnectStatus.refused); + } else { + slot.streamSocket.state = ConnectionState.connected; + cb(cast(StreamSocketFD)sock, ConnectStatus.connected); + } + break; + case FD_READ: + break; + case FD_WRITE: + break; + } + break; + case Kind.streamListen: + if (evt == FD_ACCEPT) { + /* + sock_t sockfd; + sockaddr_storage addr; + socklen_t addr_len = addr.sizeof; + () @trusted { sockfd = accept(cast(sock_t)listenfd, () @trusted { return cast(sockaddr*)&addr; } (), &addr_len); } (); + if (sockfd == -1) break; + + setSocketNonBlocking(cast(SocketFD)sockfd); + auto fd = cast(StreamSocketFD)sockfd; + initSocketSlot(fd); + m_sockets[fd].specific = StreamSocketSlot.init; + m_sockets[fd].streamSocket.state = ConnectionState.connected; + m_loop.registerFD(fd, EventMask.read|EventMask.write|EventMask.status); + m_loop.setNotifyCallback!(EventType.status)(fd, &onConnectError); + releaseRef(fd); // setNotifyCallback adds a reference, but waiting for status/disconnect should not affect the ref count + //print("accept %d", sockfd); + scope RefAddress addrc = new RefAddress(() @trusted { return cast(sockaddr*)&addr; } (), addr_len); + m_sockets[listenfd].streamListen.acceptCallback(cast(StreamListenSocketFD)listenfd, fd, addrc); + */ + SOCKADDR_STORAGE addr; + socklen_t addr_len = addr.sizeof; + auto clientsockfd = () @trusted { return WSAAccept(sock, cast(sockaddr*)&addr, &addr_len, null, 0); } (); + if (clientsockfd == INVALID_SOCKET) return 0; + auto clientsock = driver.adoptStreamInternal(clientsockfd); + scope RefAddress addrc = new RefAddress(() @trusted { return cast(sockaddr*)&addr; } (), addr_len); + slot.streamListen.acceptCallback(cast(StreamListenSocketFD)sock, clientsock, addrc); + } + break; + case Kind.datagramSocket: + break; + } + return 0; + } + return () @trusted { return DefWindowProcA(wnd, msg, wparam, lparam); } (); } } + +void setupWindowClass() nothrow +@trusted { + static __gshared registered = false; + + if (registered) return; + + WNDCLASSA wc; + wc.lpfnWndProc = &WinAPIEventDriverSockets.onMessage; + wc.lpszClassName = "VibeWin32MessageWindow"; + RegisterClassA(&wc); + registered = true; +} + +static struct SocketSlot { + SocketFD fd; // redundant, but needed by the current IO Completion Routines based approach + WinAPIEventDriverCore core; // redundant, but needed by the current IO Completion Routines based approach + int refCount; + DataInitializer userDataDestructor; + ubyte[16*size_t.sizeof] userData; +} + +private struct StreamSocketSlot { + alias Handle = StreamSocketFD; + StreamDirection!true write; + StreamDirection!false read; + ConnectCallback connectCallback; + ConnectionState state; +} + +static struct StreamDirection(bool RO) { + WSAOVERLAPPEDX overlapped; + static if (RO) const(ubyte)[] buffer; + else ubyte[] buffer; + size_t bytesTransferred; + IOMode mode; + IOCallback callback; +} + +private struct StreamListenSocketSlot { + alias Handle = StreamListenSocketFD; + AcceptCallback acceptCallback; +} + +private struct DgramSocketSlot { + alias Handle = DatagramSocketFD; + DgramDirection!true write; + DgramDirection!false read; + Address targetAddr; +} + +static struct DgramDirection(bool RO) { + WSAOVERLAPPEDX overlapped; + static if (RO) const(ubyte)[] buffer; + else ubyte[] buffer; + size_t bytesTransferred; + IOMode mode; + DatagramIOCallback callback; +} diff --git a/source/eventcore/internal/win32.d b/source/eventcore/internal/win32.d index ce93a87..a2ba839 100644 --- a/source/eventcore/internal/win32.d +++ b/source/eventcore/internal/win32.d @@ -5,7 +5,7 @@ version(Windows): public import core.sys.windows.windows; public import core.sys.windows.winsock2; -extern(System) nothrow @nogc: +extern(System) nothrow: enum { FD_READ = 0x0001, @@ -130,22 +130,25 @@ alias LPLOOKUPSERVICE_COMPLETION_ROUTINE = void function(DWORD, DWORD, WSAOVERLA alias LPCONDITIONPROC = void*; alias LPTRANSMIT_FILE_BUFFERS = void*; +int WSARecv(SOCKET s, WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesRecvd, DWORD* lpFlags, in WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); +int WSASend(SOCKET s, in WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesSent, DWORD dwFlags, in WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); +int GetAddrInfoExW(LPCWSTR pName, LPCWSTR pServiceName, DWORD dwNameSpace, GUID* lpNspId, const ADDRINFOEXW *pHints, ADDRINFOEXW **ppResult, timeval *timeout, WSAOVERLAPPEDX* lpOverlapped, LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, HANDLE* lpNameHandle); + +@nogc: SOCKET WSAAccept(SOCKET s, sockaddr *addr, INT* addrlen, LPCONDITIONPROC lpfnCondition, DWORD_PTR dwCallbackData); int WSAAsyncSelect(SOCKET s, HWND hWnd, uint wMsg, sizediff_t lEvent); SOCKET WSASocketW(int af, int type, int protocol, WSAPROTOCOL_INFOW *lpProtocolInfo, uint g, DWORD dwFlags); -int WSARecv(SOCKET s, WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesRecvd, DWORD* lpFlags, in WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); -int WSASend(SOCKET s, in WSABUF* lpBuffers, DWORD dwBufferCount, DWORD* lpNumberOfBytesSent, DWORD dwFlags, in WSAOVERLAPPEDX* lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINEX lpCompletionRoutine); int WSASendDisconnect(SOCKET s, WSABUF* lpOutboundDisconnectData); INT WSAStringToAddressA(in LPTSTR AddressString, INT AddressFamily, in WSAPROTOCOL_INFO* lpProtocolInfo, SOCKADDR* lpAddress, INT* lpAddressLength); INT WSAStringToAddressW(in LPWSTR AddressString, INT AddressFamily, in WSAPROTOCOL_INFOW* lpProtocolInfo, SOCKADDR* lpAddress, INT* lpAddressLength); INT WSAAddressToStringW(in SOCKADDR* lpsaAddress, DWORD dwAddressLength, in WSAPROTOCOL_INFO* lpProtocolInfo, LPWSTR lpszAddressString, DWORD* lpdwAddressStringLength); -int GetAddrInfoExW(LPCWSTR pName, LPCWSTR pServiceName, DWORD dwNameSpace, GUID* lpNspId, const ADDRINFOEXW *pHints, ADDRINFOEXW **ppResult, timeval *timeout, WSAOVERLAPPEDX* lpOverlapped, LPLOOKUPSERVICE_COMPLETION_ROUTINE lpCompletionRoutine, HANDLE* lpNameHandle); int GetAddrInfoW(LPCWSTR pName, LPCWSTR pServiceName, const ADDRINFOW *pHints, ADDRINFOW **ppResult); int getaddrinfo(LPCSTR pName, LPCSTR pServiceName, const ADDRINFOA *pHints, ADDRINFOA **ppResult); void FreeAddrInfoW(ADDRINFOW* pAddrInfo); void FreeAddrInfoExW(ADDRINFOEXW* pAddrInfo); void freeaddrinfo(ADDRINFOA* ai); BOOL TransmitFile(SOCKET hSocket, HANDLE hFile, DWORD nNumberOfBytesToWrite, DWORD nNumberOfBytesPerSend, OVERLAPPED* lpOverlapped, LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, DWORD dwFlags); +BOOL CancelIoEx(HANDLE hFile, LPOVERLAPPED lpOverlapped); struct WSAOVERLAPPEDX { ULONG_PTR Internal;