changeset 4063:e00f3d089991 framework

shared library of orthanc framework
author Sebastien Jodogne <s.jodogne@gmail.com>
date Thu, 11 Jun 2020 16:40:34 +0200
parents 0953b3dc3261
children d6362b2c4b61
files CMakeLists.txt OrthancFramework/Resources/CMake/Compiler.cmake OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake OrthancFramework/Resources/CMake/EmscriptenParameters.cmake OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake OrthancFramework/Resources/WebAssembly.txt OrthancFramework/SharedLibrary/CMakeLists.txt OrthancFramework/SharedLibrary/DllMain.cpp OrthancFramework/SharedLibrary/OrthancFramework.h.in OrthancFramework/Sources/Cache/MemoryCache.h OrthancFramework/Sources/Cache/MemoryObjectCache.h OrthancFramework/Sources/Cache/MemoryStringCache.h OrthancFramework/Sources/Cache/SharedArchive.h OrthancFramework/Sources/DicomFormat/DicomTag.h OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h OrthancFramework/Sources/DicomParsing/DicomModification.h OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h OrthancFramework/Sources/FileStorage/StorageAccessor.h OrthancFramework/Sources/Images/IImageWriter.h OrthancFramework/Sources/Images/JpegReader.h OrthancFramework/Sources/Images/JpegWriter.h OrthancFramework/Sources/Images/PamReader.h OrthancFramework/Sources/Images/PamWriter.h OrthancFramework/Sources/Images/PngReader.h OrthancFramework/Sources/Images/PngWriter.h OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h OrthancFramework/Sources/JobsEngine/IJob.h OrthancFramework/Sources/JobsEngine/IJobUnserializer.h OrthancFramework/Sources/JobsEngine/JobInfo.h OrthancFramework/Sources/JobsEngine/JobStepResult.h OrthancFramework/Sources/JobsEngine/JobsEngine.h OrthancFramework/Sources/JobsEngine/JobsRegistry.h OrthancFramework/Sources/JobsEngine/Operations/IJobOperation.h OrthancFramework/Sources/JobsEngine/Operations/JobOperationValue.h OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h OrthancFramework/UnitTestsSources/CMakeLists.txt OrthancFramework/UnitTestsSources/DicomMapTests.cpp OrthancFramework/UnitTestsSources/FileStorageTests.cpp OrthancFramework/UnitTestsSources/FrameworkTests.cpp OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp OrthancFramework/UnitTestsSources/ImageTests.cpp OrthancFramework/UnitTestsSources/JobsTests.cpp OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp OrthancFramework/UnitTestsSources/LoggingTests.cpp OrthancFramework/UnitTestsSources/LuaTests.cpp OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp OrthancFramework/UnitTestsSources/RestApiTests.cpp OrthancFramework/UnitTestsSources/SQLiteChromiumTests.cpp OrthancFramework/UnitTestsSources/SQLiteTests.cpp OrthancFramework/UnitTestsSources/SharedLibraryUnitTests.cpp OrthancFramework/UnitTestsSources/StreamTests.cpp OrthancFramework/UnitTestsSources/ToolboxTests.cpp OrthancFramework/UnitTestsSources/ZipTests.cpp
diffstat 66 files changed, 1403 insertions(+), 372 deletions(-) [+]
line wrap: on
line diff
--- a/CMakeLists.txt	Thu Jun 11 14:38:31 2020 +0200
+++ b/CMakeLists.txt	Thu Jun 11 16:40:34 2020 +0200
@@ -248,6 +248,13 @@
 ## Configuration of the C/C++ macros
 #####################################################################
 
+if (STATIC_BUILD)
+  add_definitions(-DORTHANC_STATIC=1)
+else()
+  add_definitions(-DORTHANC_STATIC=0)
+endif()
+
+
 if (ENABLE_PLUGINS)
   add_definitions(-DORTHANC_ENABLE_PLUGINS=1)
 else()
--- a/OrthancFramework/Resources/CMake/Compiler.cmake	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Resources/CMake/Compiler.cmake	Thu Jun 11 16:40:34 2020 +0200
@@ -1,5 +1,11 @@
 # This file sets all the compiler-related flags
 
