I’ve come across a crash that happens when I try to use an std::map in C++ from Unity through a plugin.
I am not passing the contents of the map to Unity from the plugin; I am simply calling member functions such as the find() function (where my application crashes)
The code executes fine when I load my plugin into a C++ console application but it crashes in Unity.
I suspect there is a marshalling/memory allocation issue where Unity is not allowing me to allocate memory of some sort
Attached is the Player.log output and the calling Unity script and the callee C++ code.
Apologies that all code snippets are C#; this forum interface only let me post C#, Boo, or Javascript.
Only the first snippet is C#, the rest are C++.
Any help would be much appreciated.
Regards
Lancophone
public string filename; // The name of the audio file this behaviour corresponds to
// Import the ability to add sounds and
// move them by making native calls to the SSR
[DllImport ("Unity-Audio-Plugin")]
private static extern void addSoundToRendererBank(string filename);
[DllImport ("Unity-Audio-Plugin")]
private static extern void addSoundToRendererBankAtPosition(string filename, float xPos, float zPos);
[DllImport ("Unity-Audio-Plugin")]
private static extern bool setSoundWithNameToPosition (string filename, float xPos, float zPos);
[DllImport ("Unity-Audio-Plugin")]
private static extern bool deactivateSoundInRendererBank (string filename);
// Use this for initialization
public int incr;
void Start () {
incr = 1;
addSoundToRendererBank (filename);
InvokeRepeating ("moveMe", 0, 0.3F);
}
.....
void addSoundToRendererBank(char *filename)
{
printf("Calling the addSoundToRendererBank function with %s as a filename\n", filename);
copyStringFromManagedCaller(filename);
std::string fileNameStr(&buffer[0]);
int soundLoc = soundBank->getIDForName(fileNameStr);
if (soundLoc > 0) { // Sound is already in the bank
// If its just been deactivated, reactivate it
std::string key(&buffer[0]);
soundBank->activateSoundSource(key);
}
...
int SoundBank::getIDForName(std::string &filePath)
{
// Filepath -> sourceIDMap -> source -> ID
std::string key(filePath);
int location;
std::cerr << "About to enter the try-catch block in the SoundBank::getIDForName() function using " << key << std::endl;
// Print the current elements inside the map
std::cerr << "The contents of the sourceIDMap:" << std::endl;
SoundSourceMapIterator map_it;
// for (map_it = sourceIDMap.begin(); map_it != sourceIDMap.end(); ++map_it) {
// std::cerr << "Key: " << map_it->first << "\tValue: " << map_it->second << std::endl;
// }
std::cerr << "I'm about to die.... :-(" << std::endl;
// Throws randomers; using find instead
// try
// {
// location = sourceIDMap.at(key);
// }
// catch (const std::out_of_range& oorEx)
// {
// location = -1;
// }
SoundSourceMapIterator it = sourceIDMap.find(key);
if (it == sourceIDMap.end())
location = -1;
else
location = it->second;
.......
I’m guessing the problem is in marshalling the filename string from mono to C++, take a look at Interop with Native Libraries | Mono – look for marshalling strings.
Thanks steego, but I don’t think there is an issue with the string as the console output shows it being printed in C++.
I had a look at the docs link. Are they outdated as I couldn’t find a reference to the MarshalAs in the InteropServices package
I too believe the issue is with sourceIDMap. It is a std::map member object of the SoundBank class.
It is instantiated in the soundbank constructor.
Yeah that’s probably not it then. Those are the live docs for mono so I would assume they are up to date, but I can’t know for sure. I’ve used MarshalAs myself, but some time ago now.
So that should be instantiated since you’re obviously calling a method on an instance… Not sure what could be going on then… Can you get it to work with something simpler, like the first example from http://en.cppreference.com/w/cpp/container/map/find inside your function instead?
Just tried the example and yep, it worked! Does this imply a memory allocation issue then? The console output certainly looks like its complaining about allocation somewhere in the underlying binary tree implementation of std::map. How can I allocate memory in a native plugin then?
No, this didn’t work either. I suspected oor was the wrong exception to catch (as the output states NULL exception) so I tried find instead. They both give the same crash
Yes, my guess would be that some or all of the data inside the map is no longer valid when you get to this point.
You should be OK with using new and malloc etc. inside your native plugins as you normally would.
From the code you’ve pasted, I can’t see you creating a new SoundBank, from Unity you are calling addSoundToRendererBank (filename); and inside that, you are doing soundBank->getIDForName(fileNameStr); – maybe you can show how you create a new SoundBank and the constructor?
soundBank is a shared ptr, declared static at the plugin interface scope (file level).
Here is its initialization
....
using SoundBank = std::shared_ptr<uap::SoundBank>;
// Here's our system objects
static int RES = 0; // This is the shared result variable to indicate a successful interface call; 1 for successful, anything else for unsuccessful
static SoundBank soundBank;
static char buffer[200];
void copyStringFromManagedCaller(char *managedString)
{
strcpy(buffer, managedString);
}
void initEnv()
{
soundBank = SoundBank(new uap::SoundBank);
RES = 1;
}
I’ve tried declaring the sourceIDMap as dynamic memory. This has reduced the error to a silent NULL error in the console
@steego I’ve put together a simple plugin that uses an std::map with std::string and an int as value (same setup as me)
It passes strings from Unity which are received as char* in C++
It compiles and runs fine. Now I’m really stumped.
Any more ideas?
C++ code:
example.hpp
extern "C" {
void addToMap(char *key, int value);
void findInMapAndPrintValue(char *key);
}
.....
example.cpp
#include <stdio.h>
#include <map>
#include <string>
#include <iostream>
#include "Map_Example.h"
static std::map<std::string, int> my_example_map;
void addToMap(char *key, int value)
{
std::string keyStr(key);
my_example_map.insert(std::make_pair(keyStr, value));
}
void findInMapAndPrintValue(char *key)
{
std::string keyStr(key);
std::map<std::string, int>::iterator it;
it = my_example_map.find(keyStr);
if (it != my_example_map.end())
std::cerr << "The value for " << keyStr << " is: " << it->second << std::endl;
else
std::cerr << "I couldn't find the value for key: " << keyStr << std::endl;
}
C# code:
using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
public class TestPlugin : MonoBehaviour {
[DllImport ("MyMap")]
private static extern void addToMap (
string key,
int value);
[DllImport ("MyMap")]
private static extern void findInMapAndPrintValue (
string key);
// Use this for initialization
void Start () {
findInMapAndPrintValue ("Sample key");
addToMap ("Sample key", 45);
findInMapAndPrintValue ("Sample key");
}
// Update is called once per frame
void Update () {
}
}
@steego And the prize for most amateur mistake ever goes to … me!!!
Typically when you get a NULL exception, you should look for the NULL.
After hours at this, I’ve now solved it.
It was a simple execution order blunder; I was trying to add sounds to sourceIDMap before my initEnv() function was called as I placed all these function callbacks in the Start() function of my C# scripts.
Simply moving the initEnv function to an Awake function instead (I chose the camera object) and keeping all the addSound calls to Start callbacks for particular objects, the application now runs without crashing.
Thank you for your help anyway. Glad to have it solved now
Cheers
PS: If anyone is interested, this is where I found enlightenment