Windows : build libmxnet.dll with /MD (Multithreaded DLL runtime)

Hello,

I am trying to link mxnet to an existing C++ application under windows.
Basically, I only need to be able to use the prediction API (and nothing more), since
the networks will have been trained under python.

So far, I have tested two approaches ;

Use the amalgamation

The amalgamation is intended to provide an easy to compile only the prediction API.

However, a quick analysis of the code (esp. the Makefile) shows that this solution is not ready for windows, and there might be a lot of work in order to get it running.

Did anyone succeed in building it under windows, and is there a documented procedure?

Build mxnet myself under windows as a shared library :

I guess that this is the preferred solution, and I would link my application against it.

So, my goal was to build libmxnet.dll, and then try to build a simple prediction project that would link to it. My project is comprised of a simple cc file, which is a copy of the the image-classification-predict example. It also links to OpenCV.

However I ran into a problem, which is that mxnet (and all its third parties) are configured to use the static runtime library (/MT), whereas the default behavior of Visual Studio and cmake it to use the dynamic runtime library (aka /MD or “MultiThreaded DLL”).

Visual Studio will refuse my project to link to libmxnet since my project uses the /MT flag : this is the de facto norm, as most of the libraries (opencv and others) are compiled with this flag by default.

So far, I have had to patch mxnet as well as most of its third parties’ CMakeLists).
I was successful, but it was tedious and not pretty. I explain what I had to patch below.

Is there a better solution?
Is my patch interesting, or am I completely out of line?

=============

Below is the list of the modification I have had to make.

I added an option USE_MSVC_MT :

  • if this option is set to “OFF”, then we then compile a library with the /MD flag.
  • if this option is set to “ON”, then we compile as before (this is the default)

Main repository :

add USE_MSVC_MT option

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 483108a68..2facf17f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -9,6 +9,9 @@ endif()
 include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/Utils.cmake)

 #Some things have order. This must be put in front alone
+if (MSVC)
+  mxnet_option(USE_MSVC_MT        "Build with /MT Runtime Library instead of /MD (DLL)" ON)
+endif()
 mxnet_option(USE_CUDA             "Build with CUDA support"   ON)
 mxnet_option(USE_OLDCMAKECUDA     "Build with old cmake cuda" OFF)
 mxnet_option(USE_NCCL             "Use NVidia NCCL with CUDA" OFF)
@@ -507,7 +510,7 @@ if (NOT (EXTRA_OPERATORS STREQUAL ""))
     list(APPEND SOURCE ${EXTRA_SRC} ${EXTRA_CUSRC})
 endif()

-if(MSVC)
+if(MSVC AND USE_MSVC_MT)
   foreach(flag_var
         CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
         CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)

3rdparty/tvm

I removed the option USE_MSVC_MT from here, since it is now placed it in the main CMakeLists file