+
+# Save the current compiler flags to the cache every time cmake configures the project
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" CACHE STRING "compiler flags" FORCE)
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE STRING "compiler flags" FORCE)
+
+
 include(CheckLibraryExists)
 
 if ((CMAKE_CROSSCOMPILING AND NOT
@@ -206,19 +212,7 @@
 
 elseif (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
   message("Building using Emscripten (for WebAssembly or asm.js targets)")
-
-  # The BINARYEN_TRAP_MODE specifies what to do when divisions per
-  # zero (and similar conditions like integer overflows) are
-  # encountered: The "clamp" mode avoids throwing errors, as they
-  # cannot be properly catched by "try {} catch (...)" constructions.
-  # Setting this option to "ON" fixes error: "shared:ERROR:
-  # BINARYEN_TRAP_MODE is not supported by the LLVM wasm backend" if
-  # using the "upstream" backend of Emscripten.
-  if (NOT EMSCRIPTEN_SET_LLVM_WASM_BACKEND)
-    set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_TRAP_MODE='\"clamp\"'")
-  endif()
-
-  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+  include(EmscriptenParameters.cmake)
   
 elseif (CMAKE_SYSTEM_NAME STREQUAL "Android")
 
@@ -254,10 +248,3 @@
   # preceding batches. https://cmake.org/Bug/view.php?id=14874
   set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> <LINK_FLAGS> q <TARGET> <OBJECTS>")
 endif()
-
-
-if (STATIC_BUILD)
-  add_definitions(-DORTHANC_STATIC=1)
-else()
-  add_definitions(-DORTHANC_STATIC=0)
-endif()
--- a/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Resources/CMake/DownloadOrthancFramework.cmake	Thu Jun 11 16:40:34 2020 +0200
@@ -399,8 +399,10 @@
   include(${CMAKE_CURRENT_LIST_DIR}/AutoGeneratedCode.cmake)
   set(EMBED_RESOURCES_PYTHON ${CMAKE_CURRENT_LIST_DIR}/EmbedResources.py)
 
-  if (NOT "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" AND
-      NOT ORTHANC_FRAMEWORK_STATIC)
+  if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows" OR
+      ORTHANC_FRAMEWORK_STATIC)
+    include_directories(${ORTHANC_FRAMEWORK_ROOT}/..)
+  else()
     # Look for mandatory dependency JsonCpp (cf. JsonCppConfiguration.cmake)
     find_path(JSONCPP_INCLUDE_DIR json/reader.h
       /usr/include/jsoncpp
@@ -447,6 +449,22 @@
       endif()
       link_libraries(sqlite3)
     endif()
+
+    # Optional component - Pugixml
+    if (ENABLE_PUGIXML)
+      CHECK_INCLUDE_FILE_CXX(pugixml.hpp HAVE_PUGIXML_H)
+      if (NOT HAVE_PUGIXML_H)
+        message(FATAL_ERROR "Please install the libpugixml-dev package")
+      endif()      
+      link_libraries(pugixml)
+    endif()
+
+    # Optional component - DCMTK
+    if (ENABLE_DCMTK)
+      include(FindDCMTK)
+      include_directories(${DCMTK_INCLUDE_DIRS})
+      link_libraries(${DCMTK_LIBRARIES})
+    endif()
   endif()
 
   # Optional component - Google Test
@@ -462,7 +480,6 @@
 
   if ("${CMAKE_SYSTEM_NAME}" STREQUAL "Windows")
     set(ORTHANC_FRAMEWORK_INCLUDE_DIR ${ORTHANC_FRAMEWORK_ROOT})
-    include_directories(${ORTHANC_FRAMEWORK_ROOT}/..)
   else()
     find_path(ORTHANC_FRAMEWORK_INCLUDE_DIR OrthancFramework.h
       /usr/include/orthanc-framework
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/CMake/EmscriptenParameters.cmake	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,24 @@
+if (NOT "${EMSCRIPTEN_TRAP_MODE}" STREQUAL "")
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_TRAP_MODE='\"${EMSCRIPTEN_TRAP_MODE}\"'")
+endif()
+
+set(WASM_FLAGS "-s SIDE_MODULE=1 -s EXPORT_ALL=1")
+
+if (EMSCRIPTEN_TARGET_MODE STREQUAL "wasm")
+  # WebAssembly
+  set(WASM_FLAGS "${WASM_FLAGS} -s WASM=1 -s FETCH=1")
+  
+elseif (EMSCRIPTEN_TARGET_MODE STREQUAL "asm.js")
+  # asm.js targeting IE 11
+  set(WASM_FLAGS "-s WASM=0 -s ASM_JS=2 -s FETCH=1 -s LEGACY_VM_SUPPORT=1")
+
+else()
+  message(FATAL_ERROR "Bad value for EMSCRIPTEN_TARGET_MODE: ${EMSCRIPTEN_TARGET_MODE}")
+endif()
+
+if (CMAKE_BUILD_TYPE STREQUAL "Debug")
+  set(WASM_FLAGS "${WASM_FLAGS} -s SAFE_HEAP=1")
+endif()
+
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${WASM_FLAGS}")
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WASM_FLAGS}")
--- a/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Resources/CMake/OrthancFrameworkParameters.cmake	Thu Jun 11 16:40:34 2020 +0200
@@ -71,13 +71,16 @@
 set(USE_LEGACY_JSONCPP OFF CACHE BOOL "Use the old branch 0.x.y of JsonCpp, that does not require a C++11 compiler (for LSB and old versions of Visual Studio)")
 set(USE_LEGACY_LIBICU OFF CACHE BOOL "Use icu icu4c-58_2, latest version not requiring a C++11 compiler (for LSB and old versions of Visual Studio)")
 set(MSVC_MULTIPLE_PROCESSES OFF CACHE BOOL "Add the /MP option to build with multiple processes if using Visual Studio")
-set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND OFF CACHE BOOL "Sets the compiler flags required to use the LLVM Web Assembly backend in emscripten")
+set(EMSCRIPTEN_TARGET_MODE "wasm" CACHE STRING "Sets the target mode for Emscripten (can be \"wasm\" or \"asm.js\")")
+set(EMSCRIPTEN_TRAP_MODE "" CACHE STRING "Sets the trap mode for Emscripten for numeric errors (can notably be empty, or \"clamp\")")
 set(OPENSSL_STATIC_VERSION "1.1.1" CACHE STRING "Version of OpenSSL to be used in static builds (can be \"1.0.2\", or \"1.1.1\")")
 
-mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
+mark_as_advanced(EMSCRIPTEN_TARGET_MODE)
+mark_as_advanced(EMSCRIPTEN_TRAP_MODE)
 mark_as_advanced(SYSTEM_MONGOOSE_USE_CALLBACKS)
+mark_as_advanced(USE_DCMTK_362_PRIVATE_DIC)
+mark_as_advanced(USE_GOOGLE_TEST_DEBIAN_PACKAGE)
 mark_as_advanced(USE_PUGIXML)
-mark_as_advanced(USE_DCMTK_362_PRIVATE_DIC)
 
 
 #####################################################################
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/Resources/WebAssembly.txt	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,58 @@
+
+======================================
+== Notes about WebAssembly ===========
+======================================
+
+
+
+Trap mode
+=========
+
+The BINARYEN_TRAP_MODE specifies what to do when divisions per zero
+(and similar conditions like integer overflows) are encountered. This
+can be set through the option "EMSCRIPTEN_TRAP_MODE" in Orthanc.
+
+
+
+Previous versions (<= 1.7.1)
+----------------------------
+
+The "clamp" mode avoids throwing errors, as they cannot be properly
+catched by "try {} catch (...)" constructions. HOWEVER, the "upstream"
+backend of Emscripten (which is now the default) doesn't support this
+option.
+
+In Orthanc <= 1.7.1, the "clamp" mode was used by default. As a
+consequence, there was an old flag "EMSCRIPTEN_SET_LLVM_WASM_BACKEND"
+to setting BINARYEN_TRAP_MODE.
+
+Here is the definition of the parameter that was in
+"OrthancFrameworkParameters.cmake":
+
+>>>>>
+set(EMSCRIPTEN_SET_LLVM_WASM_BACKEND OFF CACHE BOOL "Sets the compiler flags required to use the LLVM Web Assembly backend in emscripten")
+<<<<<
+
+Setting this option to "ON" fixes error: "shared:ERROR:
+BINARYEN_TRAP_MODE is not supported by the LLVM wasm backend" if using
+the "upstream" backend of Emscripten. Here is the corresponding
+implementation that was in "Compiler.cmake":
+
+>>>>>
+if (NOT EMSCRIPTEN_SET_LLVM_WASM_BACKEND)
+  set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s BINARYEN_TRAP_MODE='\"clamp\"'")
+endif()
+<<<<<
+
+
+
+
+Linker flags
+============
+
+Since Orthanc 1.7.2, "Compiler.cmake" doesn't set any linking option
+for Emscripten. In Orthanc <= 1.7.1, the following was done:
+
+>>>>>
+set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -s EXTRA_EXPORTED_RUNTIME_METHODS='[\"ccall\", \"cwrap\"]'")
+<<<<<
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/SharedLibrary/CMakeLists.txt	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,421 @@
+
+## To see all the exported symbols in the DLL:
+##
+##  $ i686-w64-mingw32-objdump -p ./libOrthancFramework.dll
+##
+## IMPORTANT: "-static-libgcc" prevents catching exception in the
+## .EXE, which makes throwing exceptions crash the software!
+##
+
+
+cmake_minimum_required(VERSION 2.8)
+project(OrthancFramework)
+
+# *Do not* use CMAKE_INSTALL_PREFIX, otherwise CMake automatically
+# *adds CMAKE_INSTALL_PREFIX to the include_directories()!
+set(ORTHANC_INSTALL_DIR "/tmp/install" CACHE PATH "")
+SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests")
+
+
+# This must be before inclusion of "OrthancFrameworkParameters.cmake" to take effect
+if (CMAKE_SYSTEM_NAME STREQUAL "Windows" AND
+    CMAKE_COMPILER_IS_GNUCXX) # MinGW
+  set(DYNAMIC_MINGW_STDLIB ON)   # Disable static linking against libc (to throw exceptions)
+  set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -static-libstdc++")
+endif()
+
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/OrthancFrameworkParameters.cmake)
+
+set(ENABLE_DCMTK ON)
+set(ENABLE_DCMTK_TRANSCODING ON)
+set(ENABLE_GOOGLE_TEST ON)
+set(ENABLE_JPEG ON)
+set(ENABLE_LOCALE ON)
+set(ENABLE_LUA ON)
+set(ENABLE_PNG ON)
+set(ENABLE_PUGIXML ON)
+set(ENABLE_ZLIB ON)
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  # WebAssembly or asm.js
+  set(BOOST_LOCALE_BACKEND "libiconv")
+  set(ORTHANC_SANDBOXED ON)
+
+else()
+  # Enable all the remaining modules for other targets
+  set(ENABLE_CRYPTO_OPTIONS ON)
+  set(ENABLE_DCMTK_NETWORKING ON)
+  set(ENABLE_OPENSSL_ENGINES ON)
+  set(ENABLE_SQLITE ON)
+  set(ENABLE_WEB_CLIENT ON)
+  set(ENABLE_WEB_SERVER ON)
+
+  set(BOOST_LOCALE_BACKEND "icu")
+endif()
+
+
+set(ORTHANC_BUILDING_FRAMEWORK_LIBRARY ON)
+
+# Don't use the version script that is reserved for plugins
+set(ENABLE_PLUGINS_VERSION_SCRIPT OFF)
+
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/OrthancFrameworkConfiguration.cmake)
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_JSONCPP)
+  set(ORTHANC_STATIC_JSONCPP ON)
+else()
+  set(ORTHANC_STATIC_JSONCPP OFF)
+endif()
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_BOOST)
+  set(ORTHANC_STATIC_BOOST ON)
+else()
+  set(ORTHANC_STATIC_BOOST OFF)
+endif()
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_SQLITE)
+  set(ORTHANC_STATIC_SQLITE ON)
+else()
+  set(ORTHANC_STATIC_SQLITE OFF)
+endif()
+
+if (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML)
+  set(ORTHANC_STATIC_PUGIXML ON)
+else()
+  set(ORTHANC_STATIC_PUGIXML OFF)
+endif()
+
+
+if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR
+    CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+
+  # Control the visibility of JsonCpp
+  if (ORTHANC_STATIC_JSONCPP)
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+      set_source_files_properties(${JSONCPP_SOURCES}
+        PROPERTIES COMPILE_DEFINITIONS "JSON_API=__declspec(dllexport)"
+        )
+      set(ORTHANC_JSON_API "__declspec(dllimport)")
+    else()
+      set(ORTHANC_JSON_API "__attribute__((visibility(\"default\")))")
+      set_source_files_properties(${JSONCPP_SOURCES}
+        PROPERTIES COMPILE_DEFINITIONS "JSON_API=${ORTHANC_JSON_API}"
+        )
+    endif()
+  endif()
+
+  # Control the visibility of SQLite
+  if (ORTHANC_STATIC_SQLITE)
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+      set_source_files_properties(${SQLITE_SOURCES}
+        PROPERTIES COMPILE_DEFINITIONS "SQLITE_API=__declspec(dllexport)"
+        )
+      set(ORTHANC_SQLITE_API "__declspec(dllimport)")
+    else()
+      set(ORTHANC_SQLITE_API "__attribute__((visibility(\"default\")))")
+      set_source_files_properties(${SQLITE_SOURCES}
+        PROPERTIES COMPILE_DEFINITIONS "SQLITE_API=${ORTHANC_SQLITE_API}"
+        )
+    endif()
+  endif()
+
+  # Control the visibility of Boost
+  if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows" AND
+      ORTHANC_STATIC_BOOST)
+    set_source_files_properties(${ORTHANC_CORE_SOURCES_INTERNAL}
+      PROPERTIES COMPILE_DEFINITIONS "BOOST_DATE_TIME_SOURCE;BOOST_FILESYSTEM_SOURCE;BOOST_LOCALE_SOURCE;BOOST_REGEX_SOURCE"
+      )
+  endif()
+
+  # Control the visibility of Pugixml
+  if (ORTHANC_STATIC_PUGIXML)
+    if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+      set_source_files_properties(${PUGIXML_SOURCES}
+        PROPERTIES COMPILE_DEFINITIONS "PUGIXML_API=__declspec(dllexport)"
+        )
+      set(ORTHANC_PUGIXML_API "__declspec(dllimport)")
+    else()
+      set(ORTHANC_PUGIXML_API "__attribute__((visibility(\"default\")))")
+      set_source_files_properties(${PUGIXML_SOURCES}
+        PROPERTIES COMPILE_DEFINITIONS "PUGIXML_API=${ORTHANC_PUGIXML_API}"
+        )
+    endif()
+  endif()
+endif()
+
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  execute_process(
+    COMMAND 
+    ${PYTHON_EXECUTABLE} ${CMAKE_SOURCE_DIR}/../Resources/WindowsResources.py
+    ${ORTHANC_VERSION} "OrthancFramework" OrthancFramework.dll "Shared library containing the Orthanc framework"
+    ERROR_VARIABLE Failure
+    OUTPUT_FILE ${AUTOGENERATED_DIR}/Version.rc
+    )
+
+  if (Failure)
+    message(FATAL_ERROR "Error while computing the version information: ${Failure}")
+  endif()
+
+  list(APPEND AUTOGENERATED_SOURCES  ${AUTOGENERATED_DIR}/Version.rc)
+endif()
+
+
+add_definitions(
+  -DCIVETWEB_API=    # Don't export the public symbols from CivetWeb
+  )
+
+# Those two files collided with each other, and thus are merged into a
+# single "DllMain.cpp"
+list(REMOVE_ITEM ORTHANC_CORE_SOURCES
+  ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
+  ${OPENSSL_SOURCES_DIR}/crypto/dllmain.c
+  )
+    
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  # In WebAssembly, a SIDE_MODULE is an executable
+  add_executable(OrthancFramework
+    ${AUTOGENERATED_SOURCES}
+    ${ORTHANC_CORE_SOURCES}
+    ${ORTHANC_DICOM_SOURCES}
+    DllMain.cpp
+    )
+
+  # CMake does not natively handle SIDE_MODULE, and believes that
+  # Emscripten produces a ".js" file (whereas it creates only the
+  # ".wasm"). Create a dummy ".js" for target to work.
+  add_custom_command(
+    TARGET OrthancFramework POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_CURRENT_BINARY_DIR}/OrthancFramework.js
+    )
+else()
+  add_library(OrthancFramework SHARED
+    ${AUTOGENERATED_SOURCES}
+    ${ORTHANC_CORE_SOURCES}
+    ${ORTHANC_DICOM_SOURCES}
+    DllMain.cpp
+    )
+endif()
+
+# By default, hide all the symbols
+set_target_properties(OrthancFramework PROPERTIES C_VISIBILITY_PRESET hidden)
+set_target_properties(OrthancFramework PROPERTIES CXX_VISIBILITY_PRESET hidden)
+
+target_link_libraries(OrthancFramework ${DCMTK_LIBRARIES})
+
+if (LIBICU_LIBRARIES)
+  target_link_libraries(OrthancFramework ${LIBICU_LIBRARIES})
+endif()
+
+if (${CMAKE_SYSTEM_NAME} STREQUAL "Windows")
+  target_link_libraries(OrthancFramework winpthread)
+endif()
+
+
+if (NOT CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  set_target_properties(
+    OrthancFramework PROPERTIES 
+    VERSION ${ORTHANC_VERSION} 
+    SOVERSION ${ORTHANC_VERSION}
+    )
+endif()
+
+
+file(
+  COPY ${CMAKE_SOURCE_DIR}/../Sources/
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
+  NO_SOURCE_PERMISSIONS
+  FILES_MATCHING
+  PATTERN "*.h"
+  PATTERN OrthancFramework.h EXCLUDE
+  )
+
+configure_file(
+  ${CMAKE_SOURCE_DIR}/OrthancFramework.h.in
+  ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework/OrthancFramework.h
+  )
+
+
+if (ORTHANC_STATIC_BOOST)
+  file(
+    COPY ${BOOST_SOURCES_DIR}/boost/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/boost/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    PATTERN "*.hpp"
+    PATTERN "*.ipp"
+    )
+endif()
+
+
+if (ENABLE_SQLITE AND ORTHANC_STATIC_SQLITE)
+  file(
+    COPY ${SQLITE_SOURCES_DIR}/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+endif()
+
+
+if (ORTHANC_STATIC_JSONCPP)
+  file(
+    COPY ${JSONCPP_SOURCES_DIR}/include/json/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/json/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+endif()
+
+
+if (ENABLE_DCMTK AND (STATIC_BUILD OR NOT USE_SYSTEM_DCMTK))
+  file(
+    COPY ${DCMTK_SOURCES_DIR}/dcmdata/include/dcmtk/dcmdata/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/dcmdata/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+
+  file(
+    COPY ${DCMTK_SOURCES_DIR}/config/include/dcmtk/config/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/config/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+
+  file(
+    COPY ${DCMTK_SOURCES_DIR}/ofstd/include/dcmtk/ofstd/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/ofstd/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+
+  file(
+    COPY ${DCMTK_SOURCES_DIR}/oflog/include/dcmtk/oflog/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/dcmtk/oflog/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+endif()
+
+
+if (ENABLE_PUGIXML AND (STATIC_BUILD OR NOT USE_SYSTEM_PUGIXML))
+  file(
+    COPY ${PUGIXML_SOURCES_DIR}/src/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.hpp"
+    )
+endif()
+
+
+if (ENABLE_LUA AND (STATIC_BUILD OR NOT USE_SYSTEM_LUA))
+  file(
+    COPY ${LUA_SOURCES_DIR}/src/
+    DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/
+    NO_SOURCE_PERMISSIONS
+    FILES_MATCHING
+    PATTERN "*.h"
+    )
+endif()
+
+
+if (OFF)
+  # These files are fully abstracted by the Orthanc Framework classes
+  if (ENABLE_PNG AND (STATIC_BUILD OR NOT USE_SYSTEM_LIBPNG))
+    file(
+      COPY ${LIBPNG_SOURCES_DIR}/
+      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/
+      NO_SOURCE_PERMISSIONS
+      FILES_MATCHING
+      PATTERN "*.h"
+      )
+  endif()
+
+  if (ENABLE_ZLIB AND (STATIC_BUILD OR NOT USE_SYSTEM_ZLIB))
+    file(
+      COPY ${ZLIB_SOURCES_DIR}/
+      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/
+      NO_SOURCE_PERMISSIONS
+      FILES_MATCHING
+      PATTERN "*.h"
+      )
+  endif()
+
+  if (ENABLE_JPEG AND (STATIC_BUILD OR NOT USE_SYSTEM_LIBJPEG))
+    file(
+      COPY ${LIBJPEG_SOURCES_DIR}/
+      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/
+      NO_SOURCE_PERMISSIONS
+      FILES_MATCHING
+      PATTERN "*.h"
+      )
+  endif()
+
+  if (ENABLE_WEB_CLIENT AND (STATIC_BUILD OR NOT USE_SYSTEM_CURL))
+    file(
+      COPY ${CURL_SOURCES_DIR}/include/curl/
+      DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/Include/curl/
+      NO_SOURCE_PERMISSIONS
+      FILES_MATCHING
+      PATTERN "*.h"
+      )
+  endif()
+endif()
+
+
+
+
+
+include(ExternalProject)
+
+if (CMAKE_TOOLCHAIN_FILE)
+  # Take absolute path to the toolchain
+  get_filename_component(TMP ${CMAKE_TOOLCHAIN_FILE} REALPATH BASE ${CMAKE_SOURCE_DIR}/..)
+  list(APPEND Flags -DCMAKE_TOOLCHAIN_FILE=${TMP})
+endif()
+
+# Build the unit tests, linking them against the just-created
+# "OrthancFramework" library
+externalproject_add(UnitTests
+  SOURCE_DIR "${CMAKE_SOURCE_DIR}/../UnitTestsSources"
+  CMAKE_ARGS
+  -DALLOW_DOWNLOADS:BOOL=ON
+  -DCMAKE_BUILD_TYPE:STRING=${CMAKE_BUILD_TYPE}
+  -DORTHANC_FRAMEWORK_LIBDIR:PATH=${CMAKE_CURRENT_BINARY_DIR}
+  -DORTHANC_FRAMEWORK_ROOT:PATH=${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
+  -DORTHANC_FRAMEWORK_SOURCE:STRING=system
+  -DORTHANC_FRAMEWORK_STATIC:BOOL=${STATIC_BUILD}
+  -DUNIT_TESTS_WITH_HTTP_CONNEXIONS:BOOL=${UNIT_TESTS_WITH_HTTP_CONNEXIONS}
+  -DUSE_SYSTEM_GOOGLE_TEST:BOOL=OFF
+  ${Flags}
+  )
+
+add_dependencies(UnitTests OrthancFramework)
+
+
+install(
+  TARGETS OrthancFramework
+  RUNTIME DESTINATION ${ORTHANC_INSTALL_DIR}/lib    # Destination for Windows
+  LIBRARY DESTINATION ${ORTHANC_INSTALL_DIR}/lib    # Destination for Linux
+  )
+
+if (CMAKE_SYSTEM_NAME STREQUAL "Emscripten")
+  install(FILES
+    ${CMAKE_CURRENT_BINARY_DIR}/OrthancFramework.wasm
+    LIBRARY DESTINATION ${ORTHANC_INSTALL_DIR}/lib
+    )
+endif()
+
+install(
+  DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/Include/orthanc-framework
+  DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/orthanc-framework
+  )
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/SharedLibrary/DllMain.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,61 @@
+/**
+
+   This file merges 2 files:
+   ${BOOST_SOURCES_DIR}/libs/thread/src/win32/tss_dll.cpp
+   ${OPENSSL_SOURCES_DIR}/crypto/dllmain.c
+
+ **/
+
+#if defined(_WIN32) || defined(__CYGWIN__)
+# ifdef __CYGWIN__
+#  include <windows.h>
+# endif
+
+#include "e_os.h"
+#include "crypto/cryptlib.h"
+
+BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
+{
+  switch (fdwReason)
+  {
+    case DLL_PROCESS_ATTACH:
+      //OPENSSL_cpuid_setup();  // TODO - Is this necessary?
+      break;
+        
+    case DLL_THREAD_ATTACH:
+      break;
+        
+    case DLL_THREAD_DETACH:
+      OPENSSL_thread_stop();
+      break;
+        
+    case DLL_PROCESS_DETACH:
+      break;
+  }
+    
+  return TRUE;
+}
+
+#endif
+
+
+namespace boost
+{
+  void tss_cleanup_implemented()
+  {
+    /*
+      This function's sole purpose is to cause a link error in cases where
+      automatic tss cleanup is not implemented by Boost.Threads as a
+      reminder that user code is responsible for calling the necessary
+      functions at the appropriate times (and for implementing an a
+      tss_cleanup_implemented() function to eliminate the linker's
+      missing symbol error).
+
+      If Boost.Threads later implements automatic tss cleanup in cases
+      where it currently doesn't (which is the plan), the duplicate
+      symbol error will warn the user that their custom solution is no
+      longer needed and can be removed.
+    */
+  }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/SharedLibrary/OrthancFramework.h.in	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,283 @@
+#pragma once
+
+/**
+ * Besides the "pragma once" above that only protects this file,
+ * define a macro to prevent including different versions of
+ * "OrthancFramework.h"
+ **/
+#ifndef __ORTHANC_FRAMEWORK_H
+#define __ORTHANC_FRAMEWORK_H
+
+
+#if defined(_WIN32) || defined (__CYGWIN__)
+#  define ORTHANC_PUBLIC __declspec(dllimport)
+#  define ORTHANC_LOCAL
+#else
+#  if __GNUC__ >= 4
+#    define ORTHANC_PUBLIC __attribute__ ((visibility ("default")))
+#    define ORTHANC_LOCAL  __attribute__ ((visibility ("hidden")))
+#  else
+#    define ORTHANC_PUBLIC
+#    define ORTHANC_LOCAL
+#    pragma warning Unknown dynamic link import/export semantics
+#  endif
+#endif
+
+
+/**
+ * Configuration macros that are always set to the same value if using
+ * "OrthancFrameworkConfiguration.cmake"
+ **/
+
+#define ORTHANC_ENABLE_BASE64 1
+#define ORTHANC_ENABLE_MD5 1
+
+
+
+/**
+ * Configuration macros that needn't to be renamed
+ **/
+
+#define ORTHANC_SQLITE_VERSION @ORTHANC_SQLITE_VERSION@
+#define ORTHANC_VERSION "@ORTHANC_VERSION@"
+#define ORTHANC_VERSION_MAJOR @ORTHANC_VERSION_MAJOR@
+#define ORTHANC_VERSION_MINOR @ORTHANC_VERSION_MINOR@
+#define ORTHANC_VERSION_REVISION @ORTHANC_VERSION_REVISION@
+
+#cmakedefine01 ORTHANC_ENABLE_CIVETWEB
+#cmakedefine01 ORTHANC_ENABLE_LOGGING
+#cmakedefine01 ORTHANC_ENABLE_LOGGING_STDIO
+#cmakedefine01 ORTHANC_ENABLE_MONGOOSE
+#cmakedefine01 ORTHANC_SANDBOXED
+#cmakedefine01 ORTHANC_STATIC_BOOST
+#cmakedefine01 ORTHANC_STATIC_JSONCPP
+#cmakedefine01 ORTHANC_STATIC_SQLITE
+
+#if ORTHANC_STATIC_BOOST == 1 && !defined(BOOST_LEXICAL_CAST_ASSUME_C_LOCALE)
+#  define BOOST_LEXICAL_CAST_ASSUME_C_LOCALE
+#endif
+
+#if ORTHANC_STATIC_JSONCPP == 1
+#  if defined(JSON_API)
+#    error JSON_API should not be defined
+#  else
+#    define JSON_API @ORTHANC_JSON_API@
+#  endif
+#endif
+
+#if ORTHANC_STATIC_SQLITE == 1
+#  if defined(SQLITE_API)
+#    error SQLITE_API should not be defined
+#  else
+#    define SQLITE_API @ORTHANC_SQLITE_API@
+#  endif
+#endif
+
+#if ORTHANC_STATIC_PUGIXML == 1
+#  if defined(PUGIXML_API)
+#    error PUGIXML_API should not be defined
+#  else
+#    define PUGIXML_API @ORTHANC_PUGIXML_API@
+#  endif
+#endif
+
+
+#define ORTHANC_FRAMEWORK_VERSION_IS_ABOVE(major, minor, revision)      \
+  (ORTHANC_VERSION_MAJOR > major ||                                     \
+   (ORTHANC_VERSION_MAJOR == major &&                                   \
+    (ORTHANC_VERSION_MINOR > minor ||                                   \
+     (ORTHANC_VERSION_MINOR == minor &&                                 \
+      ORTHANC_VERSION_REVISION >= revision))))
+
+
+/**
+ * Configuration macros that must be renamed, prefixing them by
+ * "ORTHANC_"
+ **/
+
+#cmakedefine01 ENABLE_DCMTK
+#if !defined(ENABLE_DCMTK)
+#  error CMake error
+#elif ENABLE_DCMTK == 1
+#  define ORTHANC_ENABLE_DCMTK 1
+#else
+#  define ORTHANC_ENABLE_DCMTK 0
+#endif
+#undef ENABLE_DCMTK
+
+
+#cmakedefine01 ENABLE_DCMTK_NETWORKING
+#if !defined(ENABLE_DCMTK_NETWORKING)
+#  error CMake error
+#elif ENABLE_DCMTK_NETWORKING == 1
+#  define ORTHANC_ENABLE_DCMTK_NETWORKING 1
+#else
+#  define ORTHANC_ENABLE_DCMTK_NETWORKING 0
+#endif
+#undef ENABLE_DCMTK_NETWORKING
+
+
+#cmakedefine01 ENABLE_DCMTK_JPEG
+#if !defined(ENABLE_DCMTK_JPEG)
+#  error CMake error
+#elif ENABLE_DCMTK_JPEG == 1
+#  define ORTHANC_ENABLE_DCMTK_JPEG 1
+#else
+#  define ORTHANC_ENABLE_DCMTK_JPEG 0
+#endif
+#undef ENABLE_DCMTK_JPEG
+
+
+#cmakedefine01 ENABLE_DCMTK_JPEG_LOSSLESS
+#if !defined(ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error CMake error
+#elif ENABLE_DCMTK_JPEG_LOSSLESS == 1
+#  define ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS 1
+#else
+#  define ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS 0
+#endif
+#undef ENABLE_DCMTK_JPEG_LOSSLESS
+
+
+#cmakedefine01 ENABLE_DCMTK_TRANSCODING
+#if !defined(ENABLE_DCMTK_TRANSCODING)
+#  error CMake error
+#elif ENABLE_DCMTK_TRANSCODING == 1
+#  define ORTHANC_ENABLE_DCMTK_TRANSCODING 1
+#else
+#  define ORTHANC_ENABLE_DCMTK_TRANSCODING 0
+#endif
+#undef ENABLE_DCMTK_TRANSCODING
+
+
+#cmakedefine01 ENABLE_JPEG
+#if !defined(ENABLE_JPEG)
+#  error CMake error
+#elif ENABLE_JPEG == 1
+#  define ORTHANC_ENABLE_JPEG 1
+#else
+#  define ORTHANC_ENABLE_JPEG 0
+#endif
+#undef ENABLE_JPEG
+
+
+#cmakedefine01 ENABLE_LOCALE
+#if !defined(ENABLE_LOCALE)
+#  error CMake error
+#elif ENABLE_LOCALE == 1
+#  define ORTHANC_ENABLE_LOCALE 1
+#else
+#  define ORTHANC_ENABLE_LOCALE 0
+#endif
+#undef ENABLE_LOCALE
+
+
+#cmakedefine01 ENABLE_LUA
+#if !defined(ENABLE_LUA)
+#  error CMake error
+#elif ENABLE_LUA == 1
+#  define ORTHANC_ENABLE_LUA 1
+#else
+#  define ORTHANC_ENABLE_LUA 0
+#endif
+#undef ENABLE_LUA
+
+
+#cmakedefine01 ENABLE_PKCS11
+#if !defined(ENABLE_PKCS11)
+#  error CMake error
+#elif ENABLE_PKCS11 == 1
+#  define ORTHANC_ENABLE_PKCS11 1
+#else
+#  define ORTHANC_ENABLE_PKCS11 0
+#endif
+#undef ENABLE_PKCS11
+
+
+#cmakedefine01 ENABLE_PNG
+#if !defined(ENABLE_PNG)
+#  error CMake error
+#elif ENABLE_PNG == 1
+#  define ORTHANC_ENABLE_PNG 1
+#else
+#  define ORTHANC_ENABLE_PNG 0
+#endif
+#undef ENABLE_PNG
+
+
+#cmakedefine01 ENABLE_PUGIXML
+#if !defined(ENABLE_PUGIXML)
+#  error CMake error
+#elif ENABLE_PUGIXML == 1
+#  define ORTHANC_ENABLE_PUGIXML 1
+#else
+#  define ORTHANC_ENABLE_PUGIXML 0
+#endif
+#undef ENABLE_PUGIXML
+
+
+#cmakedefine01 ENABLE_SQLITE
+#if !defined(ENABLE_SQLITE)
+#  error CMake error
+#elif ENABLE_SQLITE == 1
+#  define ORTHANC_ENABLE_SQLITE 1
+#else
+#  define ORTHANC_ENABLE_SQLITE 0
+#endif
+#undef ENABLE_SQLITE
+
+
+#cmakedefine01 ENABLE_SSL
+#if !defined(ENABLE_SSL)
+#  error CMake error
+#elif ENABLE_SSL == 1
+#  define ORTHANC_ENABLE_SSL 1
+#else
+#  define ORTHANC_ENABLE_SSL 0
+#endif
+#undef ENABLE_SSL
+
+
+#cmakedefine01 ENABLE_WEB_CLIENT
+#if !defined(ENABLE_WEB_CLIENT)
+#  error CMake error
+#elif ENABLE_WEB_CLIENT == 1
+#  define ORTHANC_ENABLE_CURL 1
+#else
+#  define ORTHANC_ENABLE_CURL 0
+#endif
+#undef ENABLE_WEB_CLIENT
+
+
+#cmakedefine01 ENABLE_ZLIB
+#if !defined(ENABLE_ZLIB)
+#  error CMake error
+#elif ENABLE_ZLIB == 1
+#  define ORTHANC_ENABLE_ZLIB 1
+#else
+#  define ORTHANC_ENABLE_ZLIB 0
+#endif
+#undef ENABLE_ZLIB
+
+
+#if ORTHANC_ENABLE_DCMTK == 1
+#  define DCMTK_VERSION_NUMBER @DCMTK_VERSION_NUMBER@
+#endif
+
+
+/**
+ * Initialization functions.
+ **/
+
+#include <string>
+
+namespace Orthanc
+{
+  ORTHANC_PUBLIC void InitializeFramework(const std::string& locale,
+                                          bool loadPrivateDictionary);
+  
+  ORTHANC_PUBLIC void FinalizeFramework();
+}
+
+
+#endif /* __ORTHANC_FRAMEWORK_H */
--- a/OrthancFramework/Sources/Cache/MemoryCache.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryCache.h	Thu Jun 11 16:40:34 2020 +0200
@@ -46,7 +46,7 @@
     /**
      * WARNING: This class is NOT thread-safe.
      **/
-    class MemoryCache
+    class ORTHANC_PUBLIC MemoryCache : public boost::noncopyable
     {
     private:
       struct Page
--- a/OrthancFramework/Sources/Cache/MemoryObjectCache.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryObjectCache.h	Thu Jun 11 16:40:34 2020 +0200
@@ -33,6 +33,7 @@
 
 #pragma once
 
+#include "../OrthancFramework.h"
 #include "ICacheable.h"
 #include "LeastRecentlyUsedIndex.h"
 
@@ -47,7 +48,7 @@
 
 namespace Orthanc
 {
-  class MemoryObjectCache : public boost::noncopyable
+  class ORTHANC_PUBLIC MemoryObjectCache : public boost::noncopyable
   {
   private:
     class Item;
--- a/OrthancFramework/Sources/Cache/MemoryStringCache.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Cache/MemoryStringCache.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
    * Facade object around "MemoryObjectCache" that caches a dictionary
    * of strings, using the "fetch/add" paradigm of memcached.
    **/
-  class MemoryStringCache : public boost::noncopyable
+  class ORTHANC_PUBLIC MemoryStringCache : public boost::noncopyable
   {
   private:
     class StringValue;
--- a/OrthancFramework/Sources/Cache/SharedArchive.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Cache/SharedArchive.h	Thu Jun 11 16:40:34 2020 +0200
@@ -49,7 +49,7 @@
 
 namespace Orthanc
 {
-  class SharedArchive : public boost::noncopyable
+  class ORTHANC_PUBLIC SharedArchive : public boost::noncopyable
   {
   private:
     typedef std::map<std::string, IDynamicObject*>  Archive;
@@ -62,7 +62,7 @@
     void RemoveInternal(const std::string& id);
 
   public:
-    class Accessor : public boost::noncopyable
+    class ORTHANC_PUBLIC Accessor : public boost::noncopyable
     {
     private:
       boost::mutex::scoped_lock  lock_;
--- a/OrthancFramework/Sources/DicomFormat/DicomTag.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomFormat/DicomTag.h	Thu Jun 11 16:40:34 2020 +0200
@@ -91,7 +91,7 @@
     static bool ParseHexadecimal(DicomTag& tag,
                                  const char* value);
 
-    friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
+    ORTHANC_PUBLIC friend std::ostream& operator<< (std::ostream& o, const DicomTag& tag);
 
     static void AddTagsForModule(std::set<DicomTag>& target,
                                  DicomModule module);
--- a/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomAssociationParameters.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
 
 namespace Orthanc
 {
-  class DicomAssociationParameters
+  class ORTHANC_PUBLIC DicomAssociationParameters
   {
   private:
     std::string               localAet_;
--- a/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomFindAnswers.h	Thu Jun 11 16:40:34 2020 +0200
@@ -37,7 +37,7 @@
 
 namespace Orthanc
 {
-  class DicomFindAnswers : public boost::noncopyable
+  class ORTHANC_PUBLIC DicomFindAnswers : public boost::noncopyable
   {
   private:
     Encoding                      encoding_;
--- a/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/DicomStoreUserConnection.h	Thu Jun 11 16:40:34 2020 +0200
@@ -73,7 +73,7 @@
 
   class DicomAssociation;  // Forward declaration for PImpl design pattern
 
-  class DicomStoreUserConnection : public boost::noncopyable
+  class ORTHANC_PUBLIC DicomStoreUserConnection : public boost::noncopyable
   {
   private:
     typedef std::map<std::string, std::set<DicomTransferSyntax> > RegisteredClasses;
--- a/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomNetworking/RemoteModalityParameters.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
 
 namespace Orthanc
 {
-  class RemoteModalityParameters
+  class ORTHANC_PUBLIC RemoteModalityParameters
   {
   private:
     std::string           aet_;
--- a/OrthancFramework/Sources/DicomParsing/DicomModification.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomModification.h	Thu Jun 11 16:40:34 2020 +0200
@@ -37,7 +37,7 @@
 
 namespace Orthanc
 {
-  class DicomModification : public boost::noncopyable
+  class ORTHANC_PUBLIC DicomModification : public boost::noncopyable
   {
     /**
      * Process:
--- a/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomParsing/DicomWebJsonVisitor.h	Thu Jun 11 16:40:34 2020 +0200
@@ -44,7 +44,7 @@
 
 namespace Orthanc
 {
-  class DicomWebJsonVisitor : public ITagVisitor
+  class ORTHANC_PUBLIC DicomWebJsonVisitor : public ITagVisitor
   {
   public:
     enum BinaryMode
--- a/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomParsing/IDicomTranscoder.h	Thu Jun 11 16:40:34 2020 +0200
@@ -52,7 +52,7 @@
   class ORTHANC_PUBLIC IDicomTranscoder : public boost::noncopyable
   {
   public:
-    class DicomImage : public boost::noncopyable
+    class ORTHANC_PUBLIC DicomImage : public boost::noncopyable
     {
     private:
       std::unique_ptr<DcmFileFormat>  parsed_;
--- a/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomParsing/Internals/DicomImageDecoder.h	Thu Jun 11 16:40:34 2020 +0200
@@ -64,7 +64,7 @@
 {
   class ParsedDicomFile;
   
-  class DicomImageDecoder : public boost::noncopyable
+  class ORTHANC_PUBLIC DicomImageDecoder : public boost::noncopyable
   {
   private:
     class ImageSource;
--- a/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/DicomParsing/ParsedDicomFile.h	Thu Jun 11 16:40:34 2020 +0200
@@ -120,8 +120,7 @@
                     Encoding defaultEncoding,
                     bool permissive,
                     const std::string& defaultPrivateCreator,
-                    const std::map<uint16_t, std::string>& privateCreators
-                    );
+                    const std::map<uint16_t, std::string>& privateCreators);
 
     ParsedDicomFile(const void* content,
                     size_t size);
--- a/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/FileStorage/StorageAccessor.h	Thu Jun 11 16:40:34 2020 +0200
@@ -71,7 +71,7 @@
    * contained in the storage area, and monitors timing metrics (if
    * enabled).
    **/
-  class StorageAccessor : boost::noncopyable
+  class ORTHANC_PUBLIC StorageAccessor : boost::noncopyable
   {
   private:
     class MetricsTimer;
--- a/OrthancFramework/Sources/Images/IImageWriter.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/IImageWriter.h	Thu Jun 11 16:40:34 2020 +0200
@@ -43,7 +43,7 @@
 
 namespace Orthanc
 {
-  class IImageWriter : public boost::noncopyable
+  class ORTHANC_PUBLIC IImageWriter : public boost::noncopyable
   {
   protected:
     virtual void WriteToMemoryInternal(std::string& compressed,
--- a/OrthancFramework/Sources/Images/JpegReader.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/JpegReader.h	Thu Jun 11 16:40:34 2020 +0200
@@ -51,7 +51,7 @@
 
 namespace Orthanc
 {
-  class JpegReader : public ImageAccessor
+  class ORTHANC_PUBLIC JpegReader : public ImageAccessor
   {
   private:
     std::string  content_;
--- a/OrthancFramework/Sources/Images/JpegWriter.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/JpegWriter.h	Thu Jun 11 16:40:34 2020 +0200
@@ -45,7 +45,7 @@
 
 namespace Orthanc
 {
-  class JpegWriter : public IImageWriter
+  class ORTHANC_PUBLIC JpegWriter : public IImageWriter
   {
   protected:
 #if ORTHANC_SANDBOXED == 0
--- a/OrthancFramework/Sources/Images/PamReader.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/PamReader.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
 
 namespace Orthanc
 {
-  class PamReader : public ImageAccessor
+  class ORTHANC_PUBLIC PamReader : public ImageAccessor
   {
   private:
     void ParseContent();
--- a/OrthancFramework/Sources/Images/PamWriter.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/PamWriter.h	Thu Jun 11 16:40:34 2020 +0200
@@ -38,7 +38,7 @@
 namespace Orthanc
 {
   // https://en.wikipedia.org/wiki/Netpbm#PAM_graphics_format
-  class PamWriter : public IImageWriter
+  class ORTHANC_PUBLIC PamWriter : public IImageWriter
   {
   protected:
     virtual void WriteToMemoryInternal(std::string& target,
--- a/OrthancFramework/Sources/Images/PngReader.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/PngReader.h	Thu Jun 11 16:40:34 2020 +0200
@@ -55,7 +55,7 @@
 
 namespace Orthanc
 {
-  class PngReader : public ImageAccessor
+  class ORTHANC_PUBLIC PngReader : public ImageAccessor
   {
   private:
     struct PngRabi;
--- a/OrthancFramework/Sources/Images/PngWriter.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/Images/PngWriter.h	Thu Jun 11 16:40:34 2020 +0200
@@ -47,7 +47,7 @@
 
 namespace Orthanc
 {
-  class PngWriter : public IImageWriter
+  class ORTHANC_PUBLIC PngWriter : public IImageWriter
   {
   protected:
 #if ORTHANC_SANDBOXED == 0
--- a/OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/GenericJobUnserializer.h	Thu Jun 11 16:40:34 2020 +0200
@@ -37,7 +37,7 @@
 
 namespace Orthanc
 {
-  class GenericJobUnserializer : public IJobUnserializer
+  class ORTHANC_PUBLIC GenericJobUnserializer : public IJobUnserializer
   {
   public:
     virtual IJob* UnserializeJob(const Json::Value& value);
--- a/OrthancFramework/Sources/JobsEngine/IJob.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/IJob.h	Thu Jun 11 16:40:34 2020 +0200
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class IJob : public boost::noncopyable
+  class ORTHANC_PUBLIC IJob : public boost::noncopyable
   {
   public:
     virtual ~IJob()
--- a/OrthancFramework/Sources/JobsEngine/IJobUnserializer.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/IJobUnserializer.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
 
 namespace Orthanc
 {
-  class IJobUnserializer : public boost::noncopyable
+  class ORTHANC_PUBLIC IJobUnserializer : public boost::noncopyable
   {
   public:
     virtual ~IJobUnserializer()
--- a/OrthancFramework/Sources/JobsEngine/JobInfo.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobInfo.h	Thu Jun 11 16:40:34 2020 +0200
@@ -39,7 +39,7 @@
 
 namespace Orthanc
 {
-  class JobInfo
+  class ORTHANC_PUBLIC JobInfo
   {
   private:
     std::string                       id_;
--- a/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobStepResult.h	Thu Jun 11 16:40:34 2020 +0200
@@ -39,7 +39,7 @@
 {
   class OrthancException;
   
-  class JobStepResult
+  class ORTHANC_PUBLIC JobStepResult
   {
   private:
     JobStepCode   code_;
--- a/OrthancFramework/Sources/JobsEngine/JobsEngine.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsEngine.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
 
 namespace Orthanc
 {
-  class JobsEngine : public boost::noncopyable
+  class ORTHANC_PUBLIC JobsEngine : public boost::noncopyable
   {
   private:
     enum State
--- a/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/JobsRegistry.h	Thu Jun 11 16:40:34 2020 +0200
@@ -53,10 +53,10 @@
 namespace Orthanc
 {
   // This class handles the state machine of the jobs engine
-  class JobsRegistry : public boost::noncopyable
+  class ORTHANC_PUBLIC JobsRegistry : public boost::noncopyable
   {
   public:
-    class IObserver : public boost::noncopyable
+    class ORTHANC_PUBLIC IObserver : public boost::noncopyable
     {
     public:
       virtual ~IObserver()
@@ -204,7 +204,7 @@
                        unsigned int& success,
                        unsigned int& errors);
 
-    class RunningJob : public boost::noncopyable
+    class ORTHANC_PUBLIC RunningJob : public boost::noncopyable
     {
     private:
       JobsRegistry&  registry_;
--- a/OrthancFramework/Sources/JobsEngine/Operations/IJobOperation.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/IJobOperation.h	Thu Jun 11 16:40:34 2020 +0200
@@ -37,7 +37,7 @@
 
 namespace Orthanc
 {
-  class IJobOperation : public boost::noncopyable
+  class ORTHANC_PUBLIC IJobOperation : public boost::noncopyable
   {
   public:
     virtual ~IJobOperation()
--- a/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValue.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValue.h	Thu Jun 11 16:40:34 2020 +0200
@@ -33,12 +33,14 @@
 
 #pragma once
 
+#include "../../OrthancFramework.h"
+
 #include <json/value.h>
 #include <boost/noncopyable.hpp>
 
 namespace Orthanc
 {
-  class JobOperationValue : public boost::noncopyable
+  class ORTHANC_PUBLIC JobOperationValue : public boost::noncopyable
   {
   public:
     enum Type
--- a/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/JobOperationValues.h	Thu Jun 11 16:40:34 2020 +0200
@@ -41,7 +41,7 @@
 {
   class IJobUnserializer;
 
-  class JobOperationValues : public boost::noncopyable
+  class ORTHANC_PUBLIC JobOperationValues : public boost::noncopyable
   {
   private:
     std::vector<JobOperationValue*>   values_;
--- a/OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/LogJobOperation.h	Thu Jun 11 16:40:34 2020 +0200
@@ -37,7 +37,7 @@
 
 namespace Orthanc
 {
-  class LogJobOperation : public IJobOperation
+  class ORTHANC_PUBLIC LogJobOperation : public IJobOperation
   {
   public:
     virtual void Apply(JobOperationValues& outputs,
--- a/OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/NullOperationValue.h	Thu Jun 11 16:40:34 2020 +0200
@@ -37,7 +37,7 @@
 
 namespace Orthanc
 {
-  class NullOperationValue : public JobOperationValue
+  class ORTHANC_PUBLIC NullOperationValue : public JobOperationValue
   {
   public:
     NullOperationValue() :
--- a/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/SequenceOfOperationsJob.h	Thu Jun 11 16:40:34 2020 +0200
@@ -43,10 +43,10 @@
 
 namespace Orthanc
 {
-  class SequenceOfOperationsJob : public IJob
+  class ORTHANC_PUBLIC SequenceOfOperationsJob : public IJob
   {
   public:
-    class IObserver : public boost::noncopyable
+    class ORTHANC_PUBLIC IObserver : public boost::noncopyable
     {
     public:
       virtual ~IObserver()
@@ -87,7 +87,7 @@
     // This lock allows adding new operations to the end of the job,
     // from another thread than the worker thread, after the job has
     // been submitted for processing
-    class Lock : public boost::noncopyable
+    class ORTHANC_PUBLIC Lock : public boost::noncopyable
     {
     private:
       SequenceOfOperationsJob&   that_;
--- a/OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/Operations/StringOperationValue.h	Thu Jun 11 16:40:34 2020 +0200
@@ -39,7 +39,7 @@
 
 namespace Orthanc
 {
-  class StringOperationValue : public JobOperationValue
+  class ORTHANC_PUBLIC StringOperationValue : public JobOperationValue
   {
   private:
     std::string  content_;
--- a/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfCommandsJob.h	Thu Jun 11 16:40:34 2020 +0200
@@ -39,7 +39,7 @@
 
 namespace Orthanc
 {
-  class SetOfCommandsJob : public IJob
+  class ORTHANC_PUBLIC SetOfCommandsJob : public IJob
   {
   public:
     class ICommand : public boost::noncopyable
--- a/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/Sources/JobsEngine/SetOfInstancesJob.h	Thu Jun 11 16:40:34 2020 +0200
@@ -40,7 +40,7 @@
 
 namespace Orthanc
 {
-  class SetOfInstancesJob : public SetOfCommandsJob
+  class ORTHANC_PUBLIC SetOfInstancesJob : public SetOfCommandsJob
   {
   private:
     class InstanceCommand;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/UnitTestsSources/CMakeLists.txt	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,62 @@
+##
+## This file is meant to be used only by ../SharedLibrary/CMakeLists.txt
+##
+
+cmake_minimum_required(VERSION 2.8)
+project(UnitTestsProject)
+
+SET(UNIT_TESTS_WITH_HTTP_CONNEXIONS ON CACHE BOOL "Allow unit tests to make HTTP requests")
+
+if (UNIT_TESTS_WITH_HTTP_CONNEXIONS)
+  add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=1)
+else()
+  add_definitions(-DUNIT_TESTS_WITH_HTTP_CONNEXIONS=0)
+endif()
+
+if (NOT ORTHANC_FRAMEWORK_STATIC)
+  set(ENABLE_DCMTK ON)
+  set(ENABLE_LUA ON)
+  set(ENABLE_PUGIXML ON)
+  set(ENABLE_SQLITE ON)
+endif()
+  
+set(ENABLE_GOOGLE_TEST ON)
+include(${CMAKE_SOURCE_DIR}/../Resources/CMake/DownloadOrthancFramework.cmake)
+
+add_definitions(
+  -DORTHANC_UNIT_TESTS_LINK_FRAMEWORK=1
+  -DORTHANC_BUILD_UNIT_TESTS=1  # For "HierarchicalZipWriter" tests
+  )
+
+add_executable(UnitTests
+  ${CMAKE_SOURCE_DIR}/SharedLibraryUnitTests.cpp
+
+  ${CMAKE_SOURCE_DIR}/DicomMapTests.cpp
+  ${CMAKE_SOURCE_DIR}/FileStorageTests.cpp
+  ${CMAKE_SOURCE_DIR}/FrameworkTests.cpp
+  ${CMAKE_SOURCE_DIR}/FromDcmtkTests.cpp
+  ${CMAKE_SOURCE_DIR}/ImageProcessingTests.cpp
+  ${CMAKE_SOURCE_DIR}/ImageTests.cpp
+  ${CMAKE_SOURCE_DIR}/JobsTests.cpp
+  ${CMAKE_SOURCE_DIR}/JpegLosslessTests.cpp
+  ${CMAKE_SOURCE_DIR}/LoggingTests.cpp
+  ${CMAKE_SOURCE_DIR}/LuaTests.cpp
+  ${CMAKE_SOURCE_DIR}/MemoryCacheTests.cpp
+  ${CMAKE_SOURCE_DIR}/RestApiTests.cpp
+  ${CMAKE_SOURCE_DIR}/SQLiteChromiumTests.cpp
+  ${CMAKE_SOURCE_DIR}/SQLiteTests.cpp
+  ${CMAKE_SOURCE_DIR}/StreamTests.cpp
+  ${CMAKE_SOURCE_DIR}/ToolboxTests.cpp
+  ${CMAKE_SOURCE_DIR}/ZipTests.cpp
+
+  ${AUTOGENERATED_SOURCES}
+  ${GOOGLE_TEST_SOURCES}
+  )
+
+target_link_libraries(UnitTests OrthancFramework)
+set_target_properties(UnitTests PROPERTIES BUILD_WITH_INSTALL_RPATH TRUE)
+set_target_properties(UnitTests PROPERTIES INSTALL_RPATH ${ORTHANC_FRAMEWORK_LIBDIR})
+
+install(TARGETS UnitTests
+  DESTINATION ${ORTHANC_FRAMEWORK_LIBDIR}
+  )
--- a/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/DicomMapTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,9 +32,14 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
+#if !defined(DCMTK_VERSION_NUMBER)
+#  error DCMTK_VERSION_NUMBER is not defined
+#endif
+
 #include <gtest/gtest.h>
 
 #include "../Sources/Compatibility.h"
@@ -45,9 +50,6 @@
 #include "../Sources/DicomParsing/ParsedDicomFile.h"
 #include "../Sources/DicomParsing/DicomWebJsonVisitor.h"
 
-#include <memory>
-#include <dcmtk/dcmdata/dcdeftag.h>
-#include <dcmtk/dcmdata/dcvrat.h>
 
 using namespace Orthanc;
 
@@ -597,302 +599,6 @@
 }
 
 
-static void SetTagKey(ParsedDicomFile& dicom,
-                      const DicomTag& tag,
-                      const DicomTag& value)
-{
-  // This function emulates a call to function
-  // "dicom.GetDcmtkObject().getDataset()->putAndInsertTagKey(tag,
-  // value)" that was not available in DCMTK 3.6.0
-
-  std::unique_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag)));
-
-  DcmTagKey v = ToDcmtkBridge::Convert(value);
-  if (!element->putTagVal(v).good())
-  {
-    throw OrthancException(ErrorCode_InternalError);
-  }
-
-  dicom.GetDcmtkObject().getDataset()->insert(element.release());
-}
-
-
-TEST(DicomWebJson, ValueRepresentation)
-{
-  // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html
-
-  ParsedDicomFile dicom(false);
-  dicom.ReplacePlainString(DicomTag(0x0040, 0x0241), "AE");
-  dicom.ReplacePlainString(DicomTag(0x0010, 0x1010), "AS");
-  SetTagKey(dicom, DicomTag(0x0020, 0x9165), DicomTag(0x0010, 0x0020));
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0052), "CS");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0012), "DA");
-  dicom.ReplacePlainString(DicomTag(0x0010, 0x1020), "42");  // DS
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x002a), "DT");
-  dicom.ReplacePlainString(DicomTag(0x0010, 0x9431), "43");  // FL
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x1163), "44");  // FD
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x1160), "45");  // IS
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO");
-  dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT");
-  dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB");
-  dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159");  // OD (other double)
-  dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828");  // OF (other float)
-  dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46");  // OL (other long)
-  ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException);
-  dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW");
-  dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0050), "SH");
-  dicom.ReplacePlainString(DicomTag(0x0018, 0x6020), "-15");  // SL
-  dicom.ReplacePlainString(DicomTag(0x0018, 0x9219), "-16");  // SS
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0081), "ST");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0013), "TM");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0119), "UC");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0016), "UI");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x1161), "128");  // UL
-  dicom.ReplacePlainString(DicomTag(0x4342, 0x1234), "UN");   // Inexistent tag
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0120), "UR");
-  dicom.ReplacePlainString(DicomTag(0x0008, 0x0301), "17");   // US
-  dicom.ReplacePlainString(DicomTag(0x0040, 0x0031), "UT");  
-
-  DicomWebJsonVisitor visitor;
-  dicom.Apply(visitor);
-
-  std::string s;
-
-  // The tag (0002,0002) is "Media Storage SOP Class UID" and is
-  // automatically copied by DCMTK from tag (0008,0016)
-  ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["vr"].asString());
-  ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["Value"][0].asString());
-  ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["vr"].asString());
-  ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["Value"][0].asString());
-  ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["vr"].asString());
-  ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["Value"][0].asString());
-  ASSERT_EQ("AT", visitor.GetResult() ["00209165"]["vr"].asString());
-  ASSERT_EQ("00100020", visitor.GetResult() ["00209165"]["Value"][0].asString());
-  ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["vr"].asString());
-  ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["Value"][0].asString());
-  ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString());
-  ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString());
-  ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString());
-  ASSERT_FLOAT_EQ(42.0f, visitor.GetResult() ["00101020"]["Value"][0].asFloat());
-  ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString());
-  ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString());
-  ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString());
-  ASSERT_FLOAT_EQ(43.0f, visitor.GetResult() ["00109431"]["Value"][0].asFloat());
-  ASSERT_EQ("FD", visitor.GetResult() ["00081163"]["vr"].asString());
-  ASSERT_FLOAT_EQ(44.0f, visitor.GetResult() ["00081163"]["Value"][0].asFloat());
-  ASSERT_EQ("IS", visitor.GetResult() ["00081160"]["vr"].asString());
-  ASSERT_FLOAT_EQ(45.0f, visitor.GetResult() ["00081160"]["Value"][0].asFloat());
-  ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["vr"].asString());
-  ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["Value"][0].asString());
-  ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["vr"].asString());
-  ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["Value"][0].asString());
-
-  ASSERT_EQ("OB", visitor.GetResult() ["00282000"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00282000"]["InlineBinary"].asString());
-  ASSERT_EQ("OB", s);
-
-#if DCMTK_VERSION_NUMBER >= 361
-  ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString());
-  ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(visitor.GetResult() ["7FE00009"]["Value"][0].asString()));
-#else
-  ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString());
-  ASSERT_EQ(8u, s.size()); // Because of padding
-  ASSERT_EQ(0, s[7]);
-  ASSERT_EQ("3.14159", s.substr(0, 7));
-#endif
-
-  ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString());
-  ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(visitor.GetResult() ["00640009"]["Value"][0].asString()));
-
-#if DCMTK_VERSION_NUMBER < 361
-  ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString());
-  ASSERT_EQ("46", s);
-#elif DCMTK_VERSION_NUMBER == 361
-  ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString());
-  ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
-#else
-  ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString());
-  ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
-#endif
-
-  ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00281201"]["InlineBinary"].asString());
-  ASSERT_EQ("OWOW", s);
-
-  ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["vr"].asString());
-  ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["Value"][0]["Alphabetic"].asString());
-
-  ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["vr"].asString());
-  ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["Value"][0].asString());
-
-  ASSERT_EQ("SL", visitor.GetResult() ["00186020"]["vr"].asString());
-  ASSERT_EQ(-15, visitor.GetResult() ["00186020"]["Value"][0].asInt());
-
-  ASSERT_EQ("SS", visitor.GetResult() ["00189219"]["vr"].asString());
-  ASSERT_EQ(-16, visitor.GetResult() ["00189219"]["Value"][0].asInt());
-
-  ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["vr"].asString());
-  ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["Value"][0].asString());
-
-  ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["vr"].asString());
-  ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["Value"][0].asString());
-
-#if DCMTK_VERSION_NUMBER >= 361
-  ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["vr"].asString());
-  ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["Value"][0].asString());
-#else
-  ASSERT_EQ("UN", visitor.GetResult() ["00080119"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00080119"]["InlineBinary"].asString());
-  ASSERT_EQ("UC", s);
-#endif
-
-  ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["vr"].asString());
-  ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["Value"][0].asString());
-
-  ASSERT_EQ("UL", visitor.GetResult() ["00081161"]["vr"].asString());
-  ASSERT_EQ(128u, visitor.GetResult() ["00081161"]["Value"][0].asUInt());
-
-  ASSERT_EQ("UN", visitor.GetResult() ["43421234"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["43421234"]["InlineBinary"].asString());
-  ASSERT_EQ("UN", s);
-
-#if DCMTK_VERSION_NUMBER >= 361
-  ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["vr"].asString());
-  ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["Value"][0].asString());
-#else
-  ASSERT_EQ("UN", visitor.GetResult() ["00080120"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00080120"]["InlineBinary"].asString());
-  ASSERT_EQ("UR", s);
-#endif
-
-#if DCMTK_VERSION_NUMBER >= 361
-  ASSERT_EQ("US", visitor.GetResult() ["00080301"]["vr"].asString());
-  ASSERT_EQ(17u, visitor.GetResult() ["00080301"]["Value"][0].asUInt());
-#else
-  ASSERT_EQ("UN", visitor.GetResult() ["00080301"]["vr"].asString());
-  Toolbox::DecodeBase64(s, visitor.GetResult() ["00080301"]["InlineBinary"].asString());
-  ASSERT_EQ("17", s);
-#endif
-
-  ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["vr"].asString());
-  ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["Value"][0].asString());
-
-  std::string xml;
-  visitor.FormatXml(xml);
-  
-  {
-    DicomMap m;
-    m.FromDicomWeb(visitor.GetResult());
-    ASSERT_EQ(31u, m.GetSize());
-
-    std::string s;
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0002, 0x0002), false));  ASSERT_EQ("UI", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0241), false));  ASSERT_EQ("AE", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1010), false));  ASSERT_EQ("AS", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0020, 0x9165), false));  ASSERT_EQ("00100020", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0052), false));  ASSERT_EQ("CS", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0012), false));  ASSERT_EQ("DA", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1020), false));  ASSERT_EQ("42", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x002a), false));  ASSERT_EQ("DT", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x9431), false));  ASSERT_EQ("43", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1163), false));  ASSERT_EQ("44", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1160), false));  ASSERT_EQ("45", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false));  ASSERT_EQ("LO", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false));  ASSERT_EQ("LT", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true));   ASSERT_EQ("OB", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true));
-
-#if DCMTK_VERSION_NUMBER >= 361
-    ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(s));
-#else
-    ASSERT_EQ(8u, s.size()); // Because of padding
-    ASSERT_EQ(0, s[7]);
-    ASSERT_EQ("3.14159", s.substr(0, 7));
-#endif
-
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true));
-    ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(s));
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true));   ASSERT_EQ("OWOW", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false));  ASSERT_EQ("PN", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false));  ASSERT_EQ("SH", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x6020), false));  ASSERT_EQ("-15", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false));  ASSERT_EQ("-16", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false));  ASSERT_EQ("ST", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false));  ASSERT_EQ("TM", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false));  ASSERT_EQ("UI", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false));  ASSERT_EQ("128", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true));   ASSERT_EQ("UN", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false));  ASSERT_EQ("UT", s);
-
-#if DCMTK_VERSION_NUMBER >= 361
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false));  ASSERT_EQ("46", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false));  ASSERT_EQ("UC", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false));  ASSERT_EQ("UR", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false));  ASSERT_EQ("17", s);
-#else
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true));  ASSERT_EQ("46", s);  // OL
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true));  ASSERT_EQ("UC", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true));  ASSERT_EQ("UR", s);
-    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true));  ASSERT_EQ("17", s);  // US (but tag unknown to DCMTK 3.6.0)
-#endif
-    
-  }
-}
-
-
-TEST(DicomWebJson, Sequence)
-{
-  ParsedDicomFile dicom(false);
-  
-  {
-    std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
-
-    for (unsigned int i = 0; i < 3; i++)
-    {
-      std::unique_ptr<DcmItem> item(new DcmItem);
-      std::string s = "item" + boost::lexical_cast<std::string>(i);
-      item->putAndInsertString(DCM_ReferencedSOPInstanceUID, s.c_str(), OFFalse);
-      ASSERT_TRUE(sequence->insert(item.release(), false, false).good());
-    }
-
-    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->insert(sequence.release(), false, false).good());
-  }
-
-  DicomWebJsonVisitor visitor;
-  dicom.Apply(visitor);
-
-  ASSERT_EQ("SQ", visitor.GetResult() ["00081115"]["vr"].asString());
-  ASSERT_EQ(3u, visitor.GetResult() ["00081115"]["Value"].size());
-
-  std::set<std::string> items;
-  
-  for (Json::Value::ArrayIndex i = 0; i < 3; i++)
-  {
-    ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i].size());
-    ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"].size());
-    ASSERT_EQ("UI", visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["vr"].asString());
-    items.insert(visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"][0].asString());
-  }
-
-  ASSERT_EQ(3u, items.size());
-  ASSERT_TRUE(items.find("item0") != items.end());
-  ASSERT_TRUE(items.find("item1") != items.end());
-  ASSERT_TRUE(items.find("item2") != items.end());
-
-  std::string xml;
-  visitor.FormatXml(xml);
-
-  {
-    DicomMap m;
-    m.FromDicomWeb(visitor.GetResult());
-    ASSERT_EQ(0u, m.GetSize());  // Sequences are not handled by DicomMap
-  }
-}
-
-
 TEST(DicomWebJson, PixelSpacing)
 {
   // Test related to locales: Make sure that decimal separator is
--- a/OrthancFramework/UnitTestsSources/FileStorageTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/FileStorageTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/FrameworkTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,9 +32,14 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error ORTHANC_ENABLE_PUGIXML is not defined
+#endif
+
 #include "../Sources/EnumerationDictionary.h"
 
 #include <gtest/gtest.h>
--- a/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/FromDcmtkTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,9 +32,18 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
+#if !defined(ORTHANC_ENABLE_DCMTK_TRANSCODING)
+#  error ORTHANC_ENABLE_DCMTK_TRANSCODING is not defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_PUGIXML)
+#  error ORTHANC_ENABLE_PUGIXML is not defined
+#endif
+
 #include <gtest/gtest.h>
 
 #include "../Sources/Compatibility.h"
@@ -54,12 +63,17 @@
 #include "../Sources/SystemToolbox.h"
 #include "../Resources/CodeGeneration/EncodingTests.h"
 
+#include <dcmtk/dcmdata/dcdeftag.h>
 #include <dcmtk/dcmdata/dcelem.h>
-#include <dcmtk/dcmdata/dcdeftag.h>
+#include <dcmtk/dcmdata/dcvrat.h>
+
 #include <boost/algorithm/string/predicate.hpp>
 
 #if ORTHANC_ENABLE_PUGIXML == 1
 #  include <pugixml.hpp>
+#  if !defined(PUGIXML_VERSION)
+#    error PUGIXML_VERSION is not available
+#  endif
 #endif
 
 using namespace Orthanc;
@@ -1776,6 +1790,302 @@
 }
 
 
