From 628c6cd498105327c1b4d6db6fc4472db4730507 Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Sun, 5 Jan 2020 23:13:13 +0900 Subject: [PATCH 01/11] Add tsm::IAsyncDiapatcher interface and default implementation. --- StateMachine/AsyncStateMachine.cpp | 16 +++++++++++++++- StateMachine/Handles.h | 1 + public/include/StateMachine/Interface.h | 9 +++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/StateMachine/AsyncStateMachine.cpp b/StateMachine/AsyncStateMachine.cpp index f91c278..55e7d34 100644 --- a/StateMachine/AsyncStateMachine.cpp +++ b/StateMachine/AsyncStateMachine.cpp @@ -16,6 +16,15 @@ namespace tsm { return context->isAsync() ? new AsyncStateMachine() : new StateMachine(); } +class DefaultAsyncDispatcher : public IAsyncDispatcher +{ +public: + virtual HANDLE dispatch(Method method, LPVOID param) override + { + return CreateThread(nullptr, 0, method, param, 0, nullptr); + } +}; + HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEvent * event) { // Ensure to release object on error. @@ -38,7 +47,12 @@ HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEve asyncData->hEventShutdown.Attach(CreateEvent(NULL, TRUE, FALSE, NULL)); // param will be deleted in worker thread. auto param = new SetupParam(context, this, event); - asyncData->hWorkerThread.Attach(CreateThread(nullptr, 0, workerThreadProc, param, 0, nullptr)); + + auto& dispatcher = asyncData->asyncDispatcher; + if(!dispatcher) { + dispatcher.reset(new DefaultAsyncDispatcher()); + } + asyncData->hWorkerThread.Attach(dispatcher->dispatch(workerThreadProc, param)); if(!asyncData->hWorkerThread) { delete param; return HRESULT_FROM_WIN32(GetLastError()); diff --git a/StateMachine/Handles.h b/StateMachine/Handles.h index 254a509..fa450e1 100644 --- a/StateMachine/Handles.h +++ b/StateMachine/Handles.h @@ -32,6 +32,7 @@ struct ContextHandle // Data for IAsyncContext used to perform async operation. struct AsyncData { + std::unique_ptr asyncDispatcher; std::deque> eventQueue; // FIFO of IEvent to be handled. std::recursive_mutex eventQueueLock; // Lock to modify IEvent queue. CHandle hEventReady; // Event handle set when ready to handle IEvent(entry() of Initial state completes). diff --git a/public/include/StateMachine/Interface.h b/public/include/StateMachine/Interface.h index 144ffd0..0d1ee96 100644 --- a/public/include/StateMachine/Interface.h +++ b/public/include/StateMachine/Interface.h @@ -38,12 +38,21 @@ class HandleOwner std::unique_ptr m_handle; }; +class IAsyncDispatcher +{ +public: + using Method = DWORD(WINAPI *)(LPVOID lpParam); + virtual HANDLE dispatch(Method, LPVOID) = 0; +}; + class IContext : public HandleOwner { public: virtual ~IContext() {} virtual bool isAsync() const = 0; + virtual IAsyncDispatcher* _getAsyncDispatcher() { return nullptr; } + virtual void _setAsyncDispatcher(IAsyncDispatcher* disp) {}; virtual IStateMachine* _getStateMachine() = 0; virtual IState* _getCurrentState() = 0; From 2d14b2dbc322128f10284d4accda98783f95e0ed Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Tue, 7 Jan 2020 22:28:12 +0900 Subject: [PATCH 02/11] Implemented StateMachine.NET::AsyncDispatcher to call StateMachine on native thread. * Running test in Test Explorer passed. * Debugging test in Test Explorer thorws invalid handle exception at calling `CloseHandle(exitThreadEvent)` in StateMachine.NET::ManagedDispatcher::!ManagedDispatcher(). * nunit3-console.exe failed by exception. --- StateMachine.NET.UnitTest/Class1.cs | 1 - StateMachine.NET.UnitTest/Class2.cs | 4 +- StateMachine.NET/NativeObjects.cpp | 57 +++++++++++++++++++++++++ StateMachine.NET/NativeObjects.h | 1 + StateMachine/AsyncStateMachine.cpp | 14 +----- public/include/StateMachine/Context.h | 13 ++++++ public/include/StateMachine/Interface.h | 19 +++++++-- 7 files changed, 91 insertions(+), 18 deletions(-) diff --git a/StateMachine.NET.UnitTest/Class1.cs b/StateMachine.NET.UnitTest/Class1.cs index 7dc11d2..0f2d771 100644 --- a/StateMachine.NET.UnitTest/Class1.cs +++ b/StateMachine.NET.UnitTest/Class1.cs @@ -1,7 +1,6 @@ using NSubstitute; using NUnit.Framework; using System; -using System.Diagnostics; using System.Threading; using tsm_NET.Generic; diff --git a/StateMachine.NET.UnitTest/Class2.cs b/StateMachine.NET.UnitTest/Class2.cs index b9f2b24..a38972a 100644 --- a/StateMachine.NET.UnitTest/Class2.cs +++ b/StateMachine.NET.UnitTest/Class2.cs @@ -1,6 +1,6 @@ using NSubstitute; using NUnit.Framework; -using System.Diagnostics; +using System; using System.Threading; using tsm_NET; @@ -26,7 +26,7 @@ public void BasicTest() .handleEvent(Arg.Is(c), Arg.Is(mockEvent), ref Arg.Is((State)null)) .Returns(x => { - Trace.WriteLine($"{mockInitialState.GetType()}.handleEvent({x[0]}) is called."); + Console.WriteLine($"{mockInitialState.GetType()}.handleEvent({x[0]}) is called."); x[2] = mockNextState; return HResult.Ok; }); diff --git a/StateMachine.NET/NativeObjects.cpp b/StateMachine.NET/NativeObjects.cpp index 0f4fc13..3b9e0fd 100644 --- a/StateMachine.NET/NativeObjects.cpp +++ b/StateMachine.NET/NativeObjects.cpp @@ -60,6 +60,63 @@ void StateMonitor::onWorkerThreadExit(tsm::IContext* context, HRESULT exitCode) m_onWorkerThreadExitCallback(context, exitCode); } +/** + * Managed class to dispatch tsm::IAsyncDispatcher::Method(lpParam) in managed worker thread. + */ +ref class ManagedDispatcher +{ +public: + ManagedDispatcher(tsm::IAsyncDispatcher::Method method, LPVOID lpParam) + : method(method), lpParam(lpParam) + { + exitThreadEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); + auto threadStart = gcnew Threading::ThreadStart(this, &ManagedDispatcher::threadMethod); + auto thread = gcnew Threading::Thread(threadStart); + thread->Start(); + } + + ~ManagedDispatcher() + { + this->!ManagedDispatcher(); + } + + !ManagedDispatcher() + { + CloseHandle(exitThreadEvent); + } + + HANDLE exitThreadEvent; + +protected: + tsm::IAsyncDispatcher::Method method; + LPVOID lpParam; + + // Method called in the managed worker thread. + void threadMethod() { + method(lpParam); + + // Set event to notify that worker thread exit. + SetEvent(exitThreadEvent); + } +}; + +/** + * Implementation of tsm::IAsyncDispatcher + */ +class AsyncDispatcher : public tsm::IAsyncDispatcher +{ +public: + virtual HANDLE dispatch(Method method, LPVOID lpParam) override { + auto md = gcnew ManagedDispatcher(method, lpParam); + return md->exitThreadEvent; + } +}; + +tsm::IAsyncDispatcher* Context::_createAsyncDispatcher() +{ + return isAsync() ? new AsyncDispatcher() : nullptr; +} + Context::Context(ManagedType^ context, bool isAsync /*= true*/) : m_isAsync(isAsync) , m_managedContext(context) diff --git a/StateMachine.NET/NativeObjects.h b/StateMachine.NET/NativeObjects.h index c086351..fe4fc09 100644 --- a/StateMachine.NET/NativeObjects.h +++ b/StateMachine.NET/NativeObjects.h @@ -49,6 +49,7 @@ class Context : public tsm::IContext, public tsm::TimerClient Context(ManagedType^ context, bool isAsync = true); virtual bool isAsync() const override { return m_isAsync; } + virtual tsm::IAsyncDispatcher* _createAsyncDispatcher() override; HRESULT setup(tsm::IState* initialState, tsm::IEvent* event = nullptr) { return _getStateMachine()->setup(this, initialState, event); } HRESULT shutdown(DWORD timeout = 100) { return _getStateMachine()->shutdown(this, timeout); } diff --git a/StateMachine/AsyncStateMachine.cpp b/StateMachine/AsyncStateMachine.cpp index 55e7d34..f082db2 100644 --- a/StateMachine/AsyncStateMachine.cpp +++ b/StateMachine/AsyncStateMachine.cpp @@ -16,15 +16,6 @@ namespace tsm { return context->isAsync() ? new AsyncStateMachine() : new StateMachine(); } -class DefaultAsyncDispatcher : public IAsyncDispatcher -{ -public: - virtual HANDLE dispatch(Method method, LPVOID param) override - { - return CreateThread(nullptr, 0, method, param, 0, nullptr); - } -}; - HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEvent * event) { // Ensure to release object on error. @@ -49,9 +40,8 @@ HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEve auto param = new SetupParam(context, this, event); auto& dispatcher = asyncData->asyncDispatcher; - if(!dispatcher) { - dispatcher.reset(new DefaultAsyncDispatcher()); - } + dispatcher.reset(context->_createAsyncDispatcher()); + HR_ASSERT(dispatcher, E_POINTER); asyncData->hWorkerThread.Attach(dispatcher->dispatch(workerThreadProc, param)); if(!asyncData->hWorkerThread) { delete param; diff --git a/public/include/StateMachine/Context.h b/public/include/StateMachine/Context.h index 2256ff7..8b0c687 100644 --- a/public/include/StateMachine/Context.h +++ b/public/include/StateMachine/Context.h @@ -13,6 +13,7 @@ class Context : public IContext, public TimerClient virtual ~Context() {} virtual bool isAsync() const { return false; } + virtual IAsyncDispatcher* _createAsyncDispatcher() override { return nullptr; } HRESULT setup(S* initialState, E* event = nullptr) { return _getStateMachine()->setup(this, initialState, event); } HRESULT shutdown(DWORD timeout = 100) { return _getStateMachine()->shutdown(this, timeout); } @@ -43,10 +44,22 @@ class Context : public IContext, public TimerClient template class AsyncContext : public Context { + class AsyncDispatcher : public IAsyncDispatcher + { + public: + virtual HANDLE dispatch(Method method, LPVOID param) override + { + return CreateThread(nullptr, 0, method, param, 0, nullptr); + } + }; + public: virtual ~AsyncContext() {} virtual bool isAsync() const { return true; } + virtual IAsyncDispatcher* _createAsyncDispatcher() override { + return new AsyncDispatcher(); + } }; } diff --git a/public/include/StateMachine/Interface.h b/public/include/StateMachine/Interface.h index 0d1ee96..2543b69 100644 --- a/public/include/StateMachine/Interface.h +++ b/public/include/StateMachine/Interface.h @@ -38,11 +38,25 @@ class HandleOwner std::unique_ptr m_handle; }; +/** + * IAsyncDispatcher interface + */ class IAsyncDispatcher { public: + /** + * Signature of method to be dispatched. + */ using Method = DWORD(WINAPI *)(LPVOID lpParam); - virtual HANDLE dispatch(Method, LPVOID) = 0; + + /** + * dispathc method. + * + * method: Method to be dispatched. + * lpParam: Pointer to parameter to be passed to `method`. + * Returns event handle that is set when the method terminates. + */ + virtual HANDLE dispatch(Method method, LPVOID lpParam) = 0; }; class IContext : public HandleOwner @@ -51,8 +65,7 @@ class IContext : public HandleOwner virtual ~IContext() {} virtual bool isAsync() const = 0; - virtual IAsyncDispatcher* _getAsyncDispatcher() { return nullptr; } - virtual void _setAsyncDispatcher(IAsyncDispatcher* disp) {}; + virtual IAsyncDispatcher* _createAsyncDispatcher() = 0; virtual IStateMachine* _getStateMachine() = 0; virtual IState* _getCurrentState() = 0; From c3fdf9d38ae4ead7a5de69fcd15a521c23759d9f Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Tue, 7 Jan 2020 22:47:56 +0900 Subject: [PATCH 03/11] Remove build event that was changed by commit 8fef9af27c40f2ac5ca6258ffff1413d18bfcbd6. * Test by nunit3-console.exe passed. * Debuggin test in Test Explorer still fails. --- StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj b/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj index c757e63..1304aad 100644 --- a/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj +++ b/StateMachine.NET.UnitTest/StateMachine.NET.UnitTest.csproj @@ -88,6 +88,7 @@ - xcopy /D /Y "$(SolutionDir)packages\NUnit.ConsoleRunner.3.10.0\tools\*.*" "$(TargetDir)" + + \ No newline at end of file From 10b7f42cbd3229a1277630ad9882c7efc2ee93a8 Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Tue, 7 Jan 2020 22:54:19 +0900 Subject: [PATCH 04/11] Fix timing issue of StateMachine.NET.UnitTest. --- StateMachine.NET.UnitTest/Class1.cs | 5 +++-- StateMachine.NET.UnitTest/Class2.cs | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/StateMachine.NET.UnitTest/Class1.cs b/StateMachine.NET.UnitTest/Class1.cs index 0f2d771..dea269e 100644 --- a/StateMachine.NET.UnitTest/Class1.cs +++ b/StateMachine.NET.UnitTest/Class1.cs @@ -142,8 +142,6 @@ public void BasicTest() // Check calls to methods of IStateMonitor. Received.InOrder(() => { - mockStateMonitor.Received() - .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); mockStateMonitor.Received() .onEventHandling(Arg.Is(c), Arg.Is(mockEvent), Arg.Is(mockInitialState)); mockStateMonitor.Received() @@ -156,6 +154,9 @@ public void BasicTest() // onStateChanged() caused by Context.setup() might be called before or after onEventTriggerd(). mockStateMonitor.Received() .onStateChanged(Arg.Is(c), Arg.Is(Event.Null), Arg.Is(State.Null), Arg.Is(mockInitialState)); + // onEventTriggered() might be called before or after onEventHandling(). + mockStateMonitor.Received() + .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); } } } diff --git a/StateMachine.NET.UnitTest/Class2.cs b/StateMachine.NET.UnitTest/Class2.cs index a38972a..30a8f5b 100644 --- a/StateMachine.NET.UnitTest/Class2.cs +++ b/StateMachine.NET.UnitTest/Class2.cs @@ -62,8 +62,6 @@ public void BasicTest() // Check calls to methods of IStateMonitor. Received.InOrder(() => { - mockStateMonitor.Received() - .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); mockStateMonitor.Received() .onEventHandling(Arg.Is(c), Arg.Is(mockEvent), Arg.Is(mockInitialState)); mockStateMonitor.Received() @@ -76,6 +74,9 @@ public void BasicTest() // onStateChanged() caused by Context.setup() might be called before or after onEventTriggerd(). mockStateMonitor.Received() .onStateChanged(Arg.Is(c), Arg.Is((Event)null), Arg.Is((State)null), Arg.Is(mockInitialState)); + // onEventTriggered() might be called before or after onEventHandling(). + mockStateMonitor.Received() + .onEventTriggered(Arg.Is(c), Arg.Is(mockEvent)); } } } From af203cd1d660a0889ca0084619dd97874c45b8b9 Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Wed, 8 Jan 2020 20:47:22 +0900 Subject: [PATCH 05/11] Fix StateMachine.NET.TestConsole to terminate process. --- StateMachine.NET.TestConsole/Program.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/StateMachine.NET.TestConsole/Program.cs b/StateMachine.NET.TestConsole/Program.cs index e488858..d15ae87 100644 --- a/StateMachine.NET.TestConsole/Program.cs +++ b/StateMachine.NET.TestConsole/Program.cs @@ -33,6 +33,9 @@ static void Main(string[] args) } } + + var hr = context.shutdown(); + Console.WriteLine($"Context.shutdown() returns {hr}"); } } From f4ec6d934d54e1b47761a15aab08fe960a60f8f3 Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Wed, 8 Jan 2020 23:28:11 +0900 Subject: [PATCH 06/11] Modify IAsyncDispatcher of StateMachine.NET. - Move exitThreadEvent handle from managed class to native class to avoid exception when ManagedDispatcher is disposed. - Debugging test in Test Explorer passed. --- StateMachine.NET/NativeObjects.cpp | 57 ++++++++++++++++-------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/StateMachine.NET/NativeObjects.cpp b/StateMachine.NET/NativeObjects.cpp index 3b9e0fd..80a36e9 100644 --- a/StateMachine.NET/NativeObjects.cpp +++ b/StateMachine.NET/NativeObjects.cpp @@ -60,44 +60,25 @@ void StateMonitor::onWorkerThreadExit(tsm::IContext* context, HRESULT exitCode) m_onWorkerThreadExitCallback(context, exitCode); } +class AsyncDispatcher; + /** * Managed class to dispatch tsm::IAsyncDispatcher::Method(lpParam) in managed worker thread. */ ref class ManagedDispatcher { public: - ManagedDispatcher(tsm::IAsyncDispatcher::Method method, LPVOID lpParam) - : method(method), lpParam(lpParam) + ManagedDispatcher(AsyncDispatcher* asyncDispatcher) + : asyncDispatcher(asyncDispatcher) { - exitThreadEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); auto threadStart = gcnew Threading::ThreadStart(this, &ManagedDispatcher::threadMethod); auto thread = gcnew Threading::Thread(threadStart); thread->Start(); } - ~ManagedDispatcher() - { - this->!ManagedDispatcher(); - } - - !ManagedDispatcher() - { - CloseHandle(exitThreadEvent); - } - - HANDLE exitThreadEvent; - protected: - tsm::IAsyncDispatcher::Method method; - LPVOID lpParam; - - // Method called in the managed worker thread. - void threadMethod() { - method(lpParam); - - // Set event to notify that worker thread exit. - SetEvent(exitThreadEvent); - } + void threadMethod(); + AsyncDispatcher* asyncDispatcher; }; /** @@ -107,11 +88,33 @@ class AsyncDispatcher : public tsm::IAsyncDispatcher { public: virtual HANDLE dispatch(Method method, LPVOID lpParam) override { - auto md = gcnew ManagedDispatcher(method, lpParam); - return md->exitThreadEvent; + this->method = method; + this->lpParam = lpParam; + gcnew ManagedDispatcher(this); + + exitThreadEvent.Attach(CreateEvent(nullptr, TRUE, FALSE, nullptr)); + return exitThreadEvent; + } + + /** + * Method called in the managed thread and call IAsyncDispatcher::Method. + */ + void threadMethod() { + method(lpParam); + SetEvent(exitThreadEvent); } + +protected: + Method method; + LPVOID lpParam; + CHandle exitThreadEvent; }; +void ManagedDispatcher::threadMethod() +{ + asyncDispatcher->threadMethod(); +} + tsm::IAsyncDispatcher* Context::_createAsyncDispatcher() { return isAsync() ? new AsyncDispatcher() : nullptr; From 5e66d268c94f568ddda142d8c82f85d87da605fd Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Wed, 8 Jan 2020 23:30:57 +0900 Subject: [PATCH 07/11] Change sleep time in StateMachine.NET.UnitTest. --- StateMachine.NET.UnitTest/Class1.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StateMachine.NET.UnitTest/Class1.cs b/StateMachine.NET.UnitTest/Class1.cs index dea269e..c2cb451 100644 --- a/StateMachine.NET.UnitTest/Class1.cs +++ b/StateMachine.NET.UnitTest/Class1.cs @@ -121,7 +121,7 @@ public void BasicTest() // Shutdown mockNextState.IsExitCalledOnShutdown = true; Assume.That(c.shutdown(TimeSpan.FromSeconds(1)), Is.EqualTo(HResult.Ok)); - Thread.Sleep(1000); + Thread.Sleep(100); // Check calls to methods of State. Received.InOrder(() => From 5cb47eaab721cb6c3c3a9fd64835106d98d2dcb1 Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Fri, 10 Jan 2020 21:27:05 +0900 Subject: [PATCH 08/11] Modify StateMachine.NET to not use native::Callback class. Because native thread can run on AppDomain of native code. --- StateMachine.NET/GenericObject.cpp | 24 ++++---- StateMachine.NET/GenericObject.h | 16 ++--- StateMachine.NET/NativeCallback.h | 48 --------------- StateMachine.NET/NativeObjects.cpp | 57 +++++++----------- StateMachine.NET/NativeObjects.h | 31 ++-------- StateMachine.NET/StateMachine.NET.cpp | 71 +---------------------- StateMachine.NET/StateMachine.NET.h | 56 ------------------ StateMachine.NET/StateMachine.NET.vcxproj | 1 - 8 files changed, 45 insertions(+), 259 deletions(-) delete mode 100644 StateMachine.NET/NativeCallback.h diff --git a/StateMachine.NET/GenericObject.cpp b/StateMachine.NET/GenericObject.cpp index f87a4d5..27de3d8 100644 --- a/StateMachine.NET/GenericObject.cpp +++ b/StateMachine.NET/GenericObject.cpp @@ -66,38 +66,38 @@ namespace Generic } generic - HRESULT State::handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) + tsm_NET::HResult State::handleEvent(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^% nextState) { S _nextState; - auto hr = handleEvent((C)getManaged((native::Context*)context), (E)getManaged((native::Event*)event), _nextState); + auto hr = handleEvent((C)context, (E)event, _nextState); if(_nextState) { - *nextState = _nextState->get(); + nextState = (S)_nextState; } - return (HRESULT)hr; + return (tsm_NET::HResult)hr; } generic - HRESULT State::entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) + tsm_NET::HResult State::entry(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ previousState) { - return (HRESULT)entry((C)getManaged((native::Context*)context), (E)getManaged((native::Event*)event), (S)getManaged((native::State*)previousState)); + return (tsm_NET::HResult)entry((C)context, (E)event, (S)previousState); } generic - HRESULT State::exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) + tsm_NET::HResult State::exit(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ nextState) { - return (HRESULT)exit((C)getManaged((native::Context*)context), (E)getManaged((native::Event*)event), (S)getManaged((native::State*)nextState)); + return (tsm_NET::HResult)exit((C)context, (E)event, (S)nextState); } generic - HRESULT Event::preHandleCallback(tsm::IContext* context) + tsm_NET::HResult Event::preHandle(tsm_NET::Context^ context) { - return (HRESULT)preHandle((C)getManaged((native::Context*)context)); + return (tsm_NET::HResult)preHandle((C)context); } generic - HRESULT Event::postHandleCallback(tsm::IContext* context, HRESULT hr) + tsm_NET::HResult Event::postHandle(tsm_NET::Context^ context, tsm_NET::HResult hr) { - return (HRESULT)postHandle((C)getManaged((native::Context*)context), (HResult)hr); + return (tsm_NET::HResult)postHandle((C)context, (HResult)hr); } } } diff --git a/StateMachine.NET/GenericObject.h b/StateMachine.NET/GenericObject.h index 05ee1fa..6b966ab 100644 --- a/StateMachine.NET/GenericObject.h +++ b/StateMachine.NET/GenericObject.h @@ -93,13 +93,10 @@ public ref class State : public tsm_NET::State property S MasterState { S get() { return getMasterState(); } } -// NOTE: Callback methods that is called by native class should be `internal` -// to avoid `System.MissingMethodException` when NUnit runs with NSubstitute. -internal: -#pragma region Methods that call sub class with generic parameters. - virtual HRESULT handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) override; - virtual HRESULT entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) override; - virtual HRESULT exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) override; +#pragma region Override methods of tsm_NET::State that call sub class with generic parameters. + virtual tsm_NET::HResult handleEvent(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^% nextState) override sealed; + virtual tsm_NET::HResult entry(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ previousState) override sealed; + virtual tsm_NET::HResult exit(tsm_NET::Context^ context, tsm_NET::Event^ event, tsm_NET::State^ nextState) override sealed; #pragma endregion }; @@ -115,10 +112,9 @@ public ref class Event : public tsm_NET::Event virtual HResult postHandle(C context, HResult hr) { return hr; } #pragma endregion -internal: #pragma region Methods that call sub class with generic parameters. - virtual HRESULT preHandleCallback(tsm::IContext* context) override; - virtual HRESULT postHandleCallback(tsm::IContext* context, HRESULT hr) override; + virtual tsm_NET::HResult preHandle(tsm_NET::Context^ context) override sealed; + virtual tsm_NET::HResult postHandle(tsm_NET::Context^ context, tsm_NET::HResult hr) override sealed; #pragma endregion }; } diff --git a/StateMachine.NET/NativeCallback.h b/StateMachine.NET/NativeCallback.h deleted file mode 100644 index c62cccc..0000000 --- a/StateMachine.NET/NativeCallback.h +++ /dev/null @@ -1,48 +0,0 @@ -#pragma once - -using System::Runtime::InteropServices::Marshal; - -namespace native -{ -/* - Callback template class. - This class is used to avoid `Cannot pass a GCHandle across AppDomains` exception - when method of managed class is called by native(unmanaged) class in worker thread. - - How to use this class. - - 1. Define delegate(class D), callback signature(class C) and callback method in managed class(class M). - delegate HRESULT XxxDelegate(...); // delegate - typedef HRESULT (__stdcall *XxxCallback)(...); // callback signature - HRESULT xxxCallback(...) { method body } // callback method - - 2. Declare member variable of this class in native class. - Callback m_xxxCallback; - - 3. Initialize the member variable in the constructor of native class. - m_xxxCallback(gcnew XxxDelegate(managedObject, &ManagedClass::xxxCallback)) - - 4. Callback from native class. - m_xxxCallback(...); - - NOTE: - Parameters and return value should be native(unmanaged) type. - - See http://lambert.geek.nz/2007/05/unmanaged-appdomain-callback/. -*/ -template -class Callback -{ -public: - Callback(D^ del) : del(del) { - callback = (C)Marshal::GetFunctionPointerForDelegate(del).ToPointer(); - } - - operator C() { return callback; } - -protected: - gcroot del; - C callback; -}; - -} diff --git a/StateMachine.NET/NativeObjects.cpp b/StateMachine.NET/NativeObjects.cpp index 80a36e9..9b38c9e 100644 --- a/StateMachine.NET/NativeObjects.cpp +++ b/StateMachine.NET/NativeObjects.cpp @@ -14,50 +14,39 @@ struct EventHandle {}; struct TimerHandle {}; } -StateMonitor::StateMonitor(StateMonitor::OwnerType^ owner, - OwnerType::OnIdleCallback onIdleCallback, - OwnerType::OnEventTriggeredCallback onEventTriggeredCallback, - OwnerType::OnEventHandlingCallback onEventHandlingCallback, - OwnerType::OnStateChangedCallback onStateChangedCallback, - OwnerType::OnTimerStartedCallback onTimerStartedCallback, - OwnerType::OnWorkerThreadExitCallback onWorkerThreadExitCallback) - : m_onIdleCallback(onIdleCallback) - , m_onEventTriggeredCallback(onEventTriggeredCallback) - , m_onEventHandlingCallback(onEventHandlingCallback) - , m_onStateChangedCallback(onStateChangedCallback) - , m_onTimerStartedCallback(onTimerStartedCallback) - , m_onWorkerThreadExitCallback(onWorkerThreadExitCallback) +StateMonitor::StateMonitor(StateMonitor::OwnerType^ owner) + : m_owner(owner) { } void StateMonitor::onIdle(tsm::IContext* context) { - m_onIdleCallback(context); + m_owner->onIdleCallback(context); } void StateMonitor::onEventTriggered(tsm::IContext* context, tsm::IEvent* event) { - m_onEventTriggeredCallback(context, event); + m_owner->onEventTriggeredCallback(context, event); } void StateMonitor::onEventHandling(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current) { - m_onEventHandlingCallback(context, event, current); + m_owner->onEventHandlingCallback(context, event, current); } void StateMonitor::onStateChanged(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next) { - m_onStateChangedCallback(context, event, previous, next); + m_owner->onStateChangedCallback(context, event, previous, next); } void StateMonitor::onTimerStarted(tsm::IContext* context, tsm::IEvent* event) { - m_onTimerStartedCallback(context, event); + m_owner->onTimerStartedCallback(context, event); } void StateMonitor::onWorkerThreadExit(tsm::IContext* context, HRESULT exitCode) { - m_onWorkerThreadExitCallback(context, exitCode); + m_owner->onWorkerThreadExitCallback(context, exitCode); } class AsyncDispatcher; @@ -127,15 +116,9 @@ Context::Context(ManagedType^ context, bool isAsync /*= true*/) { } -State::State(ManagedType^ state, ManagedType^ masterState, - ManagedType::HandleEventCallback handleEventCallback, - ManagedType::EntryCallback entryCallback, - ManagedType::ExitCallback exitCallback) +State::State(ManagedType^ state, ManagedType^ masterState) : m_managedState(state) , m_masterState(getNative(masterState)) - , m_handleEventCallback(handleEventCallback) - , m_entryCallback(entryCallback) - , m_exitCallback(exitCallback) { } @@ -146,18 +129,22 @@ State::~State() HRESULT State::_handleEvent(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) { - return m_handleEventCallback(context, event, nextState); + ManagedType^ _nextState = nullptr; + auto hr = m_managedState->handleEvent(getManaged((native::Context*)context), getManaged((native::Event*)event), _nextState); + if(_nextState) { + *nextState = _nextState->get(); + } + return (HRESULT)hr; } HRESULT State::_entry(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) { - return m_entryCallback(context, event, previousState); + return (HRESULT)m_managedState->entry(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)previousState)); } HRESULT State::_exit(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) { - Trace::WriteLine(String::Format("native::State::_exit(): Current AppDomain={0}", System::AppDomain::CurrentDomain->FriendlyName)); - return m_exitCallback(context, event, nextState); + return (HRESULT)m_managedState->exit(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)nextState)); } bool State::_isExitCalledOnShutdown() const @@ -165,12 +152,8 @@ bool State::_isExitCalledOnShutdown() const return m_managedState->IsExitCalledOnShutdown; } -Event::Event(ManagedType^ event, - ManagedType::PreHandleCallback preHandleCallback, - ManagedType::PostHandleCallback postHandleCallback) +Event::Event(ManagedType^ event) : m_managedEvent(event) - , m_preHandleCallback(preHandleCallback) - , m_postHandleCallback(postHandleCallback) , m_timerClient(nullptr) { } @@ -182,10 +165,10 @@ Event::~Event() HRESULT Event::_preHandle(tsm::IContext* context) { - return m_preHandleCallback(context); + return (HRESULT)m_managedEvent->preHandle(getManaged((native::Context*)context)); } HRESULT Event::_postHandle(tsm::IContext* context, HRESULT hr) { - return m_postHandleCallback(context, hr); + return (HRESULT)m_managedEvent->postHandle(getManaged((native::Context*)context), (tsm_NET::HResult)hr); } diff --git a/StateMachine.NET/NativeObjects.h b/StateMachine.NET/NativeObjects.h index fe4fc09..899e8ce 100644 --- a/StateMachine.NET/NativeObjects.h +++ b/StateMachine.NET/NativeObjects.h @@ -13,13 +13,7 @@ class StateMonitor : public tsm::IStateMonitor public: using OwnerType = tsm_NET::StateMonitorCaller; - StateMonitor(OwnerType^ owner, - OwnerType::OnIdleCallback onIdleCallback, - OwnerType::OnEventTriggeredCallback onEventTriggeredCallback, - OwnerType::OnEventHandlingCallback onEventHandlingCallback, - OwnerType::OnStateChangedCallback onStateChangedCallback, - OwnerType::OnTimerStartedCallback onTimerStartedCallback, - OwnerType::OnWorkerThreadExitCallback onWorkerThreadExitCallback); + StateMonitor(OwnerType^ owner); virtual void onIdle(tsm::IContext* context) override; virtual void onEventTriggered(tsm::IContext* context, tsm::IEvent* event) override; @@ -34,12 +28,7 @@ class StateMonitor : public tsm::IStateMonitor virtual void onWorkerThreadExit(tsm::IContext* context, HRESULT exitCode) override; protected: - OwnerType::OnIdleCallback m_onIdleCallback; - OwnerType::OnEventTriggeredCallback m_onEventTriggeredCallback; - OwnerType::OnEventHandlingCallback m_onEventHandlingCallback; - OwnerType::OnStateChangedCallback m_onStateChangedCallback; - OwnerType::OnTimerStartedCallback m_onTimerStartedCallback; - OwnerType::OnWorkerThreadExitCallback m_onWorkerThreadExitCallback; + gcroot m_owner; }; class Context : public tsm::IContext, public tsm::TimerClient @@ -89,10 +78,7 @@ class State : public tsm::IState, public tsm::TimerClient public: using ManagedType = tsm_NET::State; - State(ManagedType^ state, ManagedType^ masterState, - ManagedType::HandleEventCallback handleEventCallback, - ManagedType::EntryCallback entryCallback, - ManagedType::ExitCallback exitCallback); + State(ManagedType^ state, ManagedType^ masterState); virtual ~State(); #pragma region Implementation of IState that call methods of managed class. @@ -115,10 +101,6 @@ class State : public tsm::IState, public tsm::TimerClient protected: gcroot m_managedState; CComPtr m_masterState; - - ManagedType::HandleEventCallback m_handleEventCallback; - ManagedType::EntryCallback m_entryCallback; - ManagedType::ExitCallback m_exitCallback; }; class Event : public tsm::IEvent @@ -126,9 +108,7 @@ class Event : public tsm::IEvent public: using ManagedType = tsm_NET::Event; - Event(ManagedType^ event, - ManagedType::PreHandleCallback preHandleCallback, - ManagedType::PostHandleCallback postHandleCallback); + Event(ManagedType^ event); virtual ~Event(); #pragma region Implementation of IState that call methods of managed class. @@ -146,9 +126,6 @@ class Event : public tsm::IEvent protected: gcroot m_managedEvent; - ManagedType::PreHandleCallback m_preHandleCallback; - ManagedType::PostHandleCallback m_postHandleCallback; - int m_priority; DWORD m_delayTime; DWORD m_intervalTime; diff --git a/StateMachine.NET/StateMachine.NET.cpp b/StateMachine.NET/StateMachine.NET.cpp index 8a8bae2..0acf8dd 100644 --- a/StateMachine.NET/StateMachine.NET.cpp +++ b/StateMachine.NET/StateMachine.NET.cpp @@ -2,7 +2,6 @@ #include "StateMachine.NET.h" #include "NativeObjects.h" -#include "NativeCallback.h" using namespace tsm_NET; using namespace tsm_NET::common; @@ -20,14 +19,7 @@ using namespace System::Diagnostics; StateMonitorCaller::StateMonitorCaller(IStateMonitor^ stateMonitor) : m_stateMonitor(stateMonitor) { - m_onIdleCallback = new native::Callback(gcnew OnIdleDelegate(this, &StateMonitorCaller::onIdleCallback)); - m_onEventTriggeredCallback = new native::Callback(gcnew OnEventTriggeredDelegate(this, &StateMonitorCaller::onEventTriggeredCallback)); - m_onEventHandlingCallback = new native::Callback(gcnew OnEventHandlingDelegate(this, &StateMonitorCaller::onEventHandlingCallback)); - m_onStateChangedCallback = new native::Callback(gcnew OnStateChangedDelegate(this, &StateMonitorCaller::onStateChangedCallback)); - m_onTimerStartedCallback = new native::Callback(gcnew OnTimerStartedDelegate(this, &StateMonitorCaller::onTimerStartedCallback)); - m_onWorkerThreadExitCallback = new native::Callback(gcnew OnWorkerThreadExitDelegate(this, &StateMonitorCaller::onWorkerThreadExitCallback)); - - m_nativeStateMonitor = new native::StateMonitor(this, *m_onIdleCallback, *m_onEventTriggeredCallback, * m_onEventHandlingCallback, *m_onStateChangedCallback, *m_onTimerStartedCallback, *m_onWorkerThreadExitCallback); + m_nativeStateMonitor = new native::StateMonitor(this); } StateMonitorCaller::~StateMonitorCaller() @@ -38,13 +30,6 @@ StateMonitorCaller::~StateMonitorCaller() StateMonitorCaller::!StateMonitorCaller() { SAFE_RELEASE(m_nativeStateMonitor); - - SAFE_RELEASE(m_onIdleCallback); - SAFE_RELEASE(m_onEventTriggeredCallback); - SAFE_RELEASE(m_onEventHandlingCallback); - SAFE_RELEASE(m_onStateChangedCallback); - SAFE_RELEASE(m_onTimerStartedCallback); - SAFE_RELEASE(m_onWorkerThreadExitCallback); } void StateMonitorCaller::onIdleCallback(tsm::IContext* context) @@ -147,12 +132,7 @@ void Context::StateMonitor::set(IStateMonitor^ value) //-------------- Managed State class. --------------------// State::State(State^ masterState) { - m_handleEventCallback = new native::Callback(gcnew HandleEventDelegate(this, &State::handleEventCallback)); - m_entryCallback = new native::Callback (gcnew EntryDelegate(this, &State::entryCallback)); - m_exitCallback = new native::Callback (gcnew ExitDelegate(this, &State::exitCallback)); - - m_nativeState = new native::State(this, masterState, *m_handleEventCallback, *m_entryCallback, *m_exitCallback); - //m_nativeState->AddRef(); + m_nativeState = new native::State(this, masterState); IsExitCalledOnShutdown = false; } @@ -170,33 +150,6 @@ State::!State() Console::WriteLine("Released State {0}, ref={1}", (IntPtr)m_nativeState, c); m_nativeState = nullptr; } - - SAFE_RELEASE(m_handleEventCallback); - SAFE_RELEASE(m_entryCallback); - SAFE_RELEASE(m_exitCallback); -} - -HRESULT State::handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState) -{ - State^ _nextState = nullptr; - auto hr = handleEvent(getManaged((native::Context*)context), getManaged((native::Event*)event), _nextState); - if(_nextState) { - *nextState = getNative(_nextState); - } - return (HRESULT)hr; -} - -HRESULT State::entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState) -{ - return (HRESULT)entry(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)previousState)); -} - -HRESULT State::exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState) -{ - Trace::WriteLine(String::Format("State::exitCallback(): Current AppDomain={0}", System::AppDomain::CurrentDomain->FriendlyName)); - auto ret = exit(getManaged((native::Context*)context), getManaged((native::Event*)event), getManaged((native::State*)nextState)); - //m_nativeState->Release(); - return (HRESULT)ret; } State^ State::getMasterState() @@ -233,10 +186,7 @@ void State::MemoryWeight::set(int value) //-------------- Managed Event class. --------------------// Event::Event() { - m_preHandleCallback = new native::Callback(gcnew PreHandleDelegate(this, &Event::preHandleCallback)); - m_postHandleCallback = new native::Callback(gcnew PostHandleDelegate(this, &Event::postHandleCallback)); - - m_nativeEvent = new native::Event(this, *m_preHandleCallback, *m_postHandleCallback); + m_nativeEvent = new native::Event(this); //m_nativeEvent->AddRef(); } @@ -253,21 +203,6 @@ Event::!Event() Console::WriteLine("Released Event {0}, ref={1}", (IntPtr)m_nativeEvent, c); m_nativeEvent = nullptr; } - - SAFE_RELEASE(m_preHandleCallback); - SAFE_RELEASE(m_postHandleCallback); -} - -HRESULT Event::preHandleCallback(tsm::IContext* context) -{ - return (HRESULT)preHandle(getManaged((native::Context*)context)); -} - -HRESULT Event::postHandleCallback(tsm::IContext* context, HRESULT hr) -{ - auto ret = (HRESULT)postHandle(getManaged((native::Context*)context), (HResult)hr); - //m_nativeEvent->Release(); - return (HRESULT)ret; } long Event::SequenceNumber::get() diff --git a/StateMachine.NET/StateMachine.NET.h b/StateMachine.NET/StateMachine.NET.h index 683bb92..be47ea1 100644 --- a/StateMachine.NET/StateMachine.NET.h +++ b/StateMachine.NET/StateMachine.NET.h @@ -8,9 +8,6 @@ class Context; class State; class Event; class StateMonitor; - -template -class Callback; } namespace tsm_NET @@ -64,28 +61,16 @@ public ref class StateMonitorCaller #pragma region Definition of delegate, callback signature and callback method. See native::Callback<> template class. // void IStateMonitor::onIdle() - delegate void OnIdleDelegate(tsm::IContext* context); - using OnIdleCallback = void(__stdcall *)(tsm::IContext* context); virtual void onIdleCallback(tsm::IContext* context); // void IstateMonitor::onEventTriggered() - delegate void OnEventTriggeredDelegate(tsm::IContext* context, tsm::IEvent* event); - using OnEventTriggeredCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event); virtual void onEventTriggeredCallback(tsm::IContext* context, tsm::IEvent* event); // void IStateMonitor::onEventHandling() - delegate void OnEventHandlingDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current); - using OnEventHandlingCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current); virtual void onEventHandlingCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* current); // void IStateMonitor::onStateChanged() - delegate void OnStateChangedDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next); - using OnStateChangedCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next); virtual void onStateChangedCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previous, tsm::IState* next); // void IStateMonitor::onTimerStarted() - delegate void OnTimerStartedDelegate(tsm::IContext* context, tsm::IEvent* event); - using OnTimerStartedCallback = void(__stdcall *)(tsm::IContext* context, tsm::IEvent* event); virtual void onTimerStartedCallback(tsm::IContext* context, tsm::IEvent* event); // void IStateMonitor::onWorkerThreadExit() - delegate void OnWorkerThreadExitDelegate(tsm::IContext* context, HRESULT exitCode); - using OnWorkerThreadExitCallback = void(__stdcall *)(tsm::IContext* context, HRESULT exitCode); virtual void onWorkerThreadExitCallback(tsm::IContext* context, HRESULT exitCode); #pragma endregion @@ -94,14 +79,6 @@ public ref class StateMonitorCaller protected: NativeType* m_nativeStateMonitor; IStateMonitor^ m_stateMonitor; - -internal: - native::Callback* m_onIdleCallback; - native::Callback* m_onEventTriggeredCallback; - native::Callback* m_onEventHandlingCallback; - native::Callback* m_onStateChangedCallback; - native::Callback* m_onTimerStartedCallback; - native::Callback* m_onWorkerThreadExitCallback; }; public ref class Context @@ -177,27 +154,8 @@ public ref class State internal: NativeType* get() { return m_nativeState; } -#pragma region Definition of delegate, callback signature and callback method. See native::Callback<> template class. - delegate HRESULT HandleEventDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState); - using HandleEventCallback = HRESULT (__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState); - virtual HRESULT handleEventCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState** nextState); - - delegate HRESULT EntryDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState); - using EntryCallback = HRESULT (__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState); - virtual HRESULT entryCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* previousState); - - delegate HRESULT ExitDelegate(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState); - using ExitCallback = HRESULT(__stdcall *)(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState); - virtual HRESULT exitCallback(tsm::IContext* context, tsm::IEvent* event, tsm::IState* nextState); -#pragma endregion - protected: NativeType* m_nativeState; - -internal: - native::Callback* m_handleEventCallback; - native::Callback* m_entryCallback; - native::Callback* m_exitCallback; }; public ref class Event @@ -226,22 +184,8 @@ public ref class Event internal: NativeType* get() { return m_nativeEvent; } -#pragma region Definition of delegate, callback signature and callback method. See tsm::ICallback<> template class. - delegate HRESULT PreHandleDelegate(tsm::IContext* context); - using PreHandleCallback = HRESULT(__stdcall *)(tsm::IContext* context); - virtual HRESULT preHandleCallback(tsm::IContext* context); - - delegate HRESULT PostHandleDelegate(tsm::IContext* context, HRESULT hr); - using PostHandleCallback = HRESULT(__stdcall *)(tsm::IContext* context, HRESULT hr); - virtual HRESULT postHandleCallback(tsm::IContext* context, HRESULT hr); -#pragma endregion - protected: NativeType* m_nativeEvent; - -internal: - native::Callback* m_preHandleCallback; - native::Callback* m_postHandleCallback; }; namespace common diff --git a/StateMachine.NET/StateMachine.NET.vcxproj b/StateMachine.NET/StateMachine.NET.vcxproj index a5994c1..4ad9495 100644 --- a/StateMachine.NET/StateMachine.NET.vcxproj +++ b/StateMachine.NET/StateMachine.NET.vcxproj @@ -138,7 +138,6 @@ - From 9f718015047336ee3af6bad492245b8696aa56ec Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Fri, 10 Jan 2020 22:05:11 +0900 Subject: [PATCH 09/11] Remove destructors of tsm_NET::Stete/Event class that do nothing. --- StateMachine.NET/GenericObject.h | 2 -- StateMachine.NET/StateMachine.NET.cpp | 41 +++------------------------ StateMachine.NET/StateMachine.NET.h | 4 --- 3 files changed, 4 insertions(+), 43 deletions(-) diff --git a/StateMachine.NET/GenericObject.h b/StateMachine.NET/GenericObject.h index 6b966ab..e0075bb 100644 --- a/StateMachine.NET/GenericObject.h +++ b/StateMachine.NET/GenericObject.h @@ -81,7 +81,6 @@ public ref class State : public tsm_NET::State public: State() : tsm_NET::State(nullptr) {} State(S masterState) : tsm_NET::State((tsm_NET::State^)masterState) {} - ~State() {} #pragma region Methods to be implemented by sub class. virtual HResult handleEvent(C context, E event, S% nextState) { return HResult::Ok; } @@ -105,7 +104,6 @@ generic public ref class Event : public tsm_NET::Event { public: - ~Event() {} #pragma region Methods to be implemented by sub class. virtual HResult preHandle(C context) { return HResult::Ok; } diff --git a/StateMachine.NET/StateMachine.NET.cpp b/StateMachine.NET/StateMachine.NET.cpp index 0acf8dd..26b4beb 100644 --- a/StateMachine.NET/StateMachine.NET.cpp +++ b/StateMachine.NET/StateMachine.NET.cpp @@ -9,12 +9,6 @@ using namespace System::Diagnostics; ///*static*/ event EventHandler^>^ IStateMonitor::AssertFailedEvent; -#define SAFE_RELEASE(ptr) \ - if(ptr) { \ - delete ptr; \ - ptr = nullptr; \ - } - //-------------- Implementation of IStateMonitorCaller. --------------------// StateMonitorCaller::StateMonitorCaller(IStateMonitor^ stateMonitor) : m_stateMonitor(stateMonitor) @@ -29,7 +23,10 @@ StateMonitorCaller::~StateMonitorCaller() StateMonitorCaller::!StateMonitorCaller() { - SAFE_RELEASE(m_nativeStateMonitor); + if(m_nativeStateMonitor) { + delete m_nativeStateMonitor; + m_nativeStateMonitor = nullptr; + } } void StateMonitorCaller::onIdleCallback(tsm::IContext* context) @@ -137,21 +134,6 @@ State::State(State^ masterState) IsExitCalledOnShutdown = false; } -State::~State() -{ - this->!State(); -} - -State::!State() -{ - if(m_nativeState) - { - auto c = 0;// m_nativeState->Release(); - Console::WriteLine("Released State {0}, ref={1}", (IntPtr)m_nativeState, c); - m_nativeState = nullptr; - } -} - State^ State::getMasterState() { return getManaged(m_nativeState->getMasterState()); @@ -190,21 +172,6 @@ Event::Event() //m_nativeEvent->AddRef(); } -Event::~Event() -{ - this->!Event(); -} - -Event::!Event() -{ - if(m_nativeEvent) - { - auto c = 0;// m_nativeEvent->Release(); - Console::WriteLine("Released Event {0}, ref={1}", (IntPtr)m_nativeEvent, c); - m_nativeEvent = nullptr; - } -} - long Event::SequenceNumber::get() { return m_nativeEvent->getSequenceNumber(); diff --git a/StateMachine.NET/StateMachine.NET.h b/StateMachine.NET/StateMachine.NET.h index be47ea1..2fa8fd4 100644 --- a/StateMachine.NET/StateMachine.NET.h +++ b/StateMachine.NET/StateMachine.NET.h @@ -129,8 +129,6 @@ public ref class State State() : State(nullptr) {} State(State^ masterState); - virtual ~State(); - !State(); #pragma region Methods to be implemented by sub class. virtual HResult handleEvent(Context^ context, Event^ event, State^% nextState) { return HResult::Ok; } @@ -164,8 +162,6 @@ public ref class Event using NativeType = native::Event; Event(); - virtual ~Event(); - !Event(); #pragma region Methods to be implemented by sub class. virtual HResult preHandle(Context^ context) { return HResult::Ok; } From 36e7fd06566a14ce07bda799c9f707be3934d0dc Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Sat, 11 Jan 2020 12:48:25 +0900 Subject: [PATCH 10/11] Move native implementation of IAsyncDispatcher from Context to AsyncStateMachine. Change Context constructor of tsm_NET to accept ThreadType parameter. --- StateMachine.NET.TestConsole/Program.cs | 4 +++- StateMachine.NET.UnitTest/Class1.cs | 8 +++---- StateMachine.NET.UnitTest/Class2.cs | 2 +- StateMachine.NET/GenericObject.cpp | 2 +- StateMachine.NET/GenericObject.h | 4 ++-- StateMachine.NET/NativeObjects.cpp | 2 +- StateMachine.NET/StateMachine.NET.cpp | 21 +++++++++++++++-- StateMachine.NET/StateMachine.NET.h | 31 ++++++++++++++++++------- StateMachine/AsyncStateMachine.cpp | 15 +++++++++--- public/include/StateMachine/Context.h | 12 ---------- 10 files changed, 65 insertions(+), 36 deletions(-) diff --git a/StateMachine.NET.TestConsole/Program.cs b/StateMachine.NET.TestConsole/Program.cs index d15ae87..58e5348 100644 --- a/StateMachine.NET.TestConsole/Program.cs +++ b/StateMachine.NET.TestConsole/Program.cs @@ -35,12 +35,14 @@ static void Main(string[] args) } var hr = context.shutdown(); - Console.WriteLine($"Context.shutdown() returns {hr}"); + Console.WriteLine($"Context.shutdown() returns {hr}.\nKey in [Enter] to exit."); + Console.ReadLine(); } } class Context : tsm_NET.Generic.Context { + public Context() : base(ThreadType.Native) { } } class State : tsm_NET.Generic.State diff --git a/StateMachine.NET.UnitTest/Class1.cs b/StateMachine.NET.UnitTest/Class1.cs index c2cb451..3c79f8d 100644 --- a/StateMachine.NET.UnitTest/Class1.cs +++ b/StateMachine.NET.UnitTest/Class1.cs @@ -8,8 +8,8 @@ namespace StateMachine.NET.UnitTest.Generic { public class Context : Context { - public Context() : base(true) { } - public Context(bool isAsync) : base(isAsync) { } + public Context() { } + public Context(ThreadType threadType) : base(threadType) { } } public class Event : Event @@ -34,7 +34,7 @@ public void SyncContextTest() var mockStateMonitor = Substitute.For>(); // Create synchronous Context object. - var c = new Context(false); + var c = new Context(); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); @@ -97,7 +97,7 @@ public void BasicTest() var mockNextState = Substitute.For(); var mockStateMonitor = Substitute.For>(); - var c = new Context(); + var c = new Context(Context.ThreadType.Managed); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); diff --git a/StateMachine.NET.UnitTest/Class2.cs b/StateMachine.NET.UnitTest/Class2.cs index 30a8f5b..ea11961 100644 --- a/StateMachine.NET.UnitTest/Class2.cs +++ b/StateMachine.NET.UnitTest/Class2.cs @@ -17,7 +17,7 @@ public void BasicTest() var mockNextState = Substitute.For(); var mockStateMonitor = Substitute.For(); - var c = new Context(); + var c = new Context(Context.ThreadType.Managed); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); diff --git a/StateMachine.NET/GenericObject.cpp b/StateMachine.NET/GenericObject.cpp index 27de3d8..6f4127b 100644 --- a/StateMachine.NET/GenericObject.cpp +++ b/StateMachine.NET/GenericObject.cpp @@ -71,7 +71,7 @@ namespace Generic S _nextState; auto hr = handleEvent((C)context, (E)event, _nextState); if(_nextState) { - nextState = (S)_nextState; + nextState = _nextState; } return (tsm_NET::HResult)hr; } diff --git a/StateMachine.NET/GenericObject.h b/StateMachine.NET/GenericObject.h index e0075bb..299c788 100644 --- a/StateMachine.NET/GenericObject.h +++ b/StateMachine.NET/GenericObject.h @@ -44,8 +44,8 @@ generic public ref class Context : public tsm_NET::Context { public: - Context() : tsm_NET::Context(true), m_stateMonitor(nullptr) {} - Context(bool isAsync ) : tsm_NET::Context(isAsync), m_stateMonitor(nullptr) {} + Context() : tsm_NET::Context(), m_stateMonitor(nullptr) {} + Context(ThreadType threadType) : tsm_NET::Context(threadType), m_stateMonitor(nullptr) {} virtual ~Context() {} HResult setup(S initialState, E event) { return (HResult)tsm_NET::Context::setup((tsm_NET::State^)initialState, (tsm_NET::Event^)event); } diff --git a/StateMachine.NET/NativeObjects.cpp b/StateMachine.NET/NativeObjects.cpp index 9b38c9e..6b280ed 100644 --- a/StateMachine.NET/NativeObjects.cpp +++ b/StateMachine.NET/NativeObjects.cpp @@ -106,7 +106,7 @@ void ManagedDispatcher::threadMethod() tsm::IAsyncDispatcher* Context::_createAsyncDispatcher() { - return isAsync() ? new AsyncDispatcher() : nullptr; + return m_managedContext->useNativeThread ? new AsyncDispatcher() : nullptr; } Context::Context(ManagedType^ context, bool isAsync /*= true*/) diff --git a/StateMachine.NET/StateMachine.NET.cpp b/StateMachine.NET/StateMachine.NET.cpp index 26b4beb..10bcced 100644 --- a/StateMachine.NET/StateMachine.NET.cpp +++ b/StateMachine.NET/StateMachine.NET.cpp @@ -60,8 +60,25 @@ void StateMonitorCaller::onWorkerThreadExitCallback(tsm::IContext* context, HRES } //-------------- Managed Context class. --------------------// -void Context::construct(bool isAsync) -{ +void Context::construct(ThreadType threadType) +{ + bool isAsync; + switch(threadType) { + case ThreadType::None: + default: + isAsync = false; + m_useNativeThread = false; + break; + case ThreadType::Native: + isAsync = true; + m_useNativeThread = false; + break; + case ThreadType::Managed: + isAsync = true; + m_useNativeThread = true; + break; + } + m_nativeContext = new native::Context(this, isAsync); } diff --git a/StateMachine.NET/StateMachine.NET.h b/StateMachine.NET/StateMachine.NET.h index 2fa8fd4..4503a40 100644 --- a/StateMachine.NET/StateMachine.NET.h +++ b/StateMachine.NET/StateMachine.NET.h @@ -83,13 +83,22 @@ public ref class StateMonitorCaller public ref class Context { - void construct(bool isAsync); - public: - using NativeType = native::Context; + // Worker thread type on which StateMachine runs. + enum class ThreadType + { + // Description Thread creation StateMachine class + None, // No worker thread. None tsm::StateMachine + Native, // Use native thread. CreateThread() Win32 API tsm::AsyncStateMachine + Managed, // Use managed thiread System::Threading::Thread tsm::AsyncStateMachine + }; - Context() { construct(true); } - Context(bool isAsync) { construct(isAsync); } +private: + void construct(ThreadType threadType); + +public: + Context() { construct(ThreadType::None); } + Context(ThreadType threadType) { construct(threadType); } virtual ~Context(); !Context(); @@ -114,10 +123,14 @@ public ref class Context #pragma endregion internal: + using NativeType = native::Context; + NativeType* get() { return m_nativeContext; } + property bool useNativeThread { bool get() { return m_useNativeThread; } } protected: NativeType* m_nativeContext; + bool m_useNativeThread; tsm_NET::IStateMonitor^ m_stateMonitor; StateMonitorCaller^ m_stateMonitorCaller; }; @@ -125,8 +138,6 @@ public ref class Context public ref class State { public: - using NativeType = native::State; - State() : State(nullptr) {} State(State^ masterState); @@ -150,6 +161,8 @@ public ref class State #pragma endregion internal: + using NativeType = native::State; + NativeType* get() { return m_nativeState; } protected: @@ -159,8 +172,6 @@ public ref class State public ref class Event { public: - using NativeType = native::Event; - Event(); #pragma region Methods to be implemented by sub class. @@ -178,6 +189,8 @@ public ref class Event #pragma endregion internal: + using NativeType = native::Event; + NativeType* get() { return m_nativeEvent; } protected: diff --git a/StateMachine/AsyncStateMachine.cpp b/StateMachine/AsyncStateMachine.cpp index f082db2..db3e4ee 100644 --- a/StateMachine/AsyncStateMachine.cpp +++ b/StateMachine/AsyncStateMachine.cpp @@ -16,6 +16,15 @@ namespace tsm { return context->isAsync() ? new AsyncStateMachine() : new StateMachine(); } +class DefaultAsyncDispatcher : public IAsyncDispatcher +{ +public: + virtual HANDLE dispatch(Method method, LPVOID param) override + { + return CreateThread(nullptr, 0, method, param, 0, nullptr); + } +}; + HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEvent * event) { // Ensure to release object on error. @@ -39,9 +48,9 @@ HRESULT AsyncStateMachine::setup(IContext * context, IState * initialState, IEve // param will be deleted in worker thread. auto param = new SetupParam(context, this, event); - auto& dispatcher = asyncData->asyncDispatcher; - dispatcher.reset(context->_createAsyncDispatcher()); - HR_ASSERT(dispatcher, E_POINTER); + auto dispatcher = context->_createAsyncDispatcher(); + if(!dispatcher) { dispatcher = new DefaultAsyncDispatcher(); } + asyncData->asyncDispatcher.reset(dispatcher); asyncData->hWorkerThread.Attach(dispatcher->dispatch(workerThreadProc, param)); if(!asyncData->hWorkerThread) { delete param; diff --git a/public/include/StateMachine/Context.h b/public/include/StateMachine/Context.h index 8b0c687..0a58eb5 100644 --- a/public/include/StateMachine/Context.h +++ b/public/include/StateMachine/Context.h @@ -44,22 +44,10 @@ class Context : public IContext, public TimerClient template class AsyncContext : public Context { - class AsyncDispatcher : public IAsyncDispatcher - { - public: - virtual HANDLE dispatch(Method method, LPVOID param) override - { - return CreateThread(nullptr, 0, method, param, 0, nullptr); - } - }; - public: virtual ~AsyncContext() {} virtual bool isAsync() const { return true; } - virtual IAsyncDispatcher* _createAsyncDispatcher() override { - return new AsyncDispatcher(); - } }; } From 73782c19b8c0327f5be1f2add292f61ac01f81aa Mon Sep 17 00:00:00 2001 From: Koji Yano Date: Sat, 11 Jan 2020 20:27:33 +0900 Subject: [PATCH 11/11] Change StateMachine.NET. - Remove Context::ThreadType enum. - Add AsyncContext class which inherits from Context. --- StateMachine.NET.TestConsole/Program.cs | 3 +- StateMachine.NET.UnitTest/Class1.cs | 54 ++++++++++++++++--------- StateMachine.NET.UnitTest/Class2.cs | 5 ++- StateMachine.NET/GenericObject.h | 14 ++++++- StateMachine.NET/StateMachine.NET.cpp | 22 ++-------- StateMachine.NET/StateMachine.NET.h | 28 ++++++------- 6 files changed, 69 insertions(+), 57 deletions(-) diff --git a/StateMachine.NET.TestConsole/Program.cs b/StateMachine.NET.TestConsole/Program.cs index 58e5348..a25f07c 100644 --- a/StateMachine.NET.TestConsole/Program.cs +++ b/StateMachine.NET.TestConsole/Program.cs @@ -40,9 +40,8 @@ static void Main(string[] args) } } - class Context : tsm_NET.Generic.Context + class Context : tsm_NET.Generic.AsyncContext { - public Context() : base(ThreadType.Native) { } } class State : tsm_NET.Generic.State diff --git a/StateMachine.NET.UnitTest/Class1.cs b/StateMachine.NET.UnitTest/Class1.cs index 3c79f8d..b28451f 100644 --- a/StateMachine.NET.UnitTest/Class1.cs +++ b/StateMachine.NET.UnitTest/Class1.cs @@ -6,27 +6,25 @@ namespace StateMachine.NET.UnitTest.Generic { - public class Context : Context + [TestFixture] + public class TestCase { - public Context() { } - public Context(ThreadType threadType) : base(threadType) { } - } + public class Context : Context + { + } - public class Event : Event - { - public static Event Null { get; } = null; - } + public class Event : Event + { + public static Event Null { get; } = null; + } - public class State : State - { - public static State Null { get; } = null; - } + public class State : State + { + public static State Null { get; } = null; + } - [TestFixture] - public class GenericTestCase - { [Test] - public void SyncContextTest() + public void BasicTest() { var mockEvent = Substitute.For(); var mockInitialState = Substitute.For(); @@ -88,16 +86,36 @@ public void SyncContextTest() mockStateMonitor.DidNotReceive().onIdle(Arg.Any()); mockStateMonitor.DidNotReceive().onWorkerThreadExit(Arg.Any(), Arg.Any()); } + } + + [TestFixture] + public class AsyncTestCase + { + public class Context : AsyncContext + { + // StateMachine should run on managed thread to test on NUnit. + public Context() : base(true) { } + } + + public class Event : Event + { + public static Event Null { get; } = null; + } + + public class State : State + { + public static State Null { get; } = null; + } [Test] public void BasicTest() { - var mockEvent = Substitute.For(); + var mockEvent = Substitute.For(); var mockInitialState = Substitute.For(); var mockNextState = Substitute.For(); var mockStateMonitor = Substitute.For>(); - var c = new Context(Context.ThreadType.Managed); + var c = new Context(); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); diff --git a/StateMachine.NET.UnitTest/Class2.cs b/StateMachine.NET.UnitTest/Class2.cs index ea11961..9110967 100644 --- a/StateMachine.NET.UnitTest/Class2.cs +++ b/StateMachine.NET.UnitTest/Class2.cs @@ -7,7 +7,7 @@ namespace StateMachine.NET.UnitTest { [TestFixture] - public class TestCase + public class AsyncTestCase { [Test] public void BasicTest() @@ -17,7 +17,8 @@ public void BasicTest() var mockNextState = Substitute.For(); var mockStateMonitor = Substitute.For(); - var c = new Context(Context.ThreadType.Managed); + // StateMachine should run on managed thread to test on NUnit. + var c = new AsyncContext(true); c.StateMonitor = mockStateMonitor; Assert.That(c.CurrentState, Is.EqualTo(null), "Context has no initial state when created."); diff --git a/StateMachine.NET/GenericObject.h b/StateMachine.NET/GenericObject.h index 299c788..2f41be4 100644 --- a/StateMachine.NET/GenericObject.h +++ b/StateMachine.NET/GenericObject.h @@ -43,9 +43,11 @@ public ref class StateMonitorCaller : public tsm_NET::StateMonitorCaller generic public ref class Context : public tsm_NET::Context { +protected: + Context(bool isAsync, bool useNativeThread) : tsm_NET::Context(isAsync, useNativeThread), m_stateMonitor(nullptr) {} + public: - Context() : tsm_NET::Context(), m_stateMonitor(nullptr) {} - Context(ThreadType threadType) : tsm_NET::Context(threadType), m_stateMonitor(nullptr) {} + Context() : tsm_NET::Context(false, false), m_stateMonitor(nullptr) {} virtual ~Context() {} HResult setup(S initialState, E event) { return (HResult)tsm_NET::Context::setup((tsm_NET::State^)initialState, (tsm_NET::Event^)event); } @@ -72,6 +74,14 @@ public ref class Context : public tsm_NET::Context StateMonitorCaller^ m_stateMonitorCaller; }; +generic +public ref class AsyncContext : public Context +{ +public: + AsyncContext() : Context(true, false) {} + AsyncContext(bool useNativeThread) : Context(true, useNativeThread) {} +}; + generic where C : tsm_NET::Context where E : tsm_NET::Event diff --git a/StateMachine.NET/StateMachine.NET.cpp b/StateMachine.NET/StateMachine.NET.cpp index 10bcced..50fd8e5 100644 --- a/StateMachine.NET/StateMachine.NET.cpp +++ b/StateMachine.NET/StateMachine.NET.cpp @@ -60,25 +60,9 @@ void StateMonitorCaller::onWorkerThreadExitCallback(tsm::IContext* context, HRES } //-------------- Managed Context class. --------------------// -void Context::construct(ThreadType threadType) -{ - bool isAsync; - switch(threadType) { - case ThreadType::None: - default: - isAsync = false; - m_useNativeThread = false; - break; - case ThreadType::Native: - isAsync = true; - m_useNativeThread = false; - break; - case ThreadType::Managed: - isAsync = true; - m_useNativeThread = true; - break; - } - +void Context::construct(bool isAsync, bool useNativeThread) +{ + m_useNativeThread = useNativeThread; m_nativeContext = new native::Context(this, isAsync); } diff --git a/StateMachine.NET/StateMachine.NET.h b/StateMachine.NET/StateMachine.NET.h index 4503a40..7ce4859 100644 --- a/StateMachine.NET/StateMachine.NET.h +++ b/StateMachine.NET/StateMachine.NET.h @@ -83,22 +83,13 @@ public ref class StateMonitorCaller public ref class Context { -public: - // Worker thread type on which StateMachine runs. - enum class ThreadType - { - // Description Thread creation StateMachine class - None, // No worker thread. None tsm::StateMachine - Native, // Use native thread. CreateThread() Win32 API tsm::AsyncStateMachine - Managed, // Use managed thiread System::Threading::Thread tsm::AsyncStateMachine - }; + void construct(bool isAsync, bool useNativeThread); -private: - void construct(ThreadType threadType); +protected: + Context(bool isAsync, bool useNativeThread) { construct(isAsync, useNativeThread); } public: - Context() { construct(ThreadType::None); } - Context(ThreadType threadType) { construct(threadType); } + Context() { construct(false, false); } virtual ~Context(); !Context(); @@ -131,8 +122,17 @@ public ref class Context protected: NativeType* m_nativeContext; bool m_useNativeThread; - tsm_NET::IStateMonitor^ m_stateMonitor; StateMonitorCaller^ m_stateMonitorCaller; + tsm_NET::IStateMonitor^ m_stateMonitor; +}; + +public ref class AsyncContext : public Context +{ +public: + AsyncContext() : Context(true, false) {} + AsyncContext(bool useNativeThread) : Context(true, useNativeThread) {} + +protected: }; public ref class State