From 63e46d35474fad5bb987e6f3e3aa565732a1a0ce Mon Sep 17 00:00:00 2001 From: vczh Date: Fri, 5 Jun 2026 00:46:06 -0700 Subject: [PATCH] Refresh release imports and tutorial --- Import/GacUI.UnitTest.cpp | 12 +- Import/GacUI.Windows.cpp | 250 ++ Import/GacUI.Windows.h | 297 +- Import/GacUI.cpp | 1890 ++++++++++- Import/GacUI.h | 532 ++- Import/VlppGlrParser.cpp | 55 + Import/VlppGlrParser.h | 22 + Import/VlppOS.Windows.cpp | 3018 +++++++++-------- Import/VlppOS.Windows.h | 386 ++- Import/VlppOS.h | 580 ++-- Import/VlppReflection.h | 101 +- Import/VlppWorkflowCompiler.cpp | 426 ++- Import/VlppWorkflowCompiler.h | 1 + Import/VlppWorkflowLibrary.cpp | 1731 +++++++--- Import/VlppWorkflowLibrary.h | 261 +- Tools/Reflection32.bin | Bin 1061230 -> 1062062 bytes Tools/Reflection64.bin | Bin 1061230 -> 1062062 bytes .../GacUI_Controls/ProgressAndAsync/Main.cpp | 31 +- 18 files changed, 6777 insertions(+), 2816 deletions(-) diff --git a/Import/GacUI.UnitTest.cpp b/Import/GacUI.UnitTest.cpp index ed9397f8..0a00743d 100644 --- a/Import/GacUI.UnitTest.cpp +++ b/Import/GacUI.UnitTest.cpp @@ -2110,9 +2110,9 @@ void GacUIUnitTest_Start(const WString& appName, Nullable } template -void RunInNewThread(T&& threadProc) +Thread* RunInNewThread(T&& threadProc) { - Thread::CreateAndStart([threadProc]() + return Thread::CreateAndStart([threadProc]() { try { @@ -2128,7 +2128,7 @@ void RunInNewThread(T&& threadProc) (void)e; throw; } - }); + }, false); } void GacUIUnitTest_StartAsync(const WString& appName, Nullable config) @@ -2147,11 +2147,11 @@ void GacUIUnitTest_StartAsync(const WString& appName, NullableConnectLocalClient(rendererClient) > 0); channeling::GuiRemoteProtocolRendererChannel rendererChannel(rendererClient.Obj(), rendererClient->GetProtocolChannel(), unitTestProtocol.GetProtocol()); - channeling::GuiRemoteProtocolAsyncJsonChannelSerializer asyncChannelSender(coreClient->GetProtocolChannel(), unitTestProtocol.GetRemoteEventProcessor()); + channeling::GuiRemoteProtocolJsonChannelRenderer_Async asyncChannelSender(coreClient->GetProtocolChannel(), unitTestProtocol.GetRemoteEventProcessor()); EventObject eventStopped; TEST_ASSERT(eventStopped.CreateManualUnsignal(false)); - RunInNewThread([&]() + auto coreThread = RunInNewThread([&]() { channeling::GuiRemoteProtocolCoreChannel channelSender(coreClient.Obj(), &asyncChannelSender, config.Value().executablePath, asyncChannelSender.GetRemoteEventProcessor()); @@ -2171,6 +2171,8 @@ void GacUIUnitTest_StartAsync(const WString& appName, NullableWait(); + delete coreThread; TEST_ASSERT(!ExceptionOccuredUnderUnitTestReleaseMode()); GacUIUnitTest_LogUI(appName, unitTestProtocol); diff --git a/Import/GacUI.Windows.cpp b/Import/GacUI.Windows.cpp index aed542d1..3478623e 100644 --- a/Import/GacUI.Windows.cpp +++ b/Import/GacUI.Windows.cpp @@ -7,6 +7,7 @@ DEVELOPER: Zihan Chen(vczh) /*********************************************************************** .\WINNATIVEDPIAWARENESS.CPP ***********************************************************************/ +#include #pragma comment(lib, "Shcore.lib") @@ -201,6 +202,8 @@ WindowsForm class WindowsForm : public Object, public INativeWindow, public IWindowsForm { + template + friend class WindowsAutomationServiceBase; protected: LONG_PTR InternalGetExStyle() @@ -1855,6 +1858,7 @@ WindowsController class WindowsController : public Object, public virtual INativeController, public virtual INativeWindowService { + friend class WindowsAutomationService; protected: WinClass windowClass; WinClass godClass; @@ -2116,6 +2120,12 @@ WindowsController return &dialogService; } + INativeAutomationService* AutomationService() + { + // Use INativeAutomationService::UnavailableService + return nullptr; + } + WString GetExecutablePath() { Array buffer(65536); @@ -2253,10 +2263,222 @@ Windows Platform Native Controller } } } + +/*********************************************************************** +WindowsAutomationServiceBase +***********************************************************************/ + + template + WString WindowsAutomationServiceBase::RunIOCommandInternal(Nullable windowId, const WString& ioCommand) + { + WindowsForm* windowsForm = dynamic_cast(this->GetNativeWindow(windowId)); + if (!windowsForm) + { + return L"!Invalid window."; + } + + return RunIOCommandOnNativeWindow(&this->ioCommandState, GetWindowsNativeController(), windowsForm, windowsForm->listeners, ioCommand); + } + + template + void WindowsAutomationServiceBase::Stop() + { + TBase::Stop(); + StopWindowsHttpAutomationService(); + } + + template + bool WindowsAutomationServiceBase::CanRunIOCommands() + { + return true; + } + +/*********************************************************************** +WindowsAutomationService +***********************************************************************/ + + Nullable WindowsAutomationService::GetNativeWindowId(INativeWindow* window) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::windows::WindowsAutomationService::GetNativeWindowId(INativeWindow*)#" + auto controller = dynamic_cast(GetWindowsNativeController()); + CHECK_ERROR(controller->windows.Values().Contains(dynamic_cast(window)), ERROR_MESSAGE_PREFIX L"The specified INativeWindow instance should be native."); + return utow(static_cast(reinterpret_cast(window))); +#undef ERROR_MESSAGE_PREFIX + } + + INativeWindow* WindowsAutomationService::GetNativeWindow(Nullable windowId) + { + auto controller = dynamic_cast(GetWindowsNativeController()); + if (windowId) + { + WindowsForm* windowsForm = reinterpret_cast(static_cast(wtou(windowId.Value()))); + if (!controller->windows.Values().Contains(windowsForm)) + { + return nullptr; + } + return windowsForm; + } + else + { + return controller->mainWindow; + } + } + + WindowsAutomationService::WindowsAutomationService() + { + } + + WindowsAutomationService::~WindowsAutomationService() + { + } + +/*********************************************************************** +WindowsAutomationServiceHosted +***********************************************************************/ + + WindowsAutomationServiceHosted::WindowsAutomationServiceHosted() + { + } + + WindowsAutomationServiceHosted::~WindowsAutomationServiceHosted() + { + } + +/*********************************************************************** +WindowsAutomationServiceRenderer +***********************************************************************/ + + WindowsAutomationServiceRenderer::WindowsAutomationServiceRenderer(remote_renderer::GuiRemoteRendererSingle* _renderer) + : WindowsAutomationServiceBase(_renderer) + { + } + + WindowsAutomationServiceRenderer::~WindowsAutomationServiceRenderer() + { + } + +/*********************************************************************** +HttpAutomationService +***********************************************************************/ + + class HttpAutomationService : public inter_process::HttpServerApi + { + protected: + WString urlControls; + WString urlDom; + WString urlIO; + + void OnHttpRequestReceived(PHTTP_REQUEST pRequest) + { + auto mainWindow = GetCurrentController()->WindowService()->GetMainWindow(); + auto asyncService = GetCurrentController()->AsyncService(); + auto automationService = GetCurrentController()->AutomationService(); + + try + { + Nullable respondString; + if (pRequest->Verb == HttpVerbGET) + { + if (pRequest->CookedUrl.pAbsPath == urlControls) + { + if (automationService->CanDumpControlTree()) + { + asyncService->InvokeInMainThreadAndWait(mainWindow, [&]() + { + respondString = automationService->DumpControlTree(); + }); + } + } + else if (pRequest->CookedUrl.pAbsPath == urlDom) + { + if (automationService->CanDumpDomTree()) + { + asyncService->InvokeInMainThreadAndWait(mainWindow, [&]() + { + respondString = automationService->DumpDomTree(); + }); + } + } + } + else if (pRequest->Verb == HttpVerbPOST) + { + if (wcsncmp(pRequest->CookedUrl.pAbsPath, urlIO.Buffer(), (size_t)urlIO.Length()) == 0) + { + Nullable windowId; + auto pId = pRequest->CookedUrl.pAbsPath + urlIO.Length(); + if (*pId == L'/') + { + windowId = ++pId; + } + else if (*pId) + { + SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"URL not supported." }); + return; + } + + if (automationService->CanRunIOCommands()) + { + WString body = GetUtf8Body(pRequest).Value(); + respondString = automationService->RunIOCommand(windowId, body); + } + } + } + + if (respondString) + { + return SendResponseUtf8(GetHttpRequestQueue(), pRequest->RequestId, respondString.Value()); + } + } + catch (const Error& error) + { + return (void)SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, WString::Unmanaged(error.Description()) }); + } + catch (const Exception& ex) + { + return (void)SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, ex.Message() }); + } + SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"URL not supported." }); + } + + public: + HttpAutomationService(const WString& applicationName, vint port) + : HttpServerApi(WString::Unmanaged(L"http://localhost:") + itow(port) + WString::Unmanaged(L"/") + applicationName + WString::Unmanaged(L"/"), false) + , urlControls (WString::Unmanaged(L"/") + applicationName + WString::Unmanaged(L"/Controls")) + , urlDom (WString::Unmanaged(L"/") + applicationName + WString::Unmanaged(L"/Dom")) + , urlIO (WString::Unmanaged(L"/") + applicationName + WString::Unmanaged(L"/IO")) + { + } + }; + + HttpAutomationService* httpAutomationService = nullptr; + + void StartWindowsHttpAutomationService(const WString& applicationName, vint port) + { + if (!GetCurrentController()->AutomationService()->Available()) + { + return; + } + if (!httpAutomationService) + { + httpAutomationService = new HttpAutomationService(applicationName, port); + httpAutomationService->Start(); + } + } + + void StopWindowsHttpAutomationService() + { + if (httpAutomationService) + { + httpAutomationService->Stop(); + delete httpAutomationService; + httpAutomationService = nullptr; + } + } } } } + /*********************************************************************** .\DIRECT2D\WINDIRECT2DAPPLICATION.CPP ***********************************************************************/ @@ -2902,6 +3124,17 @@ int SetupWindowsDirect2DRendererInternal(bool hosted, bool raw) SetNativeController(nativeController); } + Ptr automationService; + if (hosted) + { + automationService = Ptr(new WindowsAutomationServiceHosted); + } + else + { + automationService = Ptr(new WindowsAutomationService); + } + GetNativeServiceSubstitution()->Substitute(automationService.Obj(), false); + { // install listener Direct2DWindowsNativeControllerListener listener; @@ -2914,6 +3147,9 @@ int SetupWindowsDirect2DRendererInternal(bool hosted, bool raw) nativeController->CallbackService()->UninstallListener(&listener); } + GetNativeServiceSubstitution()->Unsubstitute(automationService.Obj()); + automationService = nullptr; + // destroy controller SetNativeController(nullptr); if (hostedController) @@ -8400,6 +8636,17 @@ int SetupWindowsGDIRendererInternal(bool hosted, bool raw) SetNativeController(nativeController); } + Ptr automationService; + if (hosted) + { + automationService = Ptr(new WindowsAutomationServiceHosted); + } + else + { + automationService = Ptr(new WindowsAutomationService); + } + GetNativeServiceSubstitution()->Substitute(automationService.Obj(), false); + { // install listener GdiWindowsNativeControllerListener listener; @@ -8412,6 +8659,9 @@ int SetupWindowsGDIRendererInternal(bool hosted, bool raw) nativeController->CallbackService()->UninstallListener(&listener); } + GetNativeServiceSubstitution()->Unsubstitute(automationService.Obj()); + automationService = nullptr; + // destroy controller SetNativeController(nullptr); if (hostedController) diff --git a/Import/GacUI.Windows.h b/Import/GacUI.Windows.h index 3a2b8fd8..2f5258d1 100644 --- a/Import/GacUI.Windows.h +++ b/Import/GacUI.Windows.h @@ -9,6 +9,7 @@ DEVELOPER: Zihan Chen(vczh) #include "VlppOS.h" #include "Vlpp.h" #include "VlppRegex.h" +#include "VlppOS.Windows.h" /*********************************************************************** .\WINNATIVEDPIAWARENESS.H @@ -24,8 +25,8 @@ Interfaces: #ifndef VCZH_PRESENTATION_WINDOWS_WINNATIVEDPIAWARENESS #define VCZH_PRESENTATION_WINDOWS_WINNATIVEDPIAWARENESS +#define _WINSOCKAPI_ #include -#include namespace vl { @@ -48,6 +49,147 @@ DPI Awareness Functions #endif +/*********************************************************************** +.\WINNATIVEWINDOW.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) +GacUI::Native Window::Windows Implementation + +Interfaces: +***********************************************************************/ + +#ifndef VCZH_PRESENTATION_WINDOWS_WINNATIVEWINDOW +#define VCZH_PRESENTATION_WINDOWS_WINNATIVEWINDOW + + +namespace vl +{ + namespace presentation + { + class AutomationService; + class AutomationServiceHosted; + + namespace windows + { + +/*********************************************************************** +Windows Platform Native Controller +***********************************************************************/ + + class INativeMessageHandler : public Interface + { + public: + virtual void BeforeHandle(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& skip) = 0; + virtual void AfterHandle(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& skip, LRESULT& result) = 0; + }; + + class IWindowsForm : public Interface + { + public: + virtual HWND GetWindowHandle() = 0; + virtual Interface* GetGraphicsHandler() = 0; + virtual void SetGraphicsHandler(Interface* handler) = 0; + virtual bool InstallMessageHandler(Ptr handler) = 0; + virtual bool UninstallMessageHandler(Ptr handler) = 0; + }; + + extern void SetWindowDefaultIcon(UINT resourceId); + extern void StartWindowsNativeController(HINSTANCE hInstance); + extern INativeController* GetWindowsNativeController(); + extern IWindowsForm* GetWindowsFormFromHandle(HWND hwnd); + extern IWindowsForm* GetWindowsForm(INativeWindow* window); + extern void GetAllCreatedWindows(collections::List& windows, bool rootWindowOnly); + extern void StopWindowsNativeController(); + extern void EnableCrossKernelCrashing(); + + template + class WindowsAutomationServiceBase : public TBase + { + protected: + + WString RunIOCommandInternal(Nullable windowId, const WString& ioCommand) override; + + public: + template + WindowsAutomationServiceBase(TArgs&& ...args) + :TBase(std::forward(args)...) + { + } + + void Stop() override; + bool CanRunIOCommands() override; + }; + + class WindowsAutomationService : public WindowsAutomationServiceBase + { + protected: + Nullable GetNativeWindowId(INativeWindow* window) override; + INativeWindow* GetNativeWindow(Nullable windowId) override; + + public: + WindowsAutomationService(); + ~WindowsAutomationService(); + }; + + class WindowsAutomationServiceHosted : public WindowsAutomationServiceBase + { + public: + WindowsAutomationServiceHosted(); + ~WindowsAutomationServiceHosted(); + }; + + class WindowsAutomationServiceRenderer : public WindowsAutomationServiceBase + { + public: + WindowsAutomationServiceRenderer(remote_renderer::GuiRemoteRendererSingle* _renderer); + ~WindowsAutomationServiceRenderer(); + }; + + extern void StartWindowsHttpAutomationService(const WString& applicationName, vint port); + extern void StopWindowsHttpAutomationService(); + } + } +} + +#endif + + +/*********************************************************************** +.\DIRECT2D\WINDIRECT2DAPPLICATION.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) +GacUI::Native Window::Direct2D Provider for Windows Implementation + +Interfaces: +***********************************************************************/ +#ifndef VCZH_PRESENTATION_WINDOWS_GDI_WINDIRECT2DAPPLICATION +#define VCZH_PRESENTATION_WINDOWS_GDI_WINDIRECT2DAPPLICATION + +#include +#include +#include + +namespace vl +{ + namespace presentation + { + namespace windows + { + extern ID2D1Factory* GetDirect2DFactory(); + extern IDWriteFactory* GetDirectWriteFactory(); + extern ID3D11Device* GetD3D11Device(); + } + } +} + +extern int WinMainDirect2D(HINSTANCE hInstance, void(*RendererMain)()); + +#endif + /*********************************************************************** .\DIRECT2D\RENDERERS\GUIGRAPHICSLAYOUTPROVIDERWINDOWSDIRECT2D.H ***********************************************************************/ @@ -94,8 +236,6 @@ Interfaces: #ifndef VCZH_PRESENTATION_ELEMENTS_GUIGRAPHICSWINDOWSDIRECT2D #define VCZH_PRESENTATION_ELEMENTS_GUIGRAPHICSWINDOWSDIRECT2D -#include -#include #include #include @@ -976,6 +1116,36 @@ Device Context #endif +/*********************************************************************** +.\GDI\WINGDIAPPLICATION.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) +GacUI::Native Window::GDI Provider for Windows Implementation + +Interfaces: +***********************************************************************/ +#ifndef VCZH_PRESENTATION_WINDOWS_GDI_WINGDIAPPLICATION +#define VCZH_PRESENTATION_WINDOWS_GDI_WINGDIAPPLICATION + + +namespace vl +{ + namespace presentation + { + namespace windows + { + extern WinDC* GetNativeWindowDC(INativeWindow* window); + extern HDC GetNativeWindowHDC(INativeWindow* window); + } + } +} + +extern int WinMainGDI(HINSTANCE hInstance, void(*RendererMain)()); + +#endif + /*********************************************************************** .\GDI\RENDERERS\GUIGRAPHICSLAYOUTPROVIDERWINDOWSGDI.H ***********************************************************************/ @@ -1796,6 +1966,7 @@ Interfaces: #ifndef VCZH_PRESENTATION_WINDOWS_SERVICESIMPL_WINDOWSDIALOGSERVICE #define VCZH_PRESENTATION_WINDOWS_SERVICESIMPL_WINDOWSDIALOGSERVICE +#include namespace vl { @@ -1823,6 +1994,7 @@ namespace vl #endif + /*********************************************************************** .\SERVICESIMPL\WINDOWSIMAGESERVICE.H ***********************************************************************/ @@ -1922,125 +2094,6 @@ namespace vl #endif -/*********************************************************************** -.\WINNATIVEWINDOW.H -***********************************************************************/ -/*********************************************************************** -Vczh Library++ 3.0 -Developer: Zihan Chen(vczh) -GacUI::Native Window::Windows Implementation - -Interfaces: -***********************************************************************/ - -#ifndef VCZH_PRESENTATION_WINDOWS_WINNATIVEWINDOW -#define VCZH_PRESENTATION_WINDOWS_WINNATIVEWINDOW - - -namespace vl -{ - namespace presentation - { - namespace windows - { - -/*********************************************************************** -Windows Platform Native Controller -***********************************************************************/ - - class INativeMessageHandler : public Interface - { - public: - virtual void BeforeHandle(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& skip) = 0; - virtual void AfterHandle(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, bool& skip, LRESULT& result) = 0; - }; - - class IWindowsForm : public Interface - { - public: - virtual HWND GetWindowHandle() = 0; - virtual Interface* GetGraphicsHandler() = 0; - virtual void SetGraphicsHandler(Interface* handler) = 0; - virtual bool InstallMessageHandler(Ptr handler) = 0; - virtual bool UninstallMessageHandler(Ptr handler) = 0; - }; - - extern void SetWindowDefaultIcon(UINT resourceId); - extern void StartWindowsNativeController(HINSTANCE hInstance); - extern INativeController* GetWindowsNativeController(); - extern IWindowsForm* GetWindowsFormFromHandle(HWND hwnd); - extern IWindowsForm* GetWindowsForm(INativeWindow* window); - extern void GetAllCreatedWindows(collections::List& windows, bool rootWindowOnly); - extern void StopWindowsNativeController(); - extern void EnableCrossKernelCrashing(); - } - } -} - -#endif - -/*********************************************************************** -.\DIRECT2D\WINDIRECT2DAPPLICATION.H -***********************************************************************/ -/*********************************************************************** -Vczh Library++ 3.0 -Developer: Zihan Chen(vczh) -GacUI::Native Window::Direct2D Provider for Windows Implementation - -Interfaces: -***********************************************************************/ -#ifndef VCZH_PRESENTATION_WINDOWS_GDI_WINDIRECT2DAPPLICATION -#define VCZH_PRESENTATION_WINDOWS_GDI_WINDIRECT2DAPPLICATION - -#include - -namespace vl -{ - namespace presentation - { - namespace windows - { - extern ID2D1Factory* GetDirect2DFactory(); - extern IDWriteFactory* GetDirectWriteFactory(); - extern ID3D11Device* GetD3D11Device(); - } - } -} - -extern int WinMainDirect2D(HINSTANCE hInstance, void(*RendererMain)()); - -#endif - -/*********************************************************************** -.\GDI\WINGDIAPPLICATION.H -***********************************************************************/ -/*********************************************************************** -Vczh Library++ 3.0 -Developer: Zihan Chen(vczh) -GacUI::Native Window::GDI Provider for Windows Implementation - -Interfaces: -***********************************************************************/ -#ifndef VCZH_PRESENTATION_WINDOWS_GDI_WINGDIAPPLICATION -#define VCZH_PRESENTATION_WINDOWS_GDI_WINGDIAPPLICATION - - -namespace vl -{ - namespace presentation - { - namespace windows - { - extern WinDC* GetNativeWindowDC(INativeWindow* window); - extern HDC GetNativeWindowHDC(INativeWindow* window); - } - } -} - -extern int WinMainGDI(HINSTANCE hInstance, void(*RendererMain)()); - -#endif - /*********************************************************************** .\SERVICESIMPL\WINDOWSINPUTSERVICE.H ***********************************************************************/ diff --git a/Import/GacUI.cpp b/Import/GacUI.cpp index 2a877bf5..3f32eef4 100644 --- a/Import/GacUI.cpp +++ b/Import/GacUI.cpp @@ -2597,6 +2597,11 @@ GuiControlHost { if (auto window = host->GetNativeWindow()) { + auto controller = GetCurrentController(); + if (window == controller->WindowService()->GetMainWindow()) + { + controller->AutomationService()->Stop(); + } window->Hide(false); } } @@ -14278,6 +14283,7 @@ TextItemBindableProvider { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::TextItemBindableProvider::GetTextValue(vint)#" CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); return ReadProperty(itemSource->Get(itemIndex), textProperty); #undef ERROR_MESSAGE_PREFIX } @@ -14305,14 +14311,22 @@ TextItemBindableProvider bool TextItemBindableProvider::GetChecked(vint itemIndex) { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::TextItemBindableProvider::GetChecked(vint)#" + CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); return ReadProperty(itemSource->Get(itemIndex), checkedProperty); +#undef ERROR_MESSAGE_PREFIX } void TextItemBindableProvider::SetChecked(vint itemIndex, bool value) { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::TextItemBindableProvider::SetChecked(vint, bool)#" + CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); auto thisValue = itemSource->Get(itemIndex); WriteProperty(thisValue, checkedProperty, value); InvokeOnItemModified(itemIndex, 1, 1, false); +#undef ERROR_MESSAGE_PREFIX } /*********************************************************************** @@ -14490,6 +14504,7 @@ ListViewItemBindableProvider { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::ListViewItemBindableProvider::GetSmallImage(vint)#" CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); return ReadProperty(itemSource->Get(itemIndex), smallImageProperty); #undef ERROR_MESSAGE_PREFIX } @@ -14498,6 +14513,7 @@ ListViewItemBindableProvider { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::ListViewItemBindableProvider::GetLargeImage(vint)#" CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); return ReadProperty(itemSource->Get(itemIndex), largeImageProperty); #undef ERROR_MESSAGE_PREFIX } @@ -14506,6 +14522,7 @@ ListViewItemBindableProvider { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::ListViewItemBindableProvider::GetText(vint)#" CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); return ReadProperty(itemSource->Get(itemIndex), columns[0]->GetTextProperty()); #undef ERROR_MESSAGE_PREFIX } @@ -14514,6 +14531,7 @@ ListViewItemBindableProvider { #define ERROR_MESSAGE_PREFIX L"vl::presentation::controls::ListViewItemBindableProvider::GetSubItem(vint, vint)#" CHECK_ERROR(itemSource, ERROR_MESSAGE_PREFIX L"ItemSource is not set."); + CHECK_ERROR(0 <= itemIndex && itemIndex < itemSource->GetCount(), ERROR_MESSAGE_PREFIX L"Index out of range."); CHECK_ERROR(index != -1, ERROR_MESSAGE_PREFIX L"column index cannot be -1, use GetText(itemIndex) instead."); return ReadProperty(itemSource->Get(itemIndex), columns[index + 1]->GetTextProperty()); #undef ERROR_MESSAGE_PREFIX @@ -31954,6 +31972,59 @@ namespace vl { const NativeWindowFrameConfig NativeWindowFrameConfig::Default = {}; +/*********************************************************************** +INativeAutomationService +***********************************************************************/ + + class UnavailableAutomationService : public Object, public INativeAutomationService + { + public: + bool Available() override + { + return false; + } + + void Stop() override + { + } + + bool CanDumpControlTree() override + { + CHECK_FAIL(L"Not Implemented!"); + } + + WString DumpControlTree() override + { + CHECK_FAIL(L"Not Implemented!"); + } + + bool CanDumpDomTree() override + { + CHECK_FAIL(L"Not Implemented!"); + } + + WString DumpDomTree() override + { + CHECK_FAIL(L"Not Implemented!"); + } + + bool CanRunIOCommands() override + { + CHECK_FAIL(L"Not Implemented!"); + } + + WString RunIOCommand(Nullable windowId, const WString&) override + { + CHECK_FAIL(L"Not Implemented!"); + } + }; + + INativeAutomationService* INativeAutomationService::UnavailableService() + { + static UnavailableAutomationService service; + return &service; + } + /*********************************************************************** INativeWindowListener ***********************************************************************/ @@ -32171,6 +32242,10 @@ Native Window Provider !requested, L"The service cannot be substituted because it has been used." ); + if (service && !optional && _optional) + { + return; + } service = _service; optional = _optional; } @@ -32408,6 +32483,7 @@ Helper Functions } } + /*********************************************************************** .\PLATFORMPROVIDERS\GACGEN\GACGENCONTROLLER.CPP ***********************************************************************/ @@ -32471,6 +32547,11 @@ public: CHECK_FAIL(L"Not implemented!"); } + INativeAutomationService* AutomationService() override + { + CHECK_FAIL(L"Not implemented!"); + } + WString GetExecutablePath() override { CHECK_FAIL(L"Not implemented!"); @@ -32655,6 +32736,7 @@ int SetupGacGenNativeController() return 0; } + /*********************************************************************** .\PLATFORMPROVIDERS\HOSTED\GUIHOSTEDAPPLICATION.CPP ***********************************************************************/ @@ -33281,6 +33363,11 @@ GuiHostedController::INativeWindowListener (Template) > void GuiHostedController::HandleKeyboardCallback(const TInfo& info) { + if (!wmWindow && !wmManager->activeWindow && mainWindow) + { + mainWindow->wmWindow.Activate(); + } + if (wmManager->activeWindow && !wmWindow) { auto hostedWindow = wmManager->activeWindow->id; @@ -33807,6 +33894,11 @@ GuiHostedController::IGuiHostedApplication return nativeWindow; } + INativeController* GuiHostedController::GetNativeController() + { + return nativeController; + } + /*********************************************************************** GuiHostedController ***********************************************************************/ @@ -33899,6 +33991,12 @@ GuiHostedController::INativeController return nullptr; } + INativeAutomationService* GuiHostedController::AutomationService() + { + // Use INativeAutomationService::UnavailableService + return nullptr; + } + WString GuiHostedController::GetExecutablePath() { return nativeController->GetExecutablePath(); @@ -35295,6 +35393,16 @@ GuiRemoteController::INativeInputService return true; } + void GuiRemoteController::EnsureControllerConnected() + { + if (!controllerConnected && !connectionStopped) + { + bool disconnected = false; + remoteMessages.Submit(disconnected); + RunOneCycle(); + } + } + /*********************************************************************** GuiRemoteController::INativeScreenService ***********************************************************************/ @@ -35321,11 +35429,13 @@ GuiHostedController::INativeScreen NativeRect GuiRemoteController::GetBounds() { + EnsureControllerConnected(); return remoteScreenConfig.bounds; } NativeRect GuiRemoteController::GetClientBounds() { + EnsureControllerConnected(); return remoteScreenConfig.clientBounds; } @@ -35341,11 +35451,13 @@ GuiHostedController::INativeScreen double GuiRemoteController::GetScalingX() { + EnsureControllerConnected(); return remoteScreenConfig.scalingX; } double GuiRemoteController::GetScalingY() { + EnsureControllerConnected(); return remoteScreenConfig.scalingY; } @@ -35398,6 +35510,10 @@ GuiRemoteController::INativeWindowService { CHECK_ERROR(window == &remoteWindow, L"vl::presentation::GuiRemoteController::Run(INativeWindow*)#GuiHostedController should call this function with the native window."); applicationRunning = true; + if (controllerConnected) + { + remoteWindow.SubmitStateAfterControllerConnect(); + } window->Show(); while (RunOneCycle()); asyncService.ExecuteAsyncTasks(); @@ -35429,6 +35545,7 @@ GuiRemoteController (events) void GuiRemoteController::OnControllerConnect(const remoteprotocol::ControllerGlobalConfig& _globalConfig) { + controllerConnected = true; remoteGlobalConfig = _globalConfig; UpdateGlobalShortcutKey(); vint idGetFontConfig = remoteMessages.RequestControllerGetFontConfig(); @@ -35446,6 +35563,7 @@ GuiRemoteController (events) void GuiRemoteController::OnControllerDisconnect() { + controllerConnected = false; remoteWindow.OnControllerDisconnect(); imageService.OnControllerDisconnect(); resourceManager->OnControllerDisconnect(); @@ -35552,6 +35670,12 @@ GuiRemoteController (INativeController) return nullptr; } + INativeAutomationService* GuiRemoteController::AutomationService() + { + // Use INativeAutomationService::UnavailableService + return nullptr; + } + WString GuiRemoteController::GetExecutablePath() { return remoteProtocol->GetExecutablePath(); @@ -35568,6 +35692,7 @@ GuiRemoteController (INativeController) } } + /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTECONTROLLERSETUP.CPP ***********************************************************************/ @@ -35608,6 +35733,7 @@ int SetupRemoteNativeController(vl::presentation::IGuiRemoteProtocol* protocol) return 0; } + /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTEEVENTS.CPP ***********************************************************************/ @@ -38686,10 +38812,10 @@ namespace vl::presentation::remoteprotocol::channeling using namespace vl::presentation::controls; /*********************************************************************** -GuiRemoteProtocolAsyncJsonChannelSerializer +GuiRemoteProtocolJsonChannelRenderer_Async ***********************************************************************/ - bool GuiRemoteProtocolAsyncJsonChannelSerializer::AreCurrentPendingRequestGroupSatisfied(bool disconnected) + bool GuiRemoteProtocolJsonChannelRenderer_Async::AreCurrentPendingRequestGroupSatisfied(bool disconnected) { if (!pendingRequest) return false; if (disconnected) return true; @@ -38703,8 +38829,14 @@ GuiRemoteProtocolAsyncJsonChannelSerializer return true; } - void GuiRemoteProtocolAsyncJsonChannelSerializer::ScheduleProcessRemoteEvents() + void GuiRemoteProtocolJsonChannelRenderer_Async::ScheduleProcessRemoteEvents() { + auto app = GetApplication(); + if (!app) + { + return; + } + bool shouldQueue = false; SPIN_LOCK(lockEvents) { @@ -38717,17 +38849,14 @@ GuiRemoteProtocolAsyncJsonChannelSerializer if (shouldQueue) { - if (auto app = GetApplication()) + app->InvokeInMainThread(app->GetMainWindow(), [this]() { - app->InvokeInMainThread(app->GetMainWindow(), [this]() - { - ProcessChannelEvents(); - }); - } + ProcessChannelEvents(); + }); } } - void GuiRemoteProtocolAsyncJsonChannelSerializer::ProcessChannelEvents() + void GuiRemoteProtocolJsonChannelRenderer_Async::ProcessChannelEvents() { List events; SPIN_LOCK(lockEvents) @@ -38736,25 +38865,49 @@ GuiRemoteProtocolAsyncJsonChannelSerializer uiTaskQueued = false; } + auto processEvent = [this](const ReceivedPackage& eventPackage) + { + reader->OnRead(eventPackage.senderClientId, eventPackage.package); + }; + for (auto&& eventPackage : events) { ChannelPackageInfo info; Ptr jsonArguments; JsonChannelUnpack(eventPackage.package, info, jsonArguments); - if (info.name == L"ControllerConnect") + vint currentConnectionClientId = -1; + SPIN_LOCK(lockConnection) { - SPIN_LOCK(lockConnection) - { - connectionCounter++; - connectionAvailable = true; - } + currentConnectionClientId = connectionClientId; + } + + if (info.name == L"ControllerConnect" && eventPackage.senderClientId == currentConnectionClientId) + { + processEvent(eventPackage); + } + } + + for (auto&& eventPackage : events) + { + ChannelPackageInfo info; + Ptr jsonArguments; + JsonChannelUnpack(eventPackage.package, info, jsonArguments); + + vint currentConnectionClientId = -1; + SPIN_LOCK(lockConnection) + { + currentConnectionClientId = connectionClientId; + } + + if (info.name != L"ControllerConnect" && eventPackage.senderClientId == currentConnectionClientId) + { + processEvent(eventPackage); } - reader->OnRead(eventPackage.senderClientId, eventPackage.package); } } - void GuiRemoteProtocolAsyncJsonChannelSerializer::ProcessRemoteEvents() + void GuiRemoteProtocolJsonChannelRenderer_Async::ProcessRemoteEvents() { if (remoteEventProcessor) { @@ -38763,9 +38916,9 @@ GuiRemoteProtocolAsyncJsonChannelSerializer ProcessChannelEvents(); } - void GuiRemoteProtocolAsyncJsonChannelSerializer::OnRead(vint senderClientId, const JsonPackage& package) + void GuiRemoteProtocolJsonChannelRenderer_Async::OnRead(vint senderClientId, const JsonPackage& package) { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolAsyncJsonChannelSerializer::OnRead(vint, const JsonPackage&)#" +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolJsonChannelRenderer_Async::OnRead(vint, const JsonPackage&)#" ChannelPackageInfo info; Ptr jsonArguments; JsonChannelUnpack(package, info, jsonArguments); @@ -38774,6 +38927,24 @@ GuiRemoteProtocolAsyncJsonChannelSerializer { case ChannelPackageSemantic::Event: { + if (info.name == L"ControllerConnect") + { + SPIN_LOCK(lockConnection) + { + connectionCounter++; + connectionClientId = senderClientId; + connectionAvailable = true; + } + + SPIN_LOCK(lockResponses) + { + if (pendingRequest) + { + eventAutoResponses.Signal(); + } + } + } + ReceivedPackage receivedPackage; receivedPackage.senderClientId = senderClientId; receivedPackage.package = package; @@ -38805,33 +38976,33 @@ GuiRemoteProtocolAsyncJsonChannelSerializer #undef ERROR_MESSAGE_PREFIX } - GuiRemoteProtocolAsyncJsonChannelSerializer::GuiRemoteProtocolAsyncJsonChannelSerializer(IJsonChannel* _channel, IGuiRemoteEventProcessor* _remoteEventProcessor) + GuiRemoteProtocolJsonChannelRenderer_Async::GuiRemoteProtocolJsonChannelRenderer_Async(IJsonChannel* _channel, IGuiRemoteEventProcessor* _remoteEventProcessor) : channel(_channel) , remoteEventProcessor(_remoteEventProcessor) { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolAsyncJsonChannelSerializer::GuiRemoteProtocolAsyncJsonChannelSerializer(IJsonChannel*, IGuiRemoteEventProcessor*)#" +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolJsonChannelRenderer_Async::GuiRemoteProtocolJsonChannelRenderer_Async(IJsonChannel*, IGuiRemoteEventProcessor*)#" CHECK_ERROR(channel, ERROR_MESSAGE_PREFIX L"A valid channel is required."); CHECK_ERROR(eventAutoResponses.CreateAutoUnsignal(false), ERROR_MESSAGE_PREFIX L"Failed to initialize eventAutoResponses."); #undef ERROR_MESSAGE_PREFIX } - GuiRemoteProtocolAsyncJsonChannelSerializer::~GuiRemoteProtocolAsyncJsonChannelSerializer() + GuiRemoteProtocolJsonChannelRenderer_Async::~GuiRemoteProtocolJsonChannelRenderer_Async() { } - const WString& GuiRemoteProtocolAsyncJsonChannelSerializer::GetChannelName() + const WString& GuiRemoteProtocolJsonChannelRenderer_Async::GetChannelName() { return channel->GetChannelName(); } - IJsonChannelReader* GuiRemoteProtocolAsyncJsonChannelSerializer::GetReader() + IJsonChannelReader* GuiRemoteProtocolJsonChannelRenderer_Async::GetReader() { return reader; } - void GuiRemoteProtocolAsyncJsonChannelSerializer::Initialize(IJsonChannelReader* _reader) + void GuiRemoteProtocolJsonChannelRenderer_Async::Initialize(IJsonChannelReader* _reader) { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolAsyncJsonChannelSerializer::Initialize(IJsonChannelReader*)#" +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolJsonChannelRenderer_Async::Initialize(IJsonChannelReader*)#" CHECK_ERROR(_reader, ERROR_MESSAGE_PREFIX L"A valid reader is required."); CHECK_ERROR(!reader, ERROR_MESSAGE_PREFIX L"The async channel cannot be initialized more than once."); reader = _reader; @@ -38839,7 +39010,7 @@ GuiRemoteProtocolAsyncJsonChannelSerializer #undef ERROR_MESSAGE_PREFIX } - void GuiRemoteProtocolAsyncJsonChannelSerializer::SendToClient(vint senderClientId, vint receiverClientId, const JsonPackage& package) + void GuiRemoteProtocolJsonChannelRenderer_Async::SendToClient(vint senderClientId, vint receiverClientId, const JsonPackage& package) { QueuedPackage queuedPackage; queuedPackage.senderClientId = senderClientId; @@ -38852,7 +39023,7 @@ GuiRemoteProtocolAsyncJsonChannelSerializer } } - void GuiRemoteProtocolAsyncJsonChannelSerializer::BroadcastFromClient(vint senderClientId, const JsonPackage& package) + void GuiRemoteProtocolJsonChannelRenderer_Async::BroadcastFromClient(vint senderClientId, const JsonPackage& package) { QueuedPackage queuedPackage; queuedPackage.senderClientId = senderClientId; @@ -38864,9 +39035,9 @@ GuiRemoteProtocolAsyncJsonChannelSerializer } } - void GuiRemoteProtocolAsyncJsonChannelSerializer::BatchWrite(bool& disconnected) + void GuiRemoteProtocolJsonChannelRenderer_Async::BatchWrite(bool& disconnected) { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolAsyncJsonChannelSerializer::BatchWrite(bool&)#" +#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::GuiRemoteProtocolJsonChannelRenderer_Async::BatchWrite(bool&)#" disconnected = false; List packages; @@ -38960,7 +39131,7 @@ GuiRemoteProtocolAsyncJsonChannelSerializer #undef ERROR_MESSAGE_PREFIX } - IGuiRemoteEventProcessor* GuiRemoteProtocolAsyncJsonChannelSerializer::GetRemoteEventProcessor() + IGuiRemoteEventProcessor* GuiRemoteProtocolJsonChannelRenderer_Async::GetRemoteEventProcessor() { return this; } @@ -39004,9 +39175,20 @@ GuiRemoteProtocolAsyncJsonChannelRenderer { while (true) { + IJsonChannelReader* currentReader = nullptr; + vint currentMessageVersion = -1; List messages; SPIN_LOCK(lockMessages) { + currentReader = reader; + currentMessageVersion = messageVersion; + if (!currentReader) + { + queuedMessages.Clear(); + uiTaskQueued = false; + return; + } + messages = std::move(queuedMessages); if (messages.Count() == 0) { @@ -39017,7 +39199,16 @@ GuiRemoteProtocolAsyncJsonChannelRenderer for (auto&& message : messages) { - reader->OnRead(message.senderClientId, message.package); + bool shouldProcess = false; + SPIN_LOCK(lockMessages) + { + shouldProcess = reader == currentReader && message.messageVersion == currentMessageVersion; + } + + if (shouldProcess) + { + currentReader->OnRead(message.senderClientId, message.package); + } } } } @@ -39029,6 +39220,11 @@ GuiRemoteProtocolAsyncJsonChannelRenderer receivedPackage.package = package; SPIN_LOCK(lockMessages) { + if (!reader) + { + return; + } + receivedPackage.messageVersion = messageVersion; queuedMessages.Add(std::move(receivedPackage)); } ScheduleProcessRemoteMessages(); @@ -39041,7 +39237,12 @@ GuiRemoteProtocolAsyncJsonChannelRenderer GuiRemoteProtocolAsyncJsonChannelRenderer::~GuiRemoteProtocolAsyncJsonChannelRenderer() { - Initialize(nullptr); + SPIN_LOCK(lockMessages) + { + invokeInMainThread = nullptr; + queuedMessages.Clear(); + uiTaskQueued = false; + } } const WString& GuiRemoteProtocolAsyncJsonChannelRenderer::GetChannelName() @@ -39056,8 +39257,26 @@ GuiRemoteProtocolAsyncJsonChannelRenderer void GuiRemoteProtocolAsyncJsonChannelRenderer::Initialize(IJsonChannelReader* _reader) { - reader = _reader; - channel->Initialize(_reader ? this : nullptr); + bool initializeChannel = false; + SPIN_LOCK(lockMessages) + { + reader = _reader; + if (reader && !channelInitialized) + { + channelInitialized = true; + initializeChannel = true; + } + if (!reader) + { + messageVersion++; + queuedMessages.Clear(); + uiTaskQueued = false; + } + } + if (initializeChannel) + { + channel->Initialize(this); + } } void GuiRemoteProtocolAsyncJsonChannelRenderer::SendToClient(vint senderClientId, vint receiverClientId, const JsonPackage& package) @@ -39224,35 +39443,6 @@ ChannelPackageSemantic #undef ERROR_MESSAGE_PREFIX } -/*********************************************************************** -JsonNodeListSerializer -***********************************************************************/ - - void JsonNodeListSerializer::Serialize(Ptr parser, const SourceType& source, DestType& dest) - { - auto array = Ptr(new glr::json::JsonArray); - for (auto&& package : source) - { - array->items.Add(package); - } - dest = glr::json::JsonToString(array); - } - - void JsonNodeListSerializer::Deserialize(Ptr parser, const DestType& source, SourceType& dest) - { -#define ERROR_MESSAGE_PREFIX L"vl::presentation::remoteprotocol::channeling::JsonNodeListSerializer::Deserialize(Ptr, const WString&, SourceType&)#" - auto value = glr::json::JsonParse(source, *parser.Obj()); - auto array = value.Cast(); - CHECK_ERROR(array, ERROR_MESSAGE_PREFIX L"The serialized channel package should be a JsonArray."); - - dest.Clear(); - for (auto&& package : array->items) - { - dest.Add(package); - } -#undef ERROR_MESSAGE_PREFIX - } - /*********************************************************************** GuiRemoteProtocolChannelClient ***********************************************************************/ @@ -39348,30 +39538,35 @@ GuiRemoteProtocolCoreChannel auto receiverClientId = GetRendererClientId(); if (receiverClientId == -1) { - channel->BroadcastFromClient(client->GetClientId(), package); + SPIN_LOCK(lockPackagesBeforeRenderer) + { + packagesBeforeRenderer.Add(package); + } } else { + List packages; + SPIN_LOCK(lockPackagesBeforeRenderer) + { + packages = std::move(packagesBeforeRenderer); + } + + for (auto&& pendingPackage : packages) + { + channel->SendToClient(client->GetClientId(), receiverClientId, pendingPackage); + } channel->SendToClient(client->GetClientId(), receiverClientId, package); } } void GuiRemoteProtocolCoreChannel::SetRendererClientId(vint clientId) { - SPIN_LOCK(lockRendererClientId) - { - rendererClientId = clientId; - } + rendererClientId.store(clientId); } vint GuiRemoteProtocolCoreChannel::GetRendererClientId() { - vint clientId = -1; - SPIN_LOCK(lockRendererClientId) - { - clientId = rendererClientId; - } - return clientId; + return rendererClientId.load(); } void GuiRemoteProtocolCoreChannel::OnRead(vint senderClientId, const JsonPackage& package) @@ -39382,7 +39577,6 @@ GuiRemoteProtocolCoreChannel ChannelPackageInfo info; Ptr jsonArguments; JsonChannelUnpack(package, info, jsonArguments); - BeforeOnRead(info); if (info.semantic == ChannelPackageSemantic::Event) { @@ -39394,7 +39588,12 @@ GuiRemoteProtocolCoreChannel { SetRendererClientId(-1); } + else if (GetRendererClientId() != senderClientId) + { + return; + } + BeforeOnRead(info); vint index = onReadEventHandlers.Keys().IndexOf(info.name); if (index == -1) { @@ -39407,6 +39606,12 @@ GuiRemoteProtocolCoreChannel } else if (info.semantic == ChannelPackageSemantic::Response) { + if (GetRendererClientId() != senderClientId) + { + return; + } + + BeforeOnRead(info); vint index = onReadResponseHandlers.Keys().IndexOf(info.name); if (index == -1) { @@ -39513,6 +39718,23 @@ GuiRemoteProtocolCoreChannel void GuiRemoteProtocolCoreChannel::Submit(bool& disconnected) { + auto receiverClientId = GetRendererClientId(); + List packages; + SPIN_LOCK(lockPackagesBeforeRenderer) + { + packages = std::move(packagesBeforeRenderer); + } + + if (receiverClientId == -1) + { + disconnected = false; + return; + } + + for (auto&& package : packages) + { + channel->SendToClient(client->GetClientId(), receiverClientId, package); + } channel->BatchWrite(disconnected); } @@ -39521,6 +39743,14 @@ GuiRemoteProtocolCoreChannel return eventProcessor; } + void GuiRemoteProtocolCoreChannel::DetachRenderer(vint clientId) + { + if (GetRendererClientId() == clientId) + { + SetRendererClientId(-1); + } + } + /*********************************************************************** GuiRemoteProtocolRendererChannel ***********************************************************************/ @@ -40340,6 +40570,7 @@ GuiRemoteWindow void GuiRemoteWindow::RequestGetBounds() { + remote->EnsureControllerConnected(); vint idGetBounds = remoteMessages.RequestWindowGetBounds(); bool disconnected = false; remoteMessages.Submit(disconnected); @@ -40377,6 +40608,7 @@ GuiRemoteWindow void GuiRemoteWindow::ShowWithSizeState(bool activate, INativeWindow::WindowSizeState sizeState) { + remote->EnsureControllerConnected(); if (!statusVisible || remoteWindowSizingConfig.sizeState != sizeState) { remoteprotocol::WindowShowing windowShowing; @@ -40397,6 +40629,36 @@ GuiRemoteWindow } } + void GuiRemoteWindow::SubmitStateAfterControllerConnect() + { + if (suggestedMinClientSize != NativeSize{ {0},{0} }) + { + remoteMessages.RequestWindowNotifyMinSize(suggestedMinClientSize); + } + remoteMessages.RequestWindowNotifySetTitle(styleTitle); + remoteMessages.RequestWindowNotifySetEnabled(styleEnabled); + remoteMessages.RequestWindowNotifySetTopMost(styleTopMost); + remoteMessages.RequestWindowNotifySetShowInTaskBar(styleShowInTaskBar); + remoteMessages.RequestWindowNotifySetCustomFrameMode(styleCustomFrameMode); + remoteMessages.RequestWindowNotifySetMaximizedBox(styleMaximizedBox); + remoteMessages.RequestWindowNotifySetMinimizedBox(styleMinimizedBox); + remoteMessages.RequestWindowNotifySetBorder(styleBorder); + remoteMessages.RequestWindowNotifySetSizeBox(styleSizeBox); + remoteMessages.RequestWindowNotifySetIconVisible(styleIconVisible); + remoteMessages.RequestWindowNotifySetTitleBar(styleTitleBar); + if (statusCapturing) + { + remoteMessages.RequestIORequireCapture(); + } + else + { + remoteMessages.RequestIOReleaseCapture(); + } + bool disconnected = false; + remoteMessages.Submit(disconnected); + // there is no result from these requests, assuming succeeded + } + /*********************************************************************** GuiRemoteWindow (events) ***********************************************************************/ @@ -40423,32 +40685,7 @@ GuiRemoteWindow (events) if (remote->applicationRunning) { - if (suggestedMinClientSize != NativeSize{ {0},{0} }) - { - remoteMessages.RequestWindowNotifyMinSize(suggestedMinClientSize); - } - remoteMessages.RequestWindowNotifySetTitle(styleTitle); - remoteMessages.RequestWindowNotifySetEnabled(styleEnabled); - remoteMessages.RequestWindowNotifySetTopMost(styleTopMost); - remoteMessages.RequestWindowNotifySetShowInTaskBar(styleShowInTaskBar); - remoteMessages.RequestWindowNotifySetCustomFrameMode(styleCustomFrameMode); - remoteMessages.RequestWindowNotifySetMaximizedBox(styleMaximizedBox); - remoteMessages.RequestWindowNotifySetMinimizedBox(styleMinimizedBox); - remoteMessages.RequestWindowNotifySetBorder(styleBorder); - remoteMessages.RequestWindowNotifySetSizeBox(styleSizeBox); - remoteMessages.RequestWindowNotifySetIconVisible(styleIconVisible); - remoteMessages.RequestWindowNotifySetTitleBar(styleTitleBar); - if (statusCapturing) - { - remoteMessages.RequestIORequireCapture(); - } - else - { - remoteMessages.RequestIOReleaseCapture(); - } - bool disconnected = false; - remoteMessages.Submit(disconnected); - // there is no result from this request, assuming succeeded + SubmitStateAfterControllerConnect(); } } @@ -40977,6 +41214,7 @@ GuiRemoteWindow (INativeWindow) #undef SET_REMOTE_WINDOW_STYLE } + /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\PROTOCOL\FRAMEOPERATIONS\GUIREMOTEPROTOCOLSCHEMA_BUILDFRAME.CPP ***********************************************************************/ @@ -43769,7 +44007,8 @@ namespace vl::presentation::remote_renderer events->OnWindowActivatedUpdated(false); } - GuiRemoteRendererSingle::GuiRemoteRendererSingle() + GuiRemoteRendererSingle::GuiRemoteRendererSingle(bool _enabledAutomation) + : enabledAutomation(_enabledAutomation) { } @@ -44381,6 +44620,13 @@ namespace vl::presentation::remote_renderer availableElements.Add(rc.id, element); } } + + if (enabledAutomation) + { + RenderingElement renderingElement; + renderingElement.key = rc.type; + renderingElements.Set(rc.id, renderingElement); + } } } } @@ -44395,6 +44641,11 @@ namespace vl::presentation::remote_renderer focusedParagraphElements.Remove(id); availableElements.Remove(id); solidLabelMeasurings.Remove(id); + + if (enabledAutomation) + { + renderingElements.Remove(id); + } } } } @@ -44417,6 +44668,40 @@ namespace vl::presentation::remote_renderer [&](const remoteprotocol::ElementDesc_SolidLabel& d) { RequestRendererUpdateElement_SolidLabel(d); }, [&](const remoteprotocol::ElementDesc_ImageFrame& d) { RequestRendererUpdateElement_ImageFrame(d); } )); + + if (enabledAutomation) + { + desc.Apply(Overloading( + [&](const remoteprotocol::ElementDesc_SolidLabel& d) + { + vint index = renderingElements.Keys().IndexOf(d.id); + if (index != -1) + { + auto& renderingElement = const_cast(renderingElements.Values()[index]); + auto copiedDesc = d; + if (renderingElement.value) + { + if (auto solidLabel = renderingElement.value.Value().TryGet()) + { + if (!copiedDesc.font) copiedDesc.font = solidLabel->font; + if (!copiedDesc.text) copiedDesc.text = solidLabel->text; + if (!copiedDesc.measuringRequest) copiedDesc.measuringRequest = solidLabel->measuringRequest; + } + } + renderingElement.value = copiedDesc; + } + }, + [&](const auto& d) + { + vint index = renderingElements.Keys().IndexOf(d.id); + if (index != -1) + { + auto& renderingElement = const_cast(renderingElements.Values()[index]); + renderingElement.value = d; + } + } + )); + } } } } @@ -45103,6 +45388,25 @@ namespace vl::presentation::remote_renderer UpdateElement_DocumentParagraphResponse response; wrapper->ApplyUpdateAndFillResponse(arguments, response); events->RespondRendererUpdateElement_DocumentParagraph(id, response); + + if (enabledAutomation) + { + vint index = renderingElements.Keys().IndexOf(arguments.id); + if (index != -1) + { + auto& renderingElement = const_cast(renderingElements.Values()[index]); + remoteprotocol::ElementDesc_DocumentParagraphFull copiedDesc{ arguments }; + if (renderingElement.value) + { + if (auto dp = renderingElement.value.Value().TryGet()) + { + if (!copiedDesc.paragraph.text) copiedDesc.paragraph.text = dp->paragraph.text; + copiedDesc.caret = dp->caret; + } + } + renderingElement.value = copiedDesc; + } + } } void GuiRemoteRendererSingle::RequestDocumentParagraph_GetCaret(vint id, const remoteprotocol::GetCaretRequest& arguments) @@ -45173,6 +45477,24 @@ namespace vl::presentation::remote_renderer PREPARE_DOCUMENT_WRAPPER(wrapper, arguments.id); wrapper->OpenCaretAndStore(arguments); focusedParagraphElements.Set(arguments.id, wrapper); + + if (enabledAutomation) + { + vint index = renderingElements.Keys().IndexOf(arguments.id); + if (index != -1) + { + auto& renderingElement = const_cast(renderingElements.Values()[index]); + if (renderingElement.value) + { + renderingElement.value.Value().TryApply( + [&](remoteprotocol::ElementDesc_DocumentParagraphFull& desc) + { + desc.caret = arguments; + } + ); + } + } + } } void GuiRemoteRendererSingle::RequestDocumentParagraph_CloseCaret(const vint& arguments) @@ -45180,6 +45502,24 @@ namespace vl::presentation::remote_renderer PREPARE_DOCUMENT_WRAPPER(wrapper, arguments); wrapper->CloseCaretAndStore(); focusedParagraphElements.Remove(arguments); + + if (enabledAutomation) + { + vint index = renderingElements.Keys().IndexOf(arguments); + if (index != -1) + { + auto& renderingElement = const_cast(renderingElements.Values()[index]); + if (renderingElement.value) + { + renderingElement.value.Value().TryApply( + [](remoteprotocol::ElementDesc_DocumentParagraphFull& desc) + { + desc.caret.Reset(); + } + ); + } + } + } } #undef PREPARE_DOCUMENT_WRAPPER @@ -53352,6 +53692,8 @@ Utilities Registration void GuiInitializeUtilities() { + GetNativeServiceSubstitution()->Substitute(INativeAutomationService::UnavailableService(), true); + if (!fakeClipboardService) { fakeClipboardService = new FakeClipboardService; @@ -63985,6 +64327,1346 @@ SharedAsyncService } } +/*********************************************************************** +.\UTILITIES\SHAREDSERVICES\GUISHAREDAUTOMATIONSERVICE.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace remoteprotocol; + +/*********************************************************************** +DumpRemoteProtocolRenderingDom +***********************************************************************/ + + Ptr DumpRemoteProtocolRenderingDom( + const WString& title, + const remoteprotocol::WindowSizingConfig& windowSizingConfig, + Ptr renderingDom, + collections::Dictionary>>& elementData + ) + { + auto dumpRoot = Ptr(new glr::json::JsonObject); + ConvertCustomTypeToJsonField(dumpRoot, L"Title", title); + ConvertCustomTypeToJsonField(dumpRoot, L"Window", windowSizingConfig); + + { + auto field = Ptr(new glr::json::JsonObjectField); + field->name.value = WString::Unmanaged(L"Elements"); + + auto jsonArray = Ptr(new glr::json::JsonArray); + field->value = jsonArray; + dumpRoot->fields.Add(field); + + for (auto [id, data] : elementData) + { + auto jsonElement = Ptr(new glr::json::JsonObject); + jsonArray->items.Add(jsonElement); + + ConvertCustomTypeToJsonField(jsonElement, L"Id", id); + ConvertCustomTypeToJsonField(jsonElement, L"Type", data.key); + ConvertCustomTypeToJsonField(jsonElement, L"Data", data.value); + } + } + + if (renderingDom) + { + ConvertCustomTypeToJsonField(dumpRoot, L"Dom", renderingDom); + } + return dumpRoot; + } + + WString DumpJsonToString(Ptr json) + { + return stream::GenerateToStream([=](stream::TextWriter& writer) + { + glr::json::JsonFormatting formatting; + formatting.spaceAfterColon = true; + formatting.spaceAfterComma = true; + formatting.crlf = true; + formatting.compact = true; + formatting.indentation = L" "; + return glr::json::JsonPrint(json, writer, formatting); + }); + } + +/*********************************************************************** +RunIOCommandOnNativeWindow +***********************************************************************/ + + namespace iocommands + { + const wchar_t* IO_COMMAND_SYNTAX = + L"Syntax Error!\r\n" + L"Predefined Commands:\r\n" + L"!Type:\r\n" + L"!Exit\r\n" + L"!KeyDown:Key1+Key2+...+KeyN\r\n" + L"!KeyUp:Key1+Key2+...+KeyN\r\n" + L"!KeyPress:Key1+Key2+...+KeyN\r\n" + L"!MouseMove:X,Y(,ctrl)?(,shift)?(,alt)?\r\n" + L"!(Left|Middle|Right)(Down|Up|Click|DbClick):X,Y(,ctrl)?(,shift)?(,alt)?\r\n" + L"!MouseWheel(Up|Down|Left|Right):ticks(,ctrl)?(,shift)?(,alt)?"; + + struct IOCommandModifiers + { + bool ctrl = false; + bool shift = false; + bool alt = false; + }; + + struct TemporaryModifiers + { + bool ctrl = false; + bool shift = false; + bool alt = false; + }; + + struct MouseCommandArguments + { + Point position; + IOCommandModifiers modifiers; + }; + + struct WheelCommandArguments + { + vint ticks = 0; + IOCommandModifiers modifiers; + }; + + enum class MouseButton + { + Left, + Middle, + Right, + }; + + struct SyntaxErrorCommand + { + }; + + struct ExitCommand + { + }; + + struct TypeCommand + { + WString text; + }; + + enum class KeyOperation + { + Down, + Up, + Press, + }; + + struct KeyCommand + { + KeyOperation operation = KeyOperation::Press; + collections::List keys; + }; + + struct MouseMoveCommand + { + MouseCommandArguments arguments; + }; + + struct MouseButtonCommand + { + MouseButton button = MouseButton::Left; + WString operation; + MouseCommandArguments arguments; + }; + + struct WheelCommand + { + vint direction = 0; + bool horizontal = false; + WheelCommandArguments arguments; + }; + + using IOCommand = Variant< + SyntaxErrorCommand, + ExitCommand, + TypeCommand, + KeyCommand, + MouseMoveCommand, + MouseButtonCommand, + WheelCommand + >; + + struct IOCommandHolder : Object + { + IOCommand command; + + IOCommandHolder(IOCommand&& _command) + : command(std::move(_command)) + { + } + }; + + bool StartsWith(const WString& text, const WString& prefix) + { + return text.Length() >= prefix.Length() && text.Left(prefix.Length()) == prefix; + } + + WString Trim(const WString& text) + { + vint begin = 0; + vint end = text.Length(); + while (begin < end && (text[begin] == L' ' || text[begin] == L'\t')) begin++; + while (begin < end && (text[end - 1] == L' ' || text[end - 1] == L'\t')) end--; + return text.Sub(begin, end - begin); + } + + WString ToUpperToken(const WString& text) + { + WString result; + for (vint i = 0; i < text.Length(); i++) + { + auto ch = text[i]; + if (L'a' <= ch && ch <= L'z') ch = ch - L'a' + L'A'; + result += WString::FromChar(ch); + } + return result; + } + + WString NormalizeKeyName(const WString& text) + { + WString result; + for (vint i = 0; i < text.Length(); i++) + { + auto ch = text[i]; + if (ch == L' ' || ch == L'\t') continue; + if (L'a' <= ch && ch <= L'z') ch = ch - L'a' + L'A'; + result += WString::FromChar(ch); + } + return result; + } + + void SplitByChar(const WString& text, wchar_t delimiter, collections::List& fragments) + { + vint begin = 0; + for (vint i = 0; i <= text.Length(); i++) + { + if (i == text.Length() || text[i] == delimiter) + { + fragments.Add(Trim(text.Sub(begin, i - begin))); + begin = i + 1; + } + } + } + + bool TryParseInteger(const WString& text, vint& value) + { + auto normalized = Trim(text); + if (normalized.Length() == 0) return false; + value = wtoi(normalized); + return itow(value) == normalized; + } + + bool TryParseModifier(const WString& text, IOCommandModifiers& modifiers) + { + auto token = ToUpperToken(Trim(text)); + if (token == L"CTRL" || token == L"CONTROL") + { + modifiers.ctrl = true; + return true; + } + else if (token == L"SHIFT") + { + modifiers.shift = true; + return true; + } + else if (token == L"ALT" || token == L"MENU") + { + modifiers.alt = true; + return true; + } + return false; + } + + bool TryParseMouseArguments(const WString& text, MouseCommandArguments& arguments) + { + collections::List fragments; + SplitByChar(text, L',', fragments); + if (fragments.Count() < 2) return false; + + vint x = 0; + vint y = 0; + if (!TryParseInteger(fragments[0], x)) return false; + if (!TryParseInteger(fragments[1], y)) return false; + arguments.position = Point(x, y); + + for (vint i = 2; i < fragments.Count(); i++) + { + if (!TryParseModifier(fragments[i], arguments.modifiers)) return false; + } + return true; + } + + bool TryParseWheelArguments(const WString& text, WheelCommandArguments& arguments) + { + collections::List fragments; + SplitByChar(text, L',', fragments); + if (fragments.Count() < 1) return false; + if (!TryParseInteger(fragments[0], arguments.ticks)) return false; + if (arguments.ticks < 0) return false; + + for (vint i = 1; i < fragments.Count(); i++) + { + if (!TryParseModifier(fragments[i], arguments.modifiers)) return false; + } + return true; + } + + bool IsPressing(IoCommandState* state, VKEY key) + { + return state->pressingKeys.Contains(key); + } + + bool IsCtrlPressing(IoCommandState* state) + { + return IsPressing(state, VKEY::KEY_CONTROL) || IsPressing(state, VKEY::KEY_LCONTROL) || IsPressing(state, VKEY::KEY_RCONTROL); + } + + bool IsShiftPressing(IoCommandState* state) + { + return IsPressing(state, VKEY::KEY_SHIFT) || IsPressing(state, VKEY::KEY_LSHIFT) || IsPressing(state, VKEY::KEY_RSHIFT); + } + + bool IsAltPressing(IoCommandState* state) + { + return IsPressing(state, VKEY::KEY_MENU) || IsPressing(state, VKEY::KEY_LMENU) || IsPressing(state, VKEY::KEY_RMENU); + } + + NativeWindowMouseInfo MakeMouseInfo(IoCommandState* state) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + CHECK_ERROR(state->mousePosition, ERROR_MESSAGE_PREFIX L"The mouse position is not set."); + NativeWindowMouseInfo info; + info.ctrl = IsCtrlPressing(state); + info.shift = IsShiftPressing(state); + info.left = state->leftPressing; + info.middle = state->middlePressing; + info.right = state->rightPressing; + info.x = state->mousePosition.Value().x; + info.y = state->mousePosition.Value().y; + info.wheel = 0; + info.nonClient = false; + return info; +#undef ERROR_MESSAGE_PREFIX + } + + NativeWindowKeyInfo MakeKeyInfo(IoCommandState* state, VKEY key, bool autoRepeatKeyDown = false) + { + NativeWindowKeyInfo info; + info.code = key; + info.ctrl = IsCtrlPressing(state); + info.shift = IsShiftPressing(state); + info.alt = IsAltPressing(state); + info.capslock = state->capslockToggled; + info.autoRepeatKeyDown = autoRepeatKeyDown; + return info; + } + + NativeWindowCharInfo MakeCharInfo(IoCommandState* state, wchar_t ch) + { + NativeWindowCharInfo info; + info.code = ch; + info.ctrl = IsCtrlPressing(state); + info.shift = IsShiftPressing(state); + info.alt = IsAltPressing(state); + info.capslock = state->capslockToggled; + return info; + } + + void KeyDown(IoCommandState* state, collections::List& listeners, VKEY key) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + CHECK_ERROR(!IsPressing(state, key), ERROR_MESSAGE_PREFIX L"The key is already being pressed."); + state->pressingKeys.Add(key); + if (key == VKEY::KEY_CAPITAL) + { + state->capslockToggled = !state->capslockToggled; + } + auto info = MakeKeyInfo(state, key); + for (auto listener : listeners) + { + listener->KeyDown(info); + } +#undef ERROR_MESSAGE_PREFIX + } + + void KeyUp(IoCommandState* state, collections::List& listeners, VKEY key) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + CHECK_ERROR(IsPressing(state, key), ERROR_MESSAGE_PREFIX L"The key is not being pressed."); + state->pressingKeys.Remove(key); + auto info = MakeKeyInfo(state, key); + for (auto listener : listeners) + { + listener->KeyUp(info); + } +#undef ERROR_MESSAGE_PREFIX + } + + void Char(IoCommandState* state, collections::List& listeners, wchar_t ch) + { + auto info = MakeCharInfo(state, ch); + for (auto listener : listeners) + { + listener->Char(info); + } + } + + void PressTemporaryModifiers(IoCommandState* state, collections::List& listeners, const IOCommandModifiers& modifiers, TemporaryModifiers& temporary) + { + if (modifiers.ctrl && !IsCtrlPressing(state)) + { + KeyDown(state, listeners, VKEY::KEY_CONTROL); + temporary.ctrl = true; + } + if (modifiers.shift && !IsShiftPressing(state)) + { + KeyDown(state, listeners, VKEY::KEY_SHIFT); + temporary.shift = true; + } + if (modifiers.alt && !IsAltPressing(state)) + { + KeyDown(state, listeners, VKEY::KEY_MENU); + temporary.alt = true; + } + } + + void ReleaseTemporaryModifiers(IoCommandState* state, collections::List& listeners, const TemporaryModifiers& temporary) + { + if (temporary.alt) + { + KeyUp(state, listeners, VKEY::KEY_MENU); + } + if (temporary.shift) + { + KeyUp(state, listeners, VKEY::KEY_SHIFT); + } + if (temporary.ctrl) + { + KeyUp(state, listeners, VKEY::KEY_CONTROL); + } + } + + NativePoint ConvertGuiPointToNativePoint(INativeWindow* targetWindow, Point position) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + CHECK_ERROR(targetWindow, ERROR_MESSAGE_PREFIX L"Native target window is missing."); + return targetWindow->Convert(position); +#undef ERROR_MESSAGE_PREFIX + } + + void MouseMove(IoCommandState* state, collections::List& listeners, NativePoint position) + { + if (!state->mousePosition) + { + for (auto listener : listeners) + { + listener->MouseEntered(); + } + } + else if (state->mousePosition.Value() == position) + { + return; + } + + state->mousePosition = position; + auto info = MakeMouseInfo(state); + for (auto listener : listeners) + { + listener->MouseMoving(info); + } + } + + void ButtonDown(IoCommandState* state, collections::List& listeners, MouseButton button, NativePoint position) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + MouseMove(state, listeners, position); + switch (button) + { + case MouseButton::Left: + CHECK_ERROR(!state->leftPressing, ERROR_MESSAGE_PREFIX L"The left button should not be being pressed."); + state->leftPressing = true; + for (auto listener : listeners) listener->LeftButtonDown(MakeMouseInfo(state)); + break; + case MouseButton::Middle: + CHECK_ERROR(!state->middlePressing, ERROR_MESSAGE_PREFIX L"The middle button should not be being pressed."); + state->middlePressing = true; + for (auto listener : listeners) listener->MiddleButtonDown(MakeMouseInfo(state)); + break; + case MouseButton::Right: + CHECK_ERROR(!state->rightPressing, ERROR_MESSAGE_PREFIX L"The right button should not be being pressed."); + state->rightPressing = true; + for (auto listener : listeners) listener->RightButtonDown(MakeMouseInfo(state)); + break; + } +#undef ERROR_MESSAGE_PREFIX + } + + void ButtonUp(IoCommandState* state, collections::List& listeners, MouseButton button, NativePoint position) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + MouseMove(state, listeners, position); + switch (button) + { + case MouseButton::Left: + CHECK_ERROR(state->leftPressing, ERROR_MESSAGE_PREFIX L"The left button should be being pressed."); + state->leftPressing = false; + for (auto listener : listeners) listener->LeftButtonUp(MakeMouseInfo(state)); + break; + case MouseButton::Middle: + CHECK_ERROR(state->middlePressing, ERROR_MESSAGE_PREFIX L"The middle button should be being pressed."); + state->middlePressing = false; + for (auto listener : listeners) listener->MiddleButtonUp(MakeMouseInfo(state)); + break; + case MouseButton::Right: + CHECK_ERROR(state->rightPressing, ERROR_MESSAGE_PREFIX L"The right button should be being pressed."); + state->rightPressing = false; + for (auto listener : listeners) listener->RightButtonUp(MakeMouseInfo(state)); + break; + } +#undef ERROR_MESSAGE_PREFIX + } + + void ButtonDoubleClick(IoCommandState* state, collections::List& listeners, MouseButton button, NativePoint position) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + MouseMove(state, listeners, position); + switch (button) + { + case MouseButton::Left: + CHECK_ERROR(!state->leftPressing, ERROR_MESSAGE_PREFIX L"The left button should not be being pressed."); + state->leftPressing = true; + for (auto listener : listeners) listener->LeftButtonDoubleClick(MakeMouseInfo(state)); + break; + case MouseButton::Middle: + CHECK_ERROR(!state->middlePressing, ERROR_MESSAGE_PREFIX L"The middle button should not be being pressed."); + state->middlePressing = true; + for (auto listener : listeners) listener->MiddleButtonDoubleClick(MakeMouseInfo(state)); + break; + case MouseButton::Right: + CHECK_ERROR(!state->rightPressing, ERROR_MESSAGE_PREFIX L"The right button should not be being pressed."); + state->rightPressing = true; + for (auto listener : listeners) listener->RightButtonDoubleClick(MakeMouseInfo(state)); + break; + } +#undef ERROR_MESSAGE_PREFIX + } + + void MouseButtonOperation(IoCommandState* state, collections::List& listeners, MouseButton button, const WString& operation, NativePoint position) + { + if (operation == L"Down") + { + ButtonDown(state, listeners, button, position); + } + else if (operation == L"Up") + { + ButtonUp(state, listeners, button, position); + } + else if (operation == L"Click") + { + ButtonDown(state, listeners, button, position); + ButtonUp(state, listeners, button, position); + } + else if (operation == L"DbClick") + { + ButtonDown(state, listeners, button, position); + ButtonUp(state, listeners, button, position); + ButtonDoubleClick(state, listeners, button, position); + ButtonUp(state, listeners, button, position); + } + } + + void Wheel(IoCommandState* state, collections::List& listeners, vint wheel, bool horizontal) + { + auto info = MakeMouseInfo(state); + info.wheel = wheel; + for (auto listener : listeners) + { + if (horizontal) + { + listener->HorizontalWheel(info); + } + else + { + listener->VerticalWheel(info); + } + } + } + + bool TryParseKey(INativeController* nativeController, const WString& text, VKEY& key) + { + auto token = Trim(text); + if (token.Length() == 0) return false; + if (!nativeController) return false; + + auto inputService = nativeController->InputService(); + key = inputService->GetKey(token); + if (key != VKEY::KEY_UNKNOWN) return true; + + auto normalizedToken = NormalizeKeyName(token); + for (vint i = 0; i <= (vint)VKEY::KEY_MAXIMUM; i++) + { + auto candidate = (VKEY)i; + auto candidateName = inputService->GetKeyName(candidate); + if (candidateName != WString::Empty && candidateName != L"?" && NormalizeKeyName(candidateName) == normalizedToken) + { + key = candidate; + return true; + } + } + key = VKEY::KEY_UNKNOWN; + return false; + } + + bool TryParseKeyList(INativeController* nativeController, const WString& text, collections::List& keys) + { + collections::List fragments; + SplitByChar(text, L'+', fragments); + if (fragments.Count() == 0) return false; + for (auto fragment : fragments) + { + VKEY key = VKEY::KEY_UNKNOWN; + if (!TryParseKey(nativeController, fragment, key)) return false; + keys.Add(key); + } + return true; + } + + WString MouseButtonPrefix(MouseButton button) + { + switch (button) + { + case MouseButton::Left: return WString::Unmanaged(L"!Left"); + case MouseButton::Middle: return WString::Unmanaged(L"!Middle"); + default: return WString::Unmanaged(L"!Right"); + } + } + + bool TryParseMouseButtonCommand(const WString& command, MouseButton button, const WString& operation, MouseButtonCommand& mouseCommand, bool& matched) + { + auto prefix = MouseButtonPrefix(button) + operation + WString::Unmanaged(L":"); + if (!StartsWith(command, prefix)) return false; + matched = true; + + MouseCommandArguments arguments; + if (!TryParseMouseArguments(command.Right(command.Length() - prefix.Length()), arguments)) return false; + + mouseCommand.button = button; + mouseCommand.operation = operation; + mouseCommand.arguments = arguments; + return true; + } + + bool TryParseWheelCommand(const WString& command, const WString& prefix, vint direction, bool horizontal, WheelCommand& wheelCommand, bool& matched) + { + if (!StartsWith(command, prefix)) return false; + matched = true; + + WheelCommandArguments arguments; + if (!TryParseWheelArguments(command.Right(command.Length() - prefix.Length()), arguments)) return false; + + wheelCommand.direction = direction; + wheelCommand.horizontal = horizontal; + wheelCommand.arguments = arguments; + return true; + } + + IOCommand ParseIOCommand(INativeController* nativeController, const WString& command) + { + if (command == L"!Exit") + { + return ExitCommand{}; + } + + if (StartsWith(command, L"!Type:")) + { + TypeCommand typeCommand; + typeCommand.text = command.Right(command.Length() - 6); + return typeCommand; + } + + if (StartsWith(command, L"!KeyDown:") || StartsWith(command, L"!KeyUp:") || StartsWith(command, L"!KeyPress:")) + { + WString prefix; + KeyOperation operation = KeyOperation::Press; + if (StartsWith(command, L"!KeyDown:")) + { + prefix = WString::Unmanaged(L"!KeyDown:"); + operation = KeyOperation::Down; + } + else if (StartsWith(command, L"!KeyUp:")) + { + prefix = WString::Unmanaged(L"!KeyUp:"); + operation = KeyOperation::Up; + } + else + { + prefix = WString::Unmanaged(L"!KeyPress:"); + } + + collections::List keys; + if (!TryParseKeyList(nativeController, command.Right(command.Length() - prefix.Length()), keys)) + { + return SyntaxErrorCommand{}; + } + + KeyCommand keyCommand; + keyCommand.operation = operation; + CopyFrom(keyCommand.keys, keys); + return keyCommand; + } + + if (StartsWith(command, L"!MouseMove:")) + { + MouseMoveCommand mouseCommand; + if (!TryParseMouseArguments(command.Right(command.Length() - 11), mouseCommand.arguments)) + { + return SyntaxErrorCommand{}; + } + return mouseCommand; + } + + const WString operations[] = + { + WString::Unmanaged(L"Down"), + WString::Unmanaged(L"Up"), + WString::Unmanaged(L"Click"), + WString::Unmanaged(L"DbClick"), + }; + + for (auto operation : operations) + { + MouseButtonCommand mouseCommand; + bool matched = false; + if (TryParseMouseButtonCommand(command, MouseButton::Left, operation, mouseCommand, matched)) return mouseCommand; + if (matched) return SyntaxErrorCommand{}; + + if (TryParseMouseButtonCommand(command, MouseButton::Middle, operation, mouseCommand, matched)) return mouseCommand; + if (matched) return SyntaxErrorCommand{}; + + if (TryParseMouseButtonCommand(command, MouseButton::Right, operation, mouseCommand, matched)) return mouseCommand; + if (matched) return SyntaxErrorCommand{}; + } + + { + WheelCommand wheelCommand; + bool matched = false; + if (TryParseWheelCommand(command, L"!MouseWheelUp:", 1, false, wheelCommand, matched)) return wheelCommand; + if (matched) return SyntaxErrorCommand{}; + if (TryParseWheelCommand(command, L"!MouseWheelDown:", -1, false, wheelCommand, matched)) return wheelCommand; + if (matched) return SyntaxErrorCommand{}; + if (TryParseWheelCommand(command, L"!MouseWheelRight:", 1, true, wheelCommand, matched)) return wheelCommand; + if (matched) return SyntaxErrorCommand{}; + if (TryParseWheelCommand(command, L"!MouseWheelLeft:", -1, true, wheelCommand, matched)) return wheelCommand; + if (matched) return SyntaxErrorCommand{}; + } + + return SyntaxErrorCommand{}; + } + + void ExecuteIOCommand(IoCommandState* state, INativeController* nativeController, INativeWindow* targetWindow, collections::List& listeners, IOCommand&& ioCommand) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + CHECK_ERROR(nativeController, ERROR_MESSAGE_PREFIX L"Native controller is missing."); + CHECK_ERROR(targetWindow, ERROR_MESSAGE_PREFIX L"Native target window is missing."); + auto listenerList = &listeners; + auto commandHolder = Ptr(new IOCommandHolder(std::move(ioCommand))); + nativeController->AsyncService()->InvokeInMainThread(targetWindow, [=]() mutable + { + auto activateTargetWindow = [=]() + { + if (!targetWindow->IsActivated()) + { + targetWindow->SetActivate(); + } + }; + + commandHolder->command.Apply(Overloading( + [](const SyntaxErrorCommand&) + { + CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Syntax error command should not be executed."); + }, + [=](const ExitCommand&) + { + targetWindow->Hide(true); + }, + [=](const TypeCommand& typeCommand) + { + activateTargetWindow(); + for (vint i = 0; i < typeCommand.text.Length(); i++) + { + auto ch = typeCommand.text[i]; + if (ch == L'\r') continue; + if (ch == L'\n') + { + Char(state, *listenerList, L'\r'); + Char(state, *listenerList, L'\n'); + } + else + { + Char(state, *listenerList, ch); + } + } + }, + [=](const KeyCommand& keyCommand) + { + activateTargetWindow(); + switch (keyCommand.operation) + { + case KeyOperation::Down: + for (auto key : keyCommand.keys) + { + KeyDown(state, *listenerList, key); + } + break; + case KeyOperation::Up: + for (vint i = keyCommand.keys.Count() - 1; i >= 0; i--) + { + KeyUp(state, *listenerList, keyCommand.keys[i]); + } + break; + case KeyOperation::Press: + for (auto key : keyCommand.keys) + { + KeyDown(state, *listenerList, key); + } + for (vint i = keyCommand.keys.Count() - 1; i >= 0; i--) + { + KeyUp(state, *listenerList, keyCommand.keys[i]); + } + break; + } + }, + [=](const MouseMoveCommand& mouseCommand) + { + activateTargetWindow(); + TemporaryModifiers temporary; + PressTemporaryModifiers(state, *listenerList, mouseCommand.arguments.modifiers, temporary); + MouseMove(state, *listenerList, ConvertGuiPointToNativePoint(targetWindow, mouseCommand.arguments.position)); + ReleaseTemporaryModifiers(state, *listenerList, temporary); + }, + [=](const MouseButtonCommand& mouseCommand) + { + activateTargetWindow(); + TemporaryModifiers temporary; + PressTemporaryModifiers(state, *listenerList, mouseCommand.arguments.modifiers, temporary); + MouseButtonOperation(state, *listenerList, mouseCommand.button, mouseCommand.operation, ConvertGuiPointToNativePoint(targetWindow, mouseCommand.arguments.position)); + ReleaseTemporaryModifiers(state, *listenerList, temporary); + }, + [=](const WheelCommand& wheelCommand) + { + activateTargetWindow(); + TemporaryModifiers temporary; + PressTemporaryModifiers(state, *listenerList, wheelCommand.arguments.modifiers, temporary); + Wheel(state, *listenerList, wheelCommand.arguments.ticks * 120 * wheelCommand.direction, wheelCommand.horizontal); + ReleaseTemporaryModifiers(state, *listenerList, temporary); + } + )); + }); +#undef ERROR_MESSAGE_PREFIX + } + } + + WString RunIOCommandOnNativeWindow( + IoCommandState* state, + INativeController* nativeController, + INativeWindow* nativeWindow, + collections::List& listeners, + WString command + ) + { +#define ERROR_MESSAGE_PREFIX L"vl::presentation::RunIOCommandOnNativeWindow(...)#" + CHECK_ERROR(state, ERROR_MESSAGE_PREFIX L"IO command state is missing."); + CHECK_ERROR(nativeController, ERROR_MESSAGE_PREFIX L"Native controller is missing."); + + auto targetWindow = nativeWindow; + if (!targetWindow) + { + targetWindow = nativeController->WindowService()->GetMainWindow(); + } + if (!targetWindow) + { + return WString::Unmanaged(iocommands::IO_COMMAND_SYNTAX); + } + + auto ioCommand = iocommands::ParseIOCommand(nativeController, command); + if (ioCommand.TryGet()) + { + return WString::Unmanaged(iocommands::IO_COMMAND_SYNTAX); + } + + iocommands::ExecuteIOCommand(state, nativeController, targetWindow, listeners, std::move(ioCommand)); + return WString::Unmanaged(L"Queued"); +#undef ERROR_MESSAGE_PREFIX + } + } +} + + +/*********************************************************************** +.\UTILITIES\SHAREDSERVICES\GUISHAREDAUTOMATIONSERVICE_CONTROLS.CPP +***********************************************************************/ + +namespace vl +{ + namespace presentation + { + using namespace collections; + using namespace controls; + using namespace compositions; + using namespace elements; + using namespace glr::xml; + using namespace remoteprotocol; + using namespace stream; + +/*********************************************************************** +AutomationService +***********************************************************************/ + + WString AutomationService::DumpControlTreeInternal() + { + auto app = GetApplication(); + auto mainWindow = app->GetMainWindow(); + + auto dumpRoot = Ptr(new glr::json::JsonObject); + ConvertCustomTypeToJsonField(dumpRoot, L"WindowManagement", WString::Unmanaged(L"MultiWindow")); + { + auto field = Ptr(new glr::json::JsonObjectField); + dumpRoot->fields.Add(field); + field->name.value = L"MainWindow"; + field->value = DumpWindowClientArea(mainWindow, GetNativeWindowId(mainWindow->GetNativeWindow()), { 0,0 }); + } + { + auto field = Ptr(new glr::json::JsonObjectField); + dumpRoot->fields.Add(field); + field->name.value = L"SubWindows"; + + auto subWindows = Ptr(new glr::json::JsonArray); + field->value = subWindows; + + for (auto subWindow : app->windows) + { + if (subWindow != mainWindow && subWindow->GetVisible() && !dynamic_cast(subWindow)) + { + subWindows->items.Add(DumpWindowClientArea(subWindow, GetNativeWindowId(subWindow->GetNativeWindow()), { 0,0 })); + } + } + } + { + auto field = Ptr(new glr::json::JsonObjectField); + dumpRoot->fields.Add(field); + field->name.value = L"Popups"; + + auto popups = Ptr(new glr::json::JsonArray); + field->value = popups; + + for (auto popup : app->openingPopups) + { + popups->items.Add(DumpWindowClientArea(popup, GetNativeWindowId(popup->GetNativeWindow()), { 0,0 })); + } + } + return DumpJsonToString(dumpRoot); + } + + AutomationService::AutomationService() + { + } + + AutomationService::~AutomationService() + { + } + + bool AutomationService::CanDumpControlTree() + { + return true; + } + +/*********************************************************************** +AutomationServiceHosted +***********************************************************************/ + + Nullable AutomationServiceHosted::GetNativeWindowId(INativeWindow* window) + { + return {}; + } + + INativeWindow* AutomationServiceHosted::GetNativeWindow(Nullable windowId) + { + return GetHostedApplication()->GetNativeWindowHost(); + } + + WString AutomationServiceHosted::DumpControlTreeInternal() + { + auto app = GetApplication(); + auto mainWindow = app->GetMainWindow(); + + auto dumpRoot = Ptr(new glr::json::JsonObject); + ConvertCustomTypeToJsonField(dumpRoot, L"WindowManagement", windowManagement); + { + auto field = Ptr(new glr::json::JsonObjectField); + dumpRoot->fields.Add(field); + field->name.value = L"MainWindow"; + field->value = DumpWindowClientArea(mainWindow, {}, { 0,0 }); + { + auto subWindowsField = Ptr(new glr::json::JsonObjectField); + field->value.Cast()->fields.Add(subWindowsField); + subWindowsField->name.value = L"subWindowsInZOrder"; + + auto subWindows = Ptr(new glr::json::JsonArray); + subWindowsField->value = subWindows; + + auto hostedController = static_cast(GetHostedApplication()); + for (auto wmWindow : From(hostedController->wmManager->topMostedWindowsInOrder).Concat(hostedController->wmManager->ordinaryWindowsInOrder).Reverse()) + { + if (wmWindow->id == mainWindow->GetNativeWindow()) continue; + auto subWindow = app->windowMap[wmWindow->id]; + auto offset = mainWindow->GetNativeWindow()->Convert(wmWindow->bounds.LeftTop()); + subWindows->items.Add(DumpWindowClientArea(subWindow, {}, offset)); + } + } + } + return DumpJsonToString(dumpRoot); + } + + AutomationServiceHosted::AutomationServiceHosted() + { + } + + AutomationServiceHosted::~AutomationServiceHosted() + { + } + + bool AutomationServiceHosted::CanDumpControlTree() + { + return true; + } + +/*********************************************************************** +RemoteProtocolAutomationService +***********************************************************************/ + + WString RemoteProtocolAutomationService::RunIOCommandInternal(Nullable windowId, const WString& ioCommand) + { + auto window = dynamic_cast(this->GetNativeWindow(windowId)); + if (!window) + { + return L"!Invalid window."; + } + + return RunIOCommandOnNativeWindow(&ioCommandState, GetHostedApplication()->GetNativeController(), window, window->listeners, ioCommand); + } + + RemoteProtocolAutomationService::RemoteProtocolAutomationService() + { + windowManagement = WString::Unmanaged(L"HostedRemoteProtocol"); + } + + RemoteProtocolAutomationService::~RemoteProtocolAutomationService() + { + } + + bool RemoteProtocolAutomationService::CanRunIOCommands() + { + return true; + } + +/*********************************************************************** +DumpWindowClientArea +***********************************************************************/ + + WString PrintControlThemeName(theme::ThemeName theme) + { + switch (theme) + { + case theme::ThemeName::Window: return WString::Unmanaged(L"Window"); +#define GUI_DEFINE_THEME_NAME(TEMPLATE, CONTROL) case theme::ThemeName::CONTROL: return WString::Unmanaged(L ## #CONTROL); + GUI_CONTROL_TEMPLATE_TYPES(GUI_DEFINE_THEME_NAME) +#undef GUI_DEFINE_THEME_NAME + default: return WString::Unmanaged(L"Unknown"); + } + } + + void AddJsonField(Ptr dump, const WString& name, Ptr value) + { + auto field = Ptr(new glr::json::JsonObjectField); + dump->fields.Add(field); + field->name.value = name; + field->value = value; + } + + Rect OffsetRect(Rect bounds, Point offset) + { + bounds.Move(offset.x, offset.y); + return bounds; + } + + WString HexByte(unsigned char value) + { + const wchar_t* code = L"0123456789ABCDEF"; + return WString::FromChar(code[value / 16]) + WString::FromChar(code[value % 16]); + } + + WString PrintColor(Color color) + { + return WString::Unmanaged(L"#") + HexByte(color.r) + HexByte(color.g) + HexByte(color.b) + HexByte(color.a); + } + + WString PrintElementShape(ElementShape shape) + { + switch (shape.shapeType) + { + case ElementShapeType::Rectangle: + return WString::Unmanaged(L"Rectangle"); + case ElementShapeType::Ellipse: + return WString::Unmanaged(L"Ellipse"); + case ElementShapeType::RoundRect: + return L"RoundRect(" + itow(shape.radiusX) + L"," + itow(shape.radiusY) + L")"; + default: + return WString::Unmanaged(L"Unknown"); + } + } + + WString PrintSplitterDirection(Gui3DSplitterElement::Direction direction) + { + switch (direction) + { + case Gui3DSplitterElement::Horizontal: + return WString::Unmanaged(L"Horizontal"); + case Gui3DSplitterElement::Vertical: + return WString::Unmanaged(L"Vertical"); + default: + return WString::Unmanaged(L"Unknown"); + } + } + + WString PrintGradientDirection(GuiGradientBackgroundElement::Direction direction) + { + switch (direction) + { + case GuiGradientBackgroundElement::Horizontal: + return WString::Unmanaged(L"Horizontal"); + case GuiGradientBackgroundElement::Vertical: + return WString::Unmanaged(L"Vertical"); + case GuiGradientBackgroundElement::Slash: + return WString::Unmanaged(L"Slash"); + case GuiGradientBackgroundElement::Backslash: + return WString::Unmanaged(L"Backslash"); + default: + return WString::Unmanaged(L"Unknown"); + } + } + + WString PrintTextPos(TextPos position) + { + return L"(" + itow(position.row) + L"," + itow(position.column) + L")"; + } + + WString PrintFontStyle(const FontProperties& fontProperties) + { + return fontProperties.fontFamily + L"," + itow(fontProperties.size); + } + + WString PrintLayout(GuiGraphicsComposition* composition) + { + if (auto table = dynamic_cast(composition)) + { + return L"Table:" + itow(table->GetRows()) + L"*" + itow(table->GetColumns()); + } + else if (auto cell = dynamic_cast(composition)) + { + if (dynamic_cast(cell->GetParent())) + { + return L"Cell:(" + itow(cell->GetRow()) + L"," + itow(cell->GetColumn()) + L")*(" + itow(cell->GetRowSpan()) + L"," + itow(cell->GetColumnSpan()) + L")"; + } + } + else if (auto rowSplitter = dynamic_cast(composition)) + { + return L"RowSplitter:" + itow(rowSplitter->GetRowsToTheTop()); + } + else if (auto columnSplitter = dynamic_cast(composition)) + { + return L"ColumnSplitter:" + itow(columnSplitter->GetColumnsToTheLeft()); + } + else if (dynamic_cast(composition)) + { + return WString::Unmanaged(L"Stack"); + } + else if (auto stackItem = dynamic_cast(composition)) + { + if (auto stack = dynamic_cast(stackItem->GetParent())) + { + auto index = stack->GetStackItems().IndexOf(stackItem); + if (index != -1) + { + return L"StackItem:" + itow(index); + } + } + } + else if (dynamic_cast(composition)) + { + return WString::Unmanaged(L"Flow"); + } + else if (auto flowItem = dynamic_cast(composition)) + { + if (auto flow = dynamic_cast(flowItem->GetParent())) + { + auto index = flow->GetFlowItems().IndexOf(flowItem); + if (index != -1) + { + return L"FlowItem:" + itow(index); + } + } + } + return WString::Empty; + } + + WString PrintDocument(Ptr document) + { + if (!document) return WString::Empty; + return GenerateToStream([&](StreamWriter& writer) + { + XmlPrint(document->SaveToXml(), writer); + }); + } + + void DumpElement(Ptr element, Ptr compositionDump) + { + if (!element) return; + auto rawElement = element.Obj(); + WString elementDescription; + + if (dynamic_cast(rawElement)) + { + elementDescription = WString::Unmanaged(L"FocusRectangle"); + } + else if (auto border = dynamic_cast(rawElement)) + { + elementDescription = L"Border:" + PrintColor(border->GetColor()) + L"," + PrintElementShape(border->GetShape()); + } + else if (auto border3D = dynamic_cast(rawElement)) + { + elementDescription = L"3DBorder:" + PrintColor(border3D->GetColor1()) + L"," + PrintColor(border3D->GetColor2()); + } + else if (auto splitter3D = dynamic_cast(rawElement)) + { + elementDescription = L"3DSplitter:" + PrintColor(splitter3D->GetColor1()) + L"," + PrintColor(splitter3D->GetColor2()) + L"," + PrintSplitterDirection(splitter3D->GetDirection()); + } + else if (auto background = dynamic_cast(rawElement)) + { + elementDescription = L"Background:" + PrintColor(background->GetColor()) + L"," + PrintElementShape(background->GetShape()); + } + else if (auto gradient = dynamic_cast(rawElement)) + { + elementDescription = L"Gradient:" + PrintColor(gradient->GetColor1()) + L"," + PrintColor(gradient->GetColor2()) + L"," + PrintGradientDirection(gradient->GetDirection()) + L"," + PrintElementShape(gradient->GetShape()); + } + else if (auto shadow = dynamic_cast(rawElement)) + { + elementDescription = L"InnerShadow:" + PrintColor(shadow->GetColor()) + L"," + itow(shadow->GetThickness()); + } + else if (auto label = dynamic_cast(rawElement)) + { + elementDescription = L"Label:" + PrintColor(label->GetColor()) + L"," + PrintFontStyle(label->GetFont()); + if (label->GetWrapLine()) elementDescription += L",WrapLine"; + if (label->GetEllipse()) elementDescription += L",Ellipse"; + if (label->GetMultiline()) elementDescription += L",Multiline"; + ConvertCustomTypeToJsonField(compositionDump, L"elementText", label->GetText()); + } + else if (dynamic_cast(rawElement)) + { + elementDescription = WString::Unmanaged(L"Image"); + } + else if (dynamic_cast(rawElement)) + { + elementDescription = WString::Unmanaged(L"Polygon"); + } + else if (auto document = dynamic_cast(rawElement)) + { + elementDescription = L"Document:Selection" + PrintTextPos(document->GetCaretBegin()) + L"-" + PrintTextPos(document->GetCaretEnd()); + if (auto passwordChar = document->GetPasswordChar()) + { + elementDescription += L",PasswordChar=" + WString::FromChar(passwordChar); + } + if (document->GetWrapLine()) + { + elementDescription += L",WrapLine"; + } + ConvertCustomTypeToJsonField(compositionDump, L"elementDocument", PrintDocument(document->GetDocument())); + } + + if (elementDescription != WString::Empty) + { + ConvertCustomTypeToJsonField(compositionDump, L"element", elementDescription); + } + } + + Ptr DumpComposition(GuiGraphicsComposition* composition, Point offset) + { + auto compositionDump = Ptr(new glr::json::JsonObject); + ConvertCustomTypeToJsonField(compositionDump, L"bounds", OffsetRect(composition->GetGlobalBounds(), offset)); + + auto layout = PrintLayout(composition); + if (layout != WString::Empty) + { + ConvertCustomTypeToJsonField(compositionDump, L"layout", layout); + } + + DumpElement(composition->GetOwnedElement(), compositionDump); + + if (auto control = composition->GetAssociatedControl()) + { + ConvertCustomTypeToJsonField(compositionDump, L"control", PrintControlThemeName(control->GetControlThemeName())); + } + + if (composition->Children().Count() > 0) + { + auto children = Ptr(new glr::json::JsonArray); + for (auto child : composition->Children()) + { + if (child->GetEventuallyVisible()) + { + children->items.Add(DumpComposition(child, offset)); + } + } + if (children->items.Count() > 0) + { + AddJsonField(compositionDump, L"children", children); + } + } + + return compositionDump; + } + + Ptr DumpWindowClientArea(controls::GuiWindow* window, Nullable windowId, Point offset) + { + auto windowDump = Ptr(new glr::json::JsonObject); + ConvertCustomTypeToJsonField(windowDump, L"title", window->GetText()); + if (windowId) + { + ConvertCustomTypeToJsonField(windowDump, L"windowId", windowId.Value()); + } + + { + Size size = window->GetBoundsComposition()->GetCachedBounds().GetSize(); + ConvertCustomTypeToJsonField(windowDump, L"bounds", Rect(offset, size)); + } + AddJsonField(windowDump, L"composition", DumpComposition(window->GetBoundsComposition(), offset)); + return windowDump; + } + } +} + + /*********************************************************************** .\UTILITIES\SHAREDSERVICES\GUISHAREDCALLBACKSERVICE.CPP ***********************************************************************/ diff --git a/Import/GacUI.h b/Import/GacUI.h index 6046265f..f80ee569 100644 --- a/Import/GacUI.h +++ b/Import/GacUI.h @@ -2169,6 +2169,7 @@ Interfaces: INativeInputService : Input Service INativeCallbackService : Callback Service INativeDialogService : Dialog Service + INativeAutomationService : Automation Service ***********************************************************************/ @@ -3837,6 +3838,73 @@ INativeDialogService return static_cast(static_cast(a) & static_cast(b)); } +/*********************************************************************** +INativeAutomationService +***********************************************************************/ + + /// + /// Automation service. To access this service, use [M:vl.presentation.INativeController.AutomationService]. + /// + class INativeAutomationService : public virtual Interface + { + public: + static INativeAutomationService* UnavailableService(); + + /// + /// Test if the service is available. + /// When it returns false, all other memthods should raise an exception. + /// Any real implementation should return true, in this case, unsupported features could just no-op. + /// + /// Returns false when the automation service is completely unavailable. This is different from availability of each features. + virtual bool Available() = 0; + + /// + /// Turn off all features. + /// + virtual void Stop() = 0; + + /// + /// Test if is available. + /// This feature only works on GacUI applications, or when remote protocol is in use, the core side. + /// + /// Returns true if this function is available. Otherwise it should returns an empty string. + virtual bool CanDumpControlTree() = 0; + + /// + /// Dump the control tree. + /// + /// The dump. + virtual WString DumpControlTree() = 0; + + /// + /// Test if is available. + /// This feature only works on the remote protocol renderer side. + /// + /// Returns true if this function is available. Otherwise it should returns an empty string. + virtual bool CanDumpDomTree() = 0; + + /// + /// Dump the DOM tree. + /// + /// The dump. + virtual WString DumpDomTree() = 0; + + /// + /// Test if is available. + /// This feature only works on GacUI applications, or when remote protocol is in use, the renderer side. + /// + /// Returns true if this function is available. Otherwise it should returns an empty string. + virtual bool CanRunIOCommands() = 0; + + /// + /// Run an IO command. + /// + /// The id of the window for the IO command. It should match the content from . Specify null for hosted mode application or remote protocol renderer. + /// The IO command. + /// The result of the IO commands, or an error message starting with "!". + virtual WString RunIOCommand(Nullable windowId, const WString& ioCommand) = 0; + }; + /*********************************************************************** Native Window Controller ***********************************************************************/ @@ -3893,6 +3961,11 @@ Native Window Controller /// The user dialog service virtual INativeDialogService* DialogService()=0; /// + /// Get the automation service. + /// + /// The user automation service + virtual INativeAutomationService* AutomationService()=0; + /// /// Get the file path of the current executable. /// /// The file path of the current executable. @@ -3935,10 +4008,13 @@ Native Window Controller }; /// - /// Get the global native system service controller. + /// Get the global system service controller. + /// This is not the controller passed into . + /// It is a controller with some services substitutable with . + /// Default implementation of substitutable services is specified in . /// - /// The global native system service controller. - extern INativeController* GetCurrentController(); + /// The global system service controller. + extern INativeController* GetCurrentController(); /// /// Set the global native system service controller. /// @@ -3948,6 +4024,7 @@ Native Window Controller #define GUI_SUBSTITUTABLE_SERVICES(F) \ F(Clipboard) \ F(Dialog) \ + F(Automation) \ #define GUI_UNSUBSTITUTABLE_SERVICES(F) \ F(Callback) \ @@ -4040,6 +4117,7 @@ Helper Functions #endif + /*********************************************************************** .\APPLICATION\GRAPHICSCOMPOSITIONS\GUIGRAPHICSEVENTRECEIVER.H ***********************************************************************/ @@ -7386,6 +7464,7 @@ IGuiHostedApplication public: virtual INativeWindow* GetNativeWindowHost() = 0; + virtual INativeController* GetNativeController() = 0; }; extern IGuiHostedApplication* GetHostedApplication(); @@ -10729,6 +10808,9 @@ namespace vl { namespace presentation { + class AutomationService; + class AutomationServiceHosted; + namespace controls { @@ -10744,6 +10826,8 @@ Application friend class GuiWindow; friend class GuiPopup; friend class Ptr; + friend class AutomationService; + friend class AutomationServiceHosted; private: void InvokeClipboardNotify(compositions::GuiGraphicsComposition* composition, compositions::GuiEventArgs& arguments); @@ -22763,26 +22847,12 @@ ChannelPackageSemantic extern void JsonChannelUnpack(Ptr package, ChannelPackageInfo& info, Ptr& arguments); extern void JsonChannelUnpack(Ptr package, ChannelPackageInfo& info, Ptr& arguments); -/*********************************************************************** -JsonNodeListSerializer -***********************************************************************/ - - struct JsonNodeListSerializer - { - using SourceType = collections::List; - using DestType = WString; - using ContextType = Ptr; - - static void Serialize(Ptr parser, const SourceType& source, DestType& dest); - static void Deserialize(Ptr parser, const DestType& source, SourceType& dest); - }; - - using GuiRemoteProtocolChannelServer = inter_process::NetworkProtocolChannelServer; + using GuiRemoteProtocolChannelServer = inter_process::NetworkProtocolChannelServer; class GuiRemoteProtocolChannelClient - : public inter_process::NetworkProtocolChannelClient + : public inter_process::NetworkProtocolChannelClient { - using Base = inter_process::NetworkProtocolChannelClient; + using Base = inter_process::NetworkProtocolChannelClient; protected: IJsonChannelClient::ChannelMap channelNames; @@ -22794,9 +22864,9 @@ JsonNodeListSerializer }; class GuiRemoteProtocolLocalChannelClient - : public inter_process::NetworkProtocolLocalChannelClient + : public inter_process::NetworkProtocolLocalChannelClient { - using Base = inter_process::NetworkProtocolLocalChannelClient; + using Base = inter_process::NetworkProtocolLocalChannelClient; protected: IJsonChannelClient::ChannelMap channelNames; @@ -22823,8 +22893,9 @@ GuiRemoteProtocolCoreChannel IGuiRemoteProtocolEvents* events = nullptr; IGuiRemoteEventProcessor* eventProcessor = nullptr; WString executablePath; - SpinLock lockRendererClientId; - vint rendererClientId = -1; + atomic_vint rendererClientId = -1; + SpinLock lockPackagesBeforeRenderer; + collections::List packagesBeforeRenderer; using OnReadEventHandler = void (GuiRemoteProtocolCoreChannel::*)(Ptr); using OnReadEventHandlerMap = collections::Dictionary; @@ -22879,6 +22950,7 @@ GuiRemoteProtocolCoreChannel WString GetExecutablePath() override; void Submit(bool& disconnected) override; IGuiRemoteEventProcessor* GetRemoteEventProcessor() override; + void DetachRenderer(vint clientId); }; /*********************************************************************** @@ -22956,7 +23028,7 @@ Developer: Zihan Chen(vczh) GacUI::Remote Window Interfaces: - GuiRemoteProtocolAsyncJsonChannelSerializer + GuiRemoteProtocolJsonChannelRenderer_Async ***********************************************************************/ @@ -22968,10 +23040,10 @@ namespace vl::presentation::remoteprotocol::channeling { /*********************************************************************** -GuiRemoteProtocolAsyncJsonChannelSerializer +GuiRemoteProtocolJsonChannelRenderer_Async ***********************************************************************/ - class GuiRemoteProtocolAsyncJsonChannelSerializer + class GuiRemoteProtocolJsonChannelRenderer_Async : public Object , public virtual IJsonChannel , protected virtual IJsonChannelReader @@ -23015,6 +23087,7 @@ GuiRemoteProtocolAsyncJsonChannelSerializer SpinLock lockConnection; vint connectionCounter = 0; + vint connectionClientId = -1; bool connectionAvailable = false; bool AreCurrentPendingRequestGroupSatisfied(bool disconnected); @@ -23025,8 +23098,8 @@ GuiRemoteProtocolAsyncJsonChannelSerializer void OnRead(vint senderClientId, const JsonPackage& package) override; public: - GuiRemoteProtocolAsyncJsonChannelSerializer(IJsonChannel* _channel, IGuiRemoteEventProcessor* _remoteEventProcessor = nullptr); - ~GuiRemoteProtocolAsyncJsonChannelSerializer(); + GuiRemoteProtocolJsonChannelRenderer_Async(IJsonChannel* _channel, IGuiRemoteEventProcessor* _remoteEventProcessor = nullptr); + ~GuiRemoteProtocolJsonChannelRenderer_Async(); const WString& GetChannelName() override; IJsonChannelReader* GetReader() override; @@ -23085,6 +23158,7 @@ GuiRemoteProtocolAsyncJsonChannelRenderer struct ReceivedPackage { vint senderClientId = -1; + vint messageVersion = -1; JsonPackage package; }; @@ -23095,6 +23169,8 @@ GuiRemoteProtocolAsyncJsonChannelRenderer SpinLock lockMessages; IGuiRemoteProtocolAsyncRendererInvoker* invokeInMainThread = nullptr; collections::List queuedMessages; + vint messageVersion = 0; + bool channelInitialized = false; bool uiTaskQueued = false; void ScheduleProcessRemoteMessages(); @@ -23516,6 +23592,11 @@ Interfaces: #define VCZH_PRESENTATION_GUIREMOTECONTROLLER_REMOTERENDERER_GUIREMOTERENDERERSINGLE +namespace vl::presentation +{ + class AutomationServiceRenderer; +} + namespace vl::presentation::remote_renderer { class GuiRemoteRendererSingle @@ -23525,7 +23606,7 @@ namespace vl::presentation::remote_renderer , protected virtual INativeControllerListener { friend class GuiRemoteDocumentParagraphElement; - + friend class AutomationServiceRenderer; protected: INativeWindow* window = nullptr; INativeScreen* screen = nullptr; @@ -23568,6 +23649,8 @@ namespace vl::presentation::remote_renderer Nullable minSize; }; + using RenderingElement = collections::Pair>; + using RenderingElementMap = collections::Dictionary; using ElementMap = collections::Dictionary>; using ImageMap = collections::Dictionary>; using SolidLabelMeasuringMap = collections::Dictionary; @@ -23582,6 +23665,9 @@ namespace vl::presentation::remote_renderer Ptr renderingDom; remoteprotocol::DomIndex renderingDomIndex; + bool enabledAutomation = false; + RenderingElementMap renderingElements; + Alignment GetAlignment(remoteprotocol::ElementHorizontalAlignment alignment); Alignment GetAlignment(remoteprotocol::ElementVerticalAlignment alignment); void StoreLabelMeasuring(vint id, remoteprotocol::ElementSolidLabelMeasuringRequest request, Ptr solidLabel, Size minSize); @@ -23651,7 +23737,7 @@ namespace vl::presentation::remote_renderer void RequestRendererUpdateElement_ImageFrame(const remoteprotocol::ElementDesc_ImageFrame& arguments); public: - GuiRemoteRendererSingle(); + GuiRemoteRendererSingle(bool _enabledAutomation); ~GuiRemoteRendererSingle(); void RegisterMainWindow(INativeWindow* _window); @@ -28094,6 +28180,378 @@ namespace vl #endif +/*********************************************************************** +.\UTILITIES\SHAREDSERVICES\GUISHAREDAUTOMATIONSERVICE.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) +GacUI::Native Window::Default Service Implementation + +Interfaces: +***********************************************************************/ + +#ifndef VCZH_PRESENTATION_UTILITIES_SHAREDSERVICES_SHAREDAUTOMATIONSERVICE +#define VCZH_PRESENTATION_UTILITIES_SHAREDSERVICES_SHAREDAUTOMATIONSERVICE + + +namespace vl +{ + namespace presentation + { + /* + * Schema of /Dom: + * -------------------------------------------------------------------------------- + * { + * Title: string; + * Window: remoteprotocol::WindowSizingConfig; + * Dom: remoteprotocol::RenderingDom; + * Elements: [{ + * Id: number; + * Type: remoteprotocol::RenderingType; + * Data: remoteprotocol::UnitTest_ElementDescVariant; + * }]; + * } + * -------------------------------------------------------------------------------- + */ + extern Ptr DumpRemoteProtocolRenderingDom( + const WString& title, + const remoteprotocol::WindowSizingConfig& windowSizingConfig, + Ptr renderingDom, + collections::Dictionary>>& elementData + ); + + extern WString DumpJsonToString(Ptr json); + + struct IoCommandState + { + Nullable mousePosition; + collections::SortedList pressingKeys; + bool leftPressing = false; + bool middlePressing = false; + bool rightPressing = false; + bool capslockToggled = false; + }; + + /* + * Predefined Commands: + * -------------------------------------------------------------------------------- + * !Type: + * Type to the focused control + * !Exit + * Try to quit the application, it could be blocked by the application itself + * !KeyDown:Key1+Key2+...+KeyN + * Key1 down, Key2 down, ..., KeyN down + * !KeyUp:Key1+Key2+...+KeyN + * KeyN up, ..., Key2 up, Key1 up + * !KeyPress:Key1+Key2+...+KeyN + * Key1 down, Key2 down, ..., KeyN down, KeyN up, ..., Key2 up, Key1 up + * !MouseMove:X,Y(,ctrl)?(,shift)?(,alt)? + * !(Left|Middle|Right)(Down|Up|Click|DbClick):X,Y(,ctrl)?(,shift)?(,alt)? + * Click means Down/Up + * DbClick means Down/Up/Down/DbClick/Up + * !MouseWheel(Up|Down|Left|Right):ticks(,ctrl)?(,shift)?(,alt)? + * WindowMouseInfo_::wheel = ticks * 120 * direction (1 or -1) + * + * -------------------------------------------------------------------------------- + * + * If the command satisfies the syntax, queue event handlers and then return "Queued" + * Otherwise, return "Syntax Error!" followed by command descriptions in this comment + * This function will crash if any event handler throws + * Event handlers are queued with INativeAsyncService::InvokeInMainThread after the command is parsed + * therefore the returned "Queued" only means the command was accepted, not that it has finished executing + * + * All coordinates are GuiCoordinate + * INativeWindow::Convert should be used to convert them to NativeCoordinate before calling the event handlers + * + * During calling the event handlers + * ctrl/shift/alt should be set accordingly + * the state argument is for remembering whatever is needed + * RunIOCommandOnNativeWindow assume it is the only source of IO interactions + */ + extern WString RunIOCommandOnNativeWindow( + IoCommandState* state, + INativeController* nativeController, + INativeWindow* nativeWindow, + collections::List& listeners, + WString command + ); + + class AutomationServiceBase : public Object, public INativeAutomationService + { + protected: + IoCommandState ioCommandState; + bool stopped = false; + + virtual Nullable GetNativeWindowId(INativeWindow* window) = 0; + virtual INativeWindow* GetNativeWindow(Nullable windowId) = 0; + + virtual WString DumpControlTreeInternal() { return WString::Empty; } + virtual WString DumpDomTreeInternal() { return WString::Empty; } + virtual WString RunIOCommandInternal(Nullable windowId, const WString& ioCommand) { return WString::Empty; } + public: + AutomationServiceBase() = default; + ~AutomationServiceBase() = default; + + bool Available() override + { + return true; + } + + void Stop() override + { + stopped = true; + } + + bool CanDumpControlTree() override + { + return false; + } + + WString DumpControlTree() override + { + return !stopped && CanDumpControlTree() ? DumpControlTreeInternal() : WString::Empty; + } + + bool CanDumpDomTree() override + { + return false; + } + + WString DumpDomTree() override + { + return !stopped && CanDumpDomTree() ? DumpDomTreeInternal() : WString::Empty; + } + + bool CanRunIOCommands() override + { + return false; + } + + WString RunIOCommand(Nullable windowId, const WString& ioCommand) override + { + return !stopped && CanRunIOCommands() ? RunIOCommandInternal(windowId, ioCommand) : WString::Empty; + } + }; + + class AutomationServiceRenderer : public AutomationServiceBase + { + private: + remote_renderer::GuiRemoteRendererSingle* renderer = nullptr; + + protected: + Nullable GetNativeWindowId(INativeWindow* window) override + { + return {}; + } + + INativeWindow* GetNativeWindow(Nullable windowId) override + { + return GetCurrentController()->WindowService()->GetMainWindow(); + } + + WString DumpDomTreeInternal() override + { + auto dumpRoot = DumpRemoteProtocolRenderingDom( + GetCurrentController()->WindowService()->GetMainWindow()->GetTitle(), + renderer->windowSizingConfig, + renderer->renderingDom, + renderer->renderingElements); + return DumpJsonToString(dumpRoot); + } + + public: + AutomationServiceRenderer(remote_renderer::GuiRemoteRendererSingle* _renderer) + :renderer(_renderer) + { + } + + bool CanDumpDomTree() override + { + return true; + } + }; + } +} + +#endif + + +/*********************************************************************** +.\UTILITIES\SHAREDSERVICES\GUISHAREDAUTOMATIONSERVICE_CONTROLS.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) +GacUI::Native Window::Default Service Implementation + +Interfaces: +***********************************************************************/ + +#ifndef VCZH_PRESENTATION_UTILITIES_SHAREDSERVICES_SHAREDAUTOMATIONSERVICE_CONTROLS +#define VCZH_PRESENTATION_UTILITIES_SHAREDSERVICES_SHAREDAUTOMATIONSERVICE_CONTROLS + + +namespace vl +{ + namespace presentation + { + namespace controls + { + class GuiWindow; + } + + class AutomationService : public AutomationServiceBase + { + protected: + + WString DumpControlTreeInternal() override; + + public: + AutomationService(); + ~AutomationService(); + + bool CanDumpControlTree() override; + }; + + class AutomationServiceHosted : public AutomationServiceBase + { + protected: + WString windowManagement = WString::Unmanaged(L"Hosted"); + + Nullable GetNativeWindowId(INativeWindow* window) override; + INativeWindow* GetNativeWindow(Nullable windowId) override; + WString DumpControlTreeInternal() override; + + public: + AutomationServiceHosted(); + ~AutomationServiceHosted(); + + bool CanDumpControlTree() override; + }; + + class RemoteProtocolAutomationService : public AutomationServiceHosted + { + protected: + WString RunIOCommandInternal(Nullable windowId, const WString& ioCommand) override; + + public: + RemoteProtocolAutomationService(); + ~RemoteProtocolAutomationService(); + + bool CanRunIOCommands() override; + }; + + /* + * Schema of /Controls: + * -------------------------------------------------------------------------------- + * { + * WindowManagement: "MultiWindow" | "Hosted" | "HostedRemoteProtocol"; + * MainWindow: WindowDump; + * + * // sub windows are normal window + * // no ordering + * // available for "MultiWindow" + * // otherwise sub windows become child objects of the main window + * SubWindows?: WindowDump[]; + * + * // popups are usually dropdowns, tooltips or menus + * // no ordering + * // available for "MultiWindow" + * // otherwise popups become child objects of the main window + * Popups?: WindowDump[]; + * } + * + * interface WindowDump + * { + * // windowId is used to identify a window + * // for "MultiWindow" + * // URL `.../IO/` is used to send commands to a specific window + * // for other modes + * // URL `.../IO` is used to send commands to the main window + * // since sub windows and popups are all child objects of the main window + * // the main window is responsible for window management, dispatching IO commands to the correct target + * // /IO parses the command synchronously and returns "Syntax Error!" or "Queued" + * // "Queued" only means the command was accepted, not that it has finished executing + * windowId?: string; + * + * // bounds defines the valid coordinate space for IO commands + * // IO commands use the client area of a native window as the coordinate space + * // such native window is a OS native window + * // for a window that owns a OS native window, the valid coordinate space is the client area + * // for a window that doesn't own a OS native window, the valid coordinate space is the partial rectangle of the main window client area + * + * // (x1,y1) always (0,0) for main window or when "MultiWindow" + * bounds: { x1: number, y1: number, x2: number, y2: number }; + * + * // available for main window in "Hosted" and "HostedRemoteProtocol" + * // ordered from bottom to top + * subWindowsInZOrder?: WindowDump[]; + * + * title: string; + * + * // begins with GuiWindow::GetBoundsComposition() + * composition: CompositionDump; + * } + * + * interface CompositionDump + * { + * // GuiGraphicsComposition::GetCachedBounds() converted to global bounds then offseted by "offset" + * bounds: { x1: number, y1: number, x2: number, y2: number }; + * + * // available when the composition is or inherits from: + * // GuiTableComposition : "Table:rows*columns" + * // GuiCellComposition : "Cell:(row,column)*(rowSpan,columnSpan)" + * // available only when its parent composition is GuiTableComposition + * // GuiRowSplitterComposition : "RowSplitter:rowsToTheTop" + * // GuiColumnSplitterComposition : "ColumnSplitter:columnsToTheLeft" + * // GuiStackComposition : "Stack" + * // GuiStackItemComposition : "StackItem:index" + * // available only when its parent composition is GuiStackComposition + * // index is defined by stack->GetStackItems().IndexOf(stackItem) + * // GuiFlowComposition: "Flow" + * // GuiFlowItemComposition : "FlowItem:index" + * // available only when its parent composition is GuiFlowComposition + * // index is defined by flow->GetFlowItems().IndexOf(flowItem) + * layout?: string; + * + * // available when GuiGraphicsComposition::GetOwnedElement is not null and is or inherits from: + * // GuiSolidBorderElement : "Border:color,shape + * // Gui3DBorderElement: : "3DBorder:color1,color2" + * // Gui3DSplitterElement: : "3DSplitter:color1,color2,direction" + * // GuiSolidBackground : "Background:color,shape + * // GuiGradientBackgroundElement : "Gradient:color1,color2,direction,shape" + * // GuiInnerShadowElement : "InnerShadow:color,thickness" + * // GuiSolidLabelElement : "Label:color,fontProperties.fontFamily,fontProperties.size(,WrapLine)?(,Ellipse)?(,Multiline)?" + * // GuiImageFrameElement : "Image" + * // GuiPolygonElement : "Polygon" + * // GuiDocumentElement : "Document:Selection(caretBegin.row,caretBegin.column)-(caretEnd.row,caretEnd.column)(,PasswordChar=char)?(,WrapLine)?" + * // + * // Color is in #RRGGBBAA format + * // elementText is only available for GuiSolidLabelElement, storing its text + * // elementDocument is only availaboe for GuiDocumentElement, storing its document in XML representation + * // The XML representation is done by calling GenerateToStream(XmlPrint(SaveToXml)) + * element?: string; + * elementText?: string; + * elementDocument?: string; + * + * // available when GuiGraphicsComposition::GetAssociatedControl is not null + * // storing PrintControlThemeName(GuiControl::GetControlThemeName()) + * control?: string; + * + * children?: CompositionDump[]; + * } + * -------------------------------------------------------------------------------- + * + * This function construct the WindowDump part (without subWindowsInZOrder) + */ + extern Ptr DumpWindowClientArea(controls::GuiWindow* window, Nullable windowId, Point offset); + } +} + +#endif + + /*********************************************************************** .\UTILITIES\SHAREDSERVICES\GUISHAREDCALLBACKSERVICE.H ***********************************************************************/ @@ -28165,6 +28623,7 @@ namespace vl { namespace presentation { + class AutomationServiceHosted; /*********************************************************************** GuiHostedController @@ -28184,6 +28643,7 @@ GuiHostedController { friend class GuiHostedWindow; friend class elements::GuiHostedGraphicsResourceManager; + friend class AutomationServiceHosted; protected: SharedCallbackService callbackService; hosted_window_manager::WindowManager* wmManager = nullptr; @@ -28359,6 +28819,7 @@ GuiHostedController // ============================================================= INativeWindow* GetNativeWindowHost() override; + INativeController* GetNativeController() override; public: GuiHostedController(INativeController* _nativeController); ~GuiHostedController(); @@ -28379,6 +28840,7 @@ GuiHostedController INativeImageService* ImageService() override; INativeInputService* InputService() override; INativeDialogService* DialogService() override; + INativeAutomationService* AutomationService() override; WString GetExecutablePath() override; INativeScreenService* ScreenService() override; @@ -28524,6 +28986,7 @@ Interfaces: namespace vl::presentation { class GuiRemoteController; + class RemoteProtocolAutomationService; /*********************************************************************** GuiRemoteWindow @@ -28533,6 +28996,7 @@ GuiRemoteWindow { friend class GuiRemoteEvents; friend class GuiRemoteController; + friend class RemoteProtocolAutomationService; protected: GuiRemoteController* remote; GuiRemoteMessages& remoteMessages; @@ -28571,6 +29035,7 @@ GuiRemoteWindow void Opened(); void SetActivated(bool activated); void ShowWithSizeState(bool activate, INativeWindow::WindowSizeState sizeState); + void SubmitStateAfterControllerConnect(); // ============================================================= // Events @@ -28665,6 +29130,7 @@ GuiRemoteWindow #endif + /*********************************************************************** .\PLATFORMPROVIDERS\REMOTE\GUIREMOTECONTROLLER.H ***********************************************************************/ @@ -28717,6 +29183,7 @@ GuiRemoteController SharedAsyncService asyncService; GuiRemoteGraphicsImageService imageService; bool applicationRunning = false; + bool controllerConnected = false; bool connectionForcedToStop = false; bool connectionStopped = false; @@ -28757,6 +29224,7 @@ GuiRemoteController bool IsKeyPressing(VKEY code) override; bool IsKeyToggled(VKEY code) override; void EnsureKeyInitialized(); + void EnsureControllerConnected(); WString GetKeyName(VKEY code) override; VKEY GetKey(const WString& name) override; void UpdateGlobalShortcutKey(); @@ -28824,6 +29292,7 @@ GuiRemoteController INativeImageService* ImageService() override; INativeInputService* InputService() override; INativeDialogService* DialogService() override; + INativeAutomationService* AutomationService() override; WString GetExecutablePath() override; INativeScreenService* ScreenService() override; @@ -28832,3 +29301,4 @@ GuiRemoteController } #endif + diff --git a/Import/VlppGlrParser.cpp b/Import/VlppGlrParser.cpp index 58b35e63..750dd855 100644 --- a/Import/VlppGlrParser.cpp +++ b/Import/VlppGlrParser.cpp @@ -1477,6 +1477,31 @@ API JsonPrint(node, writer, formatting); }); } + + void JsonNodeListSerializer::Serialize(Ptr parser, const SourceType& source, DestType& dest) + { + auto array = Ptr(new JsonArray); + for (auto&& package : source) + { + array->items.Add(package); + } + dest = JsonToString(array); + } + + void JsonNodeListSerializer::Deserialize(Ptr parser, const DestType& source, SourceType& dest) + { +#define ERROR_MESSAGE_PREFIX L"vl::glr::json::JsonNodeListSerializer::Deserialize(Ptr, const WString&, SourceType&)#" + auto value = JsonParse(source, *parser.Obj()); + auto array = value.Cast(); + CHECK_ERROR(array, ERROR_MESSAGE_PREFIX L"The serialized channel package should be a JsonArray."); + + dest.Clear(); + for (auto&& package : array->items) + { + dest.Add(package); + } +#undef ERROR_MESSAGE_PREFIX + } } } } @@ -7720,6 +7745,36 @@ Utility return result; } +/*********************************************************************** +XmlElementListSerializer +***********************************************************************/ + + void XmlElementListSerializer::Serialize(Ptr parser, const SourceType& source, DestType& dest) + { + auto array = Ptr(new XmlElement); + array->name.value = L"Array"; + for (auto&& element : source) + { + array->subNodes.Add(element); + } + dest = GenerateToStream([&](StreamWriter& writer) + { + XmlPrint(array, writer); + }); + } + + void XmlElementListSerializer::Deserialize(Ptr parser, const DestType& source, SourceType& dest) + { + auto array = XmlParseElement(source, *parser.Obj()); + auto elements = XmlGetElements(array); + + dest.Clear(); + for (auto&& element : elements) + { + dest.Add(element); + } + } + /*********************************************************************** XmlElementWriter ***********************************************************************/ diff --git a/Import/VlppGlrParser.h b/Import/VlppGlrParser.h index 607d575e..f09dfe66 100644 --- a/Import/VlppGlrParser.h +++ b/Import/VlppGlrParser.h @@ -1804,12 +1804,23 @@ namespace vl /// The serialized string. /// The JSON node to serialize. extern WString JsonToString(Ptr node, JsonFormatting formatting = {}); + + struct JsonNodeListSerializer + { + using SourceType = collections::List>; + using DestType = WString; + using ContextType = Ptr; + + static void Serialize(Ptr parser, const SourceType& source, DestType& dest); + static void Deserialize(Ptr parser, const DestType& source, SourceType& dest); + }; } } } #endif + /*********************************************************************** .\TRACEMANAGER\TRACEMANAGER.H ***********************************************************************/ @@ -3421,6 +3432,16 @@ namespace vl extern collections::LazyList> XmlGetElements(XmlElement* element, const WString& name); extern WString XmlGetValue(XmlElement* element); + struct XmlElementListSerializer + { + using SourceType = collections::List>; + using DestType = WString; + using ContextType = Ptr; + + static void Serialize(Ptr parser, const SourceType& source, DestType& dest); + static void Deserialize(Ptr parser, const DestType& source, SourceType& dest); + }; + class XmlElementWriter : public Object { protected: @@ -3443,3 +3464,4 @@ namespace vl } #endif + diff --git a/Import/VlppOS.Windows.cpp b/Import/VlppOS.Windows.cpp index fedea351..51f2512c 100644 --- a/Import/VlppOS.Windows.cpp +++ b/Import/VlppOS.Windows.cpp @@ -295,349 +295,6 @@ WindowsFileSystemImpl } -/*********************************************************************** -.\HTTPUTILITY.WINDOWS.CPP -***********************************************************************/ -/*********************************************************************** -Author: Zihan Chen (vczh) -Licensed under https://github.com/vczh-libraries/License -***********************************************************************/ - - -#ifndef VCZH_MSVC -static_assert(false, "Do not build this file for non-Windows applications."); -#endif - -#pragma comment(lib, "WinHttp.lib") - -namespace vl -{ - using namespace collections; - -/*********************************************************************** -HttpRequest -***********************************************************************/ - - bool HttpRequest::SetHost(const WString& inputQuery) - { - if (method == L"") - { - method = L"GET"; - } - - server = L""; - query = L""; - port = 0; - secure = false; - - { - if (server == L"") - { - if (inputQuery.Length() > 7) - { - WString protocol = inputQuery.Sub(0, 8); - if (_wcsicmp(protocol.Buffer(), L"https://") == 0) - { - const wchar_t* reading = inputQuery.Buffer() + 8; - const wchar_t* index1 = wcschr(reading, L':'); - const wchar_t* index2 = wcschr(reading, L'/'); - if (index2) - { - query = index2; - server = WString::CopyFrom(reading, (index1 ? index1 : index2) - reading); - port = INTERNET_DEFAULT_HTTPS_PORT; - secure = true; - if (index1) - { - auto portString = WString::CopyFrom(index1 + 1, index2 - index1 - 1); - port = _wtoi(portString.Buffer()); - } - return true; - } - } - } - } - if (server == L"") - { - if (inputQuery.Length() > 6) - { - WString protocol = inputQuery.Sub(0, 7); - if (_wcsicmp(protocol.Buffer(), L"http://") == 0) - { - const wchar_t* reading = inputQuery.Buffer() + 7; - const wchar_t* index1 = wcschr(reading, L':'); - const wchar_t* index2 = wcschr(reading, L'/'); - if (index2) - { - query = index2; - server = WString::CopyFrom(reading, (index1 ? index1 : index2) - reading); - port = INTERNET_DEFAULT_HTTP_PORT; - if (index1) - { - auto portString = WString::CopyFrom(index1 + 1, index2 - index1 - 1); - port = _wtoi(portString.Buffer()); - } - return true; - } - } - } - } - } - return false; - } - - void HttpRequest::SetBodyUtf8(const WString& bodyString) - { - vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), NULL, 0, NULL, NULL); - char* utf8 = new char[utf8Size + 1]; - ZeroMemory(utf8, utf8Size + 1); - WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), utf8, (int)utf8Size, NULL, NULL); - - body.Resize(utf8Size); - memcpy(&body[0], utf8, utf8Size); - delete[] utf8; - } - -/*********************************************************************** -HttpResponse -***********************************************************************/ - - WString HttpResponse::GetBodyUtf8() - { - WString response; - char* utf8 = &body[0]; - vint totalSize = body.Count(); - vint utf16Size = MultiByteToWideChar(CP_UTF8, 0, utf8, (int)totalSize, NULL, 0); - wchar_t* utf16 = new wchar_t[utf16Size + 1]; - ZeroMemory(utf16, (utf16Size + 1) * sizeof(wchar_t)); - MultiByteToWideChar(CP_UTF8, 0, utf8, (int)totalSize, utf16, (int)utf16Size); - response = utf16; - delete[] utf16; - return response; - } - -/*********************************************************************** -Utilities -***********************************************************************/ - - struct BufferPair - { - char* buffer; - vint length; - - BufferPair() - :buffer(0) - , length(0) - { - } - - BufferPair(char* _buffer, vint _length) - :buffer(_buffer) - , length(_length) - { - } - }; - - bool HttpQuery(const HttpRequest& request, HttpResponse& response) - { - // initialize - response.statusCode = -1; - HINTERNET internet = NULL; - HINTERNET connectedInternet = NULL; - HINTERNET requestInternet = NULL; - BOOL httpResult = FALSE; - DWORD error = 0; - List acceptTypes; - List availableBuffers; - - // access http - internet = WinHttpOpen(L"vczh", WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0); - error = GetLastError(); - if (!internet) goto CLEANUP; - - // connect - connectedInternet = WinHttpConnect(internet, request.server.Buffer(), (int)request.port, 0); - error = GetLastError(); - if (!connectedInternet) goto CLEANUP; - - // open request - // TODO: (enumerable) Linq:Select - for (vint i = 0; i < request.acceptTypes.Count(); i++) - { - acceptTypes.Add(request.acceptTypes.Get(i).Buffer()); - } - acceptTypes.Add(nullptr); - requestInternet = WinHttpOpenRequest(connectedInternet, request.method.Buffer(), request.query.Buffer(), NULL, WINHTTP_NO_REFERER, &acceptTypes[0], (request.secure ? WINHTTP_FLAG_SECURE : 0)); - error = GetLastError(); - if (!requestInternet) goto CLEANUP; - - // authentication, cookie and request - if (request.username != L"" && request.password != L"") - { - WinHttpSetCredentials(requestInternet, WINHTTP_AUTH_TARGET_SERVER, WINHTTP_AUTH_SCHEME_BASIC, request.username.Buffer(), request.password.Buffer(), NULL); - } - if (request.contentType != L"") - { - httpResult = WinHttpAddRequestHeaders(requestInternet, (L"Content-type:" + request.contentType).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD); - } - if (request.cookie != L"") - { - WinHttpAddRequestHeaders(requestInternet, (L"Cookie:" + request.cookie).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD); - } - - // extra headers - for (int i = 0; i < request.extraHeaders.Count(); i++) - { - WString key = request.extraHeaders.Keys()[i]; - WString value = request.extraHeaders.Values().Get(i); - WinHttpAddRequestHeaders(requestInternet, (key + L":" + value).Buffer(), -1, WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD); - } - - if (request.body.Count() > 0) - { - httpResult = WinHttpSendRequest(requestInternet, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)&request.body.Get(0), (int)request.body.Count(), (int)request.body.Count(), NULL); - } - else - { - httpResult = WinHttpSendRequest(requestInternet, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, NULL); - } - error = GetLastError(); - if (httpResult == FALSE) goto CLEANUP; - - // receive response - httpResult = WinHttpReceiveResponse(requestInternet, NULL); - error = GetLastError(); - if (httpResult != TRUE) goto CLEANUP; - - // read response status code - { - DWORD headerLength = sizeof(DWORD); - DWORD statusCode = 0; - httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, WINHTTP_HEADER_NAME_BY_INDEX, &statusCode, &headerLength, WINHTTP_NO_HEADER_INDEX); - error = GetLastError(); - if (httpResult == FALSE) goto CLEANUP; - response.statusCode = statusCode; - } - // read respons cookie - { - DWORD headerLength = sizeof(DWORD); - httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, NULL, &headerLength, WINHTTP_NO_HEADER_INDEX); - error = GetLastError(); - if (error == ERROR_INSUFFICIENT_BUFFER) - { - wchar_t* rawHeader = new wchar_t[headerLength / sizeof(wchar_t)]; - ZeroMemory(rawHeader, headerLength); - httpResult = WinHttpQueryHeaders(requestInternet, WINHTTP_QUERY_RAW_HEADERS_CRLF, WINHTTP_HEADER_NAME_BY_INDEX, rawHeader, &headerLength, WINHTTP_NO_HEADER_INDEX); - - const wchar_t* cookieStart = wcsstr(rawHeader, L"Cookie:"); - if (cookieStart) - { - const wchar_t* cookieEnd = wcsstr(cookieStart, L";"); - if (cookieEnd) - { - response.cookie = WString::CopyFrom(cookieStart + 7, cookieEnd - cookieStart - 7); - } - } - delete[] rawHeader; - } - } - - // read response body - while (true) - { - DWORD bytesAvailable = 0; - BOOL queryDataAvailableResult = WinHttpQueryDataAvailable(requestInternet, &bytesAvailable); - error = GetLastError(); - if (queryDataAvailableResult == TRUE && bytesAvailable != 0) - { - char* utf8 = new char[bytesAvailable]; - DWORD bytesRead = 0; - BOOL readDataResult = WinHttpReadData(requestInternet, utf8, bytesAvailable, &bytesRead); - error = GetLastError(); - if (readDataResult == TRUE) - { - availableBuffers.Add(BufferPair(utf8, bytesRead)); - } - else - { - delete[] utf8; - } - } - else - { - break; - } - } - - { - // concatincate response body - vint totalSize = 0; - for (auto p : availableBuffers) - { - totalSize += p.length; - } - response.body.Resize(totalSize); - if (totalSize > 0) - { - char* utf8 = new char[totalSize]; - { - char* temp = utf8; - for (auto p : availableBuffers) - { - memcpy(temp, p.buffer, p.length); - temp += p.length; - } - } - memcpy(&response.body[0], utf8, totalSize); - delete[] utf8; - } - for (auto p : availableBuffers) - { - delete[] p.buffer; - } - } - CLEANUP: - if (requestInternet) WinHttpCloseHandle(requestInternet); - if (connectedInternet) WinHttpCloseHandle(connectedInternet); - if (internet) WinHttpCloseHandle(internet); - return response.statusCode != -1; - } - - WString UrlEncodeQuery(const WString& query) - { - vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), NULL, 0, NULL, NULL); - char* utf8 = new char[utf8Size + 1]; - ZeroMemory(utf8, utf8Size + 1); - WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), utf8, (int)utf8Size, NULL, NULL); - - wchar_t* encoded = new wchar_t[utf8Size * 3 + 1]; - ZeroMemory(encoded, (utf8Size * 3 + 1) * sizeof(wchar_t)); - wchar_t* writing = encoded; - for (vint i = 0; i < utf8Size; i++) - { - unsigned char x = (unsigned char)utf8[i]; - if (L'a' <= x && x <= 'z' || L'A' <= x && x <= L'Z' || L'0' <= x && x <= L'9') - { - writing[0] = x; - writing += 1; - } - else - { - writing[0] = L'%'; - writing[1] = L"0123456789ABCDEF"[x / 16]; - writing[2] = L"0123456789ABCDEF"[x % 16]; - writing += 3; - } - } - - WString result = encoded; - delete[] encoded; - delete[] utf8; - return result; - } -} - - /*********************************************************************** .\LOCALE.WINDOWS.CPP ***********************************************************************/ @@ -2003,274 +1660,35 @@ TestEncoding namespace vl::inter_process { -using namespace vl::collections; - /*********************************************************************** HttpClient (Reading) ***********************************************************************/ -void HttpClient::RaiseErrorUnsafe(WString errorMessage) +void HttpClient::RaiseLocalError(WString errorMessage, bool fatal) { if (callback) { - callback->OnReadError(errorMessage); + callback->OnLocalError(errorMessage, fatal); } + if (fatal) + { + Stop(); + } +} + +bool HttpClient::IsStopping() +{ + bool result = false; + SPIN_LOCK(lockState) + { + result = state == State::Stopping; + } + return result; } void HttpClient::BeginReadingLoopUnsafe() { - if (state == State::Stopping) return; - CHECK_ERROR(state == State::Running, L"BeginReadingLoopUnsafe can only be called when client is running."); - DWORD lastError = 0; - BOOL httpResult = FALSE; - - LPCWSTR acceptTypes[] = { L"application/json; charset=utf8", NULL }; - HINTERNET httpRequest = WinHttpOpenRequest( - httpConnection, - L"POST", - urlRequest.Buffer(), - NULL, - WINHTTP_NO_REFERER, - acceptTypes, - WINHTTP_FLAG_REFRESH); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(state == State::Stopping, L"WinHttpOpenRequest failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpRequest != NULL, L"WinHttpOpenRequest failed."); - { - auto self = this; - WINHTTP_STATUS_CALLBACK previousCallback = WinHttpSetStatusCallback( - httpRequest, - (WINHTTP_STATUS_CALLBACK)[](HINTERNET httpRequest, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) -> void - { - if (!dwContext) return; - auto self = reinterpret_cast(dwContext); - switch (dwInternetStatus) - { - case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: - { - self->QueueCallback([=]() - { - if (self->state == State::Stopping) return; - DWORD lastError = 0; - BOOL httpResult = WinHttpReceiveResponse(httpRequest, NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpReceiveResponse failed with ERROR_INVALID_HANDLE but client is not stopping."); - WinHttpCloseHandle(httpRequest); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpReceiveResponse failed."); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: - { - self->QueueCallback([=]() - { - if (self->state == State::Stopping) return; - DWORD lastError = 0; - DWORD statusCode = 0; - DWORD dwordLength = sizeof(DWORD); - BOOL httpResult = FALSE; - { - httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &statusCode, - &dwordLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve status code."); - if (statusCode != 200) - { - self->CloseRequest(httpRequest); - self->RaiseErrorUnsafe(WString::Unmanaged(L"/Request returned status code: ") + itow(statusCode) + L", another renderer may have connected to the core."); - return; - } - } - { - DWORD headerLength = 0; - httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_CONTENT_TYPE, - WINHTTP_HEADER_NAME_BY_INDEX, - NULL, - &headerLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER, L"WinHttpQueryHeaders failed to retrieve content type."); - - Array headerBuffer(headerLength / 2 + 1); - ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t)); - - httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_CONTENT_TYPE, - WINHTTP_HEADER_NAME_BY_INDEX, - &headerBuffer[0], - &headerLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve content-type."); - - const wchar_t* header = &headerBuffer[0]; - CHECK_ERROR(wcscmp(header, L"application/json; charset=utf8") == 0, L"/Request did not return content type: application/json; charset=utf8."); - } - { - self->httpRespondBodyBufferWriting = 0; - httpResult = WinHttpQueryDataAvailable( - httpRequest, - NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed."); - } - }); - } - break; - case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: - { - DWORD dataAvailable = *(PDWORD)lpvStatusInformation; - self->QueueCallback([=]() - { - if (self->state == State::Stopping) return; - if (dataAvailable == 0) - { - if (self->callback) - { - self->httpRespondBodyBuffer[self->httpRespondBodyBufferWriting] = 0; - U8String bodyUtf8 = U8String::Unmanaged(&self->httpRespondBodyBuffer[0]); - self->callback->OnReadString(u8tow(bodyUtf8)); - } - self->CloseRequest(httpRequest); - self->BeginReadingLoopUnsafe(); - return; - } - - self->httpRespondBodyBufferWritingAvailable = dataAvailable; - DWORD bufferSize = self->httpRespondBodyBufferWriting + dataAvailable + 1; - if (self->httpRespondBodyBuffer.Count() < (vint)bufferSize) - { - self->httpRespondBodyBuffer.Resize((bufferSize + HttpRespondBodyStep - 1) / HttpRespondBodyStep * HttpRespondBodyStep); - } - - DWORD lastError = 0; - BOOL httpResult = WinHttpReadData( - httpRequest, - &self->httpRespondBodyBuffer[self->httpRespondBodyBufferWriting], - dataAvailable, - NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpReadData failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpReadData failed."); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: - { - self->QueueCallback([=]() - { - if (self->state == State::Stopping) return; - CHECK_ERROR( - self->httpRespondBodyBufferWritingAvailable == dwStatusInformationLength, - L"WinHttpReadData failed to read all available data." - ); - self->httpRespondBodyBufferWriting += self->httpRespondBodyBufferWritingAvailable; - - DWORD lastError = 0; - BOOL httpResult = WinHttpQueryDataAvailable( - httpRequest, - NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed."); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: - { - self->QueueCallback([=]() - { - if (self->state == State::Stopping) return; - self->CloseRequest(httpRequest); - self->RaiseErrorUnsafe(WString::Unmanaged(L"/Request canceled, another renderer may have connected to the core.")); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: - self->OnRequestHandleClosing(httpRequest); - break; - } - }, - WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, - NULL); - lastError = GetLastError(); - if (previousCallback == WINHTTP_INVALID_STATUS_CALLBACK && lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(state == State::Stopping, L"WinHttpSetStatusCallback failed with ERROR_INVALID_HANDLE but client is not stopping."); - WinHttpCloseHandle(httpRequest); - return; - } - CHECK_ERROR(previousCallback != WINHTTP_INVALID_STATUS_CALLBACK, L"WinHttpSetStatusCallback failed."); - } - { - AttachRequest(httpRequest); - httpResult = WinHttpSendRequest( - httpRequest, - WINHTTP_NO_ADDITIONAL_HEADERS, - 0, - WINHTTP_NO_REQUEST_DATA, - 0, - 0, - reinterpret_cast(this)); - lastError = GetLastError(); - if (httpResult == FALSE && lastError == ERROR_INVALID_HANDLE) - { - OnRequestHandleClosing(httpRequest); - CHECK_ERROR(state == State::Stopping, L"WinHttpSendRequest failed with ERROR_INVALID_HANDLE but client is not stopping."); - WinHttpCloseHandle(httpRequest); - return; - } - if (httpResult == FALSE) - { - OnRequestHandleClosing(httpRequest); - WinHttpCloseHandle(httpRequest); - CHECK_FAIL(L"WinHttpSendRequest failed."); - } - } + SendHttpRequest(HttpRequestType::Request, L"POST", urlRequest, WString::Empty); } /*********************************************************************** @@ -2282,149 +1700,72 @@ INetworkProtocolConnection* HttpClient::GetConnection() return this; } +void HttpClient::CompleteConnectRequest(const WString& response, const WString& error) +{ + SPIN_LOCK(lockConnectResult) + { + connectResponse = response; + connectError = error; + connectCompleted = true; + } + eventWaitForServer.Signal(); +} + void HttpClient::WaitForServer() { - if (state == State::Stopping) return; - CHECK_ERROR(state == State::Ready, L"WaitForServer can only be called once."); - DWORD lastError = 0; - state = State::WaitForServerConnection; - LPCWSTR acceptTypes[] = { L"application/json; charset=utf8", NULL }; - BOOL httpResult = FALSE; - - HINTERNET httpRequest = WinHttpOpenRequest( - httpConnection, - L"GET", - urlConnect.Buffer(), - NULL, - WINHTTP_NO_REFERER, - acceptTypes, - WINHTTP_FLAG_REFRESH); - lastError = GetLastError(); - CHECK_ERROR(httpRequest != NULL, L"WinHttpOpenRequest failed."); { - WINHTTP_STATUS_CALLBACK previousCallback = WinHttpSetStatusCallback( - httpRequest, - (WINHTTP_STATUS_CALLBACK)[](HINTERNET httpRequest, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) -> void - { - if (!dwContext) return; - auto self = reinterpret_cast(dwContext); - switch (dwInternetStatus) - { - case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: - case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: - case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: - case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: - { - self->dwInternetStatus_WaitForServer = dwInternetStatus; - self->dwStatusInformationLength_WaitForServer = dwStatusInformationLength; - SetEvent(self->hEventWaitForServer); - } - break; - } - }, - WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, - NULL); - lastError = GetLastError(); - CHECK_ERROR(previousCallback != WINHTTP_INVALID_STATUS_CALLBACK, L"WinHttpSetStatusCallback failed."); - } - { - ResetEvent(hEventWaitForServer); - httpResult = WinHttpSendRequest( - httpRequest, - WINHTTP_NO_ADDITIONAL_HEADERS, - 0, - WINHTTP_NO_REQUEST_DATA, - 0, - 0, - reinterpret_cast(this)); - lastError = GetLastError(); - CHECK_ERROR(httpResult == TRUE, L"WinHttpSendRequest failed."); - WaitForSingleObject(hEventWaitForServer, INFINITE); - CHECK_ERROR(dwInternetStatus_WaitForServer == WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE, L"WinHttpSendRequest failed to complete."); - } - { - ResetEvent(hEventWaitForServer); - httpResult = WinHttpReceiveResponse(httpRequest, NULL); - lastError = GetLastError(); - CHECK_ERROR(httpResult == TRUE, L"WinHttpReceiveResponse failed."); - WaitForSingleObject(hEventWaitForServer, INFINITE); - CHECK_ERROR(dwInternetStatus_WaitForServer == WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE, L"WinHttpSendRequest failed to complete."); + SPIN_LOCK(lockState) + { + if (state == State::Stopping) return; + CHECK_ERROR(state == State::Ready, L"WaitForServer can only be called once."); + state = State::WaitForServerConnection; + } } - DWORD statusCode = 0; - DWORD dataLength = 0; - DWORD dwordLength = sizeof(DWORD); { - httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &statusCode, - &dwordLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve status code."); - CHECK_ERROR(statusCode == 200, L"/Connect did not return status code: 200."); + SPIN_LOCK(lockConnectResult) + { + connectCompleted = false; + connectResponse = WString::Empty; + connectError = WString::Empty; + } } + + eventWaitForServer.Unsignal(); + if (!SendHttpRequest(HttpRequestType::Connect, L"GET", urlConnect, WString::Empty)) { - DWORD headerLength = 0; - httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_CONTENT_TYPE, - WINHTTP_HEADER_NAME_BY_INDEX, - NULL, - &headerLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - CHECK_ERROR(httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER, L"WinHttpQueryHeaders failed to retrieve content type."); - - Array headerBuffer(headerLength + 1); - ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t)); - - httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_CONTENT_TYPE, - WINHTTP_HEADER_NAME_BY_INDEX, - &headerBuffer[0], - &headerLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve content-type."); - - const wchar_t* header = &headerBuffer[0]; - CHECK_ERROR(wcscmp(header, L"application/json; charset=utf8") == 0, L"/Content did not return content type: application/json; charset=utf8."); + return; } + + eventWaitForServer.Wait(); + if (IsStopping()) return; + + WString body; + WString error; + bool completed = false; { - httpResult = WinHttpQueryDataAvailable( - httpRequest, - &dataLength); - lastError = GetLastError(); - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed."); + SPIN_LOCK(lockConnectResult) + { + body = connectResponse; + error = connectError; + completed = connectCompleted; + } } + + CHECK_ERROR(completed, L"/Connect did not complete."); + CHECK_ERROR(error == WString::Empty, L"/Connect failed."); + + vint separatorIndex = body.IndexOf(L';'); + CHECK_ERROR(separatorIndex != -1, L"/Connect response body is not in the correct format: requestUrl;responseUrl."); + urlRequest = baseUrl + body.Left(separatorIndex); + urlResponse = baseUrl + body.Right(body.Length() - separatorIndex - 1); { - Array bodyBuffer(dataLength + 1); - ZeroMemory(&bodyBuffer[0], bodyBuffer.Count() * sizeof(char8_t)); - - ResetEvent(hEventWaitForServer); - httpResult = WinHttpReadData( - httpRequest, - &bodyBuffer[0], - dataLength, - NULL); - lastError = GetLastError(); - CHECK_ERROR(httpResult == TRUE, L"WinHttpReadData failed."); - WaitForSingleObject(hEventWaitForServer, INFINITE); - CHECK_ERROR(dwInternetStatus_WaitForServer == WINHTTP_CALLBACK_STATUS_READ_COMPLETE, L"WinHttpReadData failed to complete."); - CHECK_ERROR(dwStatusInformationLength_WaitForServer == dataLength, L"WinHttpReadData failed to read full data."); - - U8String bodyUtf8 = U8String::Unmanaged(&bodyBuffer[0]); - vint separatorIndex = bodyUtf8.IndexOf(L';'); - CHECK_ERROR(separatorIndex != -1, L"/Connect response body is not in the correct format: requestUrl;responseUrl."); - urlRequest = baseUrl + u8tow(bodyUtf8.Left(separatorIndex)); - urlResponse = baseUrl + u8tow(bodyUtf8.Right(bodyUtf8.Length() - separatorIndex - 1)); + SPIN_LOCK(lockState) + { + if (state == State::Stopping) return; + state = State::Running; + } } - WinHttpCloseHandle(httpRequest); - state = State::Running; if (callback) { @@ -2434,411 +1775,215 @@ void HttpClient::WaitForServer() ClientStatus HttpClient::GetStatus() { - switch (state) + ClientStatus result = ClientStatus::Disconnected; + SPIN_LOCK(lockState) { - case State::Ready: - return ClientStatus::Ready; - case State::WaitForServerConnection: - return ClientStatus::WaitingForServer; - case State::Running: - return ClientStatus::Connected; - default: - return ClientStatus::Disconnected; + switch (state) + { + case State::Ready: + result = ClientStatus::Ready; + break; + case State::WaitForServerConnection: + result = ClientStatus::WaitingForServer; + break; + case State::Running: + result = ClientStatus::Connected; + break; + default: + result = ClientStatus::Disconnected; + break; + } } + return result; } /*********************************************************************** HttpClient (Writing) ***********************************************************************/ -void HttpClient::SendString(const WString& str) +bool HttpClient::SendHttpRequest(HttpRequestType requestType, const wchar_t* method, const WString& url, const WString& body, vint attempt) { - if (state == State::Stopping) return; - CHECK_ERROR(state == State::Running, L"SendString can only be called when client is running."); - DWORD lastError = 0; - BOOL httpResult = FALSE; - - HINTERNET httpRequest = WinHttpOpenRequest( - httpConnection, - L"POST", - urlResponse.Buffer(), - NULL, - WINHTTP_NO_REFERER, - NULL, - WINHTTP_FLAG_REFRESH); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) + Ptr api; { - CHECK_ERROR(state == State::Stopping, L"WinHttpOpenRequest failed with ERROR_INVALID_HANDLE."); - return; - } - CHECK_ERROR(httpRequest != NULL, L"WinHttpOpenRequest failed."); - - WINHTTP_STATUS_CALLBACK previousCallback = WinHttpSetStatusCallback( - httpRequest, - (WINHTTP_STATUS_CALLBACK)[](HINTERNET httpRequest, DWORD_PTR dwContext, DWORD dwInternetStatus, LPVOID lpvStatusInformation, DWORD dwStatusInformationLength) -> void + SPIN_LOCK(lockState) { - if (!dwContext) return; - auto contextPtr = reinterpret_cast*>(dwContext); - auto context = *contextPtr; - auto self = context->client; - auto requestId = context->requestId; - switch (dwInternetStatus) + if (state == State::Stopping) return false; + switch (requestType) { - case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: - { - self->QueueCallback([=, context = std::move(context)]() - { - if (self->state == State::Stopping) return; - DWORD lastError = 0; - BOOL httpResult = WinHttpReceiveResponse(httpRequest, NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpReceiveResponse failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpReceiveResponse failed."); - }); - } + case HttpRequestType::Connect: + CHECK_ERROR(state == State::WaitForServerConnection, L"/Connect can only be called when client is waiting for the server."); break; - case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: - { - self->QueueCallback([=, context = std::move(context)]() - { - if (self->state == State::Stopping) return; - DWORD lastError = 0; - DWORD statusCode = 0; - DWORD dwordLength = sizeof(DWORD); - BOOL httpResult = WinHttpQueryHeaders( - httpRequest, - WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, - &statusCode, - &dwordLength, - WINHTTP_NO_HEADER_INDEX); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryHeaders failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryHeaders failed to retrieve status code."); - - if (statusCode != 200) - { - self->CloseRequest(httpRequest, requestId); - self->RaiseErrorUnsafe(WString::Unmanaged(L"/Response returned status code: ") + itow(statusCode) + L", another renderer may have connected to the core."); - return; - } - - auto reading = context->responseReading; - - reading->bodyBufferWriting = 0; - httpResult = WinHttpQueryDataAvailable( - httpRequest, - NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed."); - }); - } + case HttpRequestType::Request: + CHECK_ERROR(state == State::Running, L"/Request can only be called when client is running."); break; - case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: - { - DWORD dataAvailable = *(PDWORD)lpvStatusInformation; - self->QueueCallback([=, context = std::move(context)]() - { - if (self->state == State::Stopping) return; - - auto reading = context->responseReading; - - if (dataAvailable == 0) - { - if (reading->bodyBufferWriting > 0 && self->callback) - { - reading->bodyBuffer[reading->bodyBufferWriting] = 0; - U8String bodyUtf8 = U8String::Unmanaged(&reading->bodyBuffer[0]); - self->callback->OnReadString(u8tow(bodyUtf8)); - } - self->CloseRequest(httpRequest, requestId); - return; - } - - reading->bodyBufferWritingAvailable = dataAvailable; - DWORD bufferSize = reading->bodyBufferWriting + dataAvailable + 1; - if (reading->bodyBuffer.Count() < (vint)bufferSize) - { - reading->bodyBuffer.Resize((bufferSize + HttpRespondBodyStep - 1) / HttpRespondBodyStep * HttpRespondBodyStep); - } - - DWORD lastError = 0; - BOOL httpResult = WinHttpReadData( - httpRequest, - &reading->bodyBuffer[reading->bodyBufferWriting], - dataAvailable, - NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpReadData failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpReadData failed."); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: - { - self->QueueCallback([=, context = std::move(context)]() - { - if (self->state == State::Stopping) return; - - auto reading = context->responseReading; - - CHECK_ERROR( - reading->bodyBufferWritingAvailable == dwStatusInformationLength, - L"WinHttpReadData failed to read all available data." - ); - reading->bodyBufferWriting += reading->bodyBufferWritingAvailable; - - DWORD lastError = 0; - BOOL httpResult = WinHttpQueryDataAvailable( - httpRequest, - NULL); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) - { - CHECK_ERROR(self->state == State::Stopping, L"WinHttpQueryDataAvailable failed with ERROR_INVALID_HANDLE but client is not stopping."); - return; - } - CHECK_ERROR(httpResult == TRUE, L"WinHttpQueryDataAvailable failed."); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: - { - self->QueueCallback([=, context = std::move(context)]() - { - if (self->state == State::Stopping) return; - self->CloseRequest(httpRequest, requestId); - self->RaiseErrorUnsafe(WString::Unmanaged(L"/Response canceled, another renderer may have connected to the core.")); - }); - } - break; - case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: - self->OnRequestHandleClosing(httpRequest, requestId); - delete contextPtr; + case HttpRequestType::Response: + CHECK_ERROR(state == State::Running, L"/Response can only be called when client is running."); break; } - }, - WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, - NULL); - lastError = GetLastError(); - CHECK_ERROR(previousCallback != WINHTTP_INVALID_STATUS_CALLBACK, L"WinHttpSetStatusCallback failed."); + api = httpClientApi; + } + } - httpResult = WinHttpAddRequestHeaders( - httpRequest, - L"Content-Type: application/json; charset=utf8", - -1, - WINHTTP_ADDREQ_FLAG_ADD); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) + if (!api) return false; + + HttpRequest request; + request.method = method; + request.query = url; + request.acceptTypes.Add(JsonContentType); + if (requestType == HttpRequestType::Response) { - CHECK_ERROR(state == State::Stopping, L"WinHttpAddRequestHeaders failed with ERROR_INVALID_HANDLE."); - WinHttpCloseHandle(httpRequest); + request.contentType = JsonContentType; + request.keepAliveOnStop = true; + } + else if (requestType == HttpRequestType::Request) + { + request.receiveTimeout = 0; + } + if (body.Length() > 0) + { + request.contentType = JsonContentType; + request.SetBodyUtf8(body); + } + + api->HttpQuery(request, [this, requestType, body, attempt](Variant result) + { + OnHttpRequestCompleted(requestType, body, attempt, std::move(result)); + }); + return true; +} + +void HttpClient::OnHttpRequestFailed(HttpRequestType requestType, const WString& body, vint attempt, const WString& errorMessage) +{ + if (IsStopping()) return; + + switch (requestType) + { + case HttpRequestType::Connect: + { + bool fatal = attempt >= HttpRequestMaxAttempts; + RaiseLocalError(errorMessage, fatal); + if (!fatal && !IsStopping()) + { + SendHttpRequest(HttpRequestType::Connect, L"GET", urlConnect, WString::Empty, attempt + 1); + } + } + break; + case HttpRequestType::Request: + SendHttpRequest(HttpRequestType::Request, L"POST", urlRequest, WString::Empty, attempt + 1); + break; + case HttpRequestType::Response: + { + bool fatal = attempt >= HttpRequestMaxAttempts; + RaiseLocalError(errorMessage, fatal); + if (!fatal && !IsStopping()) + { + SendHttpRequest(HttpRequestType::Response, L"POST", urlResponse, body, attempt + 1); + } + } + break; + } +} + +void HttpClient::OnHttpRequestCompleted(HttpRequestType requestType, WString body, vint attempt, Variant result) +{ + if (auto error = result.TryGet()) + { + switch (requestType) + { + case HttpRequestType::Connect: + OnHttpRequestFailed(requestType, body, attempt, L"/Connect failed: " + error->message); + break; + case HttpRequestType::Request: + OnHttpRequestFailed(requestType, body, attempt, L"/Request failed: " + error->message); + break; + case HttpRequestType::Response: + OnHttpRequestFailed(requestType, body, attempt, L"/Response failed: " + error->message); + break; + } return; } - CHECK_ERROR(httpResult == TRUE, L"WinHttpAddRequestHeaders failed."); - - auto contextPtr = new Ptr(new HttpRequestContext); - auto context = *contextPtr; - context->client = this; - context->httpRequest = httpRequest; - context->requestId = ++createdRequestIds; - context->requestBody = wtou8(str); - context->responseReading = Ptr(new HttpResponseReading); + auto&& response = result.Get(); + if (response.statusCode != 200) { - AttachRequest(httpRequest, context->requestId); - httpResult = WinHttpSendRequest( - httpRequest, - WINHTTP_NO_ADDITIONAL_HEADERS, - 0, - (LPVOID)context->requestBody.Buffer(), - (DWORD)context->requestBody.Length(), - (DWORD)context->requestBody.Length(), - reinterpret_cast(contextPtr)); - lastError = GetLastError(); - if (lastError == ERROR_INVALID_HANDLE) + switch (requestType) { - OnRequestHandleClosing(httpRequest, context->requestId); - delete contextPtr; - CHECK_ERROR(state == State::Stopping, L"WinHttpSendRequest failed with ERROR_INVALID_HANDLE."); - WinHttpCloseHandle(httpRequest); - return; - } - if (httpResult == FALSE) - { - OnRequestHandleClosing(httpRequest, context->requestId); - delete contextPtr; - WinHttpCloseHandle(httpRequest); - CHECK_FAIL(L"WinHttpSendRequest failed."); + case HttpRequestType::Connect: + OnHttpRequestFailed(requestType, body, attempt, WString::Unmanaged(L"/Connect returned status code: ") + itow(response.statusCode) + L"."); + break; + case HttpRequestType::Request: + OnHttpRequestFailed(requestType, body, attempt, WString::Unmanaged(L"/Request returned status code: ") + itow(response.statusCode) + L", another renderer may have connected to the core."); + break; + case HttpRequestType::Response: + OnHttpRequestFailed(requestType, body, attempt, WString::Unmanaged(L"/Response returned status code: ") + itow(response.statusCode) + L", another renderer may have connected to the core."); + break; } + return; } + + if (response.contentType != JsonContentType) + { + switch (requestType) + { + case HttpRequestType::Connect: + OnHttpRequestFailed(requestType, body, attempt, L"/Connect response did not return content type: application/json; charset=utf8."); + break; + case HttpRequestType::Request: + OnHttpRequestFailed(requestType, body, attempt, L"/Request response did not return content type: application/json; charset=utf8."); + break; + case HttpRequestType::Response: + OnHttpRequestFailed(requestType, body, attempt, L"/Response response did not return content type: application/json; charset=utf8."); + break; + } + return; + } + + auto responseBody = response.GetBodyUtf8(); + switch (requestType) + { + case HttpRequestType::Connect: + CompleteConnectRequest(responseBody, WString::Empty); + break; + case HttpRequestType::Request: + if (!IsStopping()) + { + BeginReadingLoopUnsafe(); + if (responseBody.Length() > 0 && callback) + { + callback->OnReadString(responseBody); + } + } + break; + case HttpRequestType::Response: + if (!IsStopping() && responseBody.Length() > 0 && callback) + { + callback->OnReadString(responseBody); + } + break; + } +} + +void HttpClient::SendString(const WString& str) +{ + SendHttpRequest(HttpRequestType::Response, L"POST", urlResponse, str); } /*********************************************************************** HttpClient ***********************************************************************/ -void HttpClient::BeginPendingCallback() -{ - if (pendingCallbacks++ == 0) - { - eventPendingCallbacks.Unsignal(); - } -} - -void HttpClient::EndPendingCallback() -{ - if (--pendingCallbacks == 0) - { - eventPendingCallbacks.Signal(); - } -} - -void HttpClient::QueueCallback(const Func& proc) -{ - BeginPendingCallback(); - auto queued = ThreadPoolLite::Queue([=]() - { - try - { - proc(); - } - catch (...) - { - EndPendingCallback(); - throw; - } - - EndPendingCallback(); - }); - if (!queued) - { - EndPendingCallback(); - CHECK_FAIL(L"HttpClient failed to queue asynchronous callback."); - } -} - -vint HttpClient::FindActiveRequestUnsafe(HINTERNET httpRequest, vint requestId) -{ - for (vint index = 0; index < httpActiveRequests.Count(); index++) - { - auto&& activeRequest = httpActiveRequests[index]; - if (activeRequest.requestId != -1 && activeRequest.httpRequest == httpRequest && activeRequest.requestId == requestId) - { - return index; - } - } - return -1; -} - -void HttpClient::AttachRequest(HINTERNET httpRequest, vint requestId) -{ - BeginPendingCallback(); - SPIN_LOCK(httpActiveRequestsLock) - { - for (vint index = 0; index < httpActiveRequests.Count(); index++) - { - auto&& activeRequest = httpActiveRequests[index]; - if (activeRequest.requestId == -1) - { - activeRequest.httpRequest = httpRequest; - activeRequest.requestId = requestId; - return; - } - } - - HttpActiveRequest activeRequest; - activeRequest.httpRequest = httpRequest; - activeRequest.requestId = requestId; - httpActiveRequests.Add(activeRequest); - } -} - -void HttpClient::CloseRequest(HINTERNET httpRequest, vint requestId) -{ - bool closeRequest = false; - SPIN_LOCK(httpActiveRequestsLock) - { - vint index = FindActiveRequestUnsafe(httpRequest, requestId); - if (index != -1) - { - auto&& activeRequest = httpActiveRequests[index]; - activeRequest.httpRequest = NULL; - activeRequest.requestId = -1; - closeRequest = true; - } - } - if (closeRequest) - { - WinHttpCloseHandle(httpRequest); - } -} - -void HttpClient::OnRequestHandleClosing(HINTERNET httpRequest, vint requestId) -{ - SPIN_LOCK(httpActiveRequestsLock) - { - vint index = FindActiveRequestUnsafe(httpRequest, requestId); - if (index != -1) - { - auto&& activeRequest = httpActiveRequests[index]; - activeRequest.httpRequest = NULL; - activeRequest.requestId = -1; - } - } - EndPendingCallback(); -} - HttpClient::HttpClient(const WString _baseUrl, vint port) : baseUrl(_baseUrl) { - DWORD lastError = 0; - hEventWaitForServer = CreateEvent(NULL, FALSE, TRUE, NULL); - CHECK_ERROR(hEventWaitForServer != NULL, L"HttpClient initialization failed on CreateEvent(hEventWaitForServer)."); - CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpClient initialization failed on eventPendingCallbacks.CreateManualUnsignal."); - - httpSession = WinHttpOpen( - L"vl::inter_process::HttpClient", - WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, - WINHTTP_NO_PROXY_BYPASS, - WINHTTP_FLAG_ASYNC); - lastError = GetLastError(); - CHECK_ERROR(httpSession != NULL, L"WinHttpOpen failed."); - - httpConnection = WinHttpConnect( - httpSession, - L"localhost", - (INTERNET_PORT)port, - 0); - lastError = GetLastError(); - CHECK_ERROR(httpConnection != NULL, L"WinHttpConnect failed."); + CHECK_ERROR(eventWaitForServer.CreateAutoUnsignal(false), L"HttpClient initialization failed on eventWaitForServer.CreateAutoUnsignal."); + httpClientApi = Ptr(new HttpClientApi(L"localhost", port)); urlConnect = baseUrl + HttpServerUrl_Connect; } HttpClient::~HttpClient() { Stop(); - CloseHandle(hEventWaitForServer); } void HttpClient::InstallCallback(INetworkProtocolCallback* _callback) @@ -2850,45 +1995,819 @@ void HttpClient::InstallCallback(INetworkProtocolCallback* _callback) void HttpClient::Stop() { - if (httpSession != NULL) + Ptr stoppingApi; + bool notifyDisconnected = false; { - state = State::Stopping; - - List stoppingRequests; - SPIN_LOCK(httpActiveRequestsLock) + SPIN_LOCK(lockState) { - for (auto activeRequest : httpActiveRequests) + if (httpClientApi) { - if (activeRequest.requestId != -1) - { - stoppingRequests.Add(activeRequest.httpRequest); - } + state = State::Stopping; + stoppingApi = httpClientApi; + httpClientApi = nullptr; + notifyDisconnected = true; + } + else + { + state = State::Stopping; } - httpActiveRequests.Clear(); - } - for (auto httpRequest : stoppingRequests) - { - WinHttpCloseHandle(httpRequest); - } - - WinHttpCloseHandle(httpConnection); - WinHttpSetStatusCallback( - httpSession, - NULL, - WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, - NULL); - WinHttpCloseHandle(httpSession); - - eventPendingCallbacks.Wait(); - - httpConnection = NULL; - httpSession = NULL; - - if (callback) - { - callback->OnDisconnected(); } } + + eventWaitForServer.Signal(); + if (stoppingApi) + { + stoppingApi->Stop(); + } + + if (notifyDisconnected && callback) + { + callback->OnDisconnected(); + } +} + +} + + +/*********************************************************************** +.\INTERPROCESS\WINDOWS\HTTPCLIENTAPI.WINDOWS.CPP +***********************************************************************/ + +#ifndef VCZH_MSVC +static_assert(false, "Do not build this file for non-Windows applications."); +#endif + +#pragma comment(lib, "WinHttp.lib") + +namespace vl::inter_process +{ + +using namespace vl::collections; + +/*********************************************************************** +HttpRequest +***********************************************************************/ + +void HttpRequest::SetBodyUtf8(const WString& bodyString) +{ + vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), NULL, 0, NULL, NULL); + body.Resize(utf8Size); + if (utf8Size > 0) + { + WideCharToMultiByte(CP_UTF8, 0, bodyString.Buffer(), (int)bodyString.Length(), &body[0], (int)utf8Size, NULL, NULL); + } +} + +/*********************************************************************** +HttpResponse +***********************************************************************/ + +WString HttpResponse::GetBodyUtf8() const +{ + if (body.Count() == 0) + { + return WString::Empty; + } + + vint utf16Size = MultiByteToWideChar(CP_UTF8, 0, &body[0], (int)body.Count(), NULL, 0); + Array utf16(utf16Size + 1); + ZeroMemory(&utf16[0], utf16.Count() * sizeof(wchar_t)); + if (utf16Size > 0) + { + MultiByteToWideChar(CP_UTF8, 0, &body[0], (int)body.Count(), &utf16[0], (int)utf16Size); + } + return &utf16[0]; +} + +/*********************************************************************** +HttpClientApi +***********************************************************************/ + +HttpError HttpClientApi::MakeError(const WString& operation, DWORD errorCode) +{ + HttpError error; + error.operation = operation; + error.errorCode = errorCode; + error.message = operation + L" failed with Windows error " + itow((vint)errorCode) + L"."; + return error; +} + +vint HttpClientApi::HexValue(wchar_t c) +{ + if (L'0' <= c && c <= L'9') return c - L'0'; + if (L'a' <= c && c <= L'f') return c - L'a' + 10; + if (L'A' <= c && c <= L'F') return c - L'A' + 10; + return -1; +} + +bool HttpClientApi::IsStopping() +{ + bool result = false; + SPIN_LOCK(lockActiveRequests) + { + result = stopping; + } + return result; +} + +void HttpClientApi::BeginPendingCallback() +{ + if (pendingCallbacks++ == 0) + { + eventPendingCallbacks.Unsignal(); + } +} + +void HttpClientApi::EndPendingCallback() +{ + if (--pendingCallbacks == 0) + { + eventPendingCallbacks.Signal(); + } +} + +void HttpClientApi::AttachRequestUnsafe(Ptr context) +{ + activeRequests.Add(context); +} + +void HttpClientApi::RemoveRequestUnsafe(Ptr context) +{ + for (vint index = 0; index < activeRequests.Count(); index++) + { + if (activeRequests[index] == context) + { + activeRequests.RemoveAt(index); + return; + } + } +} + +void HttpClientApi::CloseRequest(Ptr context) +{ + HINTERNET httpRequest = NULL; + SPIN_LOCK(context->lockContext) + { + if (!context->closing) + { + context->closing = true; + httpRequest = context->httpRequest; + } + } + if (httpRequest) + { + WinHttpCloseHandle(httpRequest); + } +} + +void HttpClientApi::OnRequestHandleClosing(Ptr context) +{ + SPIN_LOCK(lockActiveRequests) + { + RemoveRequestUnsafe(context); + } + EndPendingCallback(); +} + +void HttpClientApi::CompleteRequest(Ptr context, HttpResponse&& response) +{ + Func)> callback; + bool stoppingNow = IsStopping(); + bool invokeCallback = false; + SPIN_LOCK(context->lockContext) + { + if (!context->completed) + { + context->completed = true; + callback = context->callback; + invokeCallback = !stoppingNow; + } + } + + if (invokeCallback && callback) + { + CloseRequest(context); + callback(Variant(std::move(response))); + } + else + { + CloseRequest(context); + } +} + +void HttpClientApi::CompleteRequest(Ptr context, HttpError&& error) +{ + Func)> callback; + bool stoppingNow = IsStopping(); + bool invokeCallback = false; + SPIN_LOCK(context->lockContext) + { + if (!context->completed) + { + context->completed = true; + callback = context->callback; + invokeCallback = !stoppingNow; + } + } + + if (invokeCallback && callback) + { + CloseRequest(context); + callback(Variant(std::move(error))); + } + else + { + CloseRequest(context); + } +} + +void HttpClientApi::CompleteRequestWithLastError(Ptr context, const WString& operation, DWORD errorCode) +{ + if (errorCode == ERROR_INVALID_HANDLE && IsStopping() && !context->keepAliveOnStop) + { + return; + } + CompleteRequest(context, MakeError(operation, errorCode)); +} + +void CALLBACK HttpClientApi::HttpStatusCallback(HINTERNET httpRequest, DWORD_PTR contextValue, DWORD status, LPVOID statusInformation, DWORD statusInformationLength) +{ + if (!contextValue) return; + auto contextPtr = reinterpret_cast*>(contextValue); + auto context = *contextPtr; + auto self = context->api; + + switch (status) + { + case WINHTTP_CALLBACK_STATUS_SENDREQUEST_COMPLETE: + { + if (self->IsStopping() && !context->keepAliveOnStop) return; + + BOOL httpResult = WinHttpReceiveResponse(httpRequest, NULL); + DWORD lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpReceiveResponse", lastError); + } + } + break; + case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE: + { + if (self->IsStopping() && !context->keepAliveOnStop) return; + + DWORD statusCode = 0; + DWORD dwordLength = sizeof(DWORD); + BOOL httpResult = WinHttpQueryHeaders( + httpRequest, + WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, + &statusCode, + &dwordLength, + WINHTTP_NO_HEADER_INDEX); + DWORD lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(status)", lastError); + return; + } + context->response.statusCode = statusCode; + + DWORD headerLength = 0; + httpResult = WinHttpQueryHeaders( + httpRequest, + WINHTTP_QUERY_CONTENT_TYPE, + WINHTTP_HEADER_NAME_BY_INDEX, + NULL, + &headerLength, + WINHTTP_NO_HEADER_INDEX); + lastError = GetLastError(); + if (httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER) + { + Array headerBuffer(headerLength / sizeof(wchar_t) + 1); + ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t)); + httpResult = WinHttpQueryHeaders( + httpRequest, + WINHTTP_QUERY_CONTENT_TYPE, + WINHTTP_HEADER_NAME_BY_INDEX, + &headerBuffer[0], + &headerLength, + WINHTTP_NO_HEADER_INDEX); + lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(content-type)", lastError); + return; + } + context->response.contentType = &headerBuffer[0]; + } + else if (httpResult == FALSE && lastError != ERROR_WINHTTP_HEADER_NOT_FOUND) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(content-type)", lastError); + return; + } + + headerLength = 0; + httpResult = WinHttpQueryHeaders( + httpRequest, + WINHTTP_QUERY_SET_COOKIE, + WINHTTP_HEADER_NAME_BY_INDEX, + NULL, + &headerLength, + WINHTTP_NO_HEADER_INDEX); + lastError = GetLastError(); + if (httpResult == FALSE && lastError == ERROR_INSUFFICIENT_BUFFER) + { + Array headerBuffer(headerLength / sizeof(wchar_t) + 1); + ZeroMemory(&headerBuffer[0], headerBuffer.Count() * sizeof(wchar_t)); + httpResult = WinHttpQueryHeaders( + httpRequest, + WINHTTP_QUERY_SET_COOKIE, + WINHTTP_HEADER_NAME_BY_INDEX, + &headerBuffer[0], + &headerLength, + WINHTTP_NO_HEADER_INDEX); + lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(cookie)", lastError); + return; + } + context->response.cookie = &headerBuffer[0]; + } + else if (httpResult == FALSE && lastError != ERROR_WINHTTP_HEADER_NOT_FOUND) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryHeaders(cookie)", lastError); + return; + } + + context->bodyBufferWriting = 0; + httpResult = WinHttpQueryDataAvailable(httpRequest, NULL); + lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryDataAvailable", lastError); + } + } + break; + case WINHTTP_CALLBACK_STATUS_DATA_AVAILABLE: + { + if (self->IsStopping() && !context->keepAliveOnStop) return; + CHECK_ERROR(statusInformationLength == sizeof(DWORD), L"WinHttpQueryDataAvailable returned an unexpected payload."); + DWORD dataAvailable = *(PDWORD)statusInformation; + + if (dataAvailable == 0) + { + context->response.body.Resize(context->bodyBufferWriting); + self->CompleteRequest(context, std::move(context->response)); + return; + } + + context->bodyBufferWritingAvailable = dataAvailable; + DWORD bufferSize = context->bodyBufferWriting + dataAvailable + 1; + if (context->response.body.Count() < (vint)bufferSize) + { + context->response.body.Resize((bufferSize + HttpRespondBodyStep - 1) / HttpRespondBodyStep * HttpRespondBodyStep); + } + + BOOL httpResult = WinHttpReadData( + httpRequest, + &context->response.body[context->bodyBufferWriting], + dataAvailable, + NULL); + DWORD lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpReadData", lastError); + } + } + break; + case WINHTTP_CALLBACK_STATUS_READ_COMPLETE: + { + if (self->IsStopping() && !context->keepAliveOnStop) return; + if (context->bodyBufferWritingAvailable != statusInformationLength) + { + self->CompleteRequest(context, MakeError(L"WinHttpReadData", ERROR_INVALID_DATA)); + return; + } + context->bodyBufferWriting += context->bodyBufferWritingAvailable; + + BOOL httpResult = WinHttpQueryDataAvailable(httpRequest, NULL); + DWORD lastError = GetLastError(); + if (httpResult == FALSE) + { + self->CompleteRequestWithLastError(context, L"WinHttpQueryDataAvailable", lastError); + } + } + break; + case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR: + { + if (self->IsStopping() && !context->keepAliveOnStop) return; + + auto asyncResult = reinterpret_cast(statusInformation); + DWORD errorCode = asyncResult ? asyncResult->dwError : ERROR_WINHTTP_INTERNAL_ERROR; + HttpError error = MakeError(L"WinHTTP async request", errorCode); + if (asyncResult) + { + error.message += L" Operation code: " + itow((vint)asyncResult->dwResult) + L"."; + } + self->CompleteRequest(context, std::move(error)); + } + break; + case WINHTTP_CALLBACK_STATUS_HANDLE_CLOSING: + self->OnRequestHandleClosing(context); + delete contextPtr; + break; + } +} + +HttpClientApi::HttpClientApi(const WString& _server, vint _port) + : server(_server) + , port(_port) +{ + CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpClientApi initialization failed on eventPendingCallbacks.CreateManualUnsignal."); + + httpSession = WinHttpOpen( + L"vl::inter_process::HttpClientApi", + WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, + WINHTTP_NO_PROXY_BYPASS, + WINHTTP_FLAG_ASYNC); + CHECK_ERROR(httpSession != NULL, L"WinHttpOpen failed."); + + httpConnection = WinHttpConnect( + httpSession, + server.Buffer(), + (INTERNET_PORT)port, + 0); + CHECK_ERROR(httpConnection != NULL, L"WinHttpConnect failed."); +} + +HttpClientApi::~HttpClientApi() +{ + Stop(); +} + +void HttpClientApi::HttpQuery(const HttpRequest& request, Func)> callback) +{ + bool rejected = false; + { + SPIN_LOCK(lockActiveRequests) + { + if (stopping) + { + rejected = true; + } + else + { + BeginPendingCallback(); + } + } + } + if (rejected) + { + if (callback) + { + callback(Variant(MakeError(L"HttpClientApi::HttpQuery", ERROR_OPERATION_ABORTED))); + } + return; + } + + BOOL httpResult = FALSE; + DWORD lastError = 0; + List acceptTypes; + for (vint i = 0; i < request.acceptTypes.Count(); i++) + { + acceptTypes.Add(request.acceptTypes.Get(i).Buffer()); + } + acceptTypes.Add(nullptr); + + auto method = request.method == WString::Empty ? WString::Unmanaged(L"GET") : request.method; + auto httpRequest = WinHttpOpenRequest( + httpConnection, + method.Buffer(), + request.query.Buffer(), + NULL, + WINHTTP_NO_REFERER, + &acceptTypes[0], + (request.secure ? WINHTTP_FLAG_SECURE : 0) | WINHTTP_FLAG_REFRESH); + lastError = GetLastError(); + if (httpRequest == NULL) + { + EndPendingCallback(); + if (callback) + { + callback(Variant(MakeError(L"WinHttpOpenRequest", lastError))); + } + return; + } + + httpResult = WinHttpSetTimeouts( + httpRequest, + (int)request.resolveTimeout, + (int)request.connectTimeout, + (int)request.sendTimeout, + (int)request.receiveTimeout); + lastError = GetLastError(); + if (httpResult == FALSE) + { + EndPendingCallback(); + WinHttpCloseHandle(httpRequest); + if (callback) + { + callback(Variant(MakeError(L"WinHttpSetTimeouts", lastError))); + } + return; + } + + auto contextPtr = new Ptr(new HttpRequestContext); + auto context = *contextPtr; + context->api = this; + context->httpRequest = httpRequest; + context->callback = callback; + context->keepAliveOnStop = request.keepAliveOnStop; + + if (request.body.Count() > 0) + { + context->requestBody.Resize(request.body.Count()); + memcpy(&context->requestBody[0], &request.body.Get(0), request.body.Count()); + } + + auto failBeforeCallbackInstalled = [&](const WString& operation, DWORD errorCode) + { + WinHttpCloseHandle(httpRequest); + delete contextPtr; + EndPendingCallback(); + if (callback) + { + callback(Variant(MakeError(operation, errorCode))); + } + }; + + DWORD_PTR contextValue = reinterpret_cast(contextPtr); + httpResult = WinHttpSetOption( + httpRequest, + WINHTTP_OPTION_CONTEXT_VALUE, + &contextValue, + sizeof(contextValue)); + lastError = GetLastError(); + if (httpResult == FALSE) + { + failBeforeCallbackInstalled(L"WinHttpSetOption(WINHTTP_OPTION_CONTEXT_VALUE)", lastError); + return; + } + + if (request.username != WString::Empty && request.password != WString::Empty) + { + httpResult = WinHttpSetCredentials( + httpRequest, + WINHTTP_AUTH_TARGET_SERVER, + WINHTTP_AUTH_SCHEME_BASIC, + request.username.Buffer(), + request.password.Buffer(), + NULL); + lastError = GetLastError(); + if (httpResult == FALSE) + { + failBeforeCallbackInstalled(L"WinHttpSetCredentials", lastError); + return; + } + } + if (request.contentType != WString::Empty) + { + httpResult = WinHttpAddRequestHeaders( + httpRequest, + (L"Content-Type: " + request.contentType).Buffer(), + -1, + WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD); + lastError = GetLastError(); + if (httpResult == FALSE) + { + failBeforeCallbackInstalled(L"WinHttpAddRequestHeaders(content-type)", lastError); + return; + } + } + if (request.cookie != WString::Empty) + { + httpResult = WinHttpAddRequestHeaders( + httpRequest, + (L"Cookie: " + request.cookie).Buffer(), + -1, + WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD); + lastError = GetLastError(); + if (httpResult == FALSE) + { + failBeforeCallbackInstalled(L"WinHttpAddRequestHeaders(cookie)", lastError); + return; + } + } + + for (vint i = 0; i < request.extraHeaders.Count(); i++) + { + WString key = request.extraHeaders.Keys()[i]; + WString value = request.extraHeaders.Values().Get(i); + httpResult = WinHttpAddRequestHeaders( + httpRequest, + (key + L": " + value).Buffer(), + -1, + WINHTTP_ADDREQ_FLAG_REPLACE | WINHTTP_ADDREQ_FLAG_ADD); + lastError = GetLastError(); + if (httpResult == FALSE) + { + failBeforeCallbackInstalled(L"WinHttpAddRequestHeaders(extra)", lastError); + return; + } + } + + bool failedBeforeSend = false; + WString failedOperation; + DWORD failedError = 0; + SPIN_LOCK(lockActiveRequests) + { + if (stopping) + { + failedBeforeSend = true; + failedOperation = L"HttpClientApi::HttpQuery"; + failedError = ERROR_OPERATION_ABORTED; + } + else + { + auto previousCallback = WinHttpSetStatusCallback( + httpRequest, + HttpStatusCallback, + WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, + NULL); + lastError = GetLastError(); + if (previousCallback == WINHTTP_INVALID_STATUS_CALLBACK) + { + failedBeforeSend = true; + failedOperation = L"WinHttpSetStatusCallback"; + failedError = lastError; + } + else + { + AttachRequestUnsafe(context); + } + } + } + if (failedBeforeSend) + { + failBeforeCallbackInstalled(failedOperation, failedError); + return; + } + + DWORD requestBodyLength = (DWORD)context->requestBody.Count(); + LPVOID requestBodyBuffer = requestBodyLength == 0 ? WINHTTP_NO_REQUEST_DATA : (LPVOID)&context->requestBody[0]; + httpResult = WinHttpSendRequest( + httpRequest, + WINHTTP_NO_ADDITIONAL_HEADERS, + 0, + requestBodyBuffer, + requestBodyLength, + requestBodyLength, + contextValue); + lastError = GetLastError(); + if (httpResult == FALSE) + { + CompleteRequestWithLastError(context, L"WinHttpSendRequest", lastError); + } +} + +void HttpClientApi::Stop() +{ + if (httpSession == NULL) return; + + List stoppingRequests; + SPIN_LOCK(lockActiveRequests) + { + stopping = true; + for (auto&& context : activeRequests) + { + HINTERNET httpRequest = NULL; + SPIN_LOCK(context->lockContext) + { + if (!context->keepAliveOnStop && !context->closing) + { + context->closing = true; + httpRequest = context->httpRequest; + } + } + if (httpRequest) + { + stoppingRequests.Add(httpRequest); + } + } + } + for (auto httpRequest : stoppingRequests) + { + WinHttpCloseHandle(httpRequest); + } + + eventPendingCallbacks.Wait(); + + WinHttpCloseHandle(httpConnection); + WinHttpSetStatusCallback( + httpSession, + NULL, + WINHTTP_CALLBACK_FLAG_ALL_NOTIFICATIONS, + NULL); + WinHttpCloseHandle(httpSession); + + SPIN_LOCK(lockActiveRequests) + { + activeRequests.Clear(); + } + + httpConnection = NULL; + httpSession = NULL; +} + +WString HttpClientApi::UrlEncodeQuery(const WString& query) +{ + vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), NULL, 0, NULL, NULL); + Array utf8(utf8Size); + if (utf8Size > 0) + { + WideCharToMultiByte(CP_UTF8, 0, query.Buffer(), (int)query.Length(), &utf8[0], (int)utf8Size, NULL, NULL); + } + + Array encoded(utf8Size * 3 + 1); + ZeroMemory(&encoded[0], encoded.Count() * sizeof(wchar_t)); + wchar_t* writing = &encoded[0]; + for (vint i = 0; i < utf8Size; i++) + { + unsigned char x = (unsigned char)utf8[i]; + if ((L'a' <= x && x <= L'z') || (L'A' <= x && x <= L'Z') || (L'0' <= x && x <= L'9')) + { + writing[0] = x; + writing += 1; + } + else + { + writing[0] = L'%'; + writing[1] = L"0123456789ABCDEF"[x / 16]; + writing[2] = L"0123456789ABCDEF"[x % 16]; + writing += 3; + } + } + + return &encoded[0]; +} + +WString HttpClientApi::UrlDecodeQuery(const WString& query) +{ + List utf8; + for (vint i = 0; i < query.Length(); i++) + { + wchar_t c = query[i]; + if (c == L'%' && i + 2 < query.Length()) + { + vint high = HexValue(query[i + 1]); + vint low = HexValue(query[i + 2]); + if (high != -1 && low != -1) + { + utf8.Add((char)(high * 16 + low)); + i += 2; + continue; + } + } + + if (c == L'+') + { + utf8.Add(' '); + } + else if (c <= 0x7F) + { + utf8.Add((char)c); + } + else + { + wchar_t single[] = { c, 0 }; + vint utf8Size = WideCharToMultiByte(CP_UTF8, 0, single, 1, NULL, 0, NULL, NULL); + if (utf8Size > 0) + { + Array singleUtf8(utf8Size); + WideCharToMultiByte(CP_UTF8, 0, single, 1, &singleUtf8[0], (int)utf8Size, NULL, NULL); + for (vint j = 0; j < utf8Size; j++) + { + utf8.Add(singleUtf8[j]); + } + } + } + } + + if (utf8.Count() == 0) + { + return WString::Empty; + } + + vint utf16Size = MultiByteToWideChar(CP_UTF8, 0, &utf8[0], (int)utf8.Count(), NULL, 0); + Array utf16(utf16Size + 1); + ZeroMemory(&utf16[0], utf16.Count() * sizeof(wchar_t)); + if (utf16Size > 0) + { + MultiByteToWideChar(CP_UTF8, 0, &utf8[0], (int)utf8.Count(), &utf16[0], (int)utf16Size); + } + return &utf16[0]; } } @@ -2917,7 +2836,7 @@ void HttpServerConnection::OnCancelCurrentHttpRequestForPendingRequest() return; } ULONG result = HttpCancelHttpRequest( - server->httpRequestQueue, + server->GetHttpRequestQueue(), httpPendingRequestId, NULL); CHECK_ERROR( @@ -2935,44 +2854,14 @@ void HttpServerConnection::OnNewHttpRequestForPendingRequest(HTTP_REQUEST_ID htt { auto pendingRequest = pendingRequestsToSend[0]; pendingRequestsToSend.RemoveAt(0); - ULONG result = HttpServer::SendResponse(server->httpRequestQueue, httpPendingRequestId, pendingRequest); - CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed for responding /Request."); + HttpServerApi::SendResponseUtf8(server->GetHttpRequestQueue(), httpPendingRequestId, pendingRequest); httpPendingRequestId = HTTP_NULL_ID; } } WString HttpServerConnection::SubmitResponse(PHTTP_REQUEST pRequest) { - ULONG bodyLength = 0; - ULONG bodyReceived = 0; - { - auto& headerContentType = pRequest->Headers.KnownHeaders[HttpHeaderContentType]; - CHECK_ERROR(headerContentType.pRawValue != NULL, L"/Response missing Content-Type header."); - CHECK_ERROR( - strncmp((const char*)headerContentType.pRawValue, "application/json; charset=utf8", headerContentType.RawValueLength) == 0, - L"/Response Content-Type header must be \"application/json; charset=utf8\"."); - } - { - auto& headerContentLength = pRequest->Headers.KnownHeaders[HttpHeaderContentLength]; - CHECK_ERROR(headerContentLength.pRawValue != NULL, L"/Response missing Content-Type header."); - bodyLength = (ULONG)atoi(headerContentLength.pRawValue); - } - CHECK_ERROR(pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS, L"/Response must contain body data."); - - Array bodyBuffer(bodyLength + 1); - ZeroMemory(&bodyBuffer[0], bodyBuffer.Count() * sizeof(char8_t)); - { - ULONG result = NO_ERROR; - result = HttpReceiveRequestEntityBody( - server->httpRequestQueue, - pRequest->RequestId, - HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER, - &bodyBuffer[0], - bodyLength, - &bodyReceived, - NULL); - CHECK_ERROR(result == NO_ERROR, L"HttpReceiveRequestEntityBody."); - } + auto body = server->GetUtf8Body(pRequest).Value(); SPIN_LOCK(pendingRequestLock) { @@ -2983,14 +2872,13 @@ WString HttpServerConnection::SubmitResponse(PHTTP_REQUEST pRequest) { SPIN_LOCK(lockQueuedStrings) { - U8String bodyUtf8 = U8String::Unmanaged(&bodyBuffer[0]); if (callback) { - callback->OnReadString(u8tow(bodyUtf8)); + callback->OnReadString(body); } else { - queuedStrings.Add(u8tow(bodyUtf8)); + queuedStrings.Add(body); } } } @@ -3029,17 +2917,19 @@ WString HttpServerConnection::SubmitResponse(PHTTP_REQUEST pRequest) void HttpServerConnection::InstallCallback(INetworkProtocolCallback* _callback) { + CHECK_ERROR(_callback, L"HttpServerConnection::InstallCallback needs a valid INetworkProtocolCallback."); + _callback->OnInstalled(this); + + List strings; SPIN_LOCK(lockQueuedStrings) { callback = _callback; - callback->OnInstalled(this); - for (const auto& str : queuedStrings) - { - callback->OnReadString(str); - } - queuedStrings.Clear(); + strings = std::move(queuedStrings); + } + for (const auto& str : strings) + { + _callback->OnReadString(str); } - } void HttpServerConnection::BeginReadingLoopUnsafe() @@ -3057,7 +2947,7 @@ void HttpServerConnection::SendString(const WString& str) } else if (httpPendingRequestId != HTTP_NULL_ID) { - ULONG result = HttpServer::SendResponse(server->httpRequestQueue, httpPendingRequestId, str); + ULONG result = HttpServerApi::SendResponse(server->GetHttpRequestQueue(), httpPendingRequestId, { 200, L"OK", str, L"application/json; charset=utf8" }); if (result == NO_ERROR) { httpPendingRequestId = HTTP_NULL_ID; @@ -3117,38 +3007,11 @@ WString HttpServerConnection::GenerateNewGuid() } /*********************************************************************** -HttpServer (ListenToHttpRequest) +HttpServer (HttpServerApi) ***********************************************************************/ -void HttpServer::OnHttpConnectionBrokenUnsafe() +void HttpServer::OnHttpRequestReceived(PHTTP_REQUEST pRequest) { - if (state == State::Running) - { - SPIN_LOCK(lockConnections) - { - state = State::Stopping; - for (auto connection : connections.Values()) - { - connection->server = nullptr; - } - - for (auto connection : connections.Values()) - { - connection->Stop(); - } - connections.Clear(); - } - } -} - -void HttpServer::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest) -{ - if (state == State::Stopping) - { - Send404Response(httpRequestQueue, pRequest->RequestId, "Server is stopping"); - return; - } - bool isValidRequest = wcsncmp(pRequest->CookedUrl.pAbsPath, urlRequestPrefix.Buffer(), urlRequestPrefix.Length()) == 0; bool isValidResponse = wcsncmp(pRequest->CookedUrl.pAbsPath, urlResponsePrefix.Buffer(), urlResponsePrefix.Length()) == 0; @@ -3159,7 +3022,7 @@ void HttpServer::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest) vint index = connections.Keys().IndexOf(guid); if (index == -1) { - Send404Response(httpRequestQueue, pRequest->RequestId, "Unknown connection guid"); + HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"Unknown connection guid" }); } else { @@ -3187,13 +3050,13 @@ void HttpServer::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest) connections.Remove(newGuid); } connection->server = nullptr; - Send404Response(httpRequestQueue, pRequest->RequestId, "Connection rejected"); + HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"Connection rejected" }); } else { auto completeUrlRequest = WString::Unmanaged(HttpServerUrl_Request) + L"/" + newGuid; auto completeUrlResponse = WString::Unmanaged(HttpServerUrl_Response) + L"/" + newGuid; - SendResponse(httpRequestQueue, pRequest->RequestId, completeUrlRequest + L";" + completeUrlResponse); + HttpServerApi::SendResponseUtf8(GetHttpRequestQueue(), pRequest->RequestId, completeUrlRequest + L";" + completeUrlResponse); } } else if (pRequest->Verb == HttpVerbPOST && isValidRequest) @@ -3213,22 +3076,136 @@ void HttpServer::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest) if (auto connection = FindExistingConnection(guid)) { auto responseToClient = connection->SubmitResponse(pRequest); - auto result = SendResponse(httpRequestQueue, pRequest->RequestId, responseToClient); - CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed for responding /Response."); + auto result = HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 200, L"OK", responseToClient, L"application/json; charset=utf8" }); + CHECK_ERROR( + result == NO_ERROR || result == ERROR_CONNECTION_INVALID || result == ERROR_OPERATION_ABORTED, + L"HttpSendHttpResponse failed for responding /Response." + ); } } - else if (pRequest->Verb == HttpVerbOPTIONS && (isValidRequest || isValidResponse)) + else + { + HttpServerApi::SendResponse(GetHttpRequestQueue(), pRequest->RequestId, { 404, L"Unknown URL" }); + } +} + +void HttpServer::OnHttpServerStopping() +{ + List> stoppingConnections; + SPIN_LOCK(lockConnections) + { + for (auto connection : connections.Values()) + { + stoppingConnections.Add(connection); + } + connections.Clear(); + } + for (auto connection : stoppingConnections) + { + SPIN_LOCK(connection->pendingRequestLock) + { + connection->OnCancelCurrentHttpRequestForPendingRequest(); + } + connection->server = nullptr; + } + for (auto connection : stoppingConnections) + { + if (connection->callback) + { + connection->callback->OnDisconnected(); + } + } +} + +/*********************************************************************** +HttpServer +***********************************************************************/ + +HttpServer::HttpServer(const WString _baseUrl, vint port) + : HttpServerApi(WString::Unmanaged(L"http://localhost:") + itow(port) + _baseUrl + L"/", true) + , baseUrl(_baseUrl) +{ + urlConnect = baseUrl + HttpServerUrl_Connect; + urlRequestPrefix = baseUrl + HttpServerUrl_Request + L"/"; + urlResponsePrefix = baseUrl + HttpServerUrl_Response + L"/"; +} + +HttpServer::~HttpServer() +{ + Stop(); +} + +WaitForClientResult HttpServer::OnClientConnected(INetworkProtocolConnection* connection) +{ + return WaitForClientResult::Accept; +} + +void HttpServer::Start() +{ + HttpServerApi::Start(); +} + +void HttpServer::Stop() +{ + HttpServerApi::Stop(); +} + +bool HttpServer::IsStopped() +{ + return HttpServerApi::IsStopped(); +} + +} + + +/*********************************************************************** +.\INTERPROCESS\WINDOWS\HTTPSERVERAPI.WINDOWS.CPP +***********************************************************************/ + +#ifndef VCZH_MSVC +static_assert(false, "Do not build this file for non-Windows applications."); +#endif + +#pragma comment(lib, "Httpapi.lib") + +namespace vl::inter_process +{ + +using namespace vl::collections; + +/*********************************************************************** +HttpServerApi (ListenToHttpRequest) +***********************************************************************/ + +void HttpServerApi::OnHttpConnectionBrokenUnsafe() +{ + if (state == State::Running) + { + state = State::Stopping; + OnHttpServerStopping(); + } +} + +void HttpServerApi::OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest) +{ + if (state == State::Stopping) + { + SendResponse(httpRequestQueue, pRequest->RequestId, { 404, L"Server is stopping" }); + return; + } + + if (respondToOptions && pRequest->Verb == HttpVerbOPTIONS) { SendOptionsResponse(httpRequestQueue, pRequest->RequestId); } else { - Send404Response(httpRequestQueue, pRequest->RequestId, "Unknown URL"); + OnHttpRequestReceived(pRequest); } ListenToHttpRequest(); } -ULONG HttpServer::ListenToHttpRequest_Init(OVERLAPPED* overlapped) +ULONG HttpServerApi::ListenToHttpRequest_Init(OVERLAPPED* overlapped) { ZeroMemory(&bufferRequest[0], bufferRequest.Count()); @@ -3244,7 +3221,7 @@ ULONG HttpServer::ListenToHttpRequest_Init(OVERLAPPED* overlapped) return result; } -ULONG HttpServer::ListenToHttpRequest_InitMoreData(ULONG* bytesReturned) +ULONG HttpServerApi::ListenToHttpRequest_InitMoreData(ULONG* bytesReturned) { HTTP_REQUEST_ID httpRequestIdReading = ((PHTTP_REQUEST)&bufferRequest[0])->RequestId; ZeroMemory(&bufferRequest[0], bufferRequest.Count()); @@ -3261,7 +3238,7 @@ ULONG HttpServer::ListenToHttpRequest_InitMoreData(ULONG* bytesReturned) return result; } -ULONG HttpServer::ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize) +ULONG HttpServerApi::ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize) { HTTP_REQUEST_ID httpRequestIdReading = ((PHTTP_REQUEST)&bufferRequest[0])->RequestId; bufferRequest.Resize(expectedBufferSize); @@ -3280,7 +3257,7 @@ ULONG HttpServer::ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize return result; } -void HttpServer::ListenToHttpRequest() +void HttpServerApi::ListenToHttpRequest() { if (state == State::Stopping) return; @@ -3330,13 +3307,29 @@ void HttpServer::ListenToHttpRequest() CHECK_ERROR(result == ERROR_IO_PENDING, L"HttpReceiveHttpRequest(#3) failed on unexpected result."); - RegisterWaitForSingleObject( + BOOL waitResult = RegisterWaitForSingleObject( &hWaitHandleRequest, hEventRequest, [](PVOID lpParameter, BOOLEAN TimerOrWaitFired) { - auto self = (HttpServer*)lpParameter; - auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&self->hWaitHandleRequest, INVALID_HANDLE_VALUE); + auto self = (HttpServerApi*)lpParameter; + struct PendingCallbackScope + { + HttpServerApi* server; + + PendingCallbackScope(HttpServerApi* _server) + : server(_server) + { + server->BeginPendingCallback(); + } + + ~PendingCallbackScope() + { + server->EndPendingCallback(); + } + } pendingCallbackScope(self); + + auto waitHandle = std::atomic_ref(self->hWaitHandleRequest).exchange(INVALID_HANDLE_VALUE); if (waitHandle != INVALID_HANDLE_VALUE) { UnregisterWait(waitHandle); @@ -3375,47 +3368,97 @@ void HttpServer::ListenToHttpRequest() this, INFINITE, WT_EXECUTEONLYONCE); + CHECK_ERROR(waitResult, L"RegisterWaitForSingleObject failed for HttpReceiveHttpRequest."); + if (state == State::Stopping) + { + auto waitHandle = std::atomic_ref(hWaitHandleRequest).exchange(INVALID_HANDLE_VALUE); + if (waitHandle != INVALID_HANDLE_VALUE) + { + UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE); + } + } +} + +void HttpServerApi::BeginPendingCallback() +{ + if (pendingCallbacks++ == 0) + { + eventPendingCallbacks.Unsignal(); + } +} + +void HttpServerApi::EndPendingCallback() +{ + if (--pendingCallbacks == 0) + { + eventPendingCallbacks.Signal(); + } +} + +void HttpServerApi::OnHttpServerStopping() +{ +} + +HANDLE HttpServerApi::GetHttpRequestQueue() const +{ + return httpRequestQueue; +} + +Nullable HttpServerApi::GetUtf8Body(PHTTP_REQUEST pRequest) +{ + ULONG bodyLength = 0; + ULONG bodyReceived = 0; + { + auto& headerContentType = pRequest->Headers.KnownHeaders[HttpHeaderContentType]; + CHECK_ERROR(headerContentType.pRawValue != NULL, L"/Response missing Content-Type header."); + CHECK_ERROR( + strncmp((const char*)headerContentType.pRawValue, "application/json; charset=utf8", headerContentType.RawValueLength) == 0, + L"/Response Content-Type header must be \"application/json; charset=utf8\"."); + } + { + auto& headerContentLength = pRequest->Headers.KnownHeaders[HttpHeaderContentLength]; + CHECK_ERROR(headerContentLength.pRawValue != NULL, L"/Response missing Content-Type header."); + bodyLength = (ULONG)atoi(headerContentLength.pRawValue); + } + CHECK_ERROR(bodyLength > 0, L"/Response must contain body data."); + + Array bodyBuffer(bodyLength + 1); + ZeroMemory(&bodyBuffer[0], bodyBuffer.Count() * sizeof(char8_t)); + ULONG bodyWritten = 0; + for (USHORT i = 0; i < pRequest->EntityChunkCount; i++) + { + auto& chunk = pRequest->pEntityChunks[i]; + CHECK_ERROR(chunk.DataChunkType == HttpDataChunkFromMemory, L"/Response contains an unsupported body chunk."); + auto chunkLength = chunk.FromMemory.BufferLength; + CHECK_ERROR(bodyWritten + chunkLength <= bodyLength, L"/Response body is longer than Content-Length."); + memcpy(&bodyBuffer[bodyWritten], chunk.FromMemory.pBuffer, chunkLength); + bodyWritten += chunkLength; + } + if (pRequest->Flags & HTTP_REQUEST_FLAG_MORE_ENTITY_BODY_EXISTS) + { + ULONG result = NO_ERROR; + result = HttpReceiveRequestEntityBody( + httpRequestQueue, + pRequest->RequestId, + HTTP_RECEIVE_REQUEST_ENTITY_BODY_FLAG_FILL_BUFFER, + &bodyBuffer[bodyWritten], + bodyLength - bodyWritten, + &bodyReceived, + NULL); + CHECK_ERROR(result == NO_ERROR || result == ERROR_HANDLE_EOF, L"HttpReceiveRequestEntityBody."); + bodyWritten += bodyReceived; + } + CHECK_ERROR(bodyWritten == bodyLength, L"/Response body is shorter than Content-Length."); + + U8String bodyUtf8 = U8String::Unmanaged(&bodyBuffer[0]); + return u8tow(bodyUtf8); } /*********************************************************************** -HttpServer (Writing) +HttpServerApi (Writing) ***********************************************************************/ -void HttpServer::Send404Response(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, PCSTR reason) -{ - ULONG bytesSent = 0; - HTTP_RESPONSE httpResponse; - ZeroMemory(&httpResponse, sizeof(httpResponse)); - - httpResponse.StatusCode = 404; - httpResponse.pReason = reason; - - static const char headerACAOName[] = "Access-Control-Allow-Origin"; - static HTTP_UNKNOWN_HEADER unknownHeaders[] = { { - sizeof(headerACAOName) - 1, - 1, - headerACAOName, - "*" - } }; - httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER); - httpResponse.Headers.pUnknownHeaders = unknownHeaders; - - ULONG result = NO_ERROR; - result = HttpSendHttpResponse( - httpRequestQueue, - requestId, - 0, - &httpResponse, - NULL, - &bytesSent, - NULL, - 0, - NULL, - NULL); - CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed (404)."); -} - -void HttpServer::SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId) +void HttpServerApi::SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId) { ULONG bytesSent = 0; HTTP_RESPONSE httpResponse; @@ -3425,7 +3468,7 @@ void HttpServer::SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID re static const char headerACAOName[] = "Access-Control-Allow-Origin"; static const char headerACAMName[] = "Access-Control-Allow-Methods"; - static const char headerACAMValue[] = "POST, OPTIONS"; + static const char headerACAMValue[] = "GET, POST, OPTIONS"; static const char headerACAHName[] = "Access-Control-Allow-Headers"; static const char headerACAHValue[] = "Content-Type"; static HTTP_UNKNOWN_HEADER unknownHeaders[] = { { @@ -3462,7 +3505,7 @@ void HttpServer::SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID re CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed (OPTIONS)."); } -ULONG HttpServer::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, const WString& str) +ULONG HttpServerApi::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, const HttpServerResponse& response) { ULONG bytesSent = 0; HTTP_RESPONSE httpResponse; @@ -3470,22 +3513,34 @@ ULONG HttpServer::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestI ZeroMemory(&httpResponse, sizeof(httpResponse)); ZeroMemory(&httpResponseBody, sizeof(httpResponseBody)); - httpResponse.StatusCode = 200; - httpResponse.pReason = "OK"; + httpResponse.StatusCode = (USHORT)response.statusCode; - U8String body = wtou8(str); - if (body.Length() > 0) + U8String reasonUtf8; + if (response.reason != WString::Empty) { + reasonUtf8 = wtou8(response.reason); + httpResponse.pReason = (PCSTR)reasonUtf8.Buffer(); + httpResponse.ReasonLength = (USHORT)reasonUtf8.Length(); + } + + U8String bodyUtf8; + if (response.body != WString::Empty) + { + bodyUtf8 = wtou8(response.body); httpResponse.EntityChunkCount = 1; httpResponse.pEntityChunks = &httpResponseBody; httpResponseBody.DataChunkType = HttpDataChunkFromMemory; - httpResponseBody.FromMemory.pBuffer = (PVOID)body.Buffer(); - httpResponseBody.FromMemory.BufferLength = (ULONG)body.Length(); + httpResponseBody.FromMemory.pBuffer = (PVOID)bodyUtf8.Buffer(); + httpResponseBody.FromMemory.BufferLength = (ULONG)bodyUtf8.Length(); } - static const char headerContentType[] = "application/json; charset=utf8"; - httpResponse.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = headerContentType; - httpResponse.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = sizeof(headerContentType) - 1; + U8String contentTypeUtf8; + if (response.contentType != WString::Empty) + { + contentTypeUtf8 = wtou8(response.contentType); + httpResponse.Headers.KnownHeaders[HttpHeaderContentType].pRawValue = (PCSTR)contentTypeUtf8.Buffer(); + httpResponse.Headers.KnownHeaders[HttpHeaderContentType].RawValueLength = (USHORT)contentTypeUtf8.Length(); + } static const char headerACAOName[] = "Access-Control-Allow-Origin"; static HTTP_UNKNOWN_HEADER unknownHeaders[] = { { @@ -3493,7 +3548,7 @@ ULONG HttpServer::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestI 1, headerACAOName, "*" - }}; + } }; httpResponse.Headers.UnknownHeaderCount = sizeof(unknownHeaders) / sizeof(HTTP_UNKNOWN_HEADER); httpResponse.Headers.pUnknownHeaders = unknownHeaders; @@ -3512,142 +3567,97 @@ ULONG HttpServer::SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestI return result; } -/*********************************************************************** -HttpServer -***********************************************************************/ - -HttpServer::HttpServer(const WString _baseUrl, vint port) - : bufferRequest(HttpBodyInitSize) - , baseUrl(_baseUrl) +void HttpServerApi::SendResponseUtf8(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, WString body) { - urlConnect = baseUrl + HttpServerUrl_Connect; - urlRequestPrefix = baseUrl + HttpServerUrl_Request + L"/"; - urlResponsePrefix = baseUrl + HttpServerUrl_Response + L"/"; - - hEventRequest = CreateEvent(NULL, TRUE, TRUE, NULL); - CHECK_ERROR(hEventRequest != NULL, L"HttpServer initialization failed on CreateEvent(hEventRequest)."); - - { - ULONG result = NO_ERROR; - - result = HttpInitialize( - HTTPAPI_VERSION_2, - HTTP_INITIALIZE_SERVER, - NULL); - CHECK_ERROR(result == NO_ERROR, L"HttpInitialize failed."); - - result = HttpCreateRequestQueue( - HTTPAPI_VERSION_2, - NULL, - NULL, - 0, - &httpRequestQueue); - CHECK_ERROR(result == NO_ERROR, L"HttpCreateRequestQueue failed."); - - result = HttpCreateServerSession( - HTTPAPI_VERSION_2, - &httpSessionId, - 0); - CHECK_ERROR(result == NO_ERROR, L"HttpCreateServerSession failed."); - - result = HttpCreateUrlGroup( - httpSessionId, - &httpUrlGroupId, - 0); - CHECK_ERROR(result == NO_ERROR, L"HttpCreateUrlGroup failed."); - } - { - ULONG result = NO_ERROR; - - result = HttpAddUrlToUrlGroup( - httpUrlGroupId, - (WString::Unmanaged(L"http://localhost:") + itow(port) + baseUrl + WString::Unmanaged(HttpServerUrl_Connect)).Buffer(), - 0, - 0); - CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed (urlConnect)."); - - result = HttpAddUrlToUrlGroup( - httpUrlGroupId, - (WString::Unmanaged(L"http://localhost:") + itow(port) + baseUrl + WString::Unmanaged(HttpServerUrl_Request)).Buffer(), - 0, - 0); - CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed (urlRequest)."); - - result = HttpAddUrlToUrlGroup( - httpUrlGroupId, - (WString::Unmanaged(L"http://localhost:") + itow(port) + baseUrl + WString::Unmanaged(HttpServerUrl_Response)).Buffer(), - 0, - 0); - CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed (urlResponse)."); - } - { - ULONG result = NO_ERROR; - - HTTP_BINDING_INFO bindingInfo; - ZeroMemory(&bindingInfo, sizeof(bindingInfo)); - bindingInfo.Flags.Present = 1; - bindingInfo.RequestQueueHandle = httpRequestQueue; - - result = HttpSetUrlGroupProperty( - httpUrlGroupId, - HttpServerBindingProperty, - &bindingInfo, - sizeof(bindingInfo)); - CHECK_ERROR(result == NO_ERROR, L"HttpSetUrlGroupProperty failed (HttpServerBindingProperty)."); - } + auto result = SendResponse(httpRequestQueue, requestId, { 200, WString::Unmanaged(L"OK"), body, L"application/json; charset=utf8" }); + CHECK_ERROR(result == NO_ERROR, L"HttpSendHttpResponse failed for responding UTF-8 body."); } -HttpServer::~HttpServer() +/*********************************************************************** +HttpServerApi +***********************************************************************/ + +HttpServerApi::HttpServerApi(const WString& _urlPrefix, bool _respondToOptions) + : bufferRequest(HttpRequestBufferInitSize) + , urlPrefix(_urlPrefix) + , respondToOptions(_respondToOptions) +{ + hEventRequest = CreateEvent(NULL, TRUE, TRUE, NULL); + CHECK_ERROR(hEventRequest != NULL, L"HttpServerApi initialization failed on CreateEvent(hEventRequest)."); + CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"HttpServerApi initialization failed on eventPendingCallbacks.CreateManualUnsignal."); + + ULONG result = NO_ERROR; + + result = HttpInitialize( + HTTPAPI_VERSION_2, + HTTP_INITIALIZE_SERVER, + NULL); + CHECK_ERROR(result == NO_ERROR, L"HttpInitialize failed."); + + result = HttpCreateRequestQueue( + HTTPAPI_VERSION_2, + NULL, + NULL, + 0, + &httpRequestQueue); + CHECK_ERROR(result == NO_ERROR, L"HttpCreateRequestQueue failed."); + + result = HttpCreateServerSession( + HTTPAPI_VERSION_2, + &httpSessionId, + 0); + CHECK_ERROR(result == NO_ERROR, L"HttpCreateServerSession failed."); + + result = HttpCreateUrlGroup( + httpSessionId, + &httpUrlGroupId, + 0); + CHECK_ERROR(result == NO_ERROR, L"HttpCreateUrlGroup failed."); + + result = HttpAddUrlToUrlGroup( + httpUrlGroupId, + urlPrefix.Buffer(), + 0, + 0); + CHECK_ERROR(result == NO_ERROR, L"HttpAddUrlToUrlGroup failed."); + + HTTP_BINDING_INFO bindingInfo; + ZeroMemory(&bindingInfo, sizeof(bindingInfo)); + bindingInfo.Flags.Present = 1; + bindingInfo.RequestQueueHandle = httpRequestQueue; + + result = HttpSetUrlGroupProperty( + httpUrlGroupId, + HttpServerBindingProperty, + &bindingInfo, + sizeof(bindingInfo)); + CHECK_ERROR(result == NO_ERROR, L"HttpSetUrlGroupProperty failed (HttpServerBindingProperty)."); +} + +HttpServerApi::~HttpServerApi() { Stop(); CloseHandle(hEventRequest); } -WaitForClientResult HttpServer::OnClientConnected(INetworkProtocolConnection* connection) +void HttpServerApi::Start() { - return WaitForClientResult::Accept; -} - -void HttpServer::Start() -{ - CHECK_ERROR(state == State::Ready, L"HttpServer can only be started once."); + CHECK_ERROR(state == State::Ready, L"HttpServerApi can only be started once."); state = State::Running; ListenToHttpRequest(); } -void HttpServer::Stop() +void HttpServerApi::Stop() { state = State::Stopping; - auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&hWaitHandleRequest, INVALID_HANDLE_VALUE); + auto waitHandle = std::atomic_ref(hWaitHandleRequest).exchange(INVALID_HANDLE_VALUE); if (waitHandle != INVALID_HANDLE_VALUE) { UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE); } + eventPendingCallbacks.Wait(); - List> stoppingConnections; - SPIN_LOCK(lockConnections) - { - for (auto connection : connections.Values()) - { - stoppingConnections.Add(connection); - } - connections.Clear(); - } - for (auto connection : stoppingConnections) - { - SPIN_LOCK(connection->pendingRequestLock) - { - connection->OnCancelCurrentHttpRequestForPendingRequest(); - } - connection->server = nullptr; - } - for (auto connection : stoppingConnections) - { - if (connection->callback) - { - connection->callback->OnDisconnected(); - } - } + OnHttpServerStopping(); if (httpRequestQueue != INVALID_HANDLE_VALUE) { @@ -3655,6 +3665,8 @@ void HttpServer::Stop() HttpCloseServerSession(httpSessionId); HttpCloseRequestQueue(httpRequestQueue); httpRequestQueue = INVALID_HANDLE_VALUE; + httpUrlGroupId = HTTP_NULL_ID; + httpSessionId = HTTP_NULL_ID; HttpTerminate( HTTP_INITIALIZE_SERVER, @@ -3662,7 +3674,7 @@ void HttpServer::Stop() } } -bool HttpServer::IsStopped() +bool HttpServerApi::IsStopped() { return state == State::Stopping; } @@ -3680,6 +3692,35 @@ namespace vl::inter_process using namespace vl::console; using namespace vl::collections; +class NamedPipeConnection::ReadWaitContext +{ +public: + NamedPipeConnection* connection = nullptr; + HANDLE hWaitHandle = INVALID_HANDLE_VALUE; + EventObject eventRegistrationFinished; + atomic_vint callbackStarted = 0; + + ReadWaitContext() + { + CHECK_ERROR(eventRegistrationFinished.CreateManualUnsignal(false), L"ReadFile failed on eventRegistrationFinished.CreateManualUnsignal."); + } +}; + +class NamedPipeServer::PendingConnection::ConnectWaitContext +{ +public: + Ptr pendingConnection; + HANDLE hWaitHandle = INVALID_HANDLE_VALUE; + EventObject eventRegistrationFinished; + atomic_vint callbackStarted = 0; + + ConnectWaitContext(Ptr _pendingConnection) + : pendingConnection(_pendingConnection) + { + CHECK_ERROR(eventRegistrationFinished.CreateManualUnsignal(false), L"ConnectNamedPipe failed on eventRegistrationFinished.CreateManualUnsignal."); + } +}; + /*********************************************************************** NamedPipeConnection (Reading) ***********************************************************************/ @@ -3764,22 +3805,52 @@ RESTART_LOOP: DWORD error = GetLastError(); if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE) { - OnDisconnected(); + if (!stopped) + { + OnLocalError(L"ReadFile failed because the named pipe was closed."); + OnDisconnected(); + } return; } CHECK_ERROR(error == ERROR_MORE_DATA || error == ERROR_IO_PENDING, L"ReadFile failed on unexpected GetLastError."); - RegisterWaitForSingleObject( - &hWaitHandleReadFile, + auto context = new ReadWaitContext; + context->connection = this; + BeginPendingCallback(); + + ReadWaitContext* expectedContext = nullptr; + if (stopped || !readWaitContext.compare_exchange_strong(expectedContext, context)) + { + EndPendingCallback(); + delete context; + return; + } + + BOOL waitResult = RegisterWaitForSingleObject( + &context->hWaitHandle, hEventReadFile, [](PVOID lpParameter, BOOLEAN TimerOrWaitFired) { - auto self = (NamedPipeConnection*)lpParameter; - auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&self->hWaitHandleReadFile, INVALID_HANDLE_VALUE); - if (waitHandle != INVALID_HANDLE_VALUE) + auto context = (ReadWaitContext*)lpParameter; + context->callbackStarted = 1; + + auto self = context->connection; + ReadWaitContext* expectedContext = context; + bool ownsContext = self->readWaitContext.compare_exchange_strong(expectedContext, nullptr); + + auto finalize = [=]() { - UnregisterWait(waitHandle); - } + if (ownsContext) + { + context->eventRegistrationFinished.Wait(); + UnregisterWait(context->hWaitHandle); + } + self->EndPendingCallback(); + if (ownsContext) + { + delete context; + } + }; DWORD read = 0; BOOL result = GetOverlappedResult(self->hPipe, &self->overlappedReadFile, &read, FALSE); @@ -3791,9 +3862,14 @@ RESTART_LOOP: else { DWORD error = GetLastError(); - if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE) + if (error == ERROR_OPERATION_ABORTED || error == ERROR_INVALID_HANDLE || error == ERROR_BROKEN_PIPE || error == ERROR_NO_DATA) { - self->OnDisconnected(); + if (!self->stopped) + { + self->OnLocalError(L"GetOverlappedResult(ReadFile) failed because the named pipe was closed."); + self->OnDisconnected(); + } + finalize(); return; } CHECK_ERROR(error == ERROR_MORE_DATA, L"GetOverlappedResult(ReadFile) failed on unexpected GetLastError."); @@ -3803,10 +3879,23 @@ RESTART_LOOP: { self->BeginReadingLoopUnsafe(); } + finalize(); }, - this, + context, INFINITE, WT_EXECUTEONLYONCE); + + context->eventRegistrationFinished.Signal(); + if (!waitResult) + { + expectedContext = context; + if (readWaitContext.compare_exchange_strong(expectedContext, nullptr)) + { + EndPendingCallback(); + delete context; + CHECK_FAIL(L"RegisterWaitForSingleObject failed for ReadFile."); + } + } } } @@ -3860,6 +3949,10 @@ void NamedPipeConnection::EndSendStream(vint32_t bytes) auto error = GetLastError(); if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE) { + if (!stopped) + { + OnLocalError(L"WriteFile failed because the named pipe was closed."); + } OnDisconnected(); return; } @@ -3871,6 +3964,10 @@ void NamedPipeConnection::EndSendStream(vint32_t bytes) error = GetLastError(); if (error == ERROR_BROKEN_PIPE || error == ERROR_INVALID_HANDLE || error == ERROR_OPERATION_ABORTED) { + if (!stopped) + { + OnLocalError(L"GetOverlappedResult(WriteFile) failed because the named pipe was closed."); + } OnDisconnected(); return; } @@ -3901,17 +3998,30 @@ void NamedPipeConnection::SendString(const WString& str) NamedPipeConnection ***********************************************************************/ +void NamedPipeConnection::OnLocalError(const WString& errorMessage) +{ + if (callback) + { + callback->OnLocalError(errorMessage, true); + } +} + void NamedPipeConnection::OnDisconnected() { if (callback) { callback->OnDisconnected(); } - if (server) + auto owningServer = server; + if (owningServer && pendingCallbacks == 0) { - SPIN_LOCK(server->lockConnections) + SPIN_LOCK(owningServer->lockConnections) { - server->connections.Remove(this); + if (server == owningServer) + { + owningServer->connections.Remove(this); + server = nullptr; + } } } } @@ -3926,6 +4036,8 @@ NamedPipeConnection::NamedPipeConnection(HANDLE _hPipe) hEventWriteFile = CreateEvent(NULL, TRUE, TRUE, NULL); CHECK_ERROR(hEventWriteFile != NULL, L"NamedPipeConnection initialization failed on CreateEvent(hEventWriteFile)."); + + CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"NamedPipeConnection initialization failed on eventPendingCallbacks.CreateManualUnsignal."); } NamedPipeConnection::~NamedPipeConnection() @@ -3945,11 +4057,21 @@ void NamedPipeConnection::InstallCallback(INetworkProtocolCallback* _callback) void NamedPipeConnection::Stop() { stopped = 1; - auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&hWaitHandleReadFile, INVALID_HANDLE_VALUE); - if (waitHandle != INVALID_HANDLE_VALUE) + ReadWaitContext* context = readWaitContext.exchange(nullptr); + if (context) { - UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE); + context->eventRegistrationFinished.Wait(); + if (context->hWaitHandle != INVALID_HANDLE_VALUE) + { + UnregisterWaitEx(context->hWaitHandle, INVALID_HANDLE_VALUE); + } + if (context->callbackStarted == 0) + { + EndPendingCallback(); + } + delete context; } + eventPendingCallbacks.Wait(); SPIN_LOCK(lockWrite) { @@ -3962,8 +4084,24 @@ void NamedPipeConnection::Stop() } } +void NamedPipeConnection::BeginPendingCallback() +{ + if (pendingCallbacks++ == 0) + { + eventPendingCallbacks.Unsignal(); + } +} + +void NamedPipeConnection::EndPendingCallback() +{ + if (--pendingCallbacks == 0) + { + eventPendingCallbacks.Signal(); + } +} + /*********************************************************************** -NamedPipeServer +NamedPipeServer::PendingConnection ***********************************************************************/ NamedPipeServer::PendingConnection::PendingConnection(NamedPipeServer* _server, Ptr _connection) @@ -3974,6 +4112,7 @@ NamedPipeServer::PendingConnection::PendingConnection(NamedPipeServer* _server, hEventConnect = CreateEvent(NULL, TRUE, FALSE, NULL); CHECK_ERROR(hEventConnect != NULL, L"ConnectNamedPipe failed on CreateEvent."); overlappedConnect.hEvent = hEventConnect; + CHECK_ERROR(eventPendingCallbacks.CreateManualUnsignal(true), L"ConnectNamedPipe failed on eventPendingCallbacks.CreateManualUnsignal."); } NamedPipeServer::PendingConnection::~PendingConnection() @@ -3985,11 +4124,21 @@ NamedPipeServer::PendingConnection::~PendingConnection() void NamedPipeServer::PendingConnection::Stop() { server = nullptr; - auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&hWaitHandleConnect, INVALID_HANDLE_VALUE); - if (waitHandle != INVALID_HANDLE_VALUE) + ConnectWaitContext* context = connectWaitContext.exchange(nullptr); + if (context) { - UnregisterWaitEx(waitHandle, INVALID_HANDLE_VALUE); + context->eventRegistrationFinished.Wait(); + if (context->hWaitHandle != INVALID_HANDLE_VALUE) + { + UnregisterWaitEx(context->hWaitHandle, INVALID_HANDLE_VALUE); + } + if (context->callbackStarted == 0) + { + EndPendingCallback(); + } + delete context; } + eventPendingCallbacks.Wait(); if (connection) { connection->Stop(); @@ -3997,6 +4146,26 @@ void NamedPipeServer::PendingConnection::Stop() } } +void NamedPipeServer::PendingConnection::BeginPendingCallback() +{ + if (pendingCallbacks++ == 0) + { + eventPendingCallbacks.Unsignal(); + } +} + +void NamedPipeServer::PendingConnection::EndPendingCallback() +{ + if (--pendingCallbacks == 0) + { + eventPendingCallbacks.Signal(); + } +} + +/*********************************************************************** +NamedPipeServer +***********************************************************************/ + HANDLE NamedPipeServer::ServerCreatePipe(const WString& pipeName) { HANDLE hPipe = CreateNamedPipe( @@ -4053,17 +4222,35 @@ void NamedPipeServer::BeginListening() return; } pendingConnections.Add(pendingConnection); + auto context = new PendingConnection::ConnectWaitContext(pendingConnection); + pendingConnection->BeginPendingCallback(); + PendingConnection::ConnectWaitContext* expectedContext = nullptr; + CHECK_ERROR(pendingConnection->connectWaitContext.compare_exchange_strong(expectedContext, context), L"ConnectNamedPipe found an existing wait context."); BOOL waitResult = RegisterWaitForSingleObject( - &pendingConnection->hWaitHandleConnect, + &context->hWaitHandle, pendingConnection->hEventConnect, [](PVOID lpParameter, BOOLEAN TimerOrWaitFired) { - auto pendingConnection = (PendingConnection*)lpParameter; - auto waitHandle = (HANDLE)InterlockedExchangePointer((PVOID volatile*)&pendingConnection->hWaitHandleConnect, INVALID_HANDLE_VALUE); - if (waitHandle != INVALID_HANDLE_VALUE) + auto context = (PendingConnection::ConnectWaitContext*)lpParameter; + context->callbackStarted = 1; + + auto pendingConnection = context->pendingConnection; + PendingConnection::ConnectWaitContext* expectedContext = context; + bool ownsContext = pendingConnection->connectWaitContext.compare_exchange_strong(expectedContext, nullptr); + + auto finalize = [=]() { - UnregisterWait(waitHandle); - } + if (ownsContext) + { + context->eventRegistrationFinished.Wait(); + UnregisterWait(context->hWaitHandle); + } + pendingConnection->EndPendingCallback(); + if (ownsContext) + { + delete context; + } + }; DWORD transferred = 0; BOOL result = GetOverlappedResult(pendingConnection->connection->hPipe, &pendingConnection->overlappedConnect, &transferred, FALSE); @@ -4071,7 +4258,7 @@ void NamedPipeServer::BeginListening() { if (pendingConnection->server) { - pendingConnection->server->CompletePendingConnection(pendingConnection, true); + pendingConnection->server->CompletePendingConnection(pendingConnection.Obj(), true); } } else @@ -4081,17 +4268,30 @@ void NamedPipeServer::BeginListening() { if (pendingConnection->server) { - pendingConnection->server->CompletePendingConnection(pendingConnection, false); + pendingConnection->server->CompletePendingConnection(pendingConnection.Obj(), false); } + finalize(); return; } CHECK_FAIL(L"GetOverlappedResult(ConnectNamedPipe) failed on unexpected GetLastError."); } + finalize(); }, - pendingConnection.Obj(), + context, INFINITE, WT_EXECUTEONLYONCE); - CHECK_ERROR(waitResult, L"RegisterWaitForSingleObject failed for ConnectNamedPipe."); + + context->eventRegistrationFinished.Signal(); + if (!waitResult) + { + expectedContext = context; + if (pendingConnection->connectWaitContext.compare_exchange_strong(expectedContext, nullptr)) + { + pendingConnection->EndPendingCallback(); + delete context; + CHECK_FAIL(L"RegisterWaitForSingleObject failed for ConnectNamedPipe."); + } + } } break; default: diff --git a/Import/VlppOS.Windows.h b/Import/VlppOS.Windows.h index 1a43c43c..6860720d 100644 --- a/Import/VlppOS.Windows.h +++ b/Import/VlppOS.Windows.h @@ -59,6 +59,162 @@ namespace vl::inter_process #endif +/*********************************************************************** +.\HTTPCLIENTAPI.WINDOWS.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) + +Interfaces: + HttpClientApi + +***********************************************************************/ + +#ifndef VCZH_INTERPROCESS_WINDOWS_HTTPCLIENTAPI +#define VCZH_INTERPROCESS_WINDOWS_HTTPCLIENTAPI + + +namespace vl::inter_process +{ + +/// An http request. +class HttpRequest +{ + typedef collections::Array BodyBuffer; + typedef collections::List StringList; + typedef collections::Dictionary HeaderMap; +public: + /// Query of the request, like "/index.html". + WString query; + /// Set to true if the request uses SSL, or https. + bool secure = false; + /// User name to authorize. Set to empty if authorization is not needed. + WString username; + /// Password to authorize. Set to empty if authorization is not needed. + WString password; + /// HTTP method, like "GET", "POST", "PUT", "DELETE", etc. + WString method; + /// Cookie. Set to empty if cookie is not needed. + WString cookie; + /// Request body. This is a byte array. + BodyBuffer body; + /// Content type, like "text/xml". + WString contentType; + /// Accept type list, elements like "text/xml". + StringList acceptTypes; + /// A dictionary to contain extra headers. + HeaderMap extraHeaders; + /// Set to true to let this request finish when is called. + bool keepAliveOnStop = false; + /// Timeout for resolving the host name. 0 or -1 means infinite. + vint resolveTimeout = 0; + /// Timeout for connecting to the server. 0 or -1 means infinite. + vint connectTimeout = 60000; + /// Timeout for sending the request. 0 or -1 means infinite. + vint sendTimeout = 30000; + /// Timeout for receiving the response. 0 or -1 means infinite. + vint receiveTimeout = 30000; + + HttpRequest() = default; + void SetBodyUtf8(const WString& bodyString); +}; + +/// A type representing an http response. +class HttpResponse +{ + typedef collections::Array BodyBuffer; +public: + /// Status code, like 200. + vint statusCode = 0; + /// Response body. This is a byte array. + BodyBuffer body; + /// Returned cookie from the server. + WString cookie; + /// Returned content type from the server. + WString contentType; + + HttpResponse() = default; + WString GetBodyUtf8() const; +}; + +/// A transport error reported by the underlying Windows HTTP API. +class HttpError +{ +public: + DWORD errorCode = 0; + WString operation; + WString message; +}; + +/// A Windows-only async HTTP client for a single host and port. +class HttpClientApi : public Object +{ + static constexpr vint32_t HttpRespondBodyStep = 65536; + + class HttpRequestContext : public Object + { + public: + HttpClientApi* api = nullptr; + HINTERNET httpRequest = NULL; + Func)> callback; + collections::Array requestBody; + HttpResponse response; + DWORD bodyBufferWriting = 0; + DWORD bodyBufferWritingAvailable = 0; + bool keepAliveOnStop = false; + bool completed = false; + bool closing = false; + SpinLock lockContext; + }; + + WString server; + vint port = 0; + HINTERNET httpSession = NULL; + HINTERNET httpConnection = NULL; + + SpinLock lockActiveRequests; + collections::List> activeRequests; + bool stopping = false; + atomic_vint pendingCallbacks = 0; + EventObject eventPendingCallbacks; + + static void CALLBACK HttpStatusCallback(HINTERNET httpRequest, DWORD_PTR context, DWORD status, LPVOID statusInformation, DWORD statusInformationLength); + static HttpError MakeError(const WString& operation, DWORD errorCode); + static vint HexValue(wchar_t c); + + bool IsStopping(); + void BeginPendingCallback(); + void EndPendingCallback(); + void AttachRequestUnsafe(Ptr context); + void RemoveRequestUnsafe(Ptr context); + void CloseRequest(Ptr context); + void OnRequestHandleClosing(Ptr context); + void CompleteRequest(Ptr context, HttpResponse&& response); + void CompleteRequest(Ptr context, HttpError&& error); + void CompleteRequestWithLastError(Ptr context, const WString& operation, DWORD errorCode); + +public: + HttpClientApi(const WString& _server, vint _port); + ~HttpClientApi(); + + HttpClientApi(const HttpClientApi&) = delete; + HttpClientApi(HttpClientApi&&) = delete; + HttpClientApi& operator=(const HttpClientApi&) = delete; + HttpClientApi& operator=(HttpClientApi&&) = delete; + + void HttpQuery(const HttpRequest& request, Func)> callback); + void Stop(); + + static WString UrlEncodeQuery(const WString& query); + static WString UrlDecodeQuery(const WString& query); +}; + +} + +#endif + + /*********************************************************************** .\HTTPCLIENT.WINDOWS.H ***********************************************************************/ @@ -81,6 +237,7 @@ namespace vl::inter_process class HttpClient : public Object, public virtual INetworkProtocolConnection, public virtual INetworkProtocolClient { protected: + static constexpr vint HttpRequestMaxAttempts = 3; enum class State { @@ -93,35 +250,21 @@ protected: State state = State::Ready; INetworkProtocolCallback* callback = nullptr; WString baseUrl; - - HINTERNET httpSession = NULL; - HINTERNET httpConnection = NULL; + Ptr httpClientApi; WString urlConnect; WString urlRequest; WString urlResponse; - - atomic_vint pendingCallbacks = 0; - atomic_vint createdRequestIds = 0; - EventObject eventPendingCallbacks; - void BeginPendingCallback(); - void EndPendingCallback(); - void QueueCallback(const Func& proc); - vint FindActiveRequestUnsafe(HINTERNET httpRequest, vint requestId); - void AttachRequest(HINTERNET httpRequest, vint requestId = 0); - void CloseRequest(HINTERNET httpRequest, vint requestId = 0); - void OnRequestHandleClosing(HINTERNET httpRequest, vint requestId = 0); + SpinLock lockState; /*********************************************************************** HttpClient (Reading) ***********************************************************************/ protected: - static constexpr vint32_t HttpRespondBodyStep = 65536; - collections::Array httpRespondBodyBuffer; - DWORD httpRespondBodyBufferWriting = 0; - DWORD httpRespondBodyBufferWritingAvailable = 0; + static constexpr const wchar_t* JsonContentType = L"application/json; charset=utf8"; - void RaiseErrorUnsafe(WString errorMessage); + void RaiseLocalError(WString errorMessage, bool fatal); + bool IsStopping(); public: void BeginReadingLoopUnsafe() override; @@ -132,9 +275,12 @@ HttpClient (WaitForServer) protected: - HANDLE hEventWaitForServer = INVALID_HANDLE_VALUE; - DWORD dwInternetStatus_WaitForServer = 0; - DWORD dwStatusInformationLength_WaitForServer = 0; + EventObject eventWaitForServer; + SpinLock lockConnectResult; + bool connectCompleted = false; + WString connectResponse; + WString connectError; + void CompleteConnectRequest(const WString& response, const WString& error); public: @@ -147,34 +293,16 @@ HttpClient (Writing) ***********************************************************************/ protected: - class HttpResponseReading : public Object + enum class HttpRequestType { - public: - collections::Array bodyBuffer; - DWORD bodyBufferWriting = 0; - DWORD bodyBufferWritingAvailable = 0; + Connect, + Request, + Response, }; - class HttpRequestContext : public Object - { - public: - HttpClient* client = nullptr; - HINTERNET httpRequest = NULL; - vint requestId = 0; - U8String requestBody; - Ptr responseReading; - }; - - class HttpActiveRequest - { - public: - HINTERNET httpRequest = NULL; - vint requestId = -1; - }; - - SpinLock httpActiveRequestsLock; - collections::List httpActiveRequests; - + bool SendHttpRequest(HttpRequestType requestType, const wchar_t* method, const WString& url, const WString& body, vint attempt = 1); + void OnHttpRequestCompleted(HttpRequestType requestType, WString body, vint attempt, Variant result); + void OnHttpRequestFailed(HttpRequestType requestType, const WString& body, vint attempt, const WString& errorMessage); public: @@ -197,6 +325,101 @@ public: #endif +/*********************************************************************** +.\HTTPSERVERAPI.WINDOWS.H +***********************************************************************/ +/*********************************************************************** +Vczh Library++ 3.0 +Developer: Zihan Chen(vczh) + +Interfaces: + HttpServerApi + +***********************************************************************/ + +#ifndef VCZH_INTERPROCESS_WINDOWS_HTTPSERVERAPI +#define VCZH_INTERPROCESS_WINDOWS_HTTPSERVERAPI + + +namespace vl::inter_process +{ + +/// A response to be sent by . +struct HttpServerResponse +{ + vint statusCode = 200; + WString reason; + WString body; + WString contentType; +}; + +/// A Windows-only async HTTP server for a single URL prefix. +class HttpServerApi : public Object +{ + static constexpr vint32_t HttpRequestBufferInitSize = 1024; + +protected: + enum class State + { + Ready, + Running, + Stopping, + }; + + WString urlPrefix; + bool respondToOptions = false; + + HANDLE httpRequestQueue = INVALID_HANDLE_VALUE; + HTTP_SERVER_SESSION_ID httpSessionId = HTTP_NULL_ID; + HTTP_URL_GROUP_ID httpUrlGroupId = HTTP_NULL_ID; + + State state = State::Ready; + + collections::Array bufferRequest; + HANDLE hWaitHandleRequest = INVALID_HANDLE_VALUE; + OVERLAPPED overlappedRequest; + HANDLE hEventRequest = INVALID_HANDLE_VALUE; + EventObject eventPendingCallbacks; + atomic_vint pendingCallbacks = 0; + + void OnHttpConnectionBrokenUnsafe(); + void OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest); + ULONG ListenToHttpRequest_Init(OVERLAPPED* overlapped); + ULONG ListenToHttpRequest_InitMoreData(ULONG* bytesReturned); + ULONG ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize); + void ListenToHttpRequest(); + void BeginPendingCallback(); + void EndPendingCallback(); + + virtual void OnHttpRequestReceived(PHTTP_REQUEST pRequest) = 0; + virtual void OnHttpServerStopping(); + + static void SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId); + +public: + HttpServerApi(const WString& _urlPrefix, bool _respondToOptions); + ~HttpServerApi(); + + HttpServerApi(const HttpServerApi&) = delete; + HttpServerApi(HttpServerApi&&) = delete; + HttpServerApi& operator=(const HttpServerApi&) = delete; + HttpServerApi& operator=(HttpServerApi&&) = delete; + + void Start(); + void Stop(); + bool IsStopped(); + HANDLE GetHttpRequestQueue() const; + + Nullable GetUtf8Body(PHTTP_REQUEST pRequest); + static ULONG SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, const HttpServerResponse& response); + static void SendResponseUtf8(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, WString body); +}; + +} + +#endif + + /*********************************************************************** .\HTTPSERVER.WINDOWS.H ***********************************************************************/ @@ -250,10 +473,8 @@ public: static WString GenerateNewGuid(); }; -class HttpServer : public Object, public virtual INetworkProtocolServer +class HttpServer : public HttpServerApi, public virtual INetworkProtocolServer { - static constexpr vint32_t HttpBodyInitSize = 1024; - friend class HttpServerConnection; using ConnectionMap = collections::Dictionary>; protected: @@ -266,37 +487,6 @@ protected: SpinLock lockConnections; ConnectionMap connections; - HANDLE httpRequestQueue = INVALID_HANDLE_VALUE; - HTTP_SERVER_SESSION_ID httpSessionId = HTTP_NULL_ID; - HTTP_URL_GROUP_ID httpUrlGroupId = HTTP_NULL_ID; - -/*********************************************************************** -HttpServer (ListenToHttpRequest) -***********************************************************************/ - -protected: - - enum class State - { - Ready, - Running, - Stopping, - }; - - State state = State::Ready; - - collections::Array bufferRequest; - HANDLE hWaitHandleRequest = INVALID_HANDLE_VALUE; - OVERLAPPED overlappedRequest; - HANDLE hEventRequest = INVALID_HANDLE_VALUE; - - void OnHttpConnectionBrokenUnsafe(); - void OnHttpRequestReceivedUnsafe(PHTTP_REQUEST pRequest); - ULONG ListenToHttpRequest_Init(OVERLAPPED* overlapped); - ULONG ListenToHttpRequest_InitMoreData(ULONG* bytesReturned); - ULONG ListenToHttpRequest_OverlappedMoreData(vint expectedBufferSize); - void ListenToHttpRequest(); - /*********************************************************************** HttpServer (BeginReadingLoopUnsafe) ***********************************************************************/ @@ -305,14 +495,13 @@ protected: /*********************************************************************** -HttpServer (Writing) +HttpServer (HttpServerApi) ***********************************************************************/ protected: - static void Send404Response(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, PCSTR reason); - static void SendOptionsResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId); - static ULONG SendResponse(HANDLE httpRequestQueue, HTTP_REQUEST_ID requestId, const WString& str); + void OnHttpRequestReceived(PHTTP_REQUEST pRequest) override; + void OnHttpServerStopping() override; /*********************************************************************** HttpServer @@ -363,17 +552,23 @@ class NamedPipeConnection : public Object, public virtual INetworkProtocolConnec // ----------------------------------------------------------------------- private: + class ReadWaitContext; + bool firstRead = true; atomic_vint stopped = 0; collections::Array bufferReadFile; stream::MemoryStream streamReadFile; - HANDLE hWaitHandleReadFile = INVALID_HANDLE_VALUE; + std::atomic readWaitContext = nullptr; OVERLAPPED overlappedReadFile; HANDLE hEventReadFile = INVALID_HANDLE_VALUE; + atomic_vint pendingCallbacks = 0; + EventObject eventPendingCallbacks; void BeginReadingUnsafe(); void SubmitReadBufferUnsafe(vint bytes); void EndReadingUnsafe(); + void BeginPendingCallback(); + void EndPendingCallback(); public: void BeginReadingLoopUnsafe() override; @@ -407,6 +602,7 @@ protected: INetworkProtocolCallback* callback = nullptr; HANDLE hPipe = INVALID_HANDLE_VALUE; + void OnLocalError(const WString& errorMessage); void OnDisconnected(); NamedPipeConnection(HANDLE _hPipe); @@ -425,16 +621,22 @@ protected: class PendingConnection : public Object { public: - NamedPipeServer* server = nullptr; - Ptr connection; - HANDLE hWaitHandleConnect = INVALID_HANDLE_VALUE; - OVERLAPPED overlappedConnect; - HANDLE hEventConnect = INVALID_HANDLE_VALUE; + class ConnectWaitContext; + + NamedPipeServer* server = nullptr; + Ptr connection; + std::atomic connectWaitContext = nullptr; + OVERLAPPED overlappedConnect; + HANDLE hEventConnect = INVALID_HANDLE_VALUE; + atomic_vint pendingCallbacks = 0; + EventObject eventPendingCallbacks; PendingConnection(NamedPipeServer* _server, Ptr _connection); ~PendingConnection(); - void Stop(); + void Stop(); + void BeginPendingCallback(); + void EndPendingCallback(); }; static HANDLE ServerCreatePipe(const WString& pipeName); diff --git a/Import/VlppOS.h b/Import/VlppOS.h index a337ac53..5cf13409 100644 --- a/Import/VlppOS.h +++ b/Import/VlppOS.h @@ -4,137 +4,6 @@ DEVELOPER: Zihan Chen(vczh) ***********************************************************************/ #include "Vlpp.h" -/*********************************************************************** -.\HTTPUTILITY.H -***********************************************************************/ -/*********************************************************************** -Author: Zihan Chen (vczh) -Licensed under https://github.com/vczh-libraries/License -***********************************************************************/ - -#ifndef VCZH_HTTPUTILITY -#define VCZH_HTTPUTILITY - - -#ifdef VCZH_MSVC - -namespace vl -{ - -/*********************************************************************** -HTTP Utility -***********************************************************************/ - - /// An http requiest. - class HttpRequest - { - typedef collections::Array BodyBuffer; - typedef collections::List StringList; - typedef collections::Dictionary HeaderMap; - public: - /// Name of the server, like "gaclib.net". - WString server; - /// Port of the server, like 80. - vint port = 0; - /// Query of the request, like "/index.html". - WString query; - /// Set to true if the request uses SSL, or https. - bool secure = false; - /// User name to authorize. Set to empty if authorization is not needed. - WString username; - /// Password to authorize. Set to empty if authorization is not needed. - WString password; - /// HTTP method, like "GET", "POST", "PUT", "DELETE", etc. - WString method; - /// Cookie. Set to empty if cookie is not needed. - WString cookie; - /// Request body. This is a byte array. - BodyBuffer body; - /// Content type, like "text/xml". - WString contentType; - /// Accept type list, elements like "text/xml". - StringList acceptTypes; - /// A dictionary to contain extra headers. - HeaderMap extraHeaders; - - /// Create an empty request. - HttpRequest() = default; - - /// Set , , and fields for you using an URL. - /// Returns true if this operation succeeded. - /// The URL. - bool SetHost(const WString& inputQuery); - - /// Fill the text body in UTF-8. - /// The text to fill. - void SetBodyUtf8(const WString& bodyString); - }; - - /// A type representing an http response. - class HttpResponse - { - typedef collections::Array BodyBuffer; - public: - /// Status code, like 200. - vint statusCode = 0; - /// Response body. This is a byte array. - BodyBuffer body; - /// Returned cookie from the server. - WString cookie; - - HttpResponse() = default; - - /// Get the text body, encoding is assumed to be UTF-8. - /// The response body as text. - WString GetBodyUtf8(); - }; - - /// Send an http request and receive a response. - /// Returns true if this operation succeeded, even when the server returns 404. - /// The request to send. - /// Returns the response. - /// - ///

- /// This function will block the calling thread until the respons is returned. - ///

- ///

- /// This function is only available in Windows. - ///

- ///
- /// - extern bool HttpQuery(const HttpRequest& request, HttpResponse& response); - - /// Encode a text as part of the url. This function can be used to create arguments in an URL. - /// The encoded text. - /// The text to encode. - /// - ///

- /// When a character is not a digit or a letter, - /// it is first encoded to UTF-8, - /// then each byte is written as "%" with two hex digits. - ///

- ///

- /// This function is only available in Windows. - ///

- ///
- extern WString UrlEncodeQuery(const WString& query); -} - -#endif - -#endif - - /*********************************************************************** .\LOCALE.H ***********************************************************************/ @@ -1384,11 +1253,19 @@ IChannelClient /// /// Called when a fetal error occurs. - /// When any fetal error occurs at client side or server side, all clients is supposed to receive such error if possible, and the server will shut down. + /// When any fetal error is broadcasted from server side, all clients is supposed to receive such error if possible, and the server will shut down. /// This function will be implemented by the user, the default implementation will be empty. /// /// The error message. - virtual void OnError(const WString& errorMessage) = 0; + virtual void OnReadError(const WString& errorMessage) = 0; + + /// + /// Called when a local error occurs. + /// This function will be implemented by the user, the default implementation will be empty. + /// + /// The error message. + /// Indicates whether the error is not recoverable. The client will automatically stop after a fatal error. + virtual void OnLocalError(const WString& errorMessage, bool fatal) = 0; /// /// Called when available connection names are required. @@ -1755,11 +1632,18 @@ INetworkProtocolServer virtual void OnReadString(const WString& str) = 0; /// - /// Called when a localerror message is raised. + /// Called when an error message is received from the other side of the connection. /// /// The error message. virtual void OnReadError(const WString& error) = 0; + /// + /// Called when a local transport error occurs. + /// + /// The error message. + /// Indicates whether the connection should be disconnected after this callback. + virtual void OnLocalError(const WString& error, bool fatal) = 0; + /// /// Called when the connection becomes available. /// This function might not be called if is called after the connection is already established. @@ -2100,17 +1984,11 @@ NetworkProtocolChannel }; /*********************************************************************** -NetworkProtocolChannelClient +NetworkProtocolChannelClientBase ***********************************************************************/ template - class NetworkProtocolChannelServer; - - template - class NetworkProtocolLocalChannelClient; - - template - class NetworkProtocolChannelClient : public Object, public virtual IChannelClient + class NetworkProtocolChannelClientBase : public Object, public virtual IChannelClient { protected: using BaseChannel = NetworkProtocolChannel; @@ -2124,7 +2002,7 @@ NetworkProtocolChannelClient using Base = NetworkProtocolChannel; private: - NetworkProtocolChannelClient* client = nullptr; + NetworkProtocolChannelClientBase* client = nullptr; void ValidatePackage(vint senderClientId, Nullable receiverClientId) override { @@ -2143,54 +2021,15 @@ NetworkProtocolChannelClient } public: - Channel(NetworkProtocolChannelClient* _client, const WString& _channelName) + Channel(NetworkProtocolChannelClientBase* _client, const WString& _channelName) : Base(_channelName) , client(_client) { } }; - class Callback : public Object, public virtual INetworkProtocolCallback - { - private: - NetworkProtocolChannelClient* client = nullptr; - - public: - Callback(NetworkProtocolChannelClient* _client) - : client(_client) - { - } - - void OnReadString(const WString& str) override - { - client->OnReadString(str); - } - - void OnReadError(const WString& error) override - { - client->OnError(error); - client->NotifyDisconnected(); - } - - void OnConnected() override - { - } - - void OnDisconnected() override - { - client->NotifyDisconnected(); - } - - void OnInstalled(INetworkProtocolConnection*) override - { - } - }; - - private: - typename TSerialization::ContextType context; - protected: - EventObject eventWaitForServer; + typename TSerialization::ContextType context; // covers status, connectedNotified and clientId SpinLock lockStatus; @@ -2201,16 +2040,14 @@ NetworkProtocolChannelClient private: ChannelMap channels; collections::Dictionary> ownedChannels; - Ptr callback; - Ptr npClient; + protected: Channel* FindChannel(const WString& channelName) { vint index = ownedChannels.Keys().IndexOf(channelName); return index == -1 ? nullptr : ownedChannels.Values()[index].Obj(); } - protected: void SetStatus(ClientStatus newStatus) { SPIN_LOCK(lockStatus) @@ -2219,59 +2056,43 @@ NetworkProtocolChannelClient } } - virtual bool SendBatch(Nullable receiverClientId, const WString& channelName, const PackageList& batch) + void SetConnected(vint assignedClientId) { - if (GetStatus() != ClientStatus::Connected) + SPIN_LOCK(lockStatus) { - return true; + clientId = assignedClientId; + status = ClientStatus::Connected; } - - CHECK_ERROR(npClient, L"NetworkProtocolChannelClient::SendBatch needs an established network connection."); - WString messageBody; - TSerialization::Serialize(context, batch, messageBody); - npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create(std::move(receiverClientId), channelName, messageBody))); - return false; } - private: - void OnReadString(const WString& str) + bool TrySetConnected(vint assignedClientId) { - NetworkPackage package; - NetworkPackage::Parse(str, package); - if (package.channelName == ErrorChannel) + bool connected = false; + SPIN_LOCK(lockStatus) { - OnError(package.messageBody); - NotifyDisconnected(); - return; - } - else if (package.channelName == WString::Empty) - { - CHECK_ERROR(package.clientId, L"NetworkProtocolChannelClient received an invalid connection response."); - CHECK_ERROR(package.clientId.Value() > 0, L"NetworkProtocolChannelClient received an invalid client id."); + if (status == ClientStatus::Ready || status == ClientStatus::WaitingForServer) { - SPIN_LOCK(lockStatus) - { - clientId = package.clientId.Value(); - status = ClientStatus::Connected; - } + clientId = assignedClientId; + status = ClientStatus::Connected; + connected = true; } - eventWaitForServer.Signal(); - return; } + return connected; + } - auto channel = FindChannel(package.channelName); + virtual bool SendBatch(Nullable receiverClientId, const WString& channelName, const PackageList& batch) = 0; + + void ReceiveBatch(const WString& channelName, vint senderClientId, const WString& messageBody) + { + auto channel = FindChannel(channelName); if (channel) { - CHECK_ERROR(package.clientId, L"NetworkProtocolChannelClient received a channel message without senderClientId."); - auto senderClientId = package.clientId.Value(); - CHECK_ERROR(senderClientId > 0, L"NetworkProtocolChannelClient received an invalid senderClientId."); PackageList batch; - TSerialization::Deserialize(context, package.messageBody, batch); + TSerialization::Deserialize(context, messageBody, batch); channel->ReadBatch(senderClientId, batch); } } - protected: virtual void NotifyDisconnected() { bool shouldNotify = false; @@ -2283,7 +2104,6 @@ NetworkProtocolChannelClient shouldNotify = true; } } - eventWaitForServer.Signal(); if (shouldNotify) { OnDisconnected(); @@ -2320,37 +2140,20 @@ NetworkProtocolChannelClient // default implementation does nothing } - void OnError(const WString& errorMessage) override + void OnReadError(const WString& errorMessage) override + { + // default implementation does nothing + } + + void OnLocalError(const WString& errorMessage, bool fatal) override { // default implementation does nothing } protected: - NetworkProtocolChannelClient(const typename TSerialization::ContextType& _context = {}) + NetworkProtocolChannelClientBase(const typename TSerialization::ContextType& _context = {}) : context(_context) { - CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"NetworkProtocolChannelClient initialization failed on eventWaitForServer."); - } - - public: - NetworkProtocolChannelClient( - Ptr _npClient, - const typename TSerialization::ContextType& _context = {} - ) - : NetworkProtocolChannelClient(_context) - { - CHECK_ERROR(_npClient, L"NetworkProtocolChannelClient needs a valid INetworkProtocolClient."); - callback = Ptr(new Callback(this)); - npClient = _npClient; - npClient->GetConnection()->InstallCallback(callback.Obj()); - } - - ~NetworkProtocolChannelClient() - { - if (npClient) - { - npClient->GetConnection()->Stop(); - } } private: @@ -2369,6 +2172,7 @@ NetworkProtocolChannelClient return channel.Obj(); } + protected: void EnsureChannels(const ChannelNameList& channelNames) { for (auto&& channelName : channelNames) @@ -2399,37 +2203,6 @@ NetworkProtocolChannelClient return result; } - void WaitForServer() override - { - auto currentStatus = GetStatus(); - if (currentStatus == ClientStatus::Connected || currentStatus == ClientStatus::Disconnected) - { - NotifyConnected(); - return; - } - if (currentStatus == ClientStatus::WaitingForServer) - { - eventWaitForServer.Wait(); - NotifyConnected(); - return; - } - - SetStatus(ClientStatus::WaitingForServer); - npClient->WaitForServer(); - if (npClient->GetStatus() != ClientStatus::Connected) - { - NotifyDisconnected(); - return; - } - - auto&& channelNames = OnGetChannelNames(); - EnsureChannels(channelNames); - npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create({}, WString::Empty, BaseChannel::JoinChannelNames(channelNames)))); - npClient->GetConnection()->BeginReadingLoopUnsafe(); - eventWaitForServer.Wait(); - NotifyConnected(); - } - ClientStatus GetStatus() override { ClientStatus result = ClientStatus::Disconnected; @@ -2439,6 +2212,209 @@ NetworkProtocolChannelClient } return result; } + }; + +/*********************************************************************** +NetworkProtocolChannelClient +***********************************************************************/ + + template + class NetworkProtocolChannelClient : public NetworkProtocolChannelClientBase + { + protected: + using Base = NetworkProtocolChannelClientBase; + using BaseChannel = typename Base::BaseChannel; + using PackageList = typename TSerialization::SourceType; + + class Callback : public Object, public virtual INetworkProtocolCallback + { + private: + NetworkProtocolChannelClient* client = nullptr; + + public: + Callback(NetworkProtocolChannelClient* _client) + : client(_client) + { + } + + void OnReadString(const WString& str) override + { + client->OnReadString(str); + } + + void OnReadError(const WString& error) override + { + client->OnReadError(error); + client->NotifyDisconnected(); + } + + void OnLocalError(const WString& error, bool fatal) override + { + client->OnLocalError(error, fatal); + if (fatal) + { + client->NotifyDisconnected(); + } + } + + void OnConnected() override + { + } + + void OnDisconnected() override + { + client->NotifyDisconnected(); + } + + void OnInstalled(INetworkProtocolConnection*) override + { + } + }; + + private: + EventObject eventWaitForServer; + Ptr callback; + Ptr npClient; + SpinLock lockQueuedPackagesBeforeConnected; + collections::List queuedPackagesBeforeConnected; + + protected: + bool SendBatch(Nullable receiverClientId, const WString& channelName, const PackageList& batch) override + { + if (this->GetStatus() != ClientStatus::Connected) + { + return true; + } + + CHECK_ERROR(npClient, L"NetworkProtocolChannelClient::SendBatch needs an established network connection."); + WString messageBody; + TSerialization::Serialize(this->context, batch, messageBody); + npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create(std::move(receiverClientId), channelName, messageBody))); + return false; + } + + private: + void OnReadChannelPackage(const NetworkPackage& package) + { + if (this->FindChannel(package.channelName)) + { + CHECK_ERROR(package.clientId, L"NetworkProtocolChannelClient received a channel message without senderClientId."); + auto senderClientId = package.clientId.Value(); + CHECK_ERROR(senderClientId > 0, L"NetworkProtocolChannelClient received an invalid senderClientId."); + this->ReceiveBatch(package.channelName, senderClientId, package.messageBody); + } + } + + void OnReadString(const WString& str) + { + NetworkPackage package; + NetworkPackage::Parse(str, package); + if (package.channelName == ErrorChannel) + { + this->OnReadError(package.messageBody); + NotifyDisconnected(); + return; + } + else if (package.channelName == WString::Empty) + { + CHECK_ERROR(package.clientId, L"NetworkProtocolChannelClient received an invalid connection response."); + CHECK_ERROR(package.clientId.Value() > 0, L"NetworkProtocolChannelClient received an invalid client id."); + this->SetConnected(package.clientId.Value()); + this->NotifyConnected(); + eventWaitForServer.Signal(); + collections::List packages; + SPIN_LOCK(lockQueuedPackagesBeforeConnected) + { + packages = std::move(queuedPackagesBeforeConnected); + } + for (auto&& queuedPackage : packages) + { + OnReadChannelPackage(queuedPackage); + } + return; + } + + if (this->GetStatus() != ClientStatus::Connected) + { + SPIN_LOCK(lockQueuedPackagesBeforeConnected) + { + if (this->GetStatus() != ClientStatus::Connected) + { + queuedPackagesBeforeConnected.Add(std::move(package)); + return; + } + } + } + OnReadChannelPackage(package); + } + + protected: + void NotifyDisconnected() override + { + eventWaitForServer.Signal(); + Base::NotifyDisconnected(); + } + + protected: + NetworkProtocolChannelClient(const typename TSerialization::ContextType& _context = {}) + : Base(_context) + { + CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"NetworkProtocolChannelClient initialization failed on eventWaitForServer."); + } + + public: + NetworkProtocolChannelClient( + Ptr _npClient, + const typename TSerialization::ContextType& _context = {} + ) + : Base(_context) + { + CHECK_ERROR(eventWaitForServer.CreateManualUnsignal(false), L"NetworkProtocolChannelClient initialization failed on eventWaitForServer."); + CHECK_ERROR(_npClient, L"NetworkProtocolChannelClient needs a valid INetworkProtocolClient."); + callback = Ptr(new Callback(this)); + npClient = _npClient; + npClient->GetConnection()->InstallCallback(callback.Obj()); + } + + ~NetworkProtocolChannelClient() + { + if (npClient) + { + npClient->GetConnection()->Stop(); + } + } + + void WaitForServer() override + { + auto currentStatus = this->GetStatus(); + if (currentStatus == ClientStatus::Connected || currentStatus == ClientStatus::Disconnected) + { + this->NotifyConnected(); + return; + } + if (currentStatus == ClientStatus::WaitingForServer) + { + eventWaitForServer.Wait(); + this->NotifyConnected(); + return; + } + CHECK_ERROR(currentStatus == ClientStatus::Ready, L"NetworkProtocolChannelClient::WaitForServer found an unexpected client status."); + + this->SetStatus(ClientStatus::WaitingForServer); + npClient->WaitForServer(); + if (npClient->GetStatus() != ClientStatus::Connected) + { + NotifyDisconnected(); + return; + } + + auto&& channelNames = this->OnGetChannelNames(); + this->EnsureChannels(channelNames); + npClient->GetConnection()->SendString(NetworkPackage::ToString(NetworkPackage::Create({}, WString::Empty, BaseChannel::JoinChannelNames(channelNames)))); + npClient->GetConnection()->BeginReadingLoopUnsafe(); + eventWaitForServer.Wait(); + this->NotifyConnected(); + } void BroadcastError(const WString& errorMessage) override { @@ -2453,12 +2429,15 @@ NetworkProtocolLocalChannelClient ***********************************************************************/ template - class NetworkProtocolLocalChannelClient : public NetworkProtocolChannelClient + class NetworkProtocolChannelServer; + + template + class NetworkProtocolLocalChannelClient : public NetworkProtocolChannelClientBase { friend class NetworkProtocolChannelServer; private: - using Base = NetworkProtocolChannelClient; + using Base = NetworkProtocolChannelClientBase; using PackageList = typename TSerialization::SourceType; NetworkProtocolChannelServer* localServer = nullptr; @@ -2468,17 +2447,7 @@ NetworkProtocolLocalChannelClient CHECK_ERROR(server, L"NetworkProtocolLocalChannelClient::ConnectLocalServer needs a valid server."); localServer = server; - bool connected = false; - SPIN_LOCK(this->lockStatus) - { - if (this->status == ClientStatus::Ready || this->status == ClientStatus::WaitingForServer) - { - this->clientId = assignedClientId; - this->status = ClientStatus::Connected; - connected = true; - } - } - + bool connected = this->TrySetConnected(assignedClientId); if (!connected) { localServer = nullptr; @@ -2488,7 +2457,6 @@ NetworkProtocolLocalChannelClient void NotifyLocalConnected() { - this->eventWaitForServer.Signal(); this->NotifyConnected(); } @@ -2504,7 +2472,7 @@ NetworkProtocolLocalChannelClient { return localServer->SendFromLocalClient(receiverClientId, this->GetClientId(), channelName, batch); } - return Base::SendBatch(receiverClientId, channelName, batch); + return true; } void NotifyDisconnected() override @@ -2521,22 +2489,6 @@ NetworkProtocolLocalChannelClient void WaitForServer() override { - auto currentStatus = this->GetStatus(); - if (currentStatus == ClientStatus::Connected || currentStatus == ClientStatus::Disconnected) - { - this->NotifyConnected(); - return; - } - if (currentStatus == ClientStatus::WaitingForServer) - { - this->eventWaitForServer.Wait(); - this->NotifyConnected(); - return; - } - - this->SetStatus(ClientStatus::WaitingForServer); - this->eventWaitForServer.Wait(); - this->NotifyConnected(); } void BroadcastError(const WString& errorMessage) override @@ -2546,7 +2498,8 @@ NetworkProtocolLocalChannelClient localServer->BroadcastError(errorMessage); return; } - Base::BroadcastError(errorMessage); + this->OnLocalError(errorMessage, true); + this->NotifyDisconnected(); } }; @@ -2593,6 +2546,11 @@ NetworkProtocolChannelServer server->BroadcastError(error); } + void OnLocalError(const WString& error, bool fatal) override + { + // Server-side transport errors are finalized by OnDisconnected. + } + void OnConnected() override { } @@ -3057,7 +3015,7 @@ NetworkProtocolChannelServer } for (auto&& localClient : targetLocalClients) { - localClient->OnError(errorMessage); + localClient->OnReadError(errorMessage); } // Give transport clients a chance to consume the fatal package before closing. Thread::Sleep(200); diff --git a/Import/VlppReflection.h b/Import/VlppReflection.h index 830391ba..1b4ccb73 100644 --- a/Import/VlppReflection.h +++ b/Import/VlppReflection.h @@ -6159,6 +6159,19 @@ Collection Wrappers #pragma warning(push) #pragma warning(disable:4250) + template + auto ConvertCollectionError(TCallback&& callback) -> decltype(callback()) + { + try + { + return callback(); + } + catch (const Error& ex) + { + throw Exception(WString::Unmanaged(ex.Description())); + } + } + template class ValueEnumerableWrapper; @@ -6181,7 +6194,10 @@ Collection Wrappers Value GetCurrent()override { if (!enumerableWrapper->wrapperPointer) throw ObjectDisposedException(); - return BoxValue(wrapperPointer->Current()); + return ConvertCollectionError([&]() + { + return BoxValue(wrapperPointer->Current()); + }); } vint GetIndex()override @@ -6193,7 +6209,10 @@ Collection Wrappers bool Next()override { if (!enumerableWrapper->wrapperPointer) throw ObjectDisposedException(); - return wrapperPointer->Next(); + return ConvertCollectionError([&]() + { + return wrapperPointer->Next(); + }); } }; @@ -6276,21 +6295,30 @@ Collection Wrappers Value Get(vint index)override { ENSURE_WRAPPER_POINTER; - return BoxValue(WRAPPER_POINTER->Get(index)); + return ConvertCollectionError([&]() + { + return BoxValue(WRAPPER_POINTER->Get(index)); + }); } bool Contains(const Value& value)override { ENSURE_WRAPPER_POINTER; ElementKeyType item = UnboxValue(value); - return WRAPPER_POINTER->Contains(item); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->Contains(item); + }); } vint IndexOf(const Value& value)override { ENSURE_WRAPPER_POINTER; ElementKeyType item = UnboxValue(value); - return WRAPPER_POINTER->IndexOf(item); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->IndexOf(item); + }); } }; @@ -6312,13 +6340,19 @@ Collection Wrappers { ENSURE_WRAPPER_POINTER; ElementType item = UnboxValue(value); - WRAPPER_POINTER->Set(index, item); + ConvertCollectionError([&]() + { + WRAPPER_POINTER->Set(index, item); + }); } void Resize(vint size)override { ENSURE_WRAPPER_POINTER; - return WRAPPER_POINTER->Resize(size); + ConvertCollectionError([&]() + { + WRAPPER_POINTER->Resize(size); + }); } }; @@ -6340,40 +6374,58 @@ Collection Wrappers { ENSURE_WRAPPER_POINTER; ElementType item = UnboxValue(value); - WRAPPER_POINTER->Set(index, item); + ConvertCollectionError([&]() + { + WRAPPER_POINTER->Set(index, item); + }); } vint Add(const Value& value)override { ENSURE_WRAPPER_POINTER; ElementType item = UnboxValue(value); - return WRAPPER_POINTER->Add(item); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->Add(item); + }); } vint Insert(vint index, const Value& value)override { ENSURE_WRAPPER_POINTER; ElementType item = UnboxValue(value); - return WRAPPER_POINTER->Insert(index, item); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->Insert(index, item); + }); } bool Remove(const Value& value)override { ENSURE_WRAPPER_POINTER; ElementKeyType item = UnboxValue(value); - return WRAPPER_POINTER->Remove(item); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->Remove(item); + }); } bool RemoveAt(vint index)override { ENSURE_WRAPPER_POINTER; - return WRAPPER_POINTER->RemoveAt(index); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->RemoveAt(index); + }); } void Clear()override { ENSURE_WRAPPER_POINTER; - WRAPPER_POINTER->Clear(); + ConvertCollectionError([&]() + { + WRAPPER_POINTER->Clear(); + }); } }; @@ -6444,8 +6496,11 @@ Collection Wrappers { ENSURE_WRAPPER_POINTER; KeyKeyType item = UnboxValue(key); - ValueType result = wrapperPointer->Get(item); - return BoxValue(result); + return ConvertCollectionError([&]() + { + ValueType result = wrapperPointer->Get(item); + return BoxValue(result); + }); } const Object* GetCollectionObject()override @@ -6486,20 +6541,29 @@ Collection Wrappers ENSURE_WRAPPER_POINTER; KEY_VALUE_TYPE item = UnboxValue(key); VALUE_TYPE result = UnboxValue(value); - WRAPPER_POINTER->Set(item, result); + ConvertCollectionError([&]() + { + WRAPPER_POINTER->Set(item, result); + }); } bool Remove(const Value& key)override { ENSURE_WRAPPER_POINTER; KEY_KEY_TYPE item = UnboxValue(key); - return WRAPPER_POINTER->Remove(item); + return ConvertCollectionError([&]() + { + return WRAPPER_POINTER->Remove(item); + }); } void Clear()override { ENSURE_WRAPPER_POINTER; - WRAPPER_POINTER->Clear(); + ConvertCollectionError([&]() + { + WRAPPER_POINTER->Clear(); + }); } }; #undef KEY_VALUE_TYPE @@ -6527,6 +6591,7 @@ Collection Wrappers #endif + /*********************************************************************** .\BOXING\BOXINGPARAMETER_CONTAINERS.H ***********************************************************************/ diff --git a/Import/VlppWorkflowCompiler.cpp b/Import/VlppWorkflowCompiler.cpp index 4292d077..0b7ba03f 100644 --- a/Import/VlppWorkflowCompiler.cpp +++ b/Import/VlppWorkflowCompiler.cpp @@ -6193,13 +6193,12 @@ ExpandNewCoroutineExpression if (sourceType->GetDecorator() == ITypeInfo::RawPtr) { - auto tdType = Ptr(new TypeDescriptorTypeInfo(sourceType->GetTypeDescriptor(), TypeInfoHint::Normal)); - auto pointerType = Ptr(new SharedPtrTypeInfo(tdType)); - auto castExpr = Ptr(new WfTypeCastingExpression); castExpr->strategy = WfTypeCastingStrategy::Strong; castExpr->expression = varDecl->expression; - castExpr->type = GetTypeFromTypeInfo(pointerType.Obj()); + auto castType = Ptr(new WfSharedPointerType); + castType->element = GetTypeFromTypeDescriptor(sourceType->GetTypeDescriptor()); + castExpr->type = castType; varDecl->expression = castExpr; } @@ -6256,17 +6255,17 @@ ExpandNewCoroutineExpression castExpr->strategy = WfTypeCastingStrategy::Strong; castExpr->expression = refSource; { - auto tdType = Ptr(new TypeDescriptorTypeInfo(sourceType->GetTypeDescriptor(), TypeInfoHint::Normal)); - auto pointerType = Ptr(new RawPtrTypeInfo(tdType)); - castExpr->type = GetTypeFromTypeInfo(pointerType.Obj()); + auto castType = Ptr(new WfRawPointerType); + castType->element = GetTypeFromTypeDescriptor(sourceType->GetTypeDescriptor()); + castExpr->type = castType; } auto inferExpr = Ptr(new WfInferExpression); inferExpr->expression = castExpr; { - auto tdType = Ptr(new TypeDescriptorTypeInfo(method->GetOwnerTypeDescriptor(), TypeInfoHint::Normal)); - auto pointerType = Ptr(new RawPtrTypeInfo(tdType)); - inferExpr->type = GetTypeFromTypeInfo(pointerType.Obj()); + auto inferType = Ptr(new WfRawPointerType); + inferType->element = GetTypeFromTypeDescriptor(method->GetOwnerTypeDescriptor()); + inferExpr->type = inferType; } memberExpr->parent = inferExpr; @@ -6317,6 +6316,7 @@ ExpandNewCoroutineExpression } } + /*********************************************************************** .\ANALYZER\WFANALYZER_EXPANDNEWCOROUTINEEXPRESSION.CPP ***********************************************************************/ @@ -10019,15 +10019,91 @@ namespace vl return type; } - Ptr CreateMapType(Ptr keyType, Ptr valueType) + Nullable GetPredefinedTypeFromSystemName(const WString& name) { - auto type = Ptr(new WfMapType); - type->writability = WfMapWritability::Writable; - type->key = keyType; - type->value = valueType; + if (name == L"Boolean") return WfPredefinedTypeName::Bool; + if (name == (sizeof(vint) == sizeof(vint64_t) ? L"Int64" : L"Int32")) return WfPredefinedTypeName::Int; + if (name == L"String") return WfPredefinedTypeName::String; + if (name == L"Object") return WfPredefinedTypeName::Object; + if (name == L"Void") return WfPredefinedTypeName::Void; + return {}; + } + + Ptr NormalizeRpcGeneratedType(Ptr type) + { + if (!type) + { + return nullptr; + } + + if (auto child = type.Cast()) + { + child->parent = NormalizeRpcGeneratedType(child->parent); + if (auto reference = child->parent.Cast()) + { + if (reference->name.value == L"system") + { + if (auto predefined = GetPredefinedTypeFromSystemName(child->name.value)) + { + return CreatePredefinedType(predefined.Value()); + } + } + } + } + else if (auto top = type.Cast()) + { + auto reference = Ptr(new WfReferenceType); + reference->name = top->name; + return reference; + } + else if (auto raw = type.Cast()) + { + raw->element = NormalizeRpcGeneratedType(raw->element); + } + else if (auto shared = type.Cast()) + { + shared->element = NormalizeRpcGeneratedType(shared->element); + } + else if (auto nullable = type.Cast()) + { + nullable->element = NormalizeRpcGeneratedType(nullable->element); + } + else if (auto enumerable = type.Cast()) + { + enumerable->element = NormalizeRpcGeneratedType(enumerable->element); + } + else if (auto map = type.Cast()) + { + if (map->key) + { + map->key = NormalizeRpcGeneratedType(map->key); + } + map->value = NormalizeRpcGeneratedType(map->value); + } + else if (auto observable = type.Cast()) + { + observable->element = NormalizeRpcGeneratedType(observable->element); + } + else if (auto function = type.Cast()) + { + function->result = NormalizeRpcGeneratedType(function->result); + for (vint i = 0; i < function->arguments.Count(); i++) + { + function->arguments[i] = NormalizeRpcGeneratedType(function->arguments[i]); + } + } return type; } +#ifndef VCZH_WORKFLOW_RPC_GENERATING_CREATE_TYPE_FROM_CPP +#define VCZH_WORKFLOW_RPC_GENERATING_CREATE_TYPE_FROM_CPP + template + Ptr CreateTypeFromCpp() + { + return NormalizeRpcGeneratedType(GetTypeFromTypeInfo(TypeInfoRetriver::CreateTypeInfo().Obj())); + } +#endif + Ptr CreateNull() { auto expression = Ptr(new WfLiteralExpression); @@ -10182,12 +10258,12 @@ namespace vl { auto constructor = CreateConstructor(); constructor->arguments.Add(CreateConstructorArgument(CreateReference(L"message"), message)); - return CreateInfer(constructor, CreateQualifiedType(L"system::RpcException")); + return CreateInfer(constructor, CreateTypeFromCpp()); } Ptr CreateRpcEventExceptionMapType() { - return CreateMapType(CreatePredefinedType(WfPredefinedTypeName::Int), CreateQualifiedType(L"system::RpcException")); + return CreateTypeFromCpp>(); } Ptr CreateNewClass(Ptr type) @@ -10412,7 +10488,7 @@ namespace vl if (IsSharedInterfaceType(typeInfo)) { auto serializable = byref - ? CreateCast(CreateQualifiedType(L"system::RpcObjectReference"), value) + ? CreateCast(CreateTypeFromCpp(), value) : value; unboxed = CreateLifecycleHelperCall(byref ? L"RpcUnboxByref" : L"RpcUnboxByval", serializable, lifecycle); return IsRpcStrongTypedCollection(typeInfo) || IsStrongTypedCollectionType(type.Obj()) @@ -10459,7 +10535,7 @@ namespace vl void AddRpcByvalReturnValue(Ptr block, Ptr value, Ptr copiedValue) { - AddStatement(block, CreateInferredVariableStatement(L"byvalReturnValue", CreateNewClass(CreateSharedType(L"system::RpcByvalReturnValue")))); + AddStatement(block, CreateInferredVariableStatement(L"byvalReturnValue", CreateNewClass(CreateTypeFromCpp>()))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(L"byvalReturnValue"), L"value"), value))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(L"byvalReturnValue"), L"slot"), CreateReference(L"_slot")))); AddStatement(block, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(L"_byvalReturnValues"), L"Set"), CreateReference(L"_slot"), copiedValue))); @@ -11041,18 +11117,18 @@ namespace vl Ptr GenerateObjectOpsFactory(const List& interfaces) { - auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectOps", CreateSharedType(L"system::IRpcObjectOps"), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - auto newOps = CreateNewInterface(CreateSharedType(L"system::IRpcObjectOps")).Cast(); - newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); + auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectOps", CreateTypeFromCpp>(), WfFunctionKind::Normal); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + auto newOps = CreateNewInterface(CreateTypeFromCpp>()).Cast(); + newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); newOps->declarations.Add(CreateVariableDeclaration(L"_slot", CreatePredefinedType(WfPredefinedTypeName::Int), CreateInt(0))); - newOps->declarations.Add(CreateVariableDeclaration(L"_byvalReturnValues", CreateMapType(CreatePredefinedType(WfPredefinedTypeName::Int), CreatePredefinedType(WfPredefinedTypeName::Object)), CreateConstructor())); + newOps->declarations.Add(CreateVariableDeclaration(L"_byvalReturnValues", CreateTypeFromCpp>(), CreateConstructor())); { auto invokeMethod = CreateFunctionDeclaration(L"InvokeMethod", CreatePredefinedType(WfPredefinedTypeName::Object), WfFunctionKind::Override); - invokeMethod->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + invokeMethod->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); invokeMethod->arguments.Add(CreateFunctionArgument(L"methodId", CreatePredefinedType(WfPredefinedTypeName::Int))); - invokeMethod->arguments.Add(CreateFunctionArgument(L"arguments", CreateSharedType(L"system::Array"))); + invokeMethod->arguments.Add(CreateFunctionArgument(L"arguments", CreateTypeFromCpp>())); auto block = invokeMethod->statement.Cast(); AddStatement(block, CreateVariableStatement(L"unknownId", CreatePredefinedType(WfPredefinedTypeName::Bool), CreateBool(false))); auto catchBlock = CreateBlock(); @@ -11074,7 +11150,7 @@ namespace vl { auto objectHold = CreateFunctionDeclaration(L"ObjectHold", CreatePredefinedType(WfPredefinedTypeName::Void), WfFunctionKind::Override); - objectHold->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + objectHold->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); objectHold->arguments.Add(CreateFunctionArgument(L"remoteClientId", CreatePredefinedType(WfPredefinedTypeName::Int))); objectHold->arguments.Add(CreateFunctionArgument(L"hold", CreatePredefinedType(WfPredefinedTypeName::Bool))); auto trueBranch = CreateBlock(); @@ -11088,7 +11164,7 @@ namespace vl { auto registerService = CreateFunctionDeclaration(L"RegisterService", CreatePredefinedType(WfPredefinedTypeName::Void), WfFunctionKind::Override); registerService->arguments.Add(CreateFunctionArgument(L"typeId", CreatePredefinedType(WfPredefinedTypeName::Int))); - registerService->arguments.Add(CreateFunctionArgument(L"service", CreateSharedType(L"system::Interface"))); + registerService->arguments.Add(CreateFunctionArgument(L"service", CreateTypeFromCpp>())); auto block = registerService->statement.Cast(); AddStatement(block, BuildRegisterService()); newOps->declarations.Add(registerService); @@ -11100,16 +11176,16 @@ namespace vl Ptr GenerateObjectEventOpsFactory(const List& interfaces) { - auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectEventOps", CreateSharedType(L"system::IRpcObjectEventOps"), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - auto newOps = CreateNewInterface(CreateSharedType(L"system::IRpcObjectEventOps")).Cast(); - newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); + auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectEventOps", CreateTypeFromCpp>(), WfFunctionKind::Normal); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + auto newOps = CreateNewInterface(CreateTypeFromCpp>()).Cast(); + newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); { auto invokeEvent = CreateFunctionDeclaration(L"InvokeEvent", CreatePredefinedType(WfPredefinedTypeName::Object), WfFunctionKind::Override); - invokeEvent->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + invokeEvent->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); invokeEvent->arguments.Add(CreateFunctionArgument(L"eventId", CreatePredefinedType(WfPredefinedTypeName::Int))); - invokeEvent->arguments.Add(CreateFunctionArgument(L"arguments", CreateSharedType(L"system::Array"))); + invokeEvent->arguments.Add(CreateFunctionArgument(L"arguments", CreateTypeFromCpp>())); auto block = invokeEvent->statement.Cast(); if (!HasRpcEvents(interfaces)) { @@ -11325,7 +11401,7 @@ namespace vl } { - auto baseType = CreateQualifiedType(L"system::IRpcWrapperBase"); + auto baseType = CreateTypeFromCpp(); interfaceDecl->baseTypes.Add(baseType); } @@ -11359,8 +11435,8 @@ namespace vl auto mangledName = MangleRpcFullName(interfaceModel.fullName); auto functionDecl = CreateFunctionDeclaration(L"rpclistener_" + mangledName, CreatePredefinedType(WfPredefinedTypeName::Void), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); functionDecl->arguments.Add(CreateFunctionArgument(L"target", CreateRawType(interfaceModel.fullName))); functionDecl->arguments.Add(CreateFunctionArgument(L"ops", CreateSharedType(opsInterfaceName))); auto block = functionDecl->statement.Cast(); @@ -11402,9 +11478,9 @@ namespace vl { auto functionDecl = CreateFunctionDeclaration(L"rpclistener_Attach", CreatePredefinedType(WfPredefinedTypeName::Void), WfFunctionKind::Normal); functionDecl->arguments.Add(CreateFunctionArgument(L"typeId", CreatePredefinedType(WfPredefinedTypeName::Int))); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); - functionDecl->arguments.Add(CreateFunctionArgument(L"obj", CreateRawType(L"system::Interface"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); + functionDecl->arguments.Add(CreateFunctionArgument(L"obj", CreateTypeFromCpp())); functionDecl->arguments.Add(CreateFunctionArgument(L"ops", CreateSharedType(opsInterfaceName))); auto block = functionDecl->statement.Cast(); @@ -11467,7 +11543,7 @@ namespace vl void AddRpcOpsFunctionArguments(Ptr functionDecl, const List& params) { - functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); for (auto&& paramModel : params) { functionDecl->arguments.Add(CreateFunctionArgument(GetRpcOpsArgumentName(paramModel), CopyType(paramModel.type.Obj()))); @@ -11478,7 +11554,7 @@ namespace vl Ptr block, const List& params) { - AddStatement(block, CreateVariableStatement(L"arguments", CreateSharedType(L"system::Array"), CreateConstructor())); + AddStatement(block, CreateVariableStatement(L"arguments", CreateTypeFromCpp>(), CreateConstructor())); AddStatement(block, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(L"arguments"), L"Resize"), CreateInt(params.Count())))); for (vint i = 0; i < params.Count(); i++) @@ -11550,7 +11626,7 @@ namespace vl AddRpcMethodExceptionRaise(block, CreateReference(L"invokeResult")); AddStatement(block, CreateInferredVariableStatement( L"byvalReturnValue", - CreateCast(CreateSharedType(L"system::RpcByvalReturnValue"), CreateReference(L"invokeResult")))); + CreateCast(CreateTypeFromCpp>(), CreateReference(L"invokeResult")))); AddStatement(block, CreateInferredVariableStatement( L"result", CreateRpcUnboxExpression( @@ -11615,9 +11691,9 @@ namespace vl Ptr GenerateRpcOpsFactory(const WString& assemblyName, const List& interfaces) { auto functionDecl = CreateFunctionDeclaration(L"rpcops_IOps_Create", CreateSharedType(GetRpcOpsInterfaceName(assemblyName)), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); auto newOps = CreateNewInterface(CreateSharedType(GetRpcOpsInterfaceName(assemblyName))).Cast(); - newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); + newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); for (auto&& interfaceModel : interfaces) { @@ -11647,14 +11723,14 @@ namespace vl CollectInterfaceEvents(interfaceModel, interfaces, events); auto functionDecl = CreateFunctionDeclaration(L"rpcwrapper_" + mangledName, CreateSharedType(wrapperInterfaceFullName), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - functionDecl->arguments.Add(CreateFunctionArgument(L"proxyRef", CreateQualifiedType(L"system::RpcObjectReference"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + functionDecl->arguments.Add(CreateFunctionArgument(L"proxyRef", CreateTypeFromCpp())); functionDecl->arguments.Add(CreateFunctionArgument(L"ops", CreateSharedType(opsInterfaceName))); auto block = functionDecl->statement.Cast(); auto proxyExpr = CreateNewInterface(CreateSharedType(wrapperInterfaceFullName)).Cast(); - proxyExpr->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); - proxyExpr->declarations.Add(CreateVariableDeclaration(L"_ref", CreateQualifiedType(L"system::RpcObjectReference"), CreateReference(L"proxyRef"))); + proxyExpr->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); + proxyExpr->declarations.Add(CreateVariableDeclaration(L"_ref", CreateTypeFromCpp(), CreateReference(L"proxyRef"))); proxyExpr->declarations.Add(CreateVariableDeclaration(L"_ops", CreateSharedType(opsInterfaceName), CreateReference(L"ops"))); for (auto propertyModel : properties) { @@ -11828,11 +11904,9 @@ namespace vl Ptr GenerateWrapperDispatcher(const List& interfaces, const WString& opsInterfaceName) { - auto returnType = Ptr(new WfSharedPointerType); - returnType->element = CreateQualifiedType(L"system::IRpcWrapperBase"); - auto functionDecl = CreateFunctionDeclaration(L"rpcwrapper_Create", returnType, WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); + auto functionDecl = CreateFunctionDeclaration(L"rpcwrapper_Create", CreateTypeFromCpp>(), WfFunctionKind::Normal); + functionDecl->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); functionDecl->arguments.Add(CreateFunctionArgument(L"ops", CreateSharedType(opsInterfaceName))); auto block = functionDecl->statement.Cast(); @@ -11889,9 +11963,9 @@ namespace vl } { - auto getIds = CreateFunctionDeclaration(L"rpc_GetIds", CreateMapType(CreatePredefinedType(WfPredefinedTypeName::String), CreatePredefinedType(WfPredefinedTypeName::Int)), WfFunctionKind::Normal); + auto getIds = CreateFunctionDeclaration(L"rpc_GetIds", CreateTypeFromCpp>(), WfFunctionKind::Normal); auto block = getIds->statement.Cast(); - AddStatement(block, CreateVariableStatement(L"result", CreateMapType(CreatePredefinedType(WfPredefinedTypeName::String), CreatePredefinedType(WfPredefinedTypeName::Int)), CreateConstructor())); + AddStatement(block, CreateVariableStatement(L"result", CreateTypeFromCpp>(), CreateConstructor())); id = 0; for (auto fullName : manager->rpcMetadata->orderedIds) { @@ -12603,7 +12677,6 @@ namespace vl Ptr CreateRawType(const WString& fullName); Ptr CreateSharedType(const WString& fullName); Ptr CreateNullableType(const WString& fullName); - Ptr CreateMapType(Ptr keyType, Ptr valueType); Ptr CreateNull(); Ptr CreateIsNull(Ptr expression); Ptr CreateIsNotNull(Ptr expression); @@ -12665,6 +12738,16 @@ namespace vl Ptr CreateRpcOpsObjectInvoke(const RpcMethodModel& methodModel, Ptr objectOps); Ptr CreateRpcOpsObjectInvoke(const RpcMethodModel& methodModel); Ptr CreateRpcOpsObjectEventInvoke(const RpcEventModel& eventModel); + Ptr NormalizeRpcGeneratedType(Ptr type); + +#ifndef VCZH_WORKFLOW_RPC_GENERATING_CREATE_TYPE_FROM_CPP +#define VCZH_WORKFLOW_RPC_GENERATING_CREATE_TYPE_FROM_CPP + template + Ptr CreateTypeFromCpp() + { + return NormalizeRpcGeneratedType(GetTypeFromTypeInfo(TypeInfoRetriver::CreateTypeInfo().Obj())); + } +#endif enum class RpcJsonPrimitiveKind { @@ -12815,22 +12898,6 @@ namespace vl return statement; } - Ptr CreateRpcJsonSerializeCallbackType() - { - auto type = Ptr(new WfFunctionType); - type->result = CreateSharedType(L"system::JsonNode"); - type->arguments.Add(CreatePredefinedType(WfPredefinedTypeName::Object)); - return type; - } - - Ptr CreateRpcJsonDeserializeCallbackType() - { - auto type = Ptr(new WfFunctionType); - type->result = CreatePredefinedType(WfPredefinedTypeName::Object); - type->arguments.Add(CreateSharedType(L"system::JsonNode")); - return type; - } - WString GetRpcJsonSerializeEnumFunctionName(const WString& fullName) { return L"rpcjson_Serialize_Enum_" + MangleRpcFullName(fullName); @@ -12961,7 +13028,7 @@ namespace vl WString AddRpcJsonLiteral(Ptr block, RpcJsonGenerationContext& context, const WString& literal) { auto nodeName = AllocateRpcJsonTemp(context, L"jsonLiteral"); - AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateSharedType(L"system::JsonLiteral")))); + AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateTypeFromCpp>()))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(nodeName), L"value"), CreateQualifiedExpression(L"system::JsonLiteralValue::" + literal)))); return nodeName; } @@ -12969,7 +13036,7 @@ namespace vl WString AddRpcJsonBooleanLiteral(Ptr block, RpcJsonGenerationContext& context, Ptr value) { auto nodeName = AllocateRpcJsonTemp(context, L"jsonLiteral"); - AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateSharedType(L"system::JsonLiteral")))); + AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateTypeFromCpp>()))); AddStatement(block, CreateIf( value, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(nodeName), L"value"), CreateQualifiedExpression(L"system::JsonLiteralValue::True"))), @@ -12980,7 +13047,7 @@ namespace vl WString AddRpcJsonString(Ptr block, RpcJsonGenerationContext& context, Ptr value) { auto nodeName = AllocateRpcJsonTemp(context, L"jsonString"); - AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateSharedType(L"system::JsonString")))); + AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateTypeFromCpp>()))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(nodeName), L"content"), CreateRpcJsonToken(value)))); return nodeName; } @@ -12988,7 +13055,7 @@ namespace vl WString AddRpcJsonNumber(Ptr block, RpcJsonGenerationContext& context, Ptr value) { auto nodeName = AllocateRpcJsonTemp(context, L"jsonNumber"); - AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateSharedType(L"system::JsonNumber")))); + AddStatement(block, CreateInferredVariableStatement(nodeName, CreateNewClass(CreateTypeFromCpp>()))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(nodeName), L"content"), CreateRpcJsonToken(value)))); return nodeName; } @@ -13001,7 +13068,7 @@ namespace vl void AddRpcJsonObjectField(Ptr block, RpcJsonGenerationContext& context, Ptr object, const WString& fieldName, Ptr value) { auto fieldVar = AllocateRpcJsonTemp(context, L"jsonField"); - AddStatement(block, CreateInferredVariableStatement(fieldVar, CreateNewClass(CreateSharedType(L"system::JsonObjectField")))); + AddStatement(block, CreateInferredVariableStatement(fieldVar, CreateNewClass(CreateTypeFromCpp>()))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(fieldVar), L"name"), CreateRpcJsonToken(CreateString(fieldName))))); AddStatement(block, CreateExpressionStatement(CreateAssign(CreateMember(CreateReference(fieldVar), L"value"), value))); AddStatement(block, CreateExpressionStatement(CreateCall(CreateMember(CreateMember(object, L"fields"), L"Add"), CreateReference(fieldVar)))); @@ -13011,7 +13078,7 @@ namespace vl { auto resultVar = AllocateRpcJsonTemp(context, L"jsonFieldValue"); auto fieldVar = AllocateRpcJsonTemp(context, L"jsonField"); - AddStatement(block, CreateVariableStatement(resultVar, CreateSharedType(L"system::JsonNode"), CreateNull())); + AddStatement(block, CreateVariableStatement(resultVar, CreateTypeFromCpp>(), CreateNull())); auto forBlock = CreateBlock(); AddStatement(forBlock, CreateIf( CreateBinary(WfBinaryOperator::EQ, CreateMember(CreateMember(CreateReference(fieldVar), L"name"), L"value"), CreateString(fieldName)), @@ -13072,7 +13139,7 @@ namespace vl if (auto nullable = dynamic_cast(type)) { auto resultName = AllocateRpcJsonTemp(context, L"jsonNode"); - AddStatement(block, CreateVariableStatement(resultName, CreateSharedType(L"system::JsonNode"), CreateNull())); + AddStatement(block, CreateVariableStatement(resultName, CreateTypeFromCpp>(), CreateNull())); auto nullBranch = CreateBlock(); auto nullNode = AddRpcJsonLiteral(nullBranch, context, L"Null"); AddStatement(nullBranch, CreateExpressionStatement(CreateAssign(CreateReference(resultName), CreateReference(nullNode)))); @@ -13128,7 +13195,7 @@ namespace vl if (auto enumerable = dynamic_cast(type)) { auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateTypeFromCpp>()))); auto forBlock = CreateBlock(); auto itemNode = AddKnownRpcJsonSerializeValue(context, forBlock, CreateReference(L"item"), enumerable->element.Obj()); AddRpcJsonArrayItem(forBlock, arrayName, CreateReference(itemNode)); @@ -13139,12 +13206,12 @@ namespace vl if (auto map = dynamic_cast(type)) { auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateTypeFromCpp>()))); auto forBlock = CreateBlock(); if (map->key) { auto pairName = AllocateRpcJsonTemp(context, L"jsonArray"); - AddStatement(forBlock, CreateInferredVariableStatement(pairName, CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(forBlock, CreateInferredVariableStatement(pairName, CreateNewClass(CreateTypeFromCpp>()))); auto keyNode = AddKnownRpcJsonSerializeValue(context, forBlock, CreateReference(L"key"), map->key.Obj()); AddRpcJsonArrayItem(forBlock, pairName, CreateReference(keyNode)); auto valueNode = AddKnownRpcJsonSerializeValue(context, forBlock, CreateIndex(value, CreateReference(L"key")), map->value.Obj()); @@ -13164,7 +13231,7 @@ namespace vl if (auto observable = dynamic_cast(type)) { auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateTypeFromCpp>()))); auto forBlock = CreateBlock(); auto itemNode = AddKnownRpcJsonSerializeValue(context, forBlock, CreateReference(L"item"), observable->element.Obj()); AddRpcJsonArrayItem(forBlock, arrayName, CreateReference(itemNode)); @@ -13182,7 +13249,7 @@ namespace vl auto resultName = AllocateRpcJsonTemp(context, L"jsonValue"); auto literalName = AllocateRpcJsonTemp(context, L"jsonLiteral"); AddStatement(block, CreateVariableStatement(resultName, CopyType(type), CreateNull())); - AddStatement(block, CreateInferredVariableStatement(literalName, CreateWeakCast(CreateSharedType(L"system::JsonLiteral"), CopyExpression(node, true)))); + AddStatement(block, CreateInferredVariableStatement(literalName, CreateWeakCast(CreateTypeFromCpp>(), CopyExpression(node, true)))); auto assignBranch = CreateBlock(); auto valueName = AddKnownRpcJsonDeserializeValue(context, assignBranch, node, nullable->element.Obj()); AddStatement(assignBranch, CreateExpressionStatement(CreateAssign(CreateReference(resultName), CreateReference(valueName)))); @@ -13241,7 +13308,7 @@ namespace vl auto resultName = AllocateRpcJsonTemp(context, L"jsonValue"); auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); AddStatement(block, CreateVariableStatement(resultName, CreateWritableRpcJsonCollectionType(type), CreateConstructor())); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateCast(CreateSharedType(L"system::JsonArray"), node))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateCast(CreateTypeFromCpp>(), node))); auto forBlock = CreateBlock(); auto itemValue = AddKnownRpcJsonDeserializeValue(context, forBlock, CreateReference(L"item"), enumerable->element.Obj()); AddStatement(forBlock, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(resultName), L"Add"), CreateReference(itemValue)))); @@ -13254,12 +13321,12 @@ namespace vl auto resultName = AllocateRpcJsonTemp(context, L"jsonValue"); auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); AddStatement(block, CreateVariableStatement(resultName, CreateWritableRpcJsonCollectionType(type), CreateConstructor())); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateCast(CreateSharedType(L"system::JsonArray"), node))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateCast(CreateTypeFromCpp>(), node))); auto forBlock = CreateBlock(); if (map->key) { auto pairName = AllocateRpcJsonTemp(context, L"jsonArray"); - AddStatement(forBlock, CreateInferredVariableStatement(pairName, CreateCast(CreateSharedType(L"system::JsonArray"), CreateReference(L"item")))); + AddStatement(forBlock, CreateInferredVariableStatement(pairName, CreateCast(CreateTypeFromCpp>(), CreateReference(L"item")))); auto keyValue = AddKnownRpcJsonDeserializeValue(context, forBlock, CreateJsonArrayItem(pairName, 0), map->key.Obj()); auto itemValue = AddKnownRpcJsonDeserializeValue(context, forBlock, CreateJsonArrayItem(pairName, 1), map->value.Obj()); AddStatement(forBlock, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(resultName), L"Set"), CreateReference(keyValue), CreateReference(itemValue)))); @@ -13278,7 +13345,7 @@ namespace vl auto resultName = AllocateRpcJsonTemp(context, L"jsonValue"); auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); AddStatement(block, CreateVariableStatement(resultName, CreateWritableRpcJsonCollectionType(type), CreateConstructor())); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateCast(CreateSharedType(L"system::JsonArray"), node))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateCast(CreateTypeFromCpp>(), node))); auto forBlock = CreateBlock(); auto itemValue = AddKnownRpcJsonDeserializeValue(context, forBlock, CreateReference(L"item"), observable->element.Obj()); AddStatement(forBlock, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(resultName), L"Add"), CreateReference(itemValue)))); @@ -13295,7 +13362,7 @@ namespace vl { auto arrayName = AllocateRpcJsonTemp(context, L"jsonArray"); auto keywordNode = AddRpcJsonString(block, context, CreateString(keyword)); - AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(block, CreateInferredVariableStatement(arrayName, CreateNewClass(CreateTypeFromCpp>()))); AddRpcJsonArrayItem(block, arrayName, CreateReference(keywordNode)); AddRpcJsonArrayItem(block, arrayName, valueNode); AddStatement(block, CreateReturn(CreateReference(arrayName))); @@ -13303,12 +13370,12 @@ namespace vl Ptr GenerateRpcJsonSerializeEnum(RpcJsonGenerationContext& context, const RpcJsonTypeModel& enumModel) { - auto functionDecl = CreateFunctionDeclaration(GetRpcJsonSerializeEnumFunctionName(enumModel.fullName), CreateSharedType(L"system::JsonNode"), WfFunctionKind::Normal); + auto functionDecl = CreateFunctionDeclaration(GetRpcJsonSerializeEnumFunctionName(enumModel.fullName), CreateTypeFromCpp>(), WfFunctionKind::Normal); functionDecl->arguments.Add(CreateFunctionArgument(L"value", CopyType(enumModel.type.Obj()))); auto block = functionDecl->statement.Cast(); auto nodeName = AddRpcJsonNumber(block, context, CreateCast( CreatePredefinedType(WfPredefinedTypeName::String), - CreateCast(CreateQualifiedType(L"system::UInt64"), CreateReference(L"value")))); + CreateCast(CreateTypeFromCpp(), CreateReference(L"value")))); AddStatement(block, CreateReturn(CreateReference(nodeName))); return functionDecl; } @@ -13316,11 +13383,11 @@ namespace vl Ptr GenerateRpcJsonDeserializeEnum(RpcJsonGenerationContext& context, const RpcJsonTypeModel& enumModel) { auto functionDecl = CreateFunctionDeclaration(GetRpcJsonDeserializeEnumFunctionName(enumModel.fullName), CopyType(enumModel.type.Obj()), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"node", CreateSharedType(L"system::JsonNode"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"node", CreateTypeFromCpp>())); auto block = functionDecl->statement.Cast(); AddStatement(block, CreateReturn(CreateCast( CopyType(enumModel.type.Obj()), - CreateCast(CreateQualifiedType(L"system::UInt64"), CreateMember(CreateMember(CreateCast(CreateSharedType(L"system::JsonNumber"), CreateReference(L"node")), L"content"), L"value"))))); + CreateCast(CreateTypeFromCpp(), CreateMember(CreateMember(CreateCast(CreateTypeFromCpp>(), CreateReference(L"node")), L"content"), L"value"))))); return functionDecl; } @@ -13335,10 +13402,10 @@ namespace vl Ptr GenerateRpcJsonSerializeStruct(RpcJsonGenerationContext& context, const RpcJsonTypeModel& structModel) { - auto functionDecl = CreateFunctionDeclaration(GetRpcJsonSerializeStructFunctionName(structModel.fullName), CreateSharedType(L"system::JsonNode"), WfFunctionKind::Normal); + auto functionDecl = CreateFunctionDeclaration(GetRpcJsonSerializeStructFunctionName(structModel.fullName), CreateTypeFromCpp>(), WfFunctionKind::Normal); functionDecl->arguments.Add(CreateFunctionArgument(L"value", CopyType(structModel.type.Obj()))); auto block = functionDecl->statement.Cast(); - AddStatement(block, CreateInferredVariableStatement(L"object", CreateNewClass(CreateSharedType(L"system::JsonObject")))); + AddStatement(block, CreateInferredVariableStatement(L"object", CreateNewClass(CreateTypeFromCpp>()))); AddRpcJsonStructFields(context, block, structModel, L"object", L"value"); AddStatement(block, CreateReturn(CreateReference(L"object"))); return functionDecl; @@ -13347,9 +13414,9 @@ namespace vl Ptr GenerateRpcJsonDeserializeStruct(RpcJsonGenerationContext& context, const RpcJsonTypeModel& structModel) { auto functionDecl = CreateFunctionDeclaration(GetRpcJsonDeserializeStructFunctionName(structModel.fullName), CopyType(structModel.type.Obj()), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"node", CreateSharedType(L"system::JsonNode"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"node", CreateTypeFromCpp>())); auto block = functionDecl->statement.Cast(); - AddStatement(block, CreateInferredVariableStatement(L"object", CreateCast(CreateSharedType(L"system::JsonObject"), CreateReference(L"node")))); + AddStatement(block, CreateInferredVariableStatement(L"object", CreateCast(CreateTypeFromCpp>(), CreateReference(L"node")))); auto constructor = CreateConstructor(); for (auto&& field : *structModel.fields.Obj()) { @@ -13365,10 +13432,10 @@ namespace vl { AddStatement(block, CreateInferredVariableStatement(varName, CreateWeakCast(CreateSharedType(typeFullName), CreateReference(L"value")))); auto trueBranch = CreateBlock(); - AddStatement(trueBranch, CreateInferredVariableStatement(L"object", CreateNewClass(CreateSharedType(L"system::JsonObject")))); + AddStatement(trueBranch, CreateInferredVariableStatement(L"object", CreateNewClass(CreateTypeFromCpp>()))); auto keywordNode = AddRpcJsonString(trueBranch, context, CreateString(keyword)); AddRpcJsonObjectField(trueBranch, context, CreateReference(L"object"), L"$", CreateReference(keywordNode)); - AddStatement(trueBranch, CreateInferredVariableStatement(L"values", CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(trueBranch, CreateInferredVariableStatement(L"values", CreateNewClass(CreateTypeFromCpp>()))); auto forBlock = CreateBlock(); AddRpcJsonArrayItem(forBlock, L"values", CreateCall(CreateReference(L"rpcjson_Serialize"), CreateReference(L"item"))); AddStatement(trueBranch, CreateForEach(L"item", CreateReference(varName), forBlock)); @@ -13381,10 +13448,10 @@ namespace vl { AddStatement(block, CreateInferredVariableStatement(varName, CreateWeakCast(CreateSharedType(typeFullName), CreateReference(L"value")))); auto trueBranch = CreateBlock(); - AddStatement(trueBranch, CreateInferredVariableStatement(L"object", CreateNewClass(CreateSharedType(L"system::JsonObject")))); + AddStatement(trueBranch, CreateInferredVariableStatement(L"object", CreateNewClass(CreateTypeFromCpp>()))); auto keywordNode = AddRpcJsonString(trueBranch, context, CreateString(L"map")); AddRpcJsonObjectField(trueBranch, context, CreateReference(L"object"), L"$", CreateReference(keywordNode)); - AddStatement(trueBranch, CreateInferredVariableStatement(L"values", CreateNewClass(CreateSharedType(L"system::JsonArray")))); + AddStatement(trueBranch, CreateInferredVariableStatement(L"values", CreateNewClass(CreateTypeFromCpp>()))); auto forBlock = CreateBlock(); AddRpcJsonArrayItem(forBlock, L"values", CreateCall(CreateReference(L"rpcjson_Serialize"), CreateReference(L"key"))); AddRpcJsonArrayItem(forBlock, L"values", CreateCall(CreateReference(L"rpcjson_Serialize"), CreateCall(CreateMember(CreateReference(varName), L"Get"), CreateReference(L"key")))); @@ -13442,7 +13509,7 @@ namespace vl nullableType->element = CopyType(structModel.type.Obj()); AddStatement(block, CreateInferredVariableStatement(varName, CreateWeakCast(nullableType, CreateReference(L"value")))); auto trueBranch = CreateBlock(); - AddStatement(trueBranch, CreateInferredVariableStatement(L"object", CreateNewClass(CreateSharedType(L"system::JsonObject")))); + AddStatement(trueBranch, CreateInferredVariableStatement(L"object", CreateNewClass(CreateTypeFromCpp>()))); auto keywordNode = AddRpcJsonString(trueBranch, context, CreateString(structModel.fullName)); AddRpcJsonObjectField(trueBranch, context, CreateReference(L"object"), L"$", CreateReference(keywordNode)); auto varValueName = AllocateRpcJsonTemp(context, L"value"); @@ -13454,7 +13521,7 @@ namespace vl Ptr GenerateRpcJsonSerialize(RpcJsonGenerationContext& context) { - auto functionDecl = CreateFunctionDeclaration(L"rpcjson_Serialize", CreateSharedType(L"system::JsonNode"), WfFunctionKind::Normal); + auto functionDecl = CreateFunctionDeclaration(L"rpcjson_Serialize", CreateTypeFromCpp>(), WfFunctionKind::Normal); functionDecl->arguments.Add(CreateFunctionArgument(L"value", CreatePredefinedType(WfPredefinedTypeName::Object))); auto block = functionDecl->statement.Cast(); for (auto&& enumModel : *context.enums) @@ -13507,7 +13574,7 @@ namespace vl auto block = CreateBlock(); AddStatement(block, CreateVariableStatement(L"result", CreateSharedType(variableType), CreateConstructor())); auto valuesNode = AddRpcJsonObjectFieldLookup(block, context, CreateReference(L"object"), L"values"); - AddStatement(block, CreateInferredVariableStatement(L"values", CreateCast(CreateSharedType(L"system::JsonArray"), CreateReference(valuesNode)))); + AddStatement(block, CreateInferredVariableStatement(L"values", CreateCast(CreateTypeFromCpp>(), CreateReference(valuesNode)))); auto forBlock = CreateBlock(); AddStatement(forBlock, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(L"result"), L"Add"), CreateCall(CreateReference(L"rpcjson_Deserialize"), CreateReference(L"item"))))); AddStatement(block, CreateForEach(L"item", CreateMember(CreateReference(L"values"), L"items"), forBlock)); @@ -13518,9 +13585,9 @@ namespace vl Ptr CreateUnknownDeserializeMapCase(RpcJsonGenerationContext& context) { auto block = CreateBlock(); - AddStatement(block, CreateVariableStatement(L"result", CreateSharedType(L"system::Dictionary"), CreateConstructor())); + AddStatement(block, CreateVariableStatement(L"result", CreateTypeFromCpp>(), CreateConstructor())); auto valuesNode = AddRpcJsonObjectFieldLookup(block, context, CreateReference(L"object"), L"values"); - AddStatement(block, CreateInferredVariableStatement(L"values", CreateCast(CreateSharedType(L"system::JsonArray"), CreateReference(valuesNode)))); + AddStatement(block, CreateInferredVariableStatement(L"values", CreateCast(CreateTypeFromCpp>(), CreateReference(valuesNode)))); AddStatement(block, CreateInferredVariableStatement(L"index", CreateInt(0))); auto whileBlock = CreateBlock(); AddStatement(whileBlock, CreateExpressionStatement(CreateCall( @@ -13536,12 +13603,12 @@ namespace vl Ptr GenerateRpcJsonDeserialize(RpcJsonGenerationContext& context) { auto functionDecl = CreateFunctionDeclaration(L"rpcjson_Deserialize", CreatePredefinedType(WfPredefinedTypeName::Object), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"node", CreateSharedType(L"system::JsonNode"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"node", CreateTypeFromCpp>())); auto block = functionDecl->statement.Cast(); if (context.enums->Count() > 0) { - AddStatement(block, CreateInferredVariableStatement(L"array", CreateWeakCast(CreateSharedType(L"system::JsonArray"), CreateReference(L"node")))); + AddStatement(block, CreateInferredVariableStatement(L"array", CreateWeakCast(CreateTypeFromCpp>(), CreateReference(L"node")))); auto arrayBranch = CreateBlock(); AddStatement(arrayBranch, CreateInferredVariableStatement(L"keyword", CreateJsonArrayItemContent(L"array", 0, L"system::JsonString"))); auto arraySwitch = Ptr(new WfSwitchStatement); @@ -13554,12 +13621,12 @@ namespace vl AddStatement(block, CreateIf(CreateIsNotNull(CreateReference(L"array")), arrayBranch)); } - AddStatement(block, CreateInferredVariableStatement(L"object", CreateWeakCast(CreateSharedType(L"system::JsonObject"), CreateReference(L"node")))); + AddStatement(block, CreateInferredVariableStatement(L"object", CreateWeakCast(CreateTypeFromCpp>(), CreateReference(L"node")))); auto objectBranch = CreateBlock(); { auto keywordNode = AddRpcJsonObjectFieldLookup(objectBranch, context, CreateReference(L"object"), L"$", false); auto keywordBranch = CreateBlock(); - AddStatement(keywordBranch, CreateInferredVariableStatement(L"keyword", CreateMember(CreateMember(CreateCast(CreateSharedType(L"system::JsonString"), CreateReference(keywordNode)), L"content"), L"value"))); + AddStatement(keywordBranch, CreateInferredVariableStatement(L"keyword", CreateMember(CreateMember(CreateCast(CreateTypeFromCpp>(), CreateReference(keywordNode)), L"content"), L"value"))); auto objectSwitch = Ptr(new WfSwitchStatement); objectSwitch->expression = CreateReference(L"keyword"); for (auto&& structModel : *context.structs) @@ -13607,8 +13674,8 @@ namespace vl Ptr GenerateRpcSerializerFactoryJson() { - auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcSerializer", CreateSharedType(L"system::IRpcSerializer"), WfFunctionKind::Normal); - auto newSerializer = CreateNewInterface(CreateSharedType(L"system::IRpcSerializer")).Cast(); + auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcSerializer", CreateTypeFromCpp>(), WfFunctionKind::Normal); + auto newSerializer = CreateNewInterface(CreateTypeFromCpp>()).Cast(); { auto serialize = CreateFunctionDeclaration(L"Serialize", CreatePredefinedType(WfPredefinedTypeName::Object), WfFunctionKind::Override); @@ -13625,7 +13692,7 @@ namespace vl auto block = deserialize->statement.Cast(); AddStatement(block, CreateReturn(CreateCall( CreateReference(L"rpcjson_Deserialize"), - CreateCast(CreateSharedType(L"system::JsonNode"), CreateReference(L"value"))))); + CreateCast(CreateTypeFromCpp>(), CreateReference(L"value"))))); newSerializer->declarations.Add(deserialize); } @@ -13678,7 +13745,7 @@ namespace vl if (IsSharedInterfaceType(typeInfo)) { return byref - ? CreateQualifiedType(L"system::RpcObjectReference") + ? CreateTypeFromCpp() : CreatePredefinedType(WfPredefinedTypeName::Object); } return CopyType(type); @@ -13728,7 +13795,7 @@ namespace vl manager, tempIndex, block, - CreateCast(CreateSharedType(L"system::JsonNode"), CreateRpcJsonSerializedArgument(i)), + CreateCast(CreateTypeFromCpp>(), CreateRpcJsonSerializedArgument(i)), paramModel)); } @@ -13785,7 +13852,7 @@ namespace vl manager, tempIndex, block, - CreateCast(CreateSharedType(L"system::JsonNode"), CreateRpcJsonSerializedArgument(i)), + CreateCast(CreateTypeFromCpp>(), CreateRpcJsonSerializedArgument(i)), paramModel)); } AddStatement(block, CreateExpressionStatement(invoke)); @@ -13829,18 +13896,18 @@ namespace vl Ptr GenerateObjectOpsFactoryJson(WfLexicalScopeManager* manager, const List& interfaces) { - auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectOpsJson", CreateSharedType(L"system::IRpcObjectOps"), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - auto newOps = CreateNewInterface(CreateSharedType(L"system::IRpcObjectOps")).Cast(); - newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); + auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectOpsJson", CreateTypeFromCpp>(), WfFunctionKind::Normal); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + auto newOps = CreateNewInterface(CreateTypeFromCpp>()).Cast(); + newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); newOps->declarations.Add(CreateVariableDeclaration(L"_slot", CreatePredefinedType(WfPredefinedTypeName::Int), CreateInt(0))); - newOps->declarations.Add(CreateVariableDeclaration(L"_byvalReturnValues", CreateMapType(CreatePredefinedType(WfPredefinedTypeName::Int), CreatePredefinedType(WfPredefinedTypeName::Object)), CreateConstructor())); + newOps->declarations.Add(CreateVariableDeclaration(L"_byvalReturnValues", CreateTypeFromCpp>(), CreateConstructor())); { auto invokeMethod = CreateFunctionDeclaration(L"InvokeMethod", CreatePredefinedType(WfPredefinedTypeName::Object), WfFunctionKind::Override); - invokeMethod->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + invokeMethod->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); invokeMethod->arguments.Add(CreateFunctionArgument(L"methodId", CreatePredefinedType(WfPredefinedTypeName::Int))); - invokeMethod->arguments.Add(CreateFunctionArgument(L"arguments", CreateSharedType(L"system::Array"))); + invokeMethod->arguments.Add(CreateFunctionArgument(L"arguments", CreateTypeFromCpp>())); auto block = invokeMethod->statement.Cast(); AddStatement(block, CreateVariableStatement(L"unknownId", CreatePredefinedType(WfPredefinedTypeName::Bool), CreateBool(false))); auto catchBlock = CreateBlock(); @@ -13862,7 +13929,7 @@ namespace vl { auto objectHold = CreateFunctionDeclaration(L"ObjectHold", CreatePredefinedType(WfPredefinedTypeName::Void), WfFunctionKind::Override); - objectHold->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + objectHold->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); objectHold->arguments.Add(CreateFunctionArgument(L"remoteClientId", CreatePredefinedType(WfPredefinedTypeName::Int))); objectHold->arguments.Add(CreateFunctionArgument(L"hold", CreatePredefinedType(WfPredefinedTypeName::Bool))); auto trueBranch = CreateBlock(); @@ -13876,7 +13943,7 @@ namespace vl { auto registerService = CreateFunctionDeclaration(L"RegisterService", CreatePredefinedType(WfPredefinedTypeName::Void), WfFunctionKind::Override); registerService->arguments.Add(CreateFunctionArgument(L"typeId", CreatePredefinedType(WfPredefinedTypeName::Int))); - registerService->arguments.Add(CreateFunctionArgument(L"service", CreateSharedType(L"system::Interface"))); + registerService->arguments.Add(CreateFunctionArgument(L"service", CreateTypeFromCpp>())); auto block = registerService->statement.Cast(); AddStatement(block, BuildRegisterService()); newOps->declarations.Add(registerService); @@ -13888,16 +13955,16 @@ namespace vl Ptr GenerateObjectEventOpsFactoryJson(WfLexicalScopeManager* manager, const List& interfaces) { - auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectEventOpsJson", CreateSharedType(L"system::IRpcObjectEventOps"), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); - auto newOps = CreateNewInterface(CreateSharedType(L"system::IRpcObjectEventOps")).Cast(); - newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); + auto functionDecl = CreateFunctionDeclaration(L"rpcops_IRpcObjectEventOpsJson", CreateTypeFromCpp>(), WfFunctionKind::Normal); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); + auto newOps = CreateNewInterface(CreateTypeFromCpp>()).Cast(); + newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); { auto invokeEvent = CreateFunctionDeclaration(L"InvokeEvent", CreatePredefinedType(WfPredefinedTypeName::Object), WfFunctionKind::Override); - invokeEvent->arguments.Add(CreateFunctionArgument(L"ref", CreateQualifiedType(L"system::RpcObjectReference"))); + invokeEvent->arguments.Add(CreateFunctionArgument(L"ref", CreateTypeFromCpp())); invokeEvent->arguments.Add(CreateFunctionArgument(L"eventId", CreatePredefinedType(WfPredefinedTypeName::Int))); - invokeEvent->arguments.Add(CreateFunctionArgument(L"arguments", CreateSharedType(L"system::Array"))); + invokeEvent->arguments.Add(CreateFunctionArgument(L"arguments", CreateTypeFromCpp>())); auto block = invokeEvent->statement.Cast(); if (!HasRpcEvents(interfaces)) { @@ -13941,7 +14008,7 @@ namespace vl const List& params, vint& tempIndex) { - AddStatement(block, CreateVariableStatement(L"arguments", CreateSharedType(L"system::Array"), CreateConstructor())); + AddStatement(block, CreateVariableStatement(L"arguments", CreateTypeFromCpp>(), CreateConstructor())); AddStatement(block, CreateExpressionStatement(CreateCall(CreateMember(CreateReference(L"arguments"), L"Resize"), CreateInt(params.Count())))); for (vint i = 0; i < params.Count(); i++) @@ -13969,7 +14036,7 @@ namespace vl AddStatement(block, CreateInferredVariableStatement(L"invokeResult", invoke)); AddStatement(block, CreateInferredVariableStatement( L"jsonResult", - CreateCast(CreateSharedType(L"system::JsonNode"), CreateReference(L"invokeResult")))); + CreateCast(CreateTypeFromCpp>(), CreateReference(L"invokeResult")))); AddStatement(block, CreateInferredVariableStatement( L"methodResult", CreateCall(CreateReference(L"rpcjson_Deserialize"), CreateReference(L"jsonResult")))); @@ -13982,16 +14049,16 @@ namespace vl AddStatement(block, CreateInferredVariableStatement(L"invokeResult", invoke)); AddStatement(block, CreateInferredVariableStatement( L"byvalReturnValue", - CreateWeakCast(CreateSharedType(L"system::RpcByvalReturnValue"), CreateReference(L"invokeResult")))); - AddStatement(block, CreateVariableStatement(L"jsonResult", CreateSharedType(L"system::JsonNode"), CreateNull())); + CreateWeakCast(CreateTypeFromCpp>(), CreateReference(L"invokeResult")))); + AddStatement(block, CreateVariableStatement(L"jsonResult", CreateTypeFromCpp>(), CreateNull())); auto exceptionBranch = CreateBlock(); AddStatement(exceptionBranch, CreateExpressionStatement(CreateAssign( CreateReference(L"jsonResult"), - CreateCast(CreateSharedType(L"system::JsonNode"), CreateReference(L"invokeResult"))))); + CreateCast(CreateTypeFromCpp>(), CreateReference(L"invokeResult"))))); auto returnBranch = CreateBlock(); AddStatement(returnBranch, CreateExpressionStatement(CreateAssign( CreateReference(L"jsonResult"), - CreateCast(CreateSharedType(L"system::JsonNode"), CreateMember(CreateReference(L"byvalReturnValue"), L"value"))))); + CreateCast(CreateTypeFromCpp>(), CreateMember(CreateReference(L"byvalReturnValue"), L"value"))))); AddStatement(block, CreateIf(CreateIsNull(CreateReference(L"byvalReturnValue")), exceptionBranch, returnBranch)); AddStatement(block, CreateInferredVariableStatement( L"methodResult", @@ -13999,7 +14066,7 @@ namespace vl AddRpcMethodExceptionRaise(block, CreateReference(L"methodResult")); AddStatement(block, CreateInferredVariableStatement( L"strongByvalReturnValue", - CreateCast(CreateSharedType(L"system::RpcByvalReturnValue"), CreateReference(L"byvalReturnValue")))); + CreateCast(CreateTypeFromCpp>(), CreateReference(L"byvalReturnValue")))); auto transferType = CreateRpcJsonTransferType(methodModel.returnTypeInfo, methodModel.returnByref, methodModel.returnType.Obj()); auto valueName = AddRpcJsonDeserializeValue(manager, tempIndex, block, CreateReference(L"jsonResult"), transferType.Obj()); AddStatement(block, CreateInferredVariableStatement( @@ -14016,7 +14083,7 @@ namespace vl { auto invoke = CreateRpcOpsObjectInvoke(methodModel); AddStatement(block, CreateInferredVariableStatement(L"invokeResult", invoke)); - AddStatement(block, CreateInferredVariableStatement(L"jsonResult", CreateCast(CreateSharedType(L"system::JsonNode"), CreateReference(L"invokeResult")))); + AddStatement(block, CreateInferredVariableStatement(L"jsonResult", CreateCast(CreateTypeFromCpp>(), CreateReference(L"invokeResult")))); AddStatement(block, CreateInferredVariableStatement( L"methodResult", CreateCall(CreateReference(L"rpcjson_Deserialize"), CreateReference(L"jsonResult")))); @@ -14047,7 +14114,7 @@ namespace vl AddStatement(block, CreateInferredVariableStatement(L"invokeResult", CreateRpcOpsObjectEventInvoke(eventModel))); AddStatement(block, CreateInferredVariableStatement( L"jsonResult", - CreateCast(CreateSharedType(L"system::JsonNode"), CreateReference(L"invokeResult")))); + CreateCast(CreateTypeFromCpp>(), CreateReference(L"invokeResult")))); AddStatement(block, CreateInferredVariableStatement( L"eventResult", CreateCall(CreateReference(L"rpcjson_Deserialize"), CreateReference(L"jsonResult")))); @@ -14058,9 +14125,9 @@ namespace vl Ptr GenerateRpcOpsFactoryJson(WfLexicalScopeManager* manager, const WString& assemblyName, const List& interfaces) { auto functionDecl = CreateFunctionDeclaration(L"rpcops_IOps_CreateJson", CreateSharedType(GetRpcOpsInterfaceName(assemblyName)), WfFunctionKind::Normal); - functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateRawType(L"system::IRpcLifecycle"))); + functionDecl->arguments.Add(CreateFunctionArgument(L"lc", CreateTypeFromCpp())); auto newOps = CreateNewInterface(CreateSharedType(GetRpcOpsInterfaceName(assemblyName))).Cast(); - newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateRawType(L"system::IRpcLifecycle"), CreateReference(L"lc"))); + newOps->declarations.Add(CreateVariableDeclaration(L"_lc", CreateTypeFromCpp(), CreateReference(L"lc"))); for (auto&& interfaceModel : interfaces) { @@ -14511,6 +14578,35 @@ GetExpressionFromTypeDescriptor return parentExpr; } +/*********************************************************************** +GetTypeFromTypeDescriptor +***********************************************************************/ + + Ptr GetTypeFromTypeDescriptor(reflection::description::ITypeDescriptor* typeDescriptor) + { + List fragments; + GetTypeFragments(typeDescriptor, fragments); + + Ptr parentType; + for (auto fragment : fragments) + { + if (!parentType) + { + auto type = Ptr(new WfTopQualifiedType); + type->name.value = fragment; + parentType = type; + } + else + { + auto type = Ptr(new WfChildType); + type->parent = parentType; + type->name.value = fragment; + parentType = type; + } + } + return parentType; + } + /*********************************************************************** GetTypeFromTypeInfo ***********************************************************************/ @@ -14561,27 +14657,7 @@ GetTypeFromTypeInfo } case ITypeInfo::TypeDescriptor: { - List fragments; - GetTypeFragments(typeInfo->GetTypeDescriptor(), fragments); - - Ptr parentType; - for (auto fragment : fragments) - { - if (!parentType) - { - auto type = Ptr(new WfTopQualifiedType); - type->name.value = fragment; - parentType = type; - } - else - { - auto type = Ptr(new WfChildType); - type->parent = parentType; - type->name.value = fragment; - parentType = type; - } - } - return parentType; + return GetTypeFromTypeDescriptor(typeInfo->GetTypeDescriptor()); } case ITypeInfo::Generic: { @@ -15462,6 +15538,7 @@ CreateTypeInfoFromMethodInfo } } + /*********************************************************************** .\ANALYZER\WFANALYZER_VALIDATERPC.CPP ***********************************************************************/ @@ -16794,26 +16871,7 @@ ValidateModuleRPC_GenerateMetadata auto baseTd = td->GetBaseTypeDescriptor(i); if (!IsRpcInterfaceTd(baseTd, rpcInterfaceAttrTd, rpcInterfaceTds)) continue; - List baseFragments; - GetTypeFragments(baseTd, baseFragments); - - Ptr parentType; - for (auto fragment : baseFragments) - { - if (!parentType) - { - auto type = Ptr(new WfTopQualifiedType); - type->name.value = fragment; - parentType = type; - } - else - { - auto type = Ptr(new WfChildType); - type->parent = parentType; - type->name.value = fragment; - parentType = type; - } - } + auto parentType = GetTypeFromTypeDescriptor(baseTd); if (parentType) { decl->baseTypes.Add(parentType); diff --git a/Import/VlppWorkflowCompiler.h b/Import/VlppWorkflowCompiler.h index 0c1ce82b..2cd8509c 100644 --- a/Import/VlppWorkflowCompiler.h +++ b/Import/VlppWorkflowCompiler.h @@ -5227,6 +5227,7 @@ Type Analyzing extern void GetTypeFragments(reflection::description::ITypeDescriptor* typeDescriptor, collections::List& fragments); extern Ptr GetExpressionFromTypeDescriptor(reflection::description::ITypeDescriptor* typeDescriptor); + extern Ptr GetTypeFromTypeDescriptor(reflection::description::ITypeDescriptor* typeDescriptor); extern Ptr GetTypeFromTypeInfo(reflection::description::ITypeInfo* typeInfo); extern Ptr GetScopeNameFromReferenceType(WfLexicalScope* scope, Ptr type); extern Ptr CreateTypeInfoFromType(WfLexicalScope* scope, Ptr type, bool checkTypeForValue = true); diff --git a/Import/VlppWorkflowLibrary.cpp b/Import/VlppWorkflowLibrary.cpp index e62e0ca5..bc892f77 100644 --- a/Import/VlppWorkflowLibrary.cpp +++ b/Import/VlppWorkflowLibrary.cpp @@ -1037,6 +1037,7 @@ TypeName IMPL_TYPE_INFO_RENAME(vl::rpc_controller::RpcException, system::RpcException) IMPL_TYPE_INFO_RENAME(vl::rpc_controller::RpcByvalReturnValue, system::RpcByvalReturnValue) IMPL_TYPE_INFO_RENAME(vl::rpc_controller::IRpcSerializer, system::IRpcSerializer) + IMPL_TYPE_INFO_RENAME(vl::rpc_controller::IRpcJsonMessageDispatcher, system::IRpcJsonMessageDispatcher) IMPL_TYPE_INFO_RENAME(vl::rpc_controller::IRpcListOps, system::IRpcListOps) IMPL_TYPE_INFO_RENAME(vl::rpc_controller::IRpcListEventOps, system::IRpcListEventOps) IMPL_TYPE_INFO_RENAME(vl::rpc_controller::IRpcObjectOps, system::IRpcObjectOps) @@ -1141,6 +1142,11 @@ WfLoadLibraryTypes CLASS_MEMBER_METHOD(Deserialize, { L"value" }) END_INTERFACE_MEMBER(vl::rpc_controller::IRpcSerializer) + BEGIN_INTERFACE_MEMBER(vl::rpc_controller::IRpcJsonMessageDispatcher) + CLASS_MEMBER_METHOD(AllocateRequestId, NO_PARAMETER) + CLASS_MEMBER_METHOD(OnJsonRequest, { L"message" }) + END_INTERFACE_MEMBER(vl::rpc_controller::IRpcJsonMessageDispatcher) + BEGIN_INTERFACE_MEMBER(vl::rpc_controller::IRpcListOps) CLASS_MEMBER_METHOD(EnumCreate, { L"ref" }) CLASS_MEMBER_METHOD(EnumNext, { L"enumerator" }) @@ -1155,6 +1161,7 @@ WfLoadLibraryTypes CLASS_MEMBER_METHOD(ListClear, { L"ref" }) CLASS_MEMBER_METHOD(ListContains, { L"ref" _ L"value" }) CLASS_MEMBER_METHOD(ListIndexOf, { L"ref" _ L"value" }) + CLASS_MEMBER_METHOD(ArrayResize, { L"ref" _ L"size" }) CLASS_MEMBER_METHOD(DictGetCount, { L"ref" }) CLASS_MEMBER_METHOD(DictGet, { L"ref" _ L"key" }) @@ -1193,9 +1200,7 @@ WfLoadLibraryTypes CLASS_MEMBER_METHOD(IsRegisteredService, { L"ref" }) CLASS_MEMBER_METHOD(RegisterService, { L"typeId" _ L"ref" }) CLASS_MEMBER_METHOD(RequestService, { L"typeId" }) - CLASS_MEMBER_METHOD(BroadcastFromClient_ListEventOps, { L"selfClientId" }) CLASS_MEMBER_METHOD(BroadcastFromClient_ObjectEventOps, { L"selfClientId" }) - CLASS_MEMBER_METHOD(SendToClient_ListOps, { L"targetClientId" }) CLASS_MEMBER_METHOD(SendToClient_ObjectOps, { L"targetClientId" }) END_INTERFACE_MEMBER(vl::rpc_controller::IRpcDispatcher) @@ -1511,21 +1516,288 @@ namespace vl { namespace rpc_controller { + using namespace collections; + using namespace reflection; using namespace reflection::description; - Value BoxRpcObjectReference(RpcObjectReference ref) + namespace { - return Value::From(Ptr(new IValueType::TypedBox(ref)), nullptr); - } +#ifndef VCZH_WORKFLOW_RPC_OBJECT_REFERENCE_VALUE_HELPERS +#define VCZH_WORKFLOW_RPC_OBJECT_REFERENCE_VALUE_HELPERS + bool IsRpcObjectReferenceValue(const Value& value) + { + return value.GetValueType() == Value::BoxedValue && value.GetBoxedValue().Cast>(); + } - Value BoxRpcException(RpcException exception) - { - return Value::From(Ptr(new IValueType::TypedBox(exception)), nullptr); - } + RpcObjectReference GetRpcObjectReference(const Value& value) + { + auto boxed = value.GetBoxedValue().Cast>(); + CHECK_ERROR(boxed, L"RpcObjectReference is expected."); + return boxed->value; + } +#endif - RpcEventExceptionMap CreateRpcEventExceptionMap() - { - return IValueDictionary::Create(); + bool IsNullRpcObjectReference(RpcObjectReference ref) + { + return ref.clientId == RpcClientId_Invalid + && ref.objectId == RpcObjectId_Invalid + && ref.typeId == RpcTypeId_NotFound; + } + + template + bool ContainsKey(const Dictionary& xs, const K& key) + { + return xs.Keys().Contains(key); + } + + template + Ptr TryGetValueInterface(const Value& value) + { + if (auto raw = value.GetRawPtr()) + { + return Ptr(dynamic_cast(raw)); + } + return nullptr; + } + + Value RpcCopyValueByvalInternal(const Value& trivial, Dictionary& visited) + { + if (trivial.IsNull()) return trivial; + if (trivial.GetValueType() == Value::SharedPtr) + { + if (auto roDict = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); + visited.Add(key, true); + + auto dict = IValueDictionary::Create(); + auto keys = roDict->GetKeys(); + for (vint i = 0; i < keys->GetCount(); i++) + { + auto dictKey = keys->Get(i); + dict->Set( + RpcCopyValueByvalInternal(dictKey, visited), + RpcCopyValueByvalInternal(roDict->Get(dictKey), visited) + ); + } + visited.Remove(key); + return BoxValue(dict); + } + + if (auto obsList = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); + visited.Add(key, true); + + auto list = IValueObservableList::Create(); + for (vint i = 0; i < obsList->GetCount(); i++) + { + list->Add(RpcCopyValueByvalInternal(obsList->Get(i), visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto array = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); + visited.Add(key, true); + + auto list = IValueArray::Create(); + list->Resize(array->GetCount()); + for (vint i = 0; i < array->GetCount(); i++) + { + list->Set(i, RpcCopyValueByvalInternal(array->Get(i), visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto roList = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); + visited.Add(key, true); + + auto list = IValueList::Create(); + for (vint i = 0; i < roList->GetCount(); i++) + { + list->Add(RpcCopyValueByvalInternal(roList->Get(i), visited)); + } + visited.Remove(key); + return BoxValue(list); + } + } + return trivial; + } + + Value RpcBoxValueByvalInternal(const Value& trivial, IRpcLifecycle* lc, Dictionary& visited) + { + if (trivial.IsNull()) return trivial; + if (trivial.GetValueType() == Value::SharedPtr) + { + if (auto roDict = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); + visited.Add(key, true); + + auto dict = IValueDictionary::Create(); + auto keys = roDict->GetKeys(); + for (vint i = 0; i < keys->GetCount(); i++) + { + auto dictKey = keys->Get(i); + dict->Set( + RpcBoxValueByvalInternal(dictKey, lc, visited), + RpcBoxValueByvalInternal(roDict->Get(dictKey), lc, visited) + ); + } + visited.Remove(key); + return BoxValue(dict); + } + + if (auto obsList = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); + visited.Add(key, true); + + auto list = IValueObservableList::Create(); + for (vint i = 0; i < obsList->GetCount(); i++) + { + list->Add(RpcBoxValueByvalInternal(obsList->Get(i), lc, visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto array = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); + visited.Add(key, true); + + auto list = IValueArray::Create(); + list->Resize(array->GetCount()); + for (vint i = 0; i < array->GetCount(); i++) + { + list->Set(i, RpcBoxValueByvalInternal(array->Get(i), lc, visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto roList = TryGetValueInterface(trivial)) + { + auto key = static_cast(trivial.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); + visited.Add(key, true); + + auto list = IValueList::Create(); + for (vint i = 0; i < roList->GetCount(); i++) + { + list->Add(RpcBoxValueByvalInternal(roList->Get(i), lc, visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto raw = trivial.GetRawPtr()) + { + if (auto obj = dynamic_cast(raw)) + { + auto ref = lc->PtrToRef(Ptr(obj)); + return BoxValue(ref); + } + } + } + return trivial; + } + + Value RpcUnboxValueByvalInternal(const Value& serializable, IRpcLifecycle* lc, Dictionary& visited) + { + if (serializable.IsNull()) return serializable; + + if (IsRpcObjectReferenceValue(serializable)) + { + auto ref = GetRpcObjectReference(serializable); + auto obj = RpcUnboxByref(ref, lc); + return BoxValue(obj); + } + + if (serializable.GetValueType() == Value::SharedPtr) + { + if (auto roDict = TryGetValueInterface(serializable)) + { + auto key = static_cast(serializable.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); + visited.Add(key, true); + + auto dict = IValueDictionary::Create(); + auto keys = roDict->GetKeys(); + for (vint i = 0; i < keys->GetCount(); i++) + { + auto dictKey = keys->Get(i); + dict->Set( + RpcUnboxValueByvalInternal(dictKey, lc, visited), + RpcUnboxValueByvalInternal(roDict->Get(dictKey), lc, visited) + ); + } + visited.Remove(key); + return BoxValue(dict); + } + + if (auto obsList = TryGetValueInterface(serializable)) + { + auto key = static_cast(serializable.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); + visited.Add(key, true); + + auto list = IValueObservableList::Create(); + for (vint i = 0; i < obsList->GetCount(); i++) + { + list->Add(RpcUnboxValueByvalInternal(obsList->Get(i), lc, visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto array = TryGetValueInterface(serializable)) + { + auto key = static_cast(serializable.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); + visited.Add(key, true); + + auto list = IValueArray::Create(); + list->Resize(array->GetCount()); + for (vint i = 0; i < array->GetCount(); i++) + { + list->Set(i, RpcUnboxValueByvalInternal(array->Get(i), lc, visited)); + } + visited.Remove(key); + return BoxValue(list); + } + + if (auto roList = TryGetValueInterface(serializable)) + { + auto key = static_cast(serializable.GetRawPtr()); + if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); + visited.Add(key, true); + + auto list = IValueList::Create(); + for (vint i = 0; i < roList->GetCount(); i++) + { + list->Add(RpcUnboxValueByvalInternal(roList->Get(i), lc, visited)); + } + visited.Remove(key); + return BoxValue(list); + } + } + return serializable; + } } void MergeRpcEventExceptionMap(RpcEventExceptionMap target, RpcEventExceptionMap source) @@ -1539,14 +1811,84 @@ namespace vl } } - Value BoxRpcEventExceptionMap(RpcEventExceptionMap exceptions) + RpcObjectReference RpcBoxByref(Ptr trivial, IRpcLifecycle* lc) { - return exceptions ? BoxValue(exceptions) : Value(); + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (!trivial) return {}; + return lc->PtrToRef(trivial); } - RpcEventExceptionMap UnboxRpcEventExceptionMap(const Value& value) + Ptr RpcUnboxByref(RpcObjectReference serializable, IRpcLifecycle* lc) { - return value.IsNull() ? nullptr : UnboxValue(value); + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (IsNullRpcObjectReference(serializable)) return nullptr; + return lc->RefToPtr(serializable); + } + + Value RpcCopyByval(const Value& trivial, IRpcLifecycle* lc) + { + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (trivial.IsNull()) return {}; + Dictionary visited; + return RpcCopyValueByvalInternal(trivial, visited); + } + + Value RpcBoxByval(Ptr trivial, IRpcLifecycle* lc) + { + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + return RpcBoxByval(BoxValue(trivial), lc); + } + + Value RpcBoxByval(const Value& trivial, IRpcLifecycle* lc) + { + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (trivial.IsNull()) return {}; + Dictionary visited; + return RpcBoxValueByvalInternal(trivial, lc, visited); + } + + Ptr RpcUnboxByval(const Value& serializable, IRpcLifecycle* lc) + { + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (serializable.IsNull()) return nullptr; + Dictionary visited; + auto trivial = RpcUnboxValueByvalInternal(serializable, lc, visited); + if (auto raw = trivial.GetRawPtr()) + { + if (auto obj = dynamic_cast(raw)) + { + return Ptr(obj); + } + } + + CHECK_FAIL(L"Interface value or null is expected."); + return nullptr; + } + + void ReadMethodException(const Value& value) + { + if (value.GetValueType() == Value::BoxedValue) + { + if (auto boxed = value.GetBoxedValue().Cast>()) + { + throw Exception(boxed->value.message); + } + } + } + + void ReadEventException(RpcEventExceptionMap exceptions) + { + if (!exceptions || exceptions->GetCount() == 0) return; + + WString message; + auto keys = exceptions->GetKeys(); + for (vint i = 0; i < keys->GetCount(); i++) + { + auto key = keys->Get(i); + auto exception = UnboxValue(exceptions->Get(key)); + message += itow(UnboxValue(key)) + L":" + exception.message + L";"; + } + throw Exception(message); } } } @@ -1715,6 +2057,20 @@ namespace vl return Ptr(new JsonObject); } + Ptr GetJsonObject(Ptr node) + { + auto object = node.Cast(); + CHECK_ERROR(object, L"JSON object is expected."); + return object; + } + + Ptr GetJsonArray(Ptr node) + { + auto array = node.Cast(); + CHECK_ERROR(array, L"JSON array is expected."); + return array; + } + void AddJsonObjectField(Ptr object, const WString& name, Ptr value) { auto field = Ptr(new JsonObjectField); @@ -1723,6 +2079,19 @@ namespace vl object->fields.Add(field); } + void SetJsonObjectField(Ptr object, const WString& name, Ptr value) + { + for (auto field : object->fields) + { + if (field->name.value == name) + { + field->value = value; + return; + } + } + AddJsonObjectField(object, name, value); + } + Ptr GetJsonObjectField(Ptr object, const WString& name) { for (auto field : object->fields) @@ -1762,6 +2131,31 @@ namespace vl return numberNode->content.value; } + vint GetJsonInt(Ptr node) + { + return __vwsn::Parse(GetJsonNumber(node)); + } + + bool GetJsonBool(Ptr node) + { + auto literal = node.Cast(); + CHECK_ERROR(literal, L"JSON boolean is expected."); + if (literal->value == JsonLiteralValue::True) return true; + if (literal->value == JsonLiteralValue::False) return false; + CHECK_FAIL(L"JSON boolean is expected."); + return false; + } + + Ptr CreateJsonNumber(vint value) + { + return CreateJsonNumber(__vwsn::ToString(value)); + } + + Ptr CreateJsonBool(bool value) + { + return CreateJsonLiteral(value ? JsonLiteralValue::True : JsonLiteralValue::False); + } + Ptr CreateUnknownTuple(const WString& keyword, Ptr value) { auto array = CreateJsonArray(); @@ -1918,7 +2312,7 @@ namespace vl { if (keyword == L"system::RpcObjectReference") { - return BoxRpcObjectReference(RpcObjectReference{ + return BoxValue(RpcObjectReference{ __vwsn::Parse(GetJsonNumber(GetJsonObjectField(object, WString::Unmanaged(L"clientId")))), __vwsn::Parse(GetJsonNumber(GetJsonObjectField(object, WString::Unmanaged(L"objectId")))), __vwsn::Parse(GetJsonNumber(GetJsonObjectField(object, WString::Unmanaged(L"typeId")))), @@ -1926,12 +2320,191 @@ namespace vl } if (keyword == L"system::RpcException") { - return BoxRpcException(RpcException{ + return BoxValue(RpcException{ GetJsonString(GetJsonObjectField(object, WString::Unmanaged(L"message"))), }); } return {}; } + + RpcObjectReference GetRpcObjectReferenceFromJson(Ptr node) + { + auto object = GetJsonObject(node); + return { + GetJsonInt(GetJsonObjectField(object, WString::Unmanaged(L"clientId"))), + GetJsonInt(GetJsonObjectField(object, WString::Unmanaged(L"objectId"))), + GetJsonInt(GetJsonObjectField(object, WString::Unmanaged(L"typeId"))), + }; + } + + Ptr CreateRpcObjectReferenceJson(RpcObjectReference ref) + { + auto object = CreateJsonObject(); + AddJsonObjectField(object, WString::Unmanaged(L"clientId"), CreateJsonNumber(ref.clientId)); + AddJsonObjectField(object, WString::Unmanaged(L"objectId"), CreateJsonNumber(ref.objectId)); + AddJsonObjectField(object, WString::Unmanaged(L"typeId"), CreateJsonNumber(ref.typeId)); + return object; + } + + Ptr ValueToJsonNode(const Value& value) + { + if (value.GetValueType() == Value::SharedPtr) + { + if (auto node = value.GetSharedPtr().Cast()) + { + return node; + } + } + CHECK_FAIL(L"RPC JSON value should be a Ptr."); + return nullptr; + } + + Ptr ValueArrayToJsonArray(Ptr arguments) + { + auto array = CreateJsonArray(); + for (vint i = 0; i < arguments->GetCount(); i++) + { + array->items.Add(ValueToJsonNode(arguments->Get(i))); + } + return array; + } + + Ptr JsonArrayToValueArray(Ptr node) + { + auto array = GetJsonArray(node); + auto arguments = IValueArray::Create(); + arguments->Resize(array->items.Count()); + for (vint i = 0; i < array->items.Count(); i++) + { + arguments->Set(i, BoxValue(array->items[i])); + } + return arguments; + } + + Ptr CreateRpcMessage(const WString& method, vint requestId, vint sourceClientId) + { + auto object = CreateJsonObject(); + AddJsonObjectField(object, WString::Unmanaged(L"rpcMethod"), CreateJsonString(method)); + AddJsonObjectField(object, WString::Unmanaged(L"rpcRequestId"), CreateJsonNumber(requestId)); + AddJsonObjectField(object, WString::Unmanaged(L"sourceClientId"), CreateJsonNumber(sourceClientId)); + return object; + } + + vint ReadSourceClientId(Ptr object) + { + return GetJsonInt(GetJsonObjectField(object, WString::Unmanaged(L"sourceClientId"))); + } + + vint ReadTargetClientId(Ptr object) + { + return GetJsonInt(GetJsonObjectField(object, WString::Unmanaged(L"targetClientId"))); + } + + Ptr MethodResultToJsonResponse(const Value& result) + { + if (result.GetValueType() == Value::SharedPtr) + { + if (auto byvalReturnValue = result.GetSharedPtr().Cast()) + { + auto object = CreateJsonObject(); + AddJsonObjectField(object, WString::Unmanaged(L"value"), ValueToJsonNode(byvalReturnValue->value)); + AddJsonObjectField(object, WString::Unmanaged(L"slot"), CreateJsonNumber(byvalReturnValue->slot)); + return object; + } + } + return ValueToJsonNode(result); + } + + Value JsonResponseToMethodResult(Ptr node) + { + if (auto object = node.Cast()) + { + auto valueNode = FindJsonObjectField(object, WString::Unmanaged(L"value")); + auto slotNode = FindJsonObjectField(object, WString::Unmanaged(L"slot")); + if (valueNode && slotNode) + { + auto byvalReturnValue = Ptr(new RpcByvalReturnValue); + byvalReturnValue->value = BoxValue(valueNode); + byvalReturnValue->slot = GetJsonInt(slotNode); + return BoxValue(byvalReturnValue); + } + } + return BoxValue(node); + } + + Ptr SerializePredefinedValue(const Value& value) + { + RpcJsonSerializeCallback rpcjson_Serialize; + rpcjson_Serialize = Func(const Value&)>([&rpcjson_Serialize](const Value& item) + { + return JsonSerializePredefinedTypes(item, rpcjson_Serialize); + }); + return JsonSerializePredefinedTypes(value, rpcjson_Serialize); + } + + Value DeserializePredefinedValue(Ptr node) + { + RpcJsonDeserializeCallback rpcjson_Deserialize; + rpcjson_Deserialize = Func)>([&rpcjson_Deserialize](Ptr item) + { + return JsonDeserializePredefinedTypes(BoxValue(item), rpcjson_Deserialize); + }); + return JsonDeserializePredefinedTypes(BoxValue(node), rpcjson_Deserialize); + } + + Ptr CreatePlainRpcException(const RpcException& exception) + { + auto result = CreateJsonObject(); + AddJsonObjectField(result, WString::Unmanaged(L"message"), CreateJsonString(exception.message)); + return result; + } + + RpcException ReadPlainRpcException(Ptr node) + { + auto object = GetJsonObject(node); + return { GetJsonString(GetJsonObjectField(object, WString::Unmanaged(L"message"))) }; + } + + Ptr CreateEventExceptionResponse(Ptr serialized) + { + auto value = DeserializePredefinedValue(serialized); + if (value.IsNull()) + { + return CreateJsonLiteral(JsonLiteralValue::Null); + } + + auto exceptions = UnboxValue(value); + auto result = CreateJsonArray(); + auto keys = exceptions->GetKeys(); + for (vint i = 0; i < keys->GetCount(); i++) + { + auto key = keys->Get(i); + auto resultPair = CreateJsonArray(); + resultPair->items.Add(CreateJsonNumber(UnboxValue(key))); + resultPair->items.Add(CreatePlainRpcException(UnboxValue(exceptions->Get(key)))); + result->items.Add(resultPair); + } + return result; + } + + Ptr CreateSerializedEventExceptionMap(Ptr response) + { + if (auto literal = response.Cast()) + { + CHECK_ERROR(literal->value == JsonLiteralValue::Null, L"RPC event exception response should be null or an array."); + return SerializePredefinedValue({}); + } + + auto array = GetJsonArray(response); + auto exceptions = IValueDictionary::Create(); + for (auto item : array->items) + { + auto pair = GetJsonArray(item); + CHECK_ERROR(pair->items.Count() == 2, L"RPC event exception response pair is expected."); + exceptions->Set(BoxValue(GetJsonInt(pair->items[0])), BoxValue(ReadPlainRpcException(pair->items[1]))); + } + return SerializePredefinedValue(BoxValue(exceptions)); + } } Ptr JsonSerializePredefinedTypes(const Value& value, const RpcJsonSerializeCallback& rpcjson_Serialize) @@ -2071,6 +2644,223 @@ namespace vl CHECK_FAIL(L"Unsupported RPC JSON node."); } + +/*********************************************************************** +* RpcJsonObjectOps +***********************************************************************/ + + RpcJsonObjectOps::RpcJsonObjectOps(IRpcJsonMessageDispatcher* _dispatcher) + : dispatcher(_dispatcher) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::RpcJsonObjectOps(...)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); +#undef ERROR_MESSAGE_PREFIX + } + + RpcJsonObjectOps::RpcJsonObjectOps(vint _sourceClientId, vint _targetClientId, IRpcJsonMessageDispatcher* _dispatcher, IRpcLifecycle* _lifecycle) + : sourceClientId(_sourceClientId) + , targetClientId(_targetClientId) + , dispatcher(_dispatcher) + , lifecycle(_lifecycle) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::RpcJsonObjectOps(...)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); +#undef ERROR_MESSAGE_PREFIX + } + + RpcJsonObjectOps::~RpcJsonObjectOps() + { + } + + Value RpcJsonObjectOps::InvokeMethod(RpcObjectReference ref, vint methodId, Ptr arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::InvokeMethod(RpcObjectReference, vint, Ptr)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); + auto request = CreateRpcMessage(WString::Unmanaged(L"IObjectOps_InvokeMethod"), dispatcher->AllocateRequestId(), sourceClientId); + AddJsonObjectField(request, WString::Unmanaged(L"targetClientId"), CreateJsonNumber(targetClientId == RpcClientId_Invalid ? ref.clientId : targetClientId)); + AddJsonObjectField(request, WString::Unmanaged(L"ref"), CreateRpcObjectReferenceJson(ref)); + AddJsonObjectField(request, WString::Unmanaged(L"methodId"), CreateJsonNumber(methodId)); + AddJsonObjectField(request, WString::Unmanaged(L"arguments"), ValueArrayToJsonArray(arguments)); + + auto response = GetJsonObject(dispatcher->OnJsonRequest(request)); + CHECK_ERROR(GetJsonString(GetJsonObjectField(response, WString::Unmanaged(L"rpcMethod"))) == WString::Unmanaged(L"IObjectOps_InvokeMethod"), ERROR_MESSAGE_PREFIX L"Unexpected response method."); + CHECK_ERROR(ReadRequestId(response) == ReadRequestId(request), ERROR_MESSAGE_PREFIX L"Unexpected response request id."); + return JsonResponseToMethodResult(GetJsonObjectField(response, WString::Unmanaged(L"response"))); +#undef ERROR_MESSAGE_PREFIX + } + + void RpcJsonObjectOps::EndInvokeMethod(vint slot) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::EndInvokeMethod(vint)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); + auto request = CreateRpcMessage(WString::Unmanaged(L"IObjectOps_EndInvokeMethod"), dispatcher->AllocateRequestId(), sourceClientId); + AddJsonObjectField(request, WString::Unmanaged(L"targetClientId"), CreateJsonNumber(targetClientId)); + AddJsonObjectField(request, WString::Unmanaged(L"slot"), CreateJsonNumber(slot)); + + auto response = GetJsonObject(dispatcher->OnJsonRequest(request)); + CHECK_ERROR(GetJsonString(GetJsonObjectField(response, WString::Unmanaged(L"rpcMethod"))) == WString::Unmanaged(L"IObjectOps_EndInvokeMethod"), ERROR_MESSAGE_PREFIX L"Unexpected response method."); + CHECK_ERROR(ReadRequestId(response) == ReadRequestId(request), ERROR_MESSAGE_PREFIX L"Unexpected response request id."); +#undef ERROR_MESSAGE_PREFIX + } + + void RpcJsonObjectOps::ObjectHold(RpcObjectReference ref, vint remoteClientId, bool hold) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::ObjectHold(RpcObjectReference, vint, bool)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); + auto request = CreateRpcMessage(WString::Unmanaged(L"IObjectOps_ObjectHold"), dispatcher->AllocateRequestId(), sourceClientId == RpcClientId_Invalid ? remoteClientId : sourceClientId); + AddJsonObjectField(request, WString::Unmanaged(L"targetClientId"), CreateJsonNumber(targetClientId == RpcClientId_Invalid ? ref.clientId : targetClientId)); + AddJsonObjectField(request, WString::Unmanaged(L"ref"), CreateRpcObjectReferenceJson(ref)); + AddJsonObjectField(request, WString::Unmanaged(L"remoteClientId"), CreateJsonNumber(remoteClientId)); + AddJsonObjectField(request, WString::Unmanaged(L"hold"), CreateJsonBool(hold)); + + auto response = GetJsonObject(dispatcher->OnJsonRequest(request)); + CHECK_ERROR(GetJsonString(GetJsonObjectField(response, WString::Unmanaged(L"rpcMethod"))) == WString::Unmanaged(L"IObjectOps_ObjectHold"), ERROR_MESSAGE_PREFIX L"Unexpected response method."); + CHECK_ERROR(ReadRequestId(response) == ReadRequestId(request), ERROR_MESSAGE_PREFIX L"Unexpected response request id."); +#undef ERROR_MESSAGE_PREFIX + } + + void RpcJsonObjectOps::RegisterService(vint typeId, Ptr service) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::RegisterService(vint, Ptr)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); + CHECK_ERROR(lifecycle, ERROR_MESSAGE_PREFIX L"Lifecycle is required."); + auto serviceRef = lifecycle->PtrToRef(service); + auto request = CreateRpcMessage(WString::Unmanaged(L"IObjectOps_RegisterService"), dispatcher->AllocateRequestId(), sourceClientId == RpcClientId_Invalid ? serviceRef.clientId : sourceClientId); + AddJsonObjectField(request, WString::Unmanaged(L"targetClientId"), CreateJsonNumber(targetClientId == RpcClientId_Invalid ? serviceRef.clientId : targetClientId)); + AddJsonObjectField(request, WString::Unmanaged(L"typeId"), CreateJsonNumber(typeId)); + AddJsonObjectField(request, WString::Unmanaged(L"service"), CreateRpcObjectReferenceJson(serviceRef)); + + auto response = GetJsonObject(dispatcher->OnJsonRequest(request)); + CHECK_ERROR(GetJsonString(GetJsonObjectField(response, WString::Unmanaged(L"rpcMethod"))) == WString::Unmanaged(L"IObjectOps_RegisterService"), ERROR_MESSAGE_PREFIX L"Unexpected response method."); + CHECK_ERROR(ReadRequestId(response) == ReadRequestId(request), ERROR_MESSAGE_PREFIX L"Unexpected response request id."); +#undef ERROR_MESSAGE_PREFIX + } + + Ptr RpcJsonObjectOps::Translate(Ptr message, IRpcObjectOps* ops, IRpcLifecycle* lifecycle) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectOps::Translate(Ptr, IRpcObjectOps*)#" + CHECK_ERROR(ops, ERROR_MESSAGE_PREFIX L"Object ops is required."); + auto request = GetJsonObject(message); + auto rpcMethod = GetJsonString(GetJsonObjectField(request, WString::Unmanaged(L"rpcMethod"))); + auto requestId = ReadRequestId(request); + auto sourceClientId = ReadSourceClientId(request); + auto targetClientId = ReadTargetClientId(request); + auto response = CreateRpcMessage(rpcMethod, requestId, targetClientId); + AddJsonObjectField(response, WString::Unmanaged(L"targetClientId"), CreateJsonNumber(sourceClientId)); + + if (rpcMethod == WString::Unmanaged(L"IObjectOps_InvokeMethod")) + { + auto result = ops->InvokeMethod( + GetRpcObjectReferenceFromJson(GetJsonObjectField(request, WString::Unmanaged(L"ref"))), + GetJsonInt(GetJsonObjectField(request, WString::Unmanaged(L"methodId"))), + JsonArrayToValueArray(GetJsonObjectField(request, WString::Unmanaged(L"arguments"))) + ); + AddJsonObjectField(response, WString::Unmanaged(L"response"), MethodResultToJsonResponse(result)); + } + else if (rpcMethod == WString::Unmanaged(L"IObjectOps_EndInvokeMethod")) + { + ops->EndInvokeMethod(GetJsonInt(GetJsonObjectField(request, WString::Unmanaged(L"slot")))); + } + else if (rpcMethod == WString::Unmanaged(L"IObjectOps_ObjectHold")) + { + ops->ObjectHold( + GetRpcObjectReferenceFromJson(GetJsonObjectField(request, WString::Unmanaged(L"ref"))), + GetJsonInt(GetJsonObjectField(request, WString::Unmanaged(L"remoteClientId"))), + GetJsonBool(GetJsonObjectField(request, WString::Unmanaged(L"hold"))) + ); + } + else if (rpcMethod == WString::Unmanaged(L"IObjectOps_RegisterService")) + { + CHECK_ERROR(lifecycle, ERROR_MESSAGE_PREFIX L"Lifecycle is required to translate RegisterService."); + ops->RegisterService( + GetJsonInt(GetJsonObjectField(request, WString::Unmanaged(L"typeId"))), + lifecycle->RefToPtr(GetRpcObjectReferenceFromJson(GetJsonObjectField(request, WString::Unmanaged(L"service")))) + ); + } + else + { + CHECK_FAIL(ERROR_MESSAGE_PREFIX L"Unexpected RPC method."); + } + return response; +#undef ERROR_MESSAGE_PREFIX + } + +/*********************************************************************** +* RpcJsonObjectEventOps +***********************************************************************/ + + RpcJsonObjectEventOps::RpcJsonObjectEventOps(IRpcJsonMessageDispatcher* _dispatcher) + : dispatcher(_dispatcher) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectEventOps::RpcJsonObjectEventOps(...)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); +#undef ERROR_MESSAGE_PREFIX + } + + RpcJsonObjectEventOps::RpcJsonObjectEventOps(vint _sourceClientId, IRpcJsonMessageDispatcher* _dispatcher) + : sourceClientId(_sourceClientId) + , dispatcher(_dispatcher) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectEventOps::RpcJsonObjectEventOps(...)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); +#undef ERROR_MESSAGE_PREFIX + } + + RpcJsonObjectEventOps::~RpcJsonObjectEventOps() + { + } + + Value RpcJsonObjectEventOps::InvokeEvent(RpcObjectReference ref, vint eventId, Ptr arguments) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectEventOps::InvokeEvent(RpcObjectReference, vint, Ptr)#" + CHECK_ERROR(dispatcher, ERROR_MESSAGE_PREFIX L"Dispatcher is required."); + auto request = CreateRpcMessage(WString::Unmanaged(L"IObjectEventOps_InvokeEvent"), dispatcher->AllocateRequestId(), sourceClientId); + AddJsonObjectField(request, WString::Unmanaged(L"ref"), CreateRpcObjectReferenceJson(ref)); + AddJsonObjectField(request, WString::Unmanaged(L"eventId"), CreateJsonNumber(eventId)); + AddJsonObjectField(request, WString::Unmanaged(L"arguments"), ValueArrayToJsonArray(arguments)); + + auto response = GetJsonObject(dispatcher->OnJsonRequest(request)); + CHECK_ERROR(GetJsonString(GetJsonObjectField(response, WString::Unmanaged(L"rpcMethod"))) == WString::Unmanaged(L"IObjectEventOps_InvokeEvent"), ERROR_MESSAGE_PREFIX L"Unexpected response method."); + CHECK_ERROR(ReadRequestId(response) == ReadRequestId(request), ERROR_MESSAGE_PREFIX L"Unexpected response request id."); + return BoxValue(CreateSerializedEventExceptionMap(GetJsonObjectField(response, WString::Unmanaged(L"response")))); +#undef ERROR_MESSAGE_PREFIX + } + + Ptr RpcJsonObjectEventOps::Translate(Ptr message, IRpcObjectEventOps* ops) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcJsonObjectEventOps::Translate(Ptr, IRpcObjectEventOps*)#" + CHECK_ERROR(ops, ERROR_MESSAGE_PREFIX L"Object event ops is required."); + auto request = GetJsonObject(message); + auto rpcMethod = GetJsonString(GetJsonObjectField(request, WString::Unmanaged(L"rpcMethod"))); + CHECK_ERROR(rpcMethod == WString::Unmanaged(L"IObjectEventOps_InvokeEvent"), ERROR_MESSAGE_PREFIX L"Unexpected RPC method."); + auto requestId = ReadRequestId(request); + auto sourceClientId = ReadSourceClientId(request); + auto response = CreateRpcMessage(rpcMethod, requestId, RpcClientId_Invalid); + AddJsonObjectField(response, WString::Unmanaged(L"targetClientId"), CreateJsonNumber(sourceClientId)); + + auto result = ops->InvokeEvent( + GetRpcObjectReferenceFromJson(GetJsonObjectField(request, WString::Unmanaged(L"ref"))), + GetJsonInt(GetJsonObjectField(request, WString::Unmanaged(L"eventId"))), + JsonArrayToValueArray(GetJsonObjectField(request, WString::Unmanaged(L"arguments"))) + ); + AddJsonObjectField(response, WString::Unmanaged(L"response"), CreateEventExceptionResponse(ValueToJsonNode(result))); + return response; +#undef ERROR_MESSAGE_PREFIX + } + +/*********************************************************************** +* Request Id +***********************************************************************/ + + vint ReadRequestId(Ptr message) + { + return GetJsonInt(GetJsonObjectField(GetJsonObject(message), WString::Unmanaged(L"rpcRequestId"))); + } + + void WriteRequestId(Ptr message, vint requestId) + { + SetJsonObjectField(GetJsonObject(message), WString::Unmanaged(L"rpcRequestId"), CreateJsonNumber(requestId)); + } } } @@ -2239,9 +3029,11 @@ namespace vl { return; } - auto eventResult = this->GetDispatcher()->BroadcastFromClient_ListEventOps(this->GetClientId())->OnItemChanged(ref, index, oldCount, newCount); - auto exceptions = serializer ? serializer->Deserialize(eventResult) : eventResult; - ReadEventException(UnboxRpcEventExceptionMap(exceptions)); + auto listEventOps = Ptr(new RpcCallerListEventOps( + this->GetDispatcher()->BroadcastFromClient_ObjectEventOps(this->GetClientId()), + serializer.Obj() + )); + listEventOps->OnItemChanged(ref, index, oldCount, newCount); }); props->eventHandler = handler; } @@ -2614,6 +3406,8 @@ namespace vl namespace { +#ifndef VCZH_WORKFLOW_RPC_OBJECT_REFERENCE_VALUE_HELPERS +#define VCZH_WORKFLOW_RPC_OBJECT_REFERENCE_VALUE_HELPERS bool IsRpcObjectReferenceValue(const Value& value) { return value.GetValueType() == Value::BoxedValue && value.GetBoxedValue().Cast>(); @@ -2625,13 +3419,7 @@ namespace vl CHECK_ERROR(boxed, L"RpcObjectReference is expected."); return boxed->value; } - - bool IsNullRpcObjectReference(RpcObjectReference ref) - { - return ref.clientId == RpcClientId_Invalid - && ref.objectId == RpcObjectId_Invalid - && ref.typeId == RpcTypeId_NotFound; - } +#endif IRpcWrapperBase* CastRpcWrapperBase(IDescriptable* obj) { @@ -2648,12 +3436,6 @@ namespace vl return obj->SafeAggregationCast(); } - template - bool ContainsKey(const Dictionary& xs, const K& key) - { - return xs.Keys().Contains(key); - } - template Ptr CreateTrackedProxy(IRpcLifecycle* lc, RpcObjectReference ref, IRpcSerializer* serializer) { @@ -2680,9 +3462,9 @@ namespace vl lc->GetDispatcher()->SendToClient_ObjectOps(ref.clientId)->ObjectHold(ref, lc->GetClientId(), hold); } - IRpcListOps* GetRemoteListOps(IRpcLifecycle* lc, RpcObjectReference ref) + Ptr GetRemoteListOps(IRpcLifecycle* lc, RpcObjectReference ref, IRpcSerializer* serializer) { - return lc->GetDispatcher()->SendToClient_ListOps(ref.clientId); + return Ptr(new RpcCallerListOps(lc->GetDispatcher()->SendToClient_ObjectOps(ref.clientId), serializer)); } Value SerializeValue(IRpcSerializer* serializer, const Value& value) @@ -2695,11 +3477,93 @@ namespace vl return serializer ? serializer->Deserialize(value) : value; } - extern Value RpcBoxValueByref(const Value& trivial, IRpcLifecycle* lc); - extern Value RpcUnboxValueByref(const Value& serializable, IRpcLifecycle* lc); - extern Value RpcCopyValueByvalInternal(const Value& trivial, Dictionary& visited); - extern Value RpcBoxValueByvalInternal(const Value& trivial, IRpcLifecycle* lc, Dictionary& visited); - extern Value RpcUnboxValueByvalInternal(const Value& serializable, IRpcLifecycle* lc, Dictionary& visited); + template + Ptr CreateRpcArguments(TArgs&& ...args) + { + auto arguments = IValueArray::Create(); + arguments->Resize(sizeof...(TArgs)); + vint index = 0; + ((arguments->Set(index++, std::forward(args))), ...); + return arguments; + } + + Value ReadMethodResult(IRpcSerializer* serializer, const Value& value) + { + auto deserialized = DeserializeValue(serializer, value); + ReadMethodException(deserialized); + return deserialized; + } + + Value InvokeListMethod(IRpcObjectOps* objectOps, IRpcSerializer* serializer, RpcObjectReference ref, vint methodId, Ptr arguments) + { + auto result = objectOps->InvokeMethod(ref, methodId, arguments); + return ReadMethodResult(serializer, result); + } + + bool IsRpcListMethodId(vint methodId) + { + switch (methodId) + { + case RpcMethodId_IValueEnumerable_CreateEnumerator: + case RpcMethodId_IValueEnumerator_Next: + case RpcMethodId_IValueEnumerator_GetCurrent: + case RpcMethodId_IValueReadonlyList_GetCount: + case RpcMethodId_IValueReadonlyList_Get: + case RpcMethodId_IValueList_Set: + case RpcMethodId_IValueList_Add: + case RpcMethodId_IValueList_Insert: + case RpcMethodId_IValueList_RemoveAt: + case RpcMethodId_IValueList_Clear: + case RpcMethodId_IValueReadonlyList_Contains: + case RpcMethodId_IValueReadonlyList_IndexOf: + case RpcMethodId_IValueReadonlyDictionary_GetCount: + case RpcMethodId_IValueReadonlyDictionary_Get: + case RpcMethodId_IValueDictionary_Set: + case RpcMethodId_IValueDictionary_Remove: + case RpcMethodId_IValueDictionary_Clear: + case RpcMethodId_IValueReadonlyDictionary_ContainsKey: + case RpcMethodId_IValueReadonlyDictionary_GetKeys: + case RpcMethodId_IValueReadonlyDictionary_GetValues: + case RpcMethodId_IValueArray_Resize: + return true; + default: + return false; + } + } + + Value RpcBoxValueByref(const Value& trivial, IRpcLifecycle* lc) + { + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (trivial.IsNull()) return trivial; + + if (trivial.GetValueType() == Value::SharedPtr) + { + if (auto raw = trivial.GetRawPtr()) + { + if (auto obj = dynamic_cast(raw)) + { + return BoxValue(RpcBoxByref(Ptr(obj), lc)); + } + } + } + + return trivial; + } + + Value RpcUnboxValueByref(const Value& serializable, IRpcLifecycle* lc) + { + if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); + if (serializable.IsNull()) return serializable; + + if (IsRpcObjectReferenceValue(serializable)) + { + auto ref = GetRpcObjectReference(serializable); + auto obj = RpcUnboxByref(ref, lc); + return BoxValue(obj); + } + + return serializable; + } } @@ -2728,7 +3592,7 @@ namespace vl Value RpcByrefEnumerator::GetCurrent() { - return RpcUnboxValueByref(DeserializeValue(serializer, GetRemoteListOps(lifecycle, ref)->EnumGetCurrent(ref)), lifecycle); + return RpcUnboxValueByref(GetRemoteListOps(lifecycle, ref, serializer)->EnumGetCurrent(ref), lifecycle); } vint RpcByrefEnumerator::GetIndex() @@ -2738,7 +3602,7 @@ namespace vl bool RpcByrefEnumerator::Next() { - if (GetRemoteListOps(lifecycle, ref)->EnumNext(ref)) + if (GetRemoteListOps(lifecycle, ref, serializer)->EnumNext(ref)) { index++; return true; @@ -2767,7 +3631,7 @@ namespace vl Ptr RpcByrefEnumerable::CreateEnumerator() { - return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref)->EnumCreate(ref), serializer); + return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref, serializer)->EnumCreate(ref), serializer); } /*********************************************************************** @@ -2795,27 +3659,27 @@ namespace vl Ptr RpcByrefReadonlyList::CreateEnumerator() { - return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref)->EnumCreate(ref), serializer); + return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref, serializer)->EnumCreate(ref), serializer); } vint RpcByrefReadonlyList::GetCount() { - return GetRemoteListOps(lifecycle, ref)->ListGetCount(ref); + return GetRemoteListOps(lifecycle, ref, serializer)->ListGetCount(ref); } Value RpcByrefReadonlyList::Get(vint index) { - return RpcUnboxValueByref(DeserializeValue(serializer, GetRemoteListOps(lifecycle, ref)->ListGet(ref, index)), lifecycle); + return RpcUnboxValueByref(GetRemoteListOps(lifecycle, ref, serializer)->ListGet(ref, index), lifecycle); } bool RpcByrefReadonlyList::Contains(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListContains(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListContains(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefReadonlyList::IndexOf(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListIndexOf(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListIndexOf(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } /*********************************************************************** @@ -2858,33 +3722,33 @@ namespace vl void RpcByrefList::Set(vint index, const Value& value) { - GetRemoteListOps(lifecycle, ref)->ListSet(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + GetRemoteListOps(lifecycle, ref, serializer)->ListSet(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefList::Add(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListAdd(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListAdd(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefList::Insert(vint index, const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListInsert(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListInsert(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } bool RpcByrefList::Remove(const Value& value) { auto index = IndexOf(value); - return index == -1 ? false : GetRemoteListOps(lifecycle, ref)->ListRemoveAt(ref, index); + return index == -1 ? false : GetRemoteListOps(lifecycle, ref, serializer)->ListRemoveAt(ref, index); } bool RpcByrefList::RemoveAt(vint index) { - return GetRemoteListOps(lifecycle, ref)->ListRemoveAt(ref, index); + return GetRemoteListOps(lifecycle, ref, serializer)->ListRemoveAt(ref, index); } void RpcByrefList::Clear() { - GetRemoteListOps(lifecycle, ref)->ListClear(ref); + GetRemoteListOps(lifecycle, ref, serializer)->ListClear(ref); } /*********************************************************************** @@ -2912,42 +3776,37 @@ namespace vl Ptr RpcByrefArray::CreateEnumerator() { - return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref)->EnumCreate(ref), serializer); + return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref, serializer)->EnumCreate(ref), serializer); } vint RpcByrefArray::GetCount() { - return GetRemoteListOps(lifecycle, ref)->ListGetCount(ref); + return GetRemoteListOps(lifecycle, ref, serializer)->ListGetCount(ref); } Value RpcByrefArray::Get(vint index) { - return RpcUnboxValueByref(DeserializeValue(serializer, GetRemoteListOps(lifecycle, ref)->ListGet(ref, index)), lifecycle); + return RpcUnboxValueByref(GetRemoteListOps(lifecycle, ref, serializer)->ListGet(ref, index), lifecycle); } bool RpcByrefArray::Contains(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListContains(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListContains(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefArray::IndexOf(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListIndexOf(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListIndexOf(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } void RpcByrefArray::Set(vint index, const Value& value) { - GetRemoteListOps(lifecycle, ref)->ListSet(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + GetRemoteListOps(lifecycle, ref, serializer)->ListSet(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } void RpcByrefArray::Resize(vint size) { - auto count = GetCount(); - if (size > count) CHECK_FAIL(L"RpcByrefArray::Resize cannot grow."); - for (vint i = count - 1; i >= size; i--) - { - GetRemoteListOps(lifecycle, ref)->ListRemoveAt(ref, i); - } + GetRemoteListOps(lifecycle, ref, serializer)->ArrayResize(ref, size); } /*********************************************************************** @@ -2975,58 +3834,58 @@ namespace vl Ptr RpcByrefObservableList::CreateEnumerator() { - return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref)->EnumCreate(ref), serializer); + return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref, serializer)->EnumCreate(ref), serializer); } vint RpcByrefObservableList::GetCount() { - return GetRemoteListOps(lifecycle, ref)->ListGetCount(ref); + return GetRemoteListOps(lifecycle, ref, serializer)->ListGetCount(ref); } Value RpcByrefObservableList::Get(vint index) { - return RpcUnboxValueByref(DeserializeValue(serializer, GetRemoteListOps(lifecycle, ref)->ListGet(ref, index)), lifecycle); + return RpcUnboxValueByref(GetRemoteListOps(lifecycle, ref, serializer)->ListGet(ref, index), lifecycle); } bool RpcByrefObservableList::Contains(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListContains(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListContains(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefObservableList::IndexOf(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListIndexOf(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListIndexOf(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } void RpcByrefObservableList::Set(vint index, const Value& value) { - GetRemoteListOps(lifecycle, ref)->ListSet(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + GetRemoteListOps(lifecycle, ref, serializer)->ListSet(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefObservableList::Add(const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListAdd(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListAdd(ref, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } vint RpcByrefObservableList::Insert(vint index, const Value& value) { - return GetRemoteListOps(lifecycle, ref)->ListInsert(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->ListInsert(ref, index, SerializeValue(serializer, RpcBoxValueByref(value, lifecycle))); } bool RpcByrefObservableList::Remove(const Value& value) { auto index = IndexOf(value); - return index == -1 ? false : GetRemoteListOps(lifecycle, ref)->ListRemoveAt(ref, index); + return index == -1 ? false : GetRemoteListOps(lifecycle, ref, serializer)->ListRemoveAt(ref, index); } bool RpcByrefObservableList::RemoveAt(vint index) { - return GetRemoteListOps(lifecycle, ref)->ListRemoveAt(ref, index); + return GetRemoteListOps(lifecycle, ref, serializer)->ListRemoveAt(ref, index); } void RpcByrefObservableList::Clear() { - GetRemoteListOps(lifecycle, ref)->ListClear(ref); + GetRemoteListOps(lifecycle, ref, serializer)->ListClear(ref); } /*********************************************************************** @@ -3054,41 +3913,41 @@ namespace vl Ptr RpcByrefDictionary::GetKeys() { - return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref)->DictGetKeys(ref), serializer); + return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref, serializer)->DictGetKeys(ref), serializer); } Ptr RpcByrefDictionary::GetValues() { - return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref)->DictGetValues(ref), serializer); + return CreateTrackedProxy(lifecycle, GetRemoteListOps(lifecycle, ref, serializer)->DictGetValues(ref), serializer); } vint RpcByrefDictionary::GetCount() { - return GetRemoteListOps(lifecycle, ref)->DictGetCount(ref); + return GetRemoteListOps(lifecycle, ref, serializer)->DictGetCount(ref); } Value RpcByrefDictionary::Get(const Value& key) { auto serializedKey = SerializeValue(serializer, RpcBoxValueByref(key, lifecycle)); - auto serializedValue = GetRemoteListOps(lifecycle, ref)->DictGet(ref, serializedKey); - return RpcUnboxValueByref(DeserializeValue(serializer, serializedValue), lifecycle); + auto serializedValue = GetRemoteListOps(lifecycle, ref, serializer)->DictGet(ref, serializedKey); + return RpcUnboxValueByref(serializedValue, lifecycle); } void RpcByrefDictionary::Set(const Value& key, const Value& value) { auto serializedKey = SerializeValue(serializer, RpcBoxValueByref(key, lifecycle)); auto serializedValue = SerializeValue(serializer, RpcBoxValueByref(value, lifecycle)); - GetRemoteListOps(lifecycle, ref)->DictSet(ref, serializedKey, serializedValue); + GetRemoteListOps(lifecycle, ref, serializer)->DictSet(ref, serializedKey, serializedValue); } bool RpcByrefDictionary::Remove(const Value& key) { - return GetRemoteListOps(lifecycle, ref)->DictRemove(ref, SerializeValue(serializer, RpcBoxValueByref(key, lifecycle))); + return GetRemoteListOps(lifecycle, ref, serializer)->DictRemove(ref, SerializeValue(serializer, RpcBoxValueByref(key, lifecycle))); } void RpcByrefDictionary::Clear() { - GetRemoteListOps(lifecycle, ref)->DictClear(ref); + GetRemoteListOps(lifecycle, ref, serializer)->DictClear(ref); } /*********************************************************************** @@ -3112,8 +3971,7 @@ namespace vl } else { - CHECK_FAIL(L"RpcCalleeListOps::EnumCreate cannot find the target collection."); - return {}; + throw Exception(L"RpcCalleeListOps::EnumCreate cannot find the target collection."); } return lifecycle->PtrToRef(enumerator); @@ -3123,7 +3981,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(enumerator); auto e = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(e, L"RpcCalleeListOps::EnumNext cannot find the target enumerator."); + if (!e) throw Exception(L"RpcCalleeListOps::EnumNext cannot find the target enumerator."); return e->Next(); } @@ -3131,7 +3989,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(enumerator); auto e = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(e, L"RpcCalleeListOps::EnumGetCurrent cannot find the target enumerator."); + if (!e) throw Exception(L"RpcCalleeListOps::EnumGetCurrent cannot find the target enumerator."); return SerializeValue(serializer, RpcBoxValueByref(e->GetCurrent(), lifecycle)); } @@ -3140,8 +3998,7 @@ namespace vl auto obj = lifecycle->RefToPtr(ref); if (auto roList = Ptr(obj.Obj()->SafeAggregationCast())) return roList->GetCount(); - CHECK_FAIL(L"RpcCalleeListOps::ListGetCount cannot find the target list."); - return 0; + throw Exception(L"RpcCalleeListOps::ListGetCount cannot find the target list."); } Value RpcCalleeListOps::ListGet(RpcObjectReference ref, vint index) @@ -3149,8 +4006,7 @@ namespace vl auto obj = lifecycle->RefToPtr(ref); if (auto roList = Ptr(obj.Obj()->SafeAggregationCast())) return SerializeValue(serializer, RpcBoxValueByref(roList->Get(index), lifecycle)); - CHECK_FAIL(L"RpcCalleeListOps::ListGet cannot find the target list."); - return {}; + throw Exception(L"RpcCalleeListOps::ListGet cannot find the target list."); } void RpcCalleeListOps::ListSet(RpcObjectReference ref, vint index, const Value& value) @@ -3167,7 +4023,7 @@ namespace vl list->Set(index, trivial); return; } - CHECK_FAIL(L"RpcCalleeListOps::ListSet cannot find the target list."); + throw Exception(L"RpcCalleeListOps::ListSet cannot find the target list."); } vint RpcCalleeListOps::ListAdd(RpcObjectReference ref, const Value& value) @@ -3176,8 +4032,7 @@ namespace vl auto obj = lifecycle->RefToPtr(ref); if (auto list = Ptr(obj.Obj()->SafeAggregationCast())) return list->Add(trivial); - CHECK_FAIL(L"RpcCalleeListOps::ListAdd cannot find a writable list."); - return -1; + throw Exception(L"RpcCalleeListOps::ListAdd cannot find a writable list."); } vint RpcCalleeListOps::ListInsert(RpcObjectReference ref, vint index, const Value& value) @@ -3186,39 +4041,26 @@ namespace vl auto obj = lifecycle->RefToPtr(ref); if (auto list = Ptr(obj.Obj()->SafeAggregationCast())) return list->Insert(index, trivial); - CHECK_FAIL(L"RpcCalleeListOps::ListInsert cannot find a writable list."); - return -1; + throw Exception(L"RpcCalleeListOps::ListInsert cannot find a writable list."); } bool RpcCalleeListOps::ListRemoveAt(RpcObjectReference ref, vint index) { auto obj = lifecycle->RefToPtr(ref); - if (auto array = Ptr(obj.Obj()->SafeAggregationCast())) - { - CHECK_ERROR(index == array->GetCount() - 1, L"RpcCalleeListOps::ListRemoveAt only supports tail removal for arrays."); - array->Resize(index); - return true; - } if (auto list = Ptr(obj.Obj()->SafeAggregationCast())) return list->RemoveAt(index); - CHECK_FAIL(L"RpcCalleeListOps::ListRemoveAt cannot find the target list."); - return false; + throw Exception(L"RpcCalleeListOps::ListRemoveAt cannot find the target list."); } void RpcCalleeListOps::ListClear(RpcObjectReference ref) { auto obj = lifecycle->RefToPtr(ref); - if (auto array = Ptr(obj.Obj()->SafeAggregationCast())) - { - array->Resize(0); - return; - } if (auto list = Ptr(obj.Obj()->SafeAggregationCast())) { list->Clear(); return; } - CHECK_FAIL(L"RpcCalleeListOps::ListClear cannot find the target list."); + throw Exception(L"RpcCalleeListOps::ListClear cannot find the target list."); } bool RpcCalleeListOps::ListContains(RpcObjectReference ref, const Value& value) @@ -3227,8 +4069,7 @@ namespace vl auto obj = lifecycle->RefToPtr(ref); if (auto roList = Ptr(obj.Obj()->SafeAggregationCast())) return roList->Contains(trivial); - CHECK_FAIL(L"RpcCalleeListOps::ListContains cannot find the target list."); - return false; + throw Exception(L"RpcCalleeListOps::ListContains cannot find the target list."); } vint RpcCalleeListOps::ListIndexOf(RpcObjectReference ref, const Value& value) @@ -3237,15 +4078,22 @@ namespace vl auto obj = lifecycle->RefToPtr(ref); if (auto roList = Ptr(obj.Obj()->SafeAggregationCast())) return roList->IndexOf(trivial); - CHECK_FAIL(L"RpcCalleeListOps::ListIndexOf cannot find the target list."); - return -1; + throw Exception(L"RpcCalleeListOps::ListIndexOf cannot find the target list."); + } + + void RpcCalleeListOps::ArrayResize(RpcObjectReference ref, vint size) + { + auto obj = lifecycle->RefToPtr(ref); + auto array = Ptr(obj.Obj()->SafeAggregationCast()); + if (!array) throw Exception(L"RpcCalleeListOps::ArrayResize cannot find the target array."); + array->Resize(size); } vint RpcCalleeListOps::DictGetCount(RpcObjectReference ref) { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictGetCount cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictGetCount cannot find the target dictionary."); return dict->GetCount(); } @@ -3253,7 +4101,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictGet cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictGet cannot find the target dictionary."); auto trivialKey = RpcUnboxValueByref(DeserializeValue(serializer, key), lifecycle); return SerializeValue(serializer, RpcBoxValueByref(dict->Get(trivialKey), lifecycle)); } @@ -3262,7 +4110,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictSet cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictSet cannot find the target dictionary."); auto trivialKey = RpcUnboxValueByref(DeserializeValue(serializer, key), lifecycle); auto trivialValue = RpcUnboxValueByref(DeserializeValue(serializer, value), lifecycle); dict->Set(trivialKey, trivialValue); @@ -3272,7 +4120,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictRemove cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictRemove cannot find the target dictionary."); return dict->Remove(RpcUnboxValueByref(DeserializeValue(serializer, key), lifecycle)); } @@ -3280,7 +4128,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictClear cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictClear cannot find the target dictionary."); dict->Clear(); } @@ -3288,7 +4136,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictContainsKey cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictContainsKey cannot find the target dictionary."); return dict->GetKeys()->Contains(RpcUnboxValueByref(DeserializeValue(serializer, key), lifecycle)); } @@ -3296,7 +4144,7 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictGetKeys cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictGetKeys cannot find the target dictionary."); return lifecycle->PtrToRef(dict->GetKeys()); } @@ -3304,422 +4152,339 @@ namespace vl { auto obj = lifecycle->RefToPtr(ref); auto dict = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(dict, L"RpcCalleeListOps::DictGetValues cannot find the target dictionary."); + if (!dict) throw Exception(L"RpcCalleeListOps::DictGetValues cannot find the target dictionary."); return lifecycle->PtrToRef(dict->GetValues()); } /*********************************************************************** -* RpcCalleeListEventBridge +* RpcCalleeListEventOps ***********************************************************************/ - RpcCalleeListEventBridge::RpcCalleeListEventBridge(IRpcLifecycle* lc, IRpcSerializer* _serializer) + RpcCalleeListEventOps::RpcCalleeListEventOps(IRpcLifecycle* lc, IRpcSerializer* _serializer) : lifecycle(lc) , serializer(_serializer) { if (!lifecycle) CHECK_FAIL(L"Invalid IRpcLifecycle."); } - Value RpcCalleeListEventBridge::OnItemChanged(RpcObjectReference ref, vint index, vint oldCount, vint newCount) + Value RpcCalleeListEventOps::OnItemChanged(RpcObjectReference ref, vint index, vint oldCount, vint newCount) { - auto controller = lifecycle->GetController(); + struct SuppressFlag + { + IRpcLifecycle* lifecycle; + RpcObjectReference ref; + + SuppressFlag(IRpcLifecycle* _lifecycle, RpcObjectReference _ref) + : lifecycle(_lifecycle) + , ref(_ref) + { + lifecycle->GetController()->SetItemChangedSuppressedFlag(ref, true); + } + ~SuppressFlag() + { + lifecycle->GetController()->SetItemChangedSuppressedFlag(ref, false); + } + }; + auto obj = lifecycle->RefToPtr(ref); auto observable = Ptr(obj.Obj()->SafeAggregationCast()); - CHECK_ERROR(observable, L"RpcCalleeListEventBridge::OnItemChanged cannot find the target observable list."); - controller->SetItemChangedSuppressedFlag(ref, true); + CHECK_ERROR(observable, L"RpcCalleeListEventOps::OnItemChanged cannot find the target observable list."); + RpcEventExceptionMap exceptions; + { + SuppressFlag suppressFlag(lifecycle, ref); + try + { + observable->ItemChanged(index, oldCount, newCount); + } + catch (const Exception& ex) + { + exceptions = IValueDictionary::Create(); + exceptions->Set(BoxValue(lifecycle->GetClientId()), BoxValue(RpcException{ ex.Message() })); + } + } + return SerializeValue(serializer, BoxValue(exceptions)); + } + +/*********************************************************************** +* RpcCalleeObjectOpsForList +***********************************************************************/ + + RpcCalleeObjectOpsForList::RpcCalleeObjectOpsForList(Ptr _listOps, Ptr _objectOps, IRpcSerializer* _serializer) + : listOps(_listOps) + , objectOps(_objectOps) + , serializer(_serializer) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcCalleeObjectOpsForList::RpcCalleeObjectOpsForList(...)#" + CHECK_ERROR(listOps && objectOps, ERROR_MESSAGE_PREFIX L"List ops and object ops are required."); +#undef ERROR_MESSAGE_PREFIX + } + + Value RpcCalleeObjectOpsForList::InvokeMethod(RpcObjectReference ref, vint methodId, Ptr arguments) + { + if (!IsRpcListMethodId(methodId)) + { + return objectOps->InvokeMethod(ref, methodId, arguments); + } + try { - observable->ItemChanged(index, oldCount, newCount); + switch (methodId) + { + case RpcMethodId_IValueEnumerable_CreateEnumerator: + return SerializeValue(serializer, BoxValue(listOps->EnumCreate(ref))); + case RpcMethodId_IValueEnumerator_Next: + return SerializeValue(serializer, BoxValue(listOps->EnumNext(ref))); + case RpcMethodId_IValueEnumerator_GetCurrent: + return listOps->EnumGetCurrent(ref); + case RpcMethodId_IValueReadonlyList_GetCount: + return SerializeValue(serializer, BoxValue(listOps->ListGetCount(ref))); + case RpcMethodId_IValueReadonlyList_Get: + return listOps->ListGet(ref, UnboxValue(DeserializeValue(serializer, arguments->Get(0)))); + case RpcMethodId_IValueList_Set: + listOps->ListSet(ref, UnboxValue(DeserializeValue(serializer, arguments->Get(0))), arguments->Get(1)); + return SerializeValue(serializer, Value()); + case RpcMethodId_IValueList_Add: + return SerializeValue(serializer, BoxValue(listOps->ListAdd(ref, arguments->Get(0)))); + case RpcMethodId_IValueList_Insert: + return SerializeValue(serializer, BoxValue(listOps->ListInsert(ref, UnboxValue(DeserializeValue(serializer, arguments->Get(0))), arguments->Get(1)))); + case RpcMethodId_IValueList_RemoveAt: + return SerializeValue(serializer, BoxValue(listOps->ListRemoveAt(ref, UnboxValue(DeserializeValue(serializer, arguments->Get(0)))))); + case RpcMethodId_IValueList_Clear: + listOps->ListClear(ref); + return SerializeValue(serializer, Value()); + case RpcMethodId_IValueReadonlyList_Contains: + return SerializeValue(serializer, BoxValue(listOps->ListContains(ref, arguments->Get(0)))); + case RpcMethodId_IValueReadonlyList_IndexOf: + return SerializeValue(serializer, BoxValue(listOps->ListIndexOf(ref, arguments->Get(0)))); + case RpcMethodId_IValueReadonlyDictionary_GetCount: + return SerializeValue(serializer, BoxValue(listOps->DictGetCount(ref))); + case RpcMethodId_IValueReadonlyDictionary_Get: + return listOps->DictGet(ref, arguments->Get(0)); + case RpcMethodId_IValueDictionary_Set: + listOps->DictSet(ref, arguments->Get(0), arguments->Get(1)); + return SerializeValue(serializer, Value()); + case RpcMethodId_IValueDictionary_Remove: + return SerializeValue(serializer, BoxValue(listOps->DictRemove(ref, arguments->Get(0)))); + case RpcMethodId_IValueDictionary_Clear: + listOps->DictClear(ref); + return SerializeValue(serializer, Value()); + case RpcMethodId_IValueReadonlyDictionary_ContainsKey: + return SerializeValue(serializer, BoxValue(listOps->DictContainsKey(ref, arguments->Get(0)))); + case RpcMethodId_IValueReadonlyDictionary_GetKeys: + return SerializeValue(serializer, BoxValue(listOps->DictGetKeys(ref))); + case RpcMethodId_IValueReadonlyDictionary_GetValues: + return SerializeValue(serializer, BoxValue(listOps->DictGetValues(ref))); + case RpcMethodId_IValueArray_Resize: + listOps->ArrayResize(ref, UnboxValue(DeserializeValue(serializer, arguments->Get(0)))); + return SerializeValue(serializer, Value()); + } } catch (const Exception& ex) { - exceptions = CreateRpcEventExceptionMap(); - exceptions->Set(BoxValue(lifecycle->GetClientId()), BoxRpcException(RpcException{ ex.Message() })); + return SerializeValue(serializer, BoxValue(RpcException{ ex.Message() })); } - catch (...) - { - controller->SetItemChangedSuppressedFlag(ref, false); - throw; - } - controller->SetItemChangedSuppressedFlag(ref, false); - return SerializeValue(serializer, BoxRpcEventExceptionMap(exceptions)); + CHECK_FAIL(L"Unknown RPC list method id."); + return {}; } - + + void RpcCalleeObjectOpsForList::EndInvokeMethod(vint slot) + { + objectOps->EndInvokeMethod(slot); + } + + void RpcCalleeObjectOpsForList::ObjectHold(RpcObjectReference ref, vint remoteClientId, bool hold) + { + objectOps->ObjectHold(ref, remoteClientId, hold); + } + + void RpcCalleeObjectOpsForList::RegisterService(vint typeId, Ptr service) + { + objectOps->RegisterService(typeId, service); + } + /*********************************************************************** -* Helpers +* RpcCalleeObjectEventOpsForList ***********************************************************************/ - RpcObjectReference RpcBoxByref(Ptr trivial, IRpcLifecycle* lc) + RpcCalleeObjectEventOpsForList::RpcCalleeObjectEventOpsForList(Ptr _listEventOps, Ptr _objectEventOps, IRpcSerializer* _serializer) + : listEventOps(_listEventOps) + , objectEventOps(_objectEventOps) + , serializer(_serializer) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (!trivial) return {}; - return lc->PtrToRef(trivial); +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcCalleeObjectEventOpsForList::RpcCalleeObjectEventOpsForList(...)#" + CHECK_ERROR(listEventOps && objectEventOps, ERROR_MESSAGE_PREFIX L"List event ops and object event ops are required."); +#undef ERROR_MESSAGE_PREFIX } - Ptr RpcUnboxByref(RpcObjectReference serializable, IRpcLifecycle* lc) + Value RpcCalleeObjectEventOpsForList::InvokeEvent(RpcObjectReference ref, vint eventId, Ptr arguments) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (IsNullRpcObjectReference(serializable)) return nullptr; - return lc->RefToPtr(serializable); - } - - namespace - { - Value RpcBoxValueByref(const Value& trivial, IRpcLifecycle* lc) + if (eventId == RpcEventId_IValueObservableList_ItemChanged) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (trivial.IsNull()) return trivial; - - if (trivial.GetValueType() == Value::SharedPtr) - { - if (auto raw = trivial.GetRawPtr()) - { - if (auto obj = dynamic_cast(raw)) - { - return BoxValue(RpcBoxByref(Ptr(obj), lc)); - } - } - } - - return trivial; - } - - Value RpcUnboxValueByref(const Value& serializable, IRpcLifecycle* lc) - { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (serializable.IsNull()) return serializable; - - if (IsRpcObjectReferenceValue(serializable)) - { - auto ref = GetRpcObjectReference(serializable); - auto obj = RpcUnboxByref(ref, lc); - return obj ? BoxValue(obj) : Value{}; - } - - return serializable; + return listEventOps->OnItemChanged( + ref, + UnboxValue(DeserializeValue(serializer, arguments->Get(0))), + UnboxValue(DeserializeValue(serializer, arguments->Get(1))), + UnboxValue(DeserializeValue(serializer, arguments->Get(2))) + ); } + return objectEventOps->InvokeEvent(ref, eventId, arguments); } - namespace +/*********************************************************************** +* RpcCallerListOps +***********************************************************************/ + + RpcCallerListOps::RpcCallerListOps(IRpcObjectOps* _objectOps, IRpcSerializer* _serializer) + : objectOps(_objectOps) + , serializer(_serializer) { - template - Ptr TryGetValueInterface(const Value& value) - { - if (auto raw = value.GetRawPtr()) - { - return Ptr(dynamic_cast(raw)); - } - return nullptr; - } - - Value RpcCopyValueByvalInternal(const Value& trivial, Dictionary& visited) - { - if (trivial.IsNull()) return trivial; - if (trivial.GetValueType() == Value::SharedPtr) - { - if (auto roDict = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); - visited.Add(key, true); - - auto dict = IValueDictionary::Create(); - auto keys = roDict->GetKeys(); - for (vint i = 0; i < keys->GetCount(); i++) - { - auto dictKey = keys->Get(i); - dict->Set( - RpcCopyValueByvalInternal(dictKey, visited), - RpcCopyValueByvalInternal(roDict->Get(dictKey), visited) - ); - } - visited.Remove(key); - return BoxValue(dict); - } - - if (auto obsList = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); - visited.Add(key, true); - - auto list = IValueObservableList::Create(); - for (vint i = 0; i < obsList->GetCount(); i++) - { - list->Add(RpcCopyValueByvalInternal(obsList->Get(i), visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto array = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); - visited.Add(key, true); - - auto list = IValueArray::Create(); - list->Resize(array->GetCount()); - for (vint i = 0; i < array->GetCount(); i++) - { - list->Set(i, RpcCopyValueByvalInternal(array->Get(i), visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto roList = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval copying does not support cycles."); - visited.Add(key, true); - - auto list = IValueList::Create(); - for (vint i = 0; i < roList->GetCount(); i++) - { - list->Add(RpcCopyValueByvalInternal(roList->Get(i), visited)); - } - visited.Remove(key); - return BoxValue(list); - } - } - return trivial; - } - - Value RpcBoxValueByvalInternal(const Value& trivial, IRpcLifecycle* lc, Dictionary& visited) - { - if (trivial.IsNull()) return trivial; - if (trivial.GetValueType() == Value::SharedPtr) - { - if (auto roDict = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); - visited.Add(key, true); - - auto dict = IValueDictionary::Create(); - auto keys = roDict->GetKeys(); - for (vint i = 0; i < keys->GetCount(); i++) - { - auto dictKey = keys->Get(i); - dict->Set( - RpcBoxValueByvalInternal(dictKey, lc, visited), - RpcBoxValueByvalInternal(roDict->Get(dictKey), lc, visited) - ); - } - visited.Remove(key); - return BoxValue(dict); - } - - if (auto obsList = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); - visited.Add(key, true); - - auto list = IValueObservableList::Create(); - for (vint i = 0; i < obsList->GetCount(); i++) - { - list->Add(RpcBoxValueByvalInternal(obsList->Get(i), lc, visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto array = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); - visited.Add(key, true); - - auto list = IValueArray::Create(); - list->Resize(array->GetCount()); - for (vint i = 0; i < array->GetCount(); i++) - { - list->Set(i, RpcBoxValueByvalInternal(array->Get(i), lc, visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto roList = TryGetValueInterface(trivial)) - { - auto key = static_cast(trivial.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval boxing does not support cycles."); - visited.Add(key, true); - - auto list = IValueList::Create(); - for (vint i = 0; i < roList->GetCount(); i++) - { - list->Add(RpcBoxValueByvalInternal(roList->Get(i), lc, visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto raw = trivial.GetRawPtr()) - { - if (auto obj = dynamic_cast(raw)) - { - auto ref = lc->PtrToRef(Ptr(obj)); - return BoxValue(ref); - } - } - } - return trivial; - } +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcCallerListOps::RpcCallerListOps(...)#" + CHECK_ERROR(objectOps, ERROR_MESSAGE_PREFIX L"Object ops are required."); +#undef ERROR_MESSAGE_PREFIX } - Value RpcCopyByval(const Value& trivial, IRpcLifecycle* lc) + RpcObjectReference RpcCallerListOps::EnumCreate(RpcObjectReference ref) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (trivial.IsNull()) return {}; - Dictionary visited; - return RpcCopyValueByvalInternal(trivial, visited); + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueEnumerable_CreateEnumerator, CreateRpcArguments()); + return UnboxValue(result); } - Value RpcBoxByval(Ptr trivial, IRpcLifecycle* lc) + bool RpcCallerListOps::EnumNext(RpcObjectReference enumerator) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (!trivial) return {}; - return RpcBoxByval(BoxValue(trivial), lc); + auto result = InvokeListMethod(objectOps, serializer, enumerator, RpcMethodId_IValueEnumerator_Next, CreateRpcArguments()); + return UnboxValue(result); } - Value RpcBoxByval(const Value& trivial, IRpcLifecycle* lc) + Value RpcCallerListOps::EnumGetCurrent(RpcObjectReference enumerator) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (trivial.IsNull()) return {}; - Dictionary visited; - return RpcBoxValueByvalInternal(trivial, lc, visited); + return InvokeListMethod(objectOps, serializer, enumerator, RpcMethodId_IValueEnumerator_GetCurrent, CreateRpcArguments()); } - namespace + vint RpcCallerListOps::ListGetCount(RpcObjectReference ref) { - Value RpcUnboxValueByvalInternal(const Value& serializable, IRpcLifecycle* lc, Dictionary& visited) - { - if (serializable.IsNull()) return serializable; - - if (IsRpcObjectReferenceValue(serializable)) - { - auto ref = GetRpcObjectReference(serializable); - auto obj = RpcUnboxByref(ref, lc); - return obj ? BoxValue(obj) : Value{}; - } - - if (serializable.GetValueType() == Value::SharedPtr) - { - if (auto roDict = TryGetValueInterface(serializable)) - { - auto key = static_cast(serializable.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); - visited.Add(key, true); - - auto dict = IValueDictionary::Create(); - auto keys = roDict->GetKeys(); - for (vint i = 0; i < keys->GetCount(); i++) - { - auto dictKey = keys->Get(i); - dict->Set( - RpcUnboxValueByvalInternal(dictKey, lc, visited), - RpcUnboxValueByvalInternal(roDict->Get(dictKey), lc, visited) - ); - } - visited.Remove(key); - return BoxValue(dict); - } - - if (auto obsList = TryGetValueInterface(serializable)) - { - auto key = static_cast(serializable.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); - visited.Add(key, true); - - auto list = IValueObservableList::Create(); - for (vint i = 0; i < obsList->GetCount(); i++) - { - list->Add(RpcUnboxValueByvalInternal(obsList->Get(i), lc, visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto array = TryGetValueInterface(serializable)) - { - auto key = static_cast(serializable.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); - visited.Add(key, true); - - auto list = IValueArray::Create(); - list->Resize(array->GetCount()); - for (vint i = 0; i < array->GetCount(); i++) - { - list->Set(i, RpcUnboxValueByvalInternal(array->Get(i), lc, visited)); - } - visited.Remove(key); - return BoxValue(list); - } - - if (auto roList = TryGetValueInterface(serializable)) - { - auto key = static_cast(serializable.GetRawPtr()); - if (ContainsKey(visited, key)) CHECK_FAIL(L"Byval unboxing does not support cycles."); - visited.Add(key, true); - - auto list = IValueList::Create(); - for (vint i = 0; i < roList->GetCount(); i++) - { - list->Add(RpcUnboxValueByvalInternal(roList->Get(i), lc, visited)); - } - visited.Remove(key); - return BoxValue(list); - } - } - return serializable; - } + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyList_GetCount, CreateRpcArguments()); + return UnboxValue(result); } - Ptr RpcUnboxByval(const Value& serializable, IRpcLifecycle* lc) + Value RpcCallerListOps::ListGet(RpcObjectReference ref, vint index) { - if (!lc) CHECK_FAIL(L"IRpcLifecycle cannot be null."); - if (serializable.IsNull()) return nullptr; - Dictionary visited; - auto trivial = RpcUnboxValueByvalInternal(serializable, lc, visited); - if (auto raw = trivial.GetRawPtr()) - { - if (auto obj = dynamic_cast(raw)) - { - return Ptr(obj); - } - } - - CHECK_FAIL(L"Interface value or null is expected."); - return nullptr; + return InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyList_Get, CreateRpcArguments(SerializeValue(serializer, BoxValue(index)))); } - void ReadMethodException(const Value& value) + void RpcCallerListOps::ListSet(RpcObjectReference ref, vint index, const Value& value) { - if (value.GetValueType() == Value::BoxedValue) - { - if (auto boxed = value.GetBoxedValue().Cast>()) - { - throw Exception(boxed->value.message); - } - } + InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueList_Set, CreateRpcArguments(SerializeValue(serializer, BoxValue(index)), value)); } - void ReadEventException(RpcEventExceptionMap exceptions) + vint RpcCallerListOps::ListAdd(RpcObjectReference ref, const Value& value) { - if (!exceptions || exceptions->GetCount() == 0) return; + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueList_Add, CreateRpcArguments(value)); + return UnboxValue(result); + } - WString message; - auto keys = exceptions->GetKeys(); - for (vint i = 0; i < keys->GetCount(); i++) - { - auto key = keys->Get(i); - auto exception = UnboxValue(exceptions->Get(key)); - message += itow(UnboxValue(key)) + L":" + exception.message + L";"; - } - throw Exception(message); + vint RpcCallerListOps::ListInsert(RpcObjectReference ref, vint index, const Value& value) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueList_Insert, CreateRpcArguments(SerializeValue(serializer, BoxValue(index)), value)); + return UnboxValue(result); + } + + bool RpcCallerListOps::ListRemoveAt(RpcObjectReference ref, vint index) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueList_RemoveAt, CreateRpcArguments(SerializeValue(serializer, BoxValue(index)))); + return UnboxValue(result); + } + + void RpcCallerListOps::ListClear(RpcObjectReference ref) + { + InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueList_Clear, CreateRpcArguments()); + } + + bool RpcCallerListOps::ListContains(RpcObjectReference ref, const Value& value) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyList_Contains, CreateRpcArguments(value)); + return UnboxValue(result); + } + + vint RpcCallerListOps::ListIndexOf(RpcObjectReference ref, const Value& value) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyList_IndexOf, CreateRpcArguments(value)); + return UnboxValue(result); + } + + void RpcCallerListOps::ArrayResize(RpcObjectReference ref, vint size) + { + InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueArray_Resize, CreateRpcArguments(SerializeValue(serializer, BoxValue(size)))); + } + + vint RpcCallerListOps::DictGetCount(RpcObjectReference ref) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyDictionary_GetCount, CreateRpcArguments()); + return UnboxValue(result); + } + + Value RpcCallerListOps::DictGet(RpcObjectReference ref, const Value& key) + { + return InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyDictionary_Get, CreateRpcArguments(key)); + } + + void RpcCallerListOps::DictSet(RpcObjectReference ref, const Value& key, const Value& value) + { + InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueDictionary_Set, CreateRpcArguments(key, value)); + } + + bool RpcCallerListOps::DictRemove(RpcObjectReference ref, const Value& key) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueDictionary_Remove, CreateRpcArguments(key)); + return UnboxValue(result); + } + + void RpcCallerListOps::DictClear(RpcObjectReference ref) + { + InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueDictionary_Clear, CreateRpcArguments()); + } + + bool RpcCallerListOps::DictContainsKey(RpcObjectReference ref, const Value& key) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyDictionary_ContainsKey, CreateRpcArguments(key)); + return UnboxValue(result); + } + + RpcObjectReference RpcCallerListOps::DictGetKeys(RpcObjectReference ref) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyDictionary_GetKeys, CreateRpcArguments()); + return UnboxValue(result); + } + + RpcObjectReference RpcCallerListOps::DictGetValues(RpcObjectReference ref) + { + auto result = InvokeListMethod(objectOps, serializer, ref, RpcMethodId_IValueReadonlyDictionary_GetValues, CreateRpcArguments()); + return UnboxValue(result); + } + +/*********************************************************************** +* RpcCallerListEventOps +***********************************************************************/ + + RpcCallerListEventOps::RpcCallerListEventOps(IRpcObjectEventOps* _objectEventOps, IRpcSerializer* _serializer) + : objectEventOps(_objectEventOps) + , serializer(_serializer) + { +#define ERROR_MESSAGE_PREFIX L"vl::rpc_controller::RpcCallerListEventOps::RpcCallerListEventOps(...)#" + CHECK_ERROR(objectEventOps, ERROR_MESSAGE_PREFIX L"Object event ops are required."); +#undef ERROR_MESSAGE_PREFIX + } + + Value RpcCallerListEventOps::OnItemChanged(RpcObjectReference ref, vint index, vint oldCount, vint newCount) + { + auto arguments = CreateRpcArguments( + SerializeValue(serializer, BoxValue(index)), + SerializeValue(serializer, BoxValue(oldCount)), + SerializeValue(serializer, BoxValue(newCount)) + ); + auto result = objectEventOps->InvokeEvent(ref, RpcEventId_IValueObservableList_ItemChanged, arguments); + ReadEventException(UnboxValue(DeserializeValue(serializer, result))); + return result; } } } diff --git a/Import/VlppWorkflowLibrary.h b/Import/VlppWorkflowLibrary.h index 482daa26..6272ac8b 100644 --- a/Import/VlppWorkflowLibrary.h +++ b/Import/VlppWorkflowLibrary.h @@ -940,8 +940,6 @@ namespace vl { namespace rpc_controller { - class IRpcSerializer; - constexpr vint RpcTypeId_NotFound = -100; constexpr vint RpcClientId_Invalid = -1; constexpr vint RpcObjectId_Invalid = -1; @@ -964,12 +962,7 @@ namespace vl using RpcEventExceptionMap = Ptr; - extern reflection::description::Value BoxRpcObjectReference(RpcObjectReference ref); - extern reflection::description::Value BoxRpcException(RpcException exception); - extern RpcEventExceptionMap CreateRpcEventExceptionMap(); extern void MergeRpcEventExceptionMap(RpcEventExceptionMap target, RpcEventExceptionMap source); - extern reflection::description::Value BoxRpcEventExceptionMap(RpcEventExceptionMap exceptions); - extern RpcEventExceptionMap UnboxRpcEventExceptionMap(const reflection::description::Value& value); class RpcByvalReturnValue : public Object @@ -988,6 +981,43 @@ namespace vl inline constexpr vint RpcTypeId_IValueDictionary = -6; inline constexpr vint RpcTypeId_IValueReadonlyList = -7; + inline constexpr vint RpcMethodId_IValueEnumerable_CreateEnumerator = -1; + inline constexpr vint RpcMethodId_IValueEnumerator_Next = -2; + inline constexpr vint RpcMethodId_IValueEnumerator_GetCurrent = -3; + inline constexpr vint RpcMethodId_IValueReadonlyList_GetCount = -4; + inline constexpr vint RpcMethodId_IValueReadonlyList_Get = -5; + inline constexpr vint RpcMethodId_IValueList_Set = -6; + inline constexpr vint RpcMethodId_IValueList_Add = -7; + inline constexpr vint RpcMethodId_IValueList_Insert = -8; + inline constexpr vint RpcMethodId_IValueList_RemoveAt = -9; + inline constexpr vint RpcMethodId_IValueList_Clear = -10; + inline constexpr vint RpcMethodId_IValueReadonlyList_Contains = -11; + inline constexpr vint RpcMethodId_IValueReadonlyList_IndexOf = -12; + inline constexpr vint RpcMethodId_IValueReadonlyDictionary_GetCount = -13; + inline constexpr vint RpcMethodId_IValueReadonlyDictionary_Get = -14; + inline constexpr vint RpcMethodId_IValueDictionary_Set = -15; + inline constexpr vint RpcMethodId_IValueDictionary_Remove = -16; + inline constexpr vint RpcMethodId_IValueDictionary_Clear = -17; + inline constexpr vint RpcMethodId_IValueReadonlyDictionary_ContainsKey = -18; + inline constexpr vint RpcMethodId_IValueReadonlyDictionary_GetKeys = -19; + inline constexpr vint RpcMethodId_IValueReadonlyDictionary_GetValues = -20; + inline constexpr vint RpcMethodId_IValueArray_Resize = -21; + + inline constexpr vint RpcEventId_IValueObservableList_ItemChanged = -1; + +/*********************************************************************** +* Interfaces (Serialization) +***********************************************************************/ + + class IRpcSerializer + : public virtual reflection::IDescriptable + , public reflection::Description + { + public: + virtual reflection::description::Value Serialize(const reflection::description::Value& value) = 0; + virtual reflection::description::Value Deserialize(const reflection::description::Value& value) = 0; + }; + /*********************************************************************** * Interfaces (Operations) ***********************************************************************/ @@ -1010,6 +1040,7 @@ namespace vl virtual void ListClear(RpcObjectReference ref) = 0; virtual bool ListContains(RpcObjectReference ref, const reflection::description::Value& value) = 0; virtual vint ListIndexOf(RpcObjectReference ref, const reflection::description::Value& value) = 0; + virtual void ArrayResize(RpcObjectReference ref, vint size) = 0; virtual vint DictGetCount(RpcObjectReference ref) = 0; virtual reflection::description::Value DictGet(RpcObjectReference ref, const reflection::description::Value& key) = 0; @@ -1073,9 +1104,7 @@ namespace vl virtual void RegisterService(vint typeId, RpcObjectReference ref) = 0; virtual RpcObjectReference RequestService(vint typeId) = 0; - virtual IRpcListEventOps* BroadcastFromClient_ListEventOps(vint selfClientId) = 0; virtual IRpcObjectEventOps* BroadcastFromClient_ObjectEventOps(vint selfClientId) = 0; - virtual IRpcListOps* SendToClient_ListOps(vint targetClientId) = 0; virtual IRpcObjectOps* SendToClient_ObjectOps(vint targetClientId) = 0; }; @@ -1121,14 +1150,14 @@ namespace vl * Helpers ***********************************************************************/ - extern RpcObjectReference RpcBoxByref (Ptr trivial, IRpcLifecycle* lc); - extern Ptr RpcUnboxByref (RpcObjectReference serializable, IRpcLifecycle* lc); - extern reflection::description::Value RpcCopyByval (const reflection::description::Value& trivial, IRpcLifecycle* lc); - extern reflection::description::Value RpcBoxByval (Ptr trivial, IRpcLifecycle* lc); - extern reflection::description::Value RpcBoxByval (const reflection::description::Value& trivial, IRpcLifecycle* lc); - extern Ptr RpcUnboxByval (const reflection::description::Value& serializable, IRpcLifecycle* lc); - extern void ReadMethodException(const reflection::description::Value& value); - extern void ReadEventException(RpcEventExceptionMap exceptions); + extern RpcObjectReference RpcBoxByref(Ptr trivial, IRpcLifecycle* lc); + extern Ptr RpcUnboxByref(RpcObjectReference serializable, IRpcLifecycle* lc); + extern reflection::description::Value RpcCopyByval(const reflection::description::Value& trivial, IRpcLifecycle* lc); + extern reflection::description::Value RpcBoxByval(Ptr trivial, IRpcLifecycle* lc); + extern reflection::description::Value RpcBoxByval(const reflection::description::Value& trivial, IRpcLifecycle* lc); + extern Ptr RpcUnboxByval(const reflection::description::Value& serializable, IRpcLifecycle* lc); + extern void ReadMethodException(const reflection::description::Value& value); + extern void ReadEventException(RpcEventExceptionMap exceptions); } } @@ -1165,38 +1194,38 @@ namespace vl class RpcControllerDefault : public Object, public IRpcController { protected: - Ptr objectCallback; - Ptr eventCallback; - Ptr listCallback; - Ptr listEventCallback; - collections::Dictionary eventSuppressedFlags; - collections::Dictionary itemChangedSuppressedFlags; + Ptr objectCallback; + Ptr eventCallback; + Ptr listCallback; + Ptr listEventCallback; + collections::Dictionary eventSuppressedFlags; + collections::Dictionary itemChangedSuppressedFlags; template - static void SetSuppressedFlag(collections::Dictionary& flags, const TKey& key, bool suppressed); + static void SetSuppressedFlag(collections::Dictionary& flags, const TKey& key, bool suppressed); template - static bool GetSuppressedFlag(const collections::Dictionary& flags, const TKey& key); + static bool GetSuppressedFlag(const collections::Dictionary& flags, const TKey& key); public: RpcControllerDefault(); ~RpcControllerDefault(); - void Register(Ptr objectCallback, Ptr eventCallback, Ptr listCallback, Ptr listEventCallback); + void Register(Ptr objectCallback, Ptr eventCallback, Ptr listCallback, Ptr listEventCallback); // IRpcController - IRpcListOps* GetListOps()override; - IRpcObjectOps* GetObjectOps()override; - IRpcListEventOps* GetListEventOps()override; - IRpcObjectEventOps* GetObjectEventOps()override; + IRpcListOps* GetListOps()override; + IRpcObjectOps* GetObjectOps()override; + IRpcListEventOps* GetListEventOps()override; + IRpcObjectEventOps* GetObjectEventOps()override; - void Finalize()override; - void SetEventSuppressedFlag(RpcObjectReference ref, vint eventId, bool suppressed)override; - bool GetEventSuppressedFlag(RpcObjectReference ref, vint eventId)override; - void SetItemChangedSuppressedFlag(RpcObjectReference ref, bool suppressed)override; - bool GetItemChangedSuppressedFlag(RpcObjectReference ref)override; + void Finalize()override; + void SetEventSuppressedFlag(RpcObjectReference ref, vint eventId, bool suppressed)override; + bool GetEventSuppressedFlag(RpcObjectReference ref, vint eventId)override; + void SetItemChangedSuppressedFlag(RpcObjectReference ref, bool suppressed)override; + bool GetItemChangedSuppressedFlag(RpcObjectReference ref)override; }; } } @@ -1227,7 +1256,56 @@ namespace vl using RpcJsonDeserializeCallback = Func)>; extern Ptr JsonSerializePredefinedTypes(const reflection::description::Value& value, const RpcJsonSerializeCallback& rpcjson_Serialize); - extern reflection::description::Value JsonDeserializePredefinedTypes(const reflection::description::Value& value, const RpcJsonDeserializeCallback& rpcjson_Deserialize); + extern reflection::description::Value JsonDeserializePredefinedTypes(const reflection::description::Value& value, const RpcJsonDeserializeCallback& rpcjson_Deserialize); + + class IRpcJsonMessageDispatcher + : public virtual reflection::IDescriptable + , public reflection::Description + { + public: + virtual vint AllocateRequestId() = 0; + virtual Ptr OnJsonRequest(Ptr message) = 0; + }; + + class RpcJsonObjectOps : public Object, public IRpcObjectOps + { + private: + vint sourceClientId = RpcClientId_Invalid; + vint targetClientId = RpcClientId_Invalid; + IRpcJsonMessageDispatcher* dispatcher = nullptr; + IRpcLifecycle* lifecycle = nullptr; + + public: + RpcJsonObjectOps(IRpcJsonMessageDispatcher* _dispatcher); + RpcJsonObjectOps(vint _sourceClientId, vint _targetClientId, IRpcJsonMessageDispatcher* _dispatcher, IRpcLifecycle* _lifecycle = nullptr); + ~RpcJsonObjectOps(); + + reflection::description::Value InvokeMethod(RpcObjectReference ref, vint methodId, Ptr arguments)override; + void EndInvokeMethod(vint slot)override; + void ObjectHold(RpcObjectReference ref, vint remoteClientId, bool hold)override; + void RegisterService(vint typeId, Ptr service)override; + + static Ptr Translate(Ptr message, IRpcObjectOps* ops, IRpcLifecycle* lifecycle = nullptr); + }; + + class RpcJsonObjectEventOps : public Object, public IRpcObjectEventOps + { + private: + vint sourceClientId = RpcClientId_Invalid; + IRpcJsonMessageDispatcher* dispatcher = nullptr; + + public: + RpcJsonObjectEventOps(IRpcJsonMessageDispatcher* _dispatcher); + RpcJsonObjectEventOps(vint _sourceClientId, IRpcJsonMessageDispatcher* _dispatcher); + ~RpcJsonObjectEventOps(); + + reflection::description::Value InvokeEvent(RpcObjectReference ref, vint eventId, Ptr arguments)override; + + static Ptr Translate(Ptr message, IRpcObjectEventOps* ops); + }; + + extern vint ReadRequestId(Ptr message); + extern void WriteRequestId(Ptr message, vint requestId); } } @@ -1386,19 +1464,6 @@ namespace vl namespace rpc_controller { -/*********************************************************************** -* Serialization -***********************************************************************/ - - class IRpcSerializer - : public virtual reflection::IDescriptable - , public reflection::Description - { - public: - virtual reflection::description::Value Serialize(const reflection::description::Value& value) = 0; - virtual reflection::description::Value Deserialize(const reflection::description::Value& value) = 0; - }; - /*********************************************************************** * Collection Caller Wrappers ***********************************************************************/ @@ -1561,6 +1626,7 @@ namespace vl void ListClear(RpcObjectReference ref)override; bool ListContains(RpcObjectReference ref, const reflection::description::Value& value)override; vint ListIndexOf(RpcObjectReference ref, const reflection::description::Value& value)override; + void ArrayResize(RpcObjectReference ref, vint size)override; vint DictGetCount(RpcObjectReference ref)override; reflection::description::Value DictGet(RpcObjectReference ref, const reflection::description::Value& key)override; @@ -1572,14 +1638,89 @@ namespace vl RpcObjectReference DictGetValues(RpcObjectReference ref)override; }; - class RpcCalleeListEventBridge : public Object, public IRpcListEventOps + class RpcCalleeListEventOps : public Object, public IRpcListEventOps { private: IRpcLifecycle* lifecycle = nullptr; IRpcSerializer* serializer = nullptr; public: - RpcCalleeListEventBridge(IRpcLifecycle* lc, IRpcSerializer* _serializer); + RpcCalleeListEventOps(IRpcLifecycle* lc, IRpcSerializer* _serializer); + + reflection::description::Value OnItemChanged(RpcObjectReference ref, vint index, vint oldCount, vint newCount)override; + }; + + class RpcCalleeObjectOpsForList : public Object, public IRpcObjectOps + { + private: + Ptr listOps; + Ptr objectOps; + IRpcSerializer* serializer = nullptr; + + public: + RpcCalleeObjectOpsForList(Ptr _listOps, Ptr _objectOps, IRpcSerializer* _serializer); + + reflection::description::Value InvokeMethod(RpcObjectReference ref, vint methodId, Ptr arguments)override; + void EndInvokeMethod(vint slot)override; + void ObjectHold(RpcObjectReference ref, vint remoteClientId, bool hold)override; + void RegisterService(vint typeId, Ptr service)override; + }; + + class RpcCalleeObjectEventOpsForList : public Object, public IRpcObjectEventOps + { + private: + Ptr listEventOps; + Ptr objectEventOps; + IRpcSerializer* serializer = nullptr; + + public: + RpcCalleeObjectEventOpsForList(Ptr _listEventOps, Ptr _objectEventOps, IRpcSerializer* _serializer); + + reflection::description::Value InvokeEvent(RpcObjectReference ref, vint eventId, Ptr arguments)override; + }; + + class RpcCallerListOps : public Object, public IRpcListOps + { + private: + IRpcObjectOps* objectOps = nullptr; + IRpcSerializer* serializer = nullptr; + + public: + RpcCallerListOps(IRpcObjectOps* _objectOps, IRpcSerializer* _serializer); + + RpcObjectReference EnumCreate(RpcObjectReference ref)override; + bool EnumNext(RpcObjectReference enumerator)override; + reflection::description::Value EnumGetCurrent(RpcObjectReference enumerator)override; + + vint ListGetCount(RpcObjectReference ref)override; + reflection::description::Value ListGet(RpcObjectReference ref, vint index)override; + void ListSet(RpcObjectReference ref, vint index, const reflection::description::Value& value)override; + vint ListAdd(RpcObjectReference ref, const reflection::description::Value& value)override; + vint ListInsert(RpcObjectReference ref, vint index, const reflection::description::Value& value)override; + bool ListRemoveAt(RpcObjectReference ref, vint index)override; + void ListClear(RpcObjectReference ref)override; + bool ListContains(RpcObjectReference ref, const reflection::description::Value& value)override; + vint ListIndexOf(RpcObjectReference ref, const reflection::description::Value& value)override; + void ArrayResize(RpcObjectReference ref, vint size)override; + + vint DictGetCount(RpcObjectReference ref)override; + reflection::description::Value DictGet(RpcObjectReference ref, const reflection::description::Value& key)override; + void DictSet(RpcObjectReference ref, const reflection::description::Value& key, const reflection::description::Value& value)override; + bool DictRemove(RpcObjectReference ref, const reflection::description::Value& key)override; + void DictClear(RpcObjectReference ref)override; + bool DictContainsKey(RpcObjectReference ref, const reflection::description::Value& key)override; + RpcObjectReference DictGetKeys(RpcObjectReference ref)override; + RpcObjectReference DictGetValues(RpcObjectReference ref)override; + }; + + class RpcCallerListEventOps : public Object, public IRpcListEventOps + { + private: + IRpcObjectEventOps* objectEventOps = nullptr; + IRpcSerializer* serializer = nullptr; + + public: + RpcCallerListEventOps(IRpcObjectEventOps* _objectEventOps, IRpcSerializer* _serializer); reflection::description::Value OnItemChanged(RpcObjectReference ref, vint index, vint oldCount, vint newCount)override; }; @@ -1655,6 +1796,7 @@ Predefined Types F(vl::rpc_controller::RpcException)\ F(vl::rpc_controller::RpcByvalReturnValue)\ F(vl::rpc_controller::IRpcSerializer)\ + F(vl::rpc_controller::IRpcJsonMessageDispatcher)\ F(vl::rpc_controller::IRpcListOps)\ F(vl::rpc_controller::IRpcListEventOps)\ F(vl::rpc_controller::IRpcObjectOps)\ @@ -1698,6 +1840,18 @@ Interface Implementation Proxy (Implement) } END_INTERFACE_PROXY(vl::rpc_controller::IRpcSerializer) + BEGIN_INTERFACE_PROXY_NOPARENT_SHAREDPTR(vl::rpc_controller::IRpcJsonMessageDispatcher) + vl::vint AllocateRequestId()override + { + INVOKEGET_INTERFACE_PROXY_NOPARAMS(AllocateRequestId); + } + + vl::Ptr OnJsonRequest(vl::Ptr message)override + { + INVOKEGET_INTERFACE_PROXY(OnJsonRequest, message); + } + END_INTERFACE_PROXY(vl::rpc_controller::IRpcJsonMessageDispatcher) + BEGIN_INTERFACE_PROXY_NOPARENT_SHAREDPTR(vl::rpc_controller::IRpcListOps) vl::rpc_controller::RpcObjectReference EnumCreate(vl::rpc_controller::RpcObjectReference ref)override { @@ -1759,6 +1913,11 @@ Interface Implementation Proxy (Implement) INVOKEGET_INTERFACE_PROXY(ListIndexOf, ref, value); } + void ArrayResize(vl::rpc_controller::RpcObjectReference ref, vl::vint size)override + { + INVOKE_INTERFACE_PROXY(ArrayResize, ref, size); + } + vl::vint DictGetCount(vl::rpc_controller::RpcObjectReference ref)override { INVOKEGET_INTERFACE_PROXY(DictGetCount, ref); diff --git a/Tools/Reflection32.bin b/Tools/Reflection32.bin index 136f63eb56868dd62c97aefc5d9fd48c197939b7..e7c21dd14f33f46a9b3d83e1bfd387d12b89add6 100644 GIT binary patch delta 73261 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 G{Qm$&4m3;v delta 74075 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 MhSASu{Jx0)18W`xWB>pF diff --git a/Tools/Reflection64.bin b/Tools/Reflection64.bin index 4fd2693f43cbf39fb62a2d3f2a140f001da32ce9..434de46905dc51d6d92f1da5842803cd74960291 100644 GIT binary patch delta 73211 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 delta 74111 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%0ANd5^rdB}&#siu|suJW6+ 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 +#include #elif defined VCZH_GCC #include #endif using namespace vl::collections; using namespace vl::stream; +#if defined VCZH_MSVC +using namespace vl::inter_process; +#endif class ViewModel : public Object, public demo::IViewModel { @@ -19,10 +22,26 @@ public: // This is a fake progress, it is just for demo #if defined VCZH_MSVC HttpRequest request; - request.SetHost(L"http://www.microsoft.com/"); + request.query = L"/"; - HttpResponse response; - HttpQuery(request, response); + WString responseText; + EventObject completed; + completed.CreateManualUnsignal(false); + HttpClientApi client(L"www.microsoft.com", 80); + client.HttpQuery(request, [&](Variant result) + { + if (auto error = result.TryGet()) + { + responseText = error->message; + } + else + { + responseText = result.Get().GetBodyUtf8(); + } + completed.Signal(); + }); + completed.Wait(); + client.Stop(); progress(1); for (vint i = 2; i <= 10; i++) @@ -31,7 +50,7 @@ public: progress(i); } - callback(response.GetBodyUtf8()); + callback(responseText); #elif defined VCZH_GCC progress(1); for (vint i = 2; i <= 10; i+=2) @@ -55,4 +74,4 @@ void GuiMain() demo::MainWindow window(Ptr(new ViewModel)); window.MoveToScreenCenter(); GetApplication()->Run(&window); -} \ No newline at end of file +}