0

I am trying to get YOLO/Darknet working inside a UE4 project. I have built YOLO as a C++ DLL and added all the dependencies to the project. It builds fine, and some parts of the code are working, but I'm getting an exception at a certain point, and after literally days trying to figure it out, I'm now at a loss and need some help.

Here is the code I'm calling to create a YOLO Detector class from inside a UE4 class:

YOLO_DataPath = FPaths::ProjectDir() + "Plugins/Stereolabs/Source/YOLO/Data/";
std::string YOLO_DataPathC(TCHAR_TO_UTF8(*YOLO_DataPath));

std::string  NamesFile = YOLO_DataPathC + "obj.names";
std::string  CFGFile = YOLO_DataPathC + "yolo-obj.cfg";
std::string  WeightsFile = YOLO_DataPathC + "yolo-obj.weights";

Detector YOLODetector(CFGFile, WeightsFile);

Here is the constructor being called (from 'yolo_v2_class.cpp', line 130):

LIB_API Detector::Detector(std::string cfg_filename, std::string weight_filename, int gpu_id) : cur_gpu_id(gpu_id)
{
    wait_stream = 0;
#ifdef GPU
    int old_gpu_index;
    check_cuda( cudaGetDevice(&old_gpu_index) );
#endif

    detector_gpu_ptr = std::make_shared<detector_gpu_t>();
    detector_gpu_t &detector_gpu = *static_cast<detector_gpu_t *>(detector_gpu_ptr.get());

#ifdef GPU
    //check_cuda( cudaSetDevice(cur_gpu_id) );
    cuda_set_device(cur_gpu_id);
    printf(" Used GPU %d \n", cur_gpu_id);
#endif
    network &net = detector_gpu.net;
    net.gpu_index = cur_gpu_id;
    //gpu_index = i;

    _cfg_filename = cfg_filename;
    _weight_filename = weight_filename;

    char *cfgfile = const_cast<char *>(_cfg_filename.c_str());
    char *weightfile = const_cast<char *>(_weight_filename.c_str());

    net = parse_network_cfg_custom(cfgfile, 1, 1);
    if (weightfile) {
        load_weights(&net, weightfile);
    }
    set_batch_network(&net, 1);
    net.gpu_index = cur_gpu_id;
    fuse_conv_batchnorm(net);

    layer l = net.layers[net.n - 1];
    int j;

    detector_gpu.avg = (float *)calloc(l.outputs, sizeof(float));
    for (j = 0; j < NFRAMES; ++j) detector_gpu.predictions[j] = (float*)calloc(l.outputs, sizeof(float));
    for (j = 0; j < NFRAMES; ++j) detector_gpu.images[j] = make_image(1, 1, 3);

    detector_gpu.track_id = (unsigned int *)calloc(l.classes, sizeof(unsigned int));
    for (j = 0; j < l.classes; ++j) detector_gpu.track_id[j] = 1;

#ifdef GPU
    check_cuda( cudaSetDevice(old_gpu_index) );
#endif
}

All this code seems to run fine, but when it reaches the end of the constructor, it hits an exception where it seems to be trying to delete a string, on this line (line 132 of xmemory - c:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\include\xmemory0):

::operator delete(_Ptr);

The full call stack is like this:

[Inline Frame] yolo_cpp_dll.dll!std::_Deallocate(void * _Ptr, unsigned __int64) Line 132
[Inline Frame] yolo_cpp_dll.dll!std::allocator<char>::deallocate(char *) Line 720
[Inline Frame] yolo_cpp_dll.dll!std::_Wrap_alloc<std::allocator<char> >::deallocate(char * _Count, unsigned __int64) Line 987
[Inline Frame] yolo_cpp_dll.dll!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::_Tidy(bool) Line 2258
[Inline Frame] yolo_cpp_dll.dll!std::basic_string<char,std::char_traits<char>,std::allocator<char> >::{dtor}() Line 1017
yolo_cpp_dll.dll!Detector::Detector(std::basic_string<char,std::char_traits<char>,std::allocator<char> > cfg_filename, std::basic_string<char,std::char_traits<char>,std::allocator<char> > weight_filename, int gpu_id) Line 177

From what limited information I can find out about this error, it seems like it might be an issue with DLLs being compiled with different compilers. I have spent days now trying different versions of compiling everything from scratch - UE4 from source, the UE4 project, the YOLO cpp DLL. I have tried totally clean installs of Visual Studio 2015 and 2017 for everything, and I'm getting this same problem each time.

Does anyone know exactly what is going on here? And how I might either fix it or work around it?

  • 1
    *it seems like it might be an issue with DLLs being compiled with different compilers.* -- Well if you're going to pass and return `std::string`'s that have been created by different compilers, then you're going to have memory issues. [See this](https://stackoverflow.com/questions/22279052/c-passing-stdstring-by-reference-to-function-in-dll/22279218#22279218) – PaulMcKenzie Mar 10 '20 at 17:44
  • That’s what I thought, but that’s why I’ve been building everything from source with the same compiler. And I’m not passing in the string by reference. – LocalStarlight Mar 10 '20 at 18:05
  • Also, no strings are being returned from the function. – LocalStarlight Mar 10 '20 at 18:38
  • You must use the same compiler, same compiler settings, same runtime library (must be the DLL version of the runtime), and it doesn't really matter how you're passing and returning `std::string`. You have a very small margin of error when you have all of those restrictions. That's why it isn't recommended to be passing and/or returning objects that manage dynamic memory across module boundaries. – PaulMcKenzie Mar 10 '20 at 19:32
  • I see, thanks for explaining that to me... I’ll have to try again to get everything with the same settings. But is there an alternative way to do this instead? Seems like it shouldn’t be such a complicated thing to do... – LocalStarlight Mar 10 '20 at 21:50

2 Answers2

0

Simple way: Never pass std::xxx objects between different modules. Use raw C types. Memory should be released in module that allocated it.

Detector(const char* cfg_filename, const char* weight_filename, int gpu_id)

Hard way: compile all modules with same compiler/options (extra hard in case of UE4).

sviborg
  • 23
  • 2
0

As other have pointed out, the trouble-free way is hiding your code behind a C interface, but that limits a lot your interface if you're using C++. So if you want to go the hard way, I know at least two projects that successfully managed to do it, you can go into their code for full details

As most comments here pointed out, use same compiler and configuration, if you open a UE4 project in Visual Studio you should be able to inspect the configuration.

Then the trick is statically linking MSVCRT.lib. Unless you compiled UE4 yourself, it uses release mode so us /MD compiler flag (See msvc reference). Here's the example in CARLA's build script, BuildLibCarla.bat#L103-L111, uses CMake to create a static library, later this library is linked into a UE4 plugin (dll).

Linux

For completion I'll add the details for Linux too, cause I wish I've found this online when I had to do it!

If you're building on Linux things get a bit more complicated, UE4 links against LLVM's libc++ runtime instead of the default GNU's libstdc++. Depending on the UE4 version they use a different version of the LLVM toolchain (they tend to update it relatively often). They come bundled with UE4, you can find them at

  • Headers and libs: Engine/Source/ThirdParty/Linux/LibCxx
  • SDK, binaries: Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/<clang version>/x86_64-unknown-linux-gnu

Then either compile and link against the bundled version of libc++.a and libc++abi.a, or compile your own (this is what CARLA does Setup.sh#L37-L58). Note that using the libc++ and libc++abi you get from apt install won't work because those are not compiled with position independent code (-fPIC) that you would need to link to an .so in UE4.

nsubiron
  • 913
  • 4
  • 15