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 136f63eb..e7c21dd1 100644 Binary files a/Tools/Reflection32.bin and b/Tools/Reflection32.bin differ diff --git a/Tools/Reflection64.bin b/Tools/Reflection64.bin index 4fd2693f..434de469 100644 Binary files a/Tools/Reflection64.bin and b/Tools/Reflection64.bin differ diff --git a/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp b/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp index 9abe9d77..2fb0377b 100644 --- a/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp +++ b/Tutorial/GacUI_Controls/ProgressAndAsync/Main.cpp @@ -1,13 +1,16 @@ #define GAC_HEADER_USE_NAMESPACE #include "UI/Source/Demo.h" #if defined VCZH_MSVC -#include +#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 +}