+static void SetTagKey(ParsedDicomFile& dicom,
+                      const DicomTag& tag,
+                      const DicomTag& value)
+{
+  // This function emulates a call to function
+  // "dicom.GetDcmtkObject().getDataset()->putAndInsertTagKey(tag,
+  // value)" that was not available in DCMTK 3.6.0
+
+  std::unique_ptr<DcmAttributeTag> element(new DcmAttributeTag(ToDcmtkBridge::Convert(tag)));
+
+  DcmTagKey v = ToDcmtkBridge::Convert(value);
+  if (!element->putTagVal(v).good())
+  {
+    throw OrthancException(ErrorCode_InternalError);
+  }
+
+  dicom.GetDcmtkObject().getDataset()->insert(element.release());
+}
+
+
+TEST(DicomWebJson, ValueRepresentation)
+{
+  // http://dicom.nema.org/medical/dicom/current/output/chtml/part18/sect_F.2.3.html
+
+  ParsedDicomFile dicom(false);
+  dicom.ReplacePlainString(DicomTag(0x0040, 0x0241), "AE");
+  dicom.ReplacePlainString(DicomTag(0x0010, 0x1010), "AS");
+  SetTagKey(dicom, DicomTag(0x0020, 0x9165), DicomTag(0x0010, 0x0020));
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0052), "CS");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0012), "DA");
+  dicom.ReplacePlainString(DicomTag(0x0010, 0x1020), "42");  // DS
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x002a), "DT");
+  dicom.ReplacePlainString(DicomTag(0x0010, 0x9431), "43");  // FL
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x1163), "44");  // FD
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x1160), "45");  // IS
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0070), "LO");
+  dicom.ReplacePlainString(DicomTag(0x0010, 0x4000), "LT");
+  dicom.ReplacePlainString(DicomTag(0x0028, 0x2000), "OB");
+  dicom.ReplacePlainString(DicomTag(0x7fe0, 0x0009), "3.14159");  // OD (other double)
+  dicom.ReplacePlainString(DicomTag(0x0064, 0x0009), "2.71828");  // OF (other float)
+  dicom.ReplacePlainString(DicomTag(0x0066, 0x0040), "46");  // OL (other long)
+  ASSERT_THROW(dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "O"), OrthancException);
+  dicom.ReplacePlainString(DicomTag(0x0028, 0x1201), "OWOW");
+  dicom.ReplacePlainString(DicomTag(0x0010, 0x0010), "PN");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0050), "SH");
+  dicom.ReplacePlainString(DicomTag(0x0018, 0x6020), "-15");  // SL
+  dicom.ReplacePlainString(DicomTag(0x0018, 0x9219), "-16");  // SS
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0081), "ST");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0013), "TM");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0119), "UC");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0016), "UI");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x1161), "128");  // UL
+  dicom.ReplacePlainString(DicomTag(0x4342, 0x1234), "UN");   // Inexistent tag
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0120), "UR");
+  dicom.ReplacePlainString(DicomTag(0x0008, 0x0301), "17");   // US
+  dicom.ReplacePlainString(DicomTag(0x0040, 0x0031), "UT");  
+
+  DicomWebJsonVisitor visitor;
+  dicom.Apply(visitor);
+
+  std::string s;
+
+  // The tag (0002,0002) is "Media Storage SOP Class UID" and is
+  // automatically copied by DCMTK from tag (0008,0016)
+  ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["vr"].asString());
+  ASSERT_EQ("UI", visitor.GetResult() ["00020002"]["Value"][0].asString());
+  ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["vr"].asString());
+  ASSERT_EQ("AE", visitor.GetResult() ["00400241"]["Value"][0].asString());
+  ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["vr"].asString());
+  ASSERT_EQ("AS", visitor.GetResult() ["00101010"]["Value"][0].asString());
+  ASSERT_EQ("AT", visitor.GetResult() ["00209165"]["vr"].asString());
+  ASSERT_EQ("00100020", visitor.GetResult() ["00209165"]["Value"][0].asString());
+  ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["vr"].asString());
+  ASSERT_EQ("CS", visitor.GetResult() ["00080052"]["Value"][0].asString());
+  ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["vr"].asString());
+  ASSERT_EQ("DA", visitor.GetResult() ["00080012"]["Value"][0].asString());
+  ASSERT_EQ("DS", visitor.GetResult() ["00101020"]["vr"].asString());
+  ASSERT_FLOAT_EQ(42.0f, visitor.GetResult() ["00101020"]["Value"][0].asFloat());
+  ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["vr"].asString());
+  ASSERT_EQ("DT", visitor.GetResult() ["0008002A"]["Value"][0].asString());
+  ASSERT_EQ("FL", visitor.GetResult() ["00109431"]["vr"].asString());
+  ASSERT_FLOAT_EQ(43.0f, visitor.GetResult() ["00109431"]["Value"][0].asFloat());
+  ASSERT_EQ("FD", visitor.GetResult() ["00081163"]["vr"].asString());
+  ASSERT_FLOAT_EQ(44.0f, visitor.GetResult() ["00081163"]["Value"][0].asFloat());
+  ASSERT_EQ("IS", visitor.GetResult() ["00081160"]["vr"].asString());
+  ASSERT_FLOAT_EQ(45.0f, visitor.GetResult() ["00081160"]["Value"][0].asFloat());
+  ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["vr"].asString());
+  ASSERT_EQ("LO", visitor.GetResult() ["00080070"]["Value"][0].asString());
+  ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["vr"].asString());
+  ASSERT_EQ("LT", visitor.GetResult() ["00104000"]["Value"][0].asString());
+
+  ASSERT_EQ("OB", visitor.GetResult() ["00282000"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["00282000"]["InlineBinary"].asString());
+  ASSERT_EQ("OB", s);
+
+#if DCMTK_VERSION_NUMBER >= 361
+  ASSERT_EQ("OD", visitor.GetResult() ["7FE00009"]["vr"].asString());
+  ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(visitor.GetResult() ["7FE00009"]["Value"][0].asString()));
+#else
+  ASSERT_EQ("UN", visitor.GetResult() ["7FE00009"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["7FE00009"]["InlineBinary"].asString());
+  ASSERT_EQ(8u, s.size()); // Because of padding
+  ASSERT_EQ(0, s[7]);
+  ASSERT_EQ("3.14159", s.substr(0, 7));
+#endif
+
+  ASSERT_EQ("OF", visitor.GetResult() ["00640009"]["vr"].asString());
+  ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(visitor.GetResult() ["00640009"]["Value"][0].asString()));
+
+#if DCMTK_VERSION_NUMBER < 361
+  ASSERT_EQ("UN", visitor.GetResult() ["00660040"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["00660040"]["InlineBinary"].asString());
+  ASSERT_EQ("46", s);
+#elif DCMTK_VERSION_NUMBER == 361
+  ASSERT_EQ("UL", visitor.GetResult() ["00660040"]["vr"].asString());
+  ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
+#else
+  ASSERT_EQ("OL", visitor.GetResult() ["00660040"]["vr"].asString());
+  ASSERT_EQ(46, visitor.GetResult() ["00660040"]["Value"][0].asInt());
+#endif
+
+  ASSERT_EQ("OW", visitor.GetResult() ["00281201"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["00281201"]["InlineBinary"].asString());
+  ASSERT_EQ("OWOW", s);
+
+  ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["vr"].asString());
+  ASSERT_EQ("PN", visitor.GetResult() ["00100010"]["Value"][0]["Alphabetic"].asString());
+
+  ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["vr"].asString());
+  ASSERT_EQ("SH", visitor.GetResult() ["00080050"]["Value"][0].asString());
+
+  ASSERT_EQ("SL", visitor.GetResult() ["00186020"]["vr"].asString());
+  ASSERT_EQ(-15, visitor.GetResult() ["00186020"]["Value"][0].asInt());
+
+  ASSERT_EQ("SS", visitor.GetResult() ["00189219"]["vr"].asString());
+  ASSERT_EQ(-16, visitor.GetResult() ["00189219"]["Value"][0].asInt());
+
+  ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["vr"].asString());
+  ASSERT_EQ("ST", visitor.GetResult() ["00080081"]["Value"][0].asString());
+
+  ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["vr"].asString());
+  ASSERT_EQ("TM", visitor.GetResult() ["00080013"]["Value"][0].asString());
+
+#if DCMTK_VERSION_NUMBER >= 361
+  ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["vr"].asString());
+  ASSERT_EQ("UC", visitor.GetResult() ["00080119"]["Value"][0].asString());
+#else
+  ASSERT_EQ("UN", visitor.GetResult() ["00080119"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["00080119"]["InlineBinary"].asString());
+  ASSERT_EQ("UC", s);
+#endif
+
+  ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["vr"].asString());
+  ASSERT_EQ("UI", visitor.GetResult() ["00080016"]["Value"][0].asString());
+
+  ASSERT_EQ("UL", visitor.GetResult() ["00081161"]["vr"].asString());
+  ASSERT_EQ(128u, visitor.GetResult() ["00081161"]["Value"][0].asUInt());
+
+  ASSERT_EQ("UN", visitor.GetResult() ["43421234"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["43421234"]["InlineBinary"].asString());
+  ASSERT_EQ("UN", s);
+
+#if DCMTK_VERSION_NUMBER >= 361
+  ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["vr"].asString());
+  ASSERT_EQ("UR", visitor.GetResult() ["00080120"]["Value"][0].asString());
+#else
+  ASSERT_EQ("UN", visitor.GetResult() ["00080120"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["00080120"]["InlineBinary"].asString());
+  ASSERT_EQ("UR", s);
+#endif
+
+#if DCMTK_VERSION_NUMBER >= 361
+  ASSERT_EQ("US", visitor.GetResult() ["00080301"]["vr"].asString());
+  ASSERT_EQ(17u, visitor.GetResult() ["00080301"]["Value"][0].asUInt());
+#else
+  ASSERT_EQ("UN", visitor.GetResult() ["00080301"]["vr"].asString());
+  Toolbox::DecodeBase64(s, visitor.GetResult() ["00080301"]["InlineBinary"].asString());
+  ASSERT_EQ("17", s);
+#endif
+
+  ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["vr"].asString());
+  ASSERT_EQ("UT", visitor.GetResult() ["00400031"]["Value"][0].asString());
+
+  std::string xml;
+  visitor.FormatXml(xml);
+  
+  {
+    DicomMap m;
+    m.FromDicomWeb(visitor.GetResult());
+    ASSERT_EQ(31u, m.GetSize());
+
+    std::string s;
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0002, 0x0002), false));  ASSERT_EQ("UI", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0241), false));  ASSERT_EQ("AE", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1010), false));  ASSERT_EQ("AS", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0020, 0x9165), false));  ASSERT_EQ("00100020", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0052), false));  ASSERT_EQ("CS", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0012), false));  ASSERT_EQ("DA", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x1020), false));  ASSERT_EQ("42", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x002a), false));  ASSERT_EQ("DT", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x9431), false));  ASSERT_EQ("43", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1163), false));  ASSERT_EQ("44", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1160), false));  ASSERT_EQ("45", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0070), false));  ASSERT_EQ("LO", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x4000), false));  ASSERT_EQ("LT", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x2000), true));   ASSERT_EQ("OB", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x7fe0, 0x0009), true));
+
+#if DCMTK_VERSION_NUMBER >= 361
+    ASSERT_FLOAT_EQ(3.14159f, boost::lexical_cast<float>(s));
+#else
+    ASSERT_EQ(8u, s.size()); // Because of padding
+    ASSERT_EQ(0, s[7]);
+    ASSERT_EQ("3.14159", s.substr(0, 7));
+#endif
+
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0064, 0x0009), true));
+    ASSERT_FLOAT_EQ(2.71828f, boost::lexical_cast<float>(s));
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0028, 0x1201), true));   ASSERT_EQ("OWOW", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0010, 0x0010), false));  ASSERT_EQ("PN", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0050), false));  ASSERT_EQ("SH", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x6020), false));  ASSERT_EQ("-15", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0018, 0x9219), false));  ASSERT_EQ("-16", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0081), false));  ASSERT_EQ("ST", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0013), false));  ASSERT_EQ("TM", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0016), false));  ASSERT_EQ("UI", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x1161), false));  ASSERT_EQ("128", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x4342, 0x1234), true));   ASSERT_EQ("UN", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0040, 0x0031), false));  ASSERT_EQ("UT", s);
+
+#if DCMTK_VERSION_NUMBER >= 361
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), false));  ASSERT_EQ("46", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), false));  ASSERT_EQ("UC", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), false));  ASSERT_EQ("UR", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), false));  ASSERT_EQ("17", s);
+#else
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0066, 0x0040), true));  ASSERT_EQ("46", s);  // OL
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0119), true));  ASSERT_EQ("UC", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0120), true));  ASSERT_EQ("UR", s);
+    ASSERT_TRUE(m.LookupStringValue(s, DicomTag(0x0008, 0x0301), true));  ASSERT_EQ("17", s);  // US (but tag unknown to DCMTK 3.6.0)
+#endif
+    
+  }
+}
+
+
+TEST(DicomWebJson, Sequence)
+{
+  ParsedDicomFile dicom(false);
+  
+  {
+    std::unique_ptr<DcmSequenceOfItems> sequence(new DcmSequenceOfItems(DCM_ReferencedSeriesSequence));
+
+    for (unsigned int i = 0; i < 3; i++)
+    {
+      std::unique_ptr<DcmItem> item(new DcmItem);
+      std::string s = "item" + boost::lexical_cast<std::string>(i);
+      item->putAndInsertString(DCM_ReferencedSOPInstanceUID, s.c_str(), OFFalse);
+      ASSERT_TRUE(sequence->insert(item.release(), false, false).good());
+    }
+
+    ASSERT_TRUE(dicom.GetDcmtkObject().getDataset()->insert(sequence.release(), false, false).good());
+  }
+
+  DicomWebJsonVisitor visitor;
+  dicom.Apply(visitor);
+
+  ASSERT_EQ("SQ", visitor.GetResult() ["00081115"]["vr"].asString());
+  ASSERT_EQ(3u, visitor.GetResult() ["00081115"]["Value"].size());
+
+  std::set<std::string> items;
+  
+  for (Json::Value::ArrayIndex i = 0; i < 3; i++)
+  {
+    ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i].size());
+    ASSERT_EQ(1u, visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"].size());
+    ASSERT_EQ("UI", visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["vr"].asString());
+    items.insert(visitor.GetResult() ["00081115"]["Value"][i]["00081155"]["Value"][0].asString());
+  }
+
+  ASSERT_EQ(3u, items.size());
+  ASSERT_TRUE(items.find("item0") != items.end());
+  ASSERT_TRUE(items.find("item1") != items.end());
+  ASSERT_TRUE(items.find("item2") != items.end());
+
+  std::string xml;
+  visitor.FormatXml(xml);
+
+  {
+    DicomMap m;
+    m.FromDicomWeb(visitor.GetResult());
+    ASSERT_EQ(0u, m.GetSize());  // Sequences are not handled by DicomMap
+  }
+}
+
+
 
 
 #if ORTHANC_ENABLE_DCMTK_TRANSCODING == 1
