DLL.cpp 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. /*
  2. * Copyright (c) Contributors to the Open 3D Engine Project.
  3. * For complete copyright and license terms please see the LICENSE at the root of this distribution.
  4. *
  5. * SPDX-License-Identifier: Apache-2.0 OR MIT
  6. *
  7. */
  8. #include <AzCore/Component/TransformBus.h>
  9. #include <AzCore/Module/DynamicModuleHandle.h>
  10. #include <AzCore/Module/Module.h>
  11. #include <AzCore/Serialization/SerializeContext.h>
  12. #include <AzCore/Module/Environment.h>
  13. #include <AzCore/Name/NameDictionary.h>
  14. #include <AzCore/UnitTest/TestTypes.h>
  15. #include <Tests/DLLTestVirtualClass.h>
  16. using namespace AZ;
  17. namespace UnitTest
  18. {
  19. #if !AZ_UNIT_TEST_SKIP_DLL_TEST
  20. class DLL
  21. : public LeakDetectionFixture
  22. {
  23. public:
  24. void SetUp() override
  25. {
  26. LeakDetectionFixture::SetUp();
  27. AZ::NameDictionary::Create();
  28. }
  29. void TearDown() override
  30. {
  31. AZ::NameDictionary::Destroy();
  32. LeakDetectionFixture::TearDown();
  33. }
  34. void LoadModule()
  35. {
  36. m_handle = DynamicModuleHandle::Create("AzCoreTestDLL");
  37. bool isLoaded = m_handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired);
  38. ASSERT_TRUE(isLoaded) << "Could not load required test module: " << m_handle->GetFilename(); // failed to load the DLL, please check the output paths
  39. auto createModule = m_handle->GetFunction<CreateModuleClassFunction>(CreateModuleClassFunctionName);
  40. // if this fails, we cannot continue as we will just nullptr exception
  41. ASSERT_NE(nullptr, createModule) << "Unable to find create module function in module: " << CreateModuleClassFunctionName;
  42. m_module = createModule();
  43. ASSERT_NE(nullptr, m_module);
  44. }
  45. void UnloadModule()
  46. {
  47. auto destroyModule = m_handle->GetFunction<DestroyModuleClassFunction>(DestroyModuleClassFunctionName);
  48. ASSERT_NE(nullptr, destroyModule) << "Could not find the destroy function in the module: " << DestroyModuleClassFunctionName;
  49. destroyModule(m_module);
  50. m_handle->Unload();
  51. m_handle.reset();
  52. }
  53. AZStd::unique_ptr<DynamicModuleHandle> m_handle;
  54. Module* m_module = nullptr;
  55. };
  56. class TransformHandler
  57. : public TransformNotificationBus::Handler
  58. {
  59. public:
  60. int m_numEBusCalls = 0;
  61. void OnParentChanged(EntityId oldParent, EntityId newParent) override
  62. {
  63. EXPECT_FALSE(oldParent.IsValid());
  64. void* systemAllocatorAddress = (void*)(u64)newParent;
  65. azfree(systemAllocatorAddress); // free memory allocated in a different module, this should be fine as we share environment/allocators
  66. ++m_numEBusCalls;
  67. }
  68. };
  69. #if AZ_TRAIT_DISABLE_FAILED_DLL_TESTS
  70. TEST_F(DLL, DISABLED_CrossModuleBusHandler)
  71. #else
  72. TEST_F(DLL, CrossModuleBusHandler)
  73. #endif // AZ_TRAIT DISABLE_FAILED_DLL_TESTS
  74. {
  75. TransformHandler transformHandler;
  76. LoadModule();
  77. AZ::SerializeContext serializeContext;
  78. m_module->Reflect(&serializeContext);
  79. using DoTests = void(*)();
  80. DoTests runTests = m_handle->GetFunction<DoTests>("DoTests");
  81. EXPECT_NE(nullptr, runTests);
  82. // use the transform bus to verify that we can call EBus messages across modules
  83. transformHandler.BusConnect(EntityId());
  84. EXPECT_EQ(0, transformHandler.m_numEBusCalls);
  85. runTests();
  86. EXPECT_EQ(1, transformHandler.m_numEBusCalls);
  87. transformHandler.BusDisconnect(EntityId());
  88. UnloadModule();
  89. }
  90. #if AZ_TRAIT_DISABLE_FAILED_DLL_TESTS
  91. TEST_F(DLL, DISABLED_CreateVariableFromModuleAndMain)
  92. #else
  93. TEST_F(DLL, CreateVariableFromModuleAndMain)
  94. #endif // AZ_TRAIT_DISABLE_FAILED_DLL_TESTS
  95. {
  96. LoadModule();
  97. const char* envVariableName = "My Variable";
  98. EnvironmentVariable<UnitTest::DLLTestVirtualClass> envVariable;
  99. // create owned environment variable (variable which uses vtable so it can't exist when the module is unloaded.
  100. typedef void(*CreateDLLVar)(const char*);
  101. CreateDLLVar createDLLVar = m_handle->GetFunction<CreateDLLVar>("CreateDLLTestVirtualClass");
  102. createDLLVar(envVariableName);
  103. envVariable = AZ::Environment::FindVariable<UnitTest::DLLTestVirtualClass>(envVariableName);
  104. EXPECT_TRUE(envVariable);
  105. EXPECT_EQ(1, envVariable->m_data);
  106. UnloadModule();
  107. // the variable is owned by the module (due to the vtable reference), once the module
  108. // is unloaded the variable should be unconstructed
  109. EXPECT_FALSE(envVariable);
  110. //////////////////////////////////////////////////////////////////////////
  111. // load the module and see if we recreate our variable
  112. LoadModule();
  113. // create variable
  114. createDLLVar = m_handle->GetFunction<CreateDLLVar>("CreateDLLTestVirtualClass");
  115. createDLLVar(envVariableName);
  116. envVariable = AZ::Environment::FindVariable<UnitTest::DLLTestVirtualClass>(envVariableName);
  117. EXPECT_TRUE(envVariable); // createDLLVar should construct the variable if already there
  118. EXPECT_EQ(1, envVariable->m_data);
  119. UnloadModule();
  120. //////////////////////////////////////////////////////////////////////////
  121. // Since the variable is valid till the last reference is gone, we have the option
  122. // to recreate the variable from a different module
  123. EXPECT_FALSE(envVariable); // Validate that the variable is not constructed
  124. // since the variable is destroyed, we can create it from a different module, the new module will be owner
  125. envVariable = AZ::Environment::CreateVariable<UnitTest::DLLTestVirtualClass>(envVariableName);
  126. EXPECT_TRUE(envVariable); // createDLLVar should construct the variable if already there
  127. EXPECT_EQ(1, envVariable->m_data);
  128. }
  129. TEST_F(DLL, CreateEnvironmentVariableThreadRace)
  130. {
  131. const int numThreads = 64;
  132. int values[numThreads] = { 0 };
  133. AZStd::thread threads[numThreads];
  134. AZ::EnvironmentVariable<int> envVar;
  135. for (int threadIdx = 0; threadIdx < numThreads; ++threadIdx)
  136. {
  137. auto threadFunc = [&values, &envVar, threadIdx]()
  138. {
  139. envVar = AZ::Environment::CreateVariable<int>("CreateEnvironmentVariableThreadRace", threadIdx);
  140. values[threadIdx] = *envVar;
  141. };
  142. threads[threadIdx] = AZStd::thread(threadFunc);
  143. }
  144. for (auto& thread : threads)
  145. {
  146. thread.join();
  147. }
  148. AZStd::unordered_set<int> uniqueValues;
  149. for (const int& value : values)
  150. {
  151. uniqueValues.insert(value);
  152. }
  153. EXPECT_EQ(1, uniqueValues.size());
  154. }
  155. TEST_F(DLL, LoadFailure)
  156. {
  157. auto handle = DynamicModuleHandle::Create("Not_a_DLL");
  158. bool isLoaded = handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired);
  159. EXPECT_FALSE(isLoaded);
  160. bool isUnloaded = handle->Unload();
  161. EXPECT_FALSE(isUnloaded);
  162. }
  163. TEST_F(DLL, LoadModuleTwice)
  164. {
  165. auto handle = DynamicModuleHandle::Create("AzCoreTestDLL");
  166. bool isLoaded = handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired);
  167. EXPECT_TRUE(isLoaded);
  168. EXPECT_TRUE(handle->IsLoaded());
  169. auto secondHandle = DynamicModuleHandle::Create("AzCoreTestDLL");
  170. isLoaded = secondHandle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired);
  171. EXPECT_TRUE(isLoaded);
  172. EXPECT_TRUE(handle->IsLoaded());
  173. EXPECT_TRUE(secondHandle->IsLoaded());
  174. bool isUnloaded = handle->Unload();
  175. EXPECT_TRUE(isUnloaded);
  176. EXPECT_FALSE(handle->IsLoaded());
  177. EXPECT_TRUE(secondHandle->IsLoaded());
  178. isUnloaded = secondHandle->Unload();
  179. EXPECT_TRUE(isUnloaded);
  180. EXPECT_FALSE(handle->IsLoaded());
  181. EXPECT_FALSE(secondHandle->IsLoaded());
  182. }
  183. TEST_F(DLL, NoLoadModule)
  184. {
  185. auto handle = DynamicModuleHandle::Create("AzCoreTestDLL");
  186. bool isLoaded = handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired | AZ::DynamicModuleHandle::LoadFlags::NoLoad);
  187. EXPECT_FALSE(isLoaded);
  188. EXPECT_FALSE(handle->IsLoaded());
  189. }
  190. TEST_F(DLL, NoLoadLoadedModule)
  191. {
  192. auto handle = DynamicModuleHandle::Create("AzCoreTestDLL");
  193. bool isLoaded = handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired);
  194. EXPECT_TRUE(isLoaded);
  195. EXPECT_TRUE(handle->IsLoaded());
  196. auto secondHandle = DynamicModuleHandle::Create("AzCoreTestDLL");
  197. isLoaded = secondHandle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired | AZ::DynamicModuleHandle::LoadFlags::NoLoad);
  198. EXPECT_TRUE(isLoaded);
  199. EXPECT_TRUE(handle->IsLoaded());
  200. EXPECT_TRUE(secondHandle->IsLoaded());
  201. bool isUnloaded = handle->Unload();
  202. EXPECT_TRUE(isUnloaded);
  203. EXPECT_FALSE(handle->IsLoaded());
  204. EXPECT_TRUE(secondHandle->IsLoaded());
  205. {
  206. // Check that the Module wasn't unloaded by the OS when unloading one DynamicModuleHandle (the OS should have 1 ref count left).
  207. isLoaded = handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired | AZ::DynamicModuleHandle::LoadFlags::NoLoad);
  208. EXPECT_TRUE(isLoaded);
  209. EXPECT_TRUE(handle->IsLoaded());
  210. handle->Unload();
  211. }
  212. isUnloaded = secondHandle->Unload();
  213. EXPECT_TRUE(isUnloaded);
  214. EXPECT_FALSE(handle->IsLoaded());
  215. EXPECT_FALSE(secondHandle->IsLoaded());
  216. {
  217. // Check that the Module was unloaded by the OS.
  218. isLoaded = handle->Load(AZ::DynamicModuleHandle::LoadFlags::InitFuncRequired | AZ::DynamicModuleHandle::LoadFlags::NoLoad);
  219. EXPECT_FALSE(isLoaded);
  220. EXPECT_FALSE(handle->IsLoaded());
  221. handle->Unload();
  222. }
  223. }
  224. #endif // !AZ_UNIT_TEST_SKIP_DLL_TEST
  225. }