Entering '3rdparty/tvm'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index e9699b1f..6603f429 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -37,7 +37,7 @@ tvm_option(USE_LLVM "Build with LLVM, can be set to specific llvm-config path" O
 tvm_option(USE_GRAPH_RUNTIME "Build with tiny graph runtime" ON)
 tvm_option(USE_GRAPH_RUNTIME_DEBUG "Build with tiny graph runtime debug mode" OFF)
 tvm_option(USE_RTTI "Build with RTTI" ON)
-tvm_option(USE_MSVC_MT "Build with MT" OFF)
+#tvm_option(USE_MSVC_MT "Build with MT" OFF)
 tvm_option(INSTALL_DEV "Install compiler infrastructure" OFF)
 
 # Contrib library options

3rdparty/cub subrepo:

Entering '3rdparty/cub'
diff --git a/common.mk b/common.mk
index 8154850..4a588e8 100644
--- a/common.mk
+++ b/common.mk
@@ -198,7 +198,8 @@ ifeq (WIN_NT, $(findstring WIN_NT, $(OSUPPER)))
 	CC = cl
 	
 	# Multithreaded runtime
-	NVCCFLAGS += -Xcompiler /MT
+	# NVCCFLAGS += -Xcompiler /MT
+	NVCCFLAGS += -Xcompiler /MD
 	
 ifneq ($(force32), 1)
 	CUDART_CYG = "$(shell dirname $(NVCC))/../lib/Win32/cudart.lib"

dlmc-core :

Take into account the USE_MSVC_MT option.
I also had to specify the library type (static).
Also, I had to make a difference for the FLAGS_gtest_death_test_style initialization (gtest is for /MT by default)

Entering '3rdparty/dmlc-core'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 7839e9f..8e88c88 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -63,13 +63,15 @@ add_definitions(-D__USE_XOPEN2K8)
 # compile
 if(MSVC)
   add_definitions(-DDMLC_USE_CXX11)
-  foreach(flag_var
-        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
-        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
-    if(${flag_var} MATCHES "/MD")
-      string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    endif(${flag_var} MATCHES "/MD")
-  endforeach(flag_var)
+  if (USE_MSVC_MT)
+    foreach(flag_var
+          CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+          CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+      if(${flag_var} MATCHES "/MD")
+        string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+      endif(${flag_var} MATCHES "/MD")
+    endforeach(flag_var)
+  endif()
   set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
 else(MSVC)
   set(CMAKE_POSITION_INDEPENDENT_CODE ON)
@@ -144,7 +146,8 @@ if(USE_AZURE)
   list(APPEND SOURCE "src/io/azure_filesys.cc")
 endif()
 
-add_library(dmlc ${SOURCE})
+# add_library(dmlc ${SOURCE})
+add_library(dmlc STATIC ${SOURCE})
 target_link_libraries(dmlc ${dmlccore_LINKER_LIBS})
 
 # ---[ Install Includes
diff --git a/test/unittest/unittest_main.cc b/test/unittest/unittest_main.cc
index 14525c7..06393c3 100644
--- a/test/unittest/unittest_main.cc
+++ b/test/unittest/unittest_main.cc
@@ -3,6 +3,8 @@
 
 int main(int argc, char ** argv) {
   testing::InitGoogleTest(&argc, argv);
+#ifndef _MSC_VER
   testing::FLAGS_gtest_death_test_style = "threadsafe";
+#endif
   return RUN_ALL_TESTS();
 }

openmp

Entering '3rdparty/openmp'
diff --git a/runtime/cmake/config-ix.cmake b/runtime/cmake/config-ix.cmake
index 0375d21..bd839b8 100644
--- a/runtime/cmake/config-ix.cmake
+++ b/runtime/cmake/config-ix.cmake
@@ -86,14 +86,16 @@ if(WIN32)
   set(LIBOMP_HAVE_SAFESEH_MASM_FLAG TRUE)
   set(LIBOMP_HAVE_COFF_MASM_FLAG TRUE)
   # Change Windows flags /MDx to /MTx
-  foreach(libomp_lang IN ITEMS C CXX)
-    foreach(libomp_btype IN ITEMS DEBUG RELWITHDEBINFO RELEASE MINSIZEREL)
-      string(REPLACE "/MD" "/MT"
-        CMAKE_${libomp_lang}_FLAGS_${libomp_btype}
-        "${CMAKE_${libomp_lang}_FLAGS_${libomp_btype}}"
-      )
+  if (USE_MSVC_MT)
+    foreach(libomp_lang IN ITEMS C CXX)
+      foreach(libomp_btype IN ITEMS DEBUG RELWITHDEBINFO RELEASE MINSIZEREL)
+        string(REPLACE "/MD" "/MT"
+          CMAKE_${libomp_lang}_FLAGS_${libomp_btype}
+          "${CMAKE_${libomp_lang}_FLAGS_${libomp_btype}}"
+        )
+      endforeach()
     endforeach()
-  endforeach()
+  endif()
 else()
   # It is difficult to create a dummy assembly file that compiles into an
   # exectuable for every architecture and then check the C compiler to

ps-lite

Entering '3rdparty/ps-lite'
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ef566e5..c8c28b9 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -34,19 +34,21 @@ list(APPEND pslite_INCLUDE_DIR_L "${PROJECT_BINARY_DIR}/include/")
 FILE(GLOB SOURCE "src/*.cc")
 
 if(MSVC)
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
-FILE(GLOB getopt_SOURCE "src/windows/getopt.c")
-list(APPEND SOURCE ${getopt_SOURCE}) 
-add_definitions(-DSTATIC_GETOPT)
-include_directories(pslite "${PROJECT_SOURCE_DIR}/src/windows")
-list(APPEND pslite_LINKER_LIBS_L "ipHlpApi.lib" "ws2_32.lib")
-  foreach(flag_var
-        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
-        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
-    if(${flag_var} MATCHES "/MD")
-      string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-    endif(${flag_var} MATCHES "/MD")
-  endforeach(flag_var)
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc")
+  FILE(GLOB getopt_SOURCE "src/windows/getopt.c")
+  list(APPEND SOURCE ${getopt_SOURCE}) 
+  add_definitions(-DSTATIC_GETOPT)
+  include_directories(pslite "${PROJECT_SOURCE_DIR}/src/windows")
+  list(APPEND pslite_LINKER_LIBS_L "ipHlpApi.lib" "ws2_32.lib")
+  if (USE_MSVC_MT)
+    foreach(flag_var
+          CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+          CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+      if(${flag_var} MATCHES "/MD")
+        string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+      endif(${flag_var} MATCHES "/MD")
+    endforeach(flag_var)
+  endif()
 endif()
 
 list(APPEND SOURCE ${proto_srcs})