--- a/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageProcessingTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/ImageTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/ImageTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/JobsTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/JobsTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/JpegLosslessTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,12 +32,17 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
+#if !defined(ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS)
+#  error ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS is not defined
+#endif
+
 #include <gtest/gtest.h>
 
-#if ORTHANC_ENABLE_JPEG_LOSSLESS == 1
+#if ORTHANC_ENABLE_DCMTK_JPEG_LOSSLESS == 1
 
 #include "../Sources/DicomParsing/Internals/DicomImageDecoder.h"
 #include "../Sources/DicomParsing/ParsedDicomFile.h"
--- a/OrthancFramework/UnitTestsSources/LoggingTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/LoggingTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/LuaTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/LuaTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/MemoryCacheTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/RestApiTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
@@ -56,7 +57,11 @@
 using namespace Orthanc;
 
 #if !defined(UNIT_TESTS_WITH_HTTP_CONNEXIONS)
-#error "Please set UNIT_TESTS_WITH_HTTP_CONNEXIONS"
+#  error UNIT_TESTS_WITH_HTTP_CONNEXIONS is not defined
+#endif
+
+#if !defined(ORTHANC_ENABLE_SSL)
+#  error ORTHANC_ENABLE_SSL is not defined
 #endif
 
 
--- a/OrthancFramework/UnitTestsSources/SQLiteChromiumTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/SQLiteChromiumTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/SQLiteTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/SQLiteTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/OrthancFramework/UnitTestsSources/SharedLibraryUnitTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -0,0 +1,63 @@
+/**
+ * Orthanc - A Lightweight, RESTful DICOM Store
+ * Copyright (C) 2012-2016 Sebastien Jodogne, Medical Physics
+ * Department, University Hospital of Liege, Belgium
+ * Copyright (C) 2017-2020 Osimis S.A., Belgium
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * In addition, as a special exception, the copyright holders of this
+ * program give permission to link the code of its release with the
+ * OpenSSL project's "OpenSSL" library (or with modified versions of it
+ * that use the same license as the "OpenSSL" library), and distribute
+ * the linked executables. You must obey the GNU General Public License
+ * in all respects for all of the code used other than "OpenSSL". If you
+ * modify file(s) with this exception, you may extend this exception to
+ * your version of the file(s), but you are not obligated to do so. If
+ * you do not wish to do so, delete this exception statement from your
+ * version. If you delete this exception statement from all source files
+ * in the program, then also delete it here.
+ * 
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ **/
+
+
+// This file is meant to be used only by ../SharedLibrary/CMakeLists.txt
+
+#if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
+#  include <OrthancFramework.h>
+#else
+#  error This file must only be used if testing the Orthanc framework shared library
+#endif
+
+#include "../Sources/Logging.h"
+#include "../Sources/Toolbox.h"
+#include "../Sources/SystemToolbox.h"
+
+#include <gtest/gtest.h>
+
+int main(int argc, char **argv)
+{
+  Orthanc::InitializeFramework("", true);
+  
+  Orthanc::Logging::EnableInfoLevel(true);
+  Orthanc::Toolbox::DetectEndianness();
+  Orthanc::SystemToolbox::MakeDirectory("UnitTestsResults");
+  
+  ::testing::InitGoogleTest(&argc, argv);
+  int result = RUN_ALL_TESTS();
+
+  Orthanc::FinalizeFramework();
+  
+  return result;
+}
--- a/OrthancFramework/UnitTestsSources/StreamTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/StreamTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/ToolboxTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/ToolboxTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif
 
--- a/OrthancFramework/UnitTestsSources/ZipTests.cpp	Thu Jun 11 14:38:31 2020 +0200
+++ b/OrthancFramework/UnitTestsSources/ZipTests.cpp	Thu Jun 11 16:40:34 2020 +0200
@@ -32,6 +32,7 @@
 
 
 #if ORTHANC_UNIT_TESTS_LINK_FRAMEWORK == 1
+// Must be the first to be sure to use the Orthanc framework shared library
 #  include <OrthancFramework.h>
 #endif