0

I have the following C++ API

#ifndef AGUR_H
#define AGUR_H

#define AGUR_EXPORT __attribute__((visibility("default")))

#include <cstdint>

extern "C"
{
    AGUR_EXPORT void sphericalRelativePoseSolveFromFile(
        const char* filePathStatic,
        const char* filePathDynamic,
        const char* solverConfigJson,
        char** solverSolutionJson);

    AGUR_EXPORT void sphericalRelativePoseSolve(
        const char* rawImageStatic,
        const char* rawImageDynamic,
        int imageWidth,
        int imageHeight,
        const char* solverConfigJson,
        char** solverSolutionJson);
}
#endif // AGUR_H

My implementation for the first method above (and the one I am concerned with) is

void sphericalRelativePoseSolveFromFile(
    const char* filePathStatic,
    const char* filePathDynamic,
    const char* solverConfigJson,
    char** solverSolutionJson)
{
    json cj = json::parse(solverConfigJson);
    types::SolverConfiguration solverConfig = cj.get<types::SolverConfiguration>();

    std::string sfp = std::string(filePathStatic);
    std::string dfp = std::string(filePathDynamic);

    agur::sfm::SphericalRelativePoseSolver sphericalRelativePoseSolver(solverConfig);
    types::SolverSolution solverSolution = sphericalRelativePoseSolver.solve(sfp, dfp);

    const json sj{ solverSolution };
    std::string jsonString = sj[0].dump();

    char* tmp = (char*)jsonString.c_str();
    size_t len = std::strlen(tmp) + 1;
    *solverSolutionJson = (char*)malloc(len);
    std::memcpy((void*)*solverSolutionJson, tmp, len);
}

I call this API from C++ as followws and the results are what I expect and want.

std::string ss = "/Alignment/Theta_R0010732_2048.jpg";
const char* staticFilePath = ss.c_str();

std::string ds = "/Alignment/Theta_R0010733_2048.jpg";
const char* dynamicFilePath = ds.c_str();

const char* solverConfigJson =
    "{"
        "\"acRansacMaxThreshold\": 4.0,"
        "\"outputEssentialGeometry\": true,"
        "\"outputFeatures\": true,"
        "\"outputInliers\": true,"
        "\"outputMatches\": false,"
        "\"unstableSolutionInlierThreshold\": 60,"
        "\"useUprightRelativePoseSolver\": true"
    "}";

char solverSolutionJson[] = "";
char* ptrSolverSolutionJson = solverSolutionJson;

sphericalRelativePoseSolveFromFile(staticFilePath, dynamicFilePath, solverConfigJson, &ptrSolverSolutionJson);

std::cout << ptrSolverSolutionJson << std::endl;

This prints out the expected solver results as jsomn - great. However, I now want to call the API from a Unity project. The Unity code so far is

using System;
using System.IO;
using System.Runtime.InteropServices;
using UnityEngine;
using UnityEngine.Networking;

public class AgurIvocation : MonoBehaviour
{
    private string _status = string.Empty; 

    #if UNITY_IOS || UNITY_TVOS
        // On iOS plugins are statically linked into
        // the executable, so we have to use __Internal as the
        // library name.
        private const string Import = "__Internal";
    #else
        // Other platforms load plugins dynamically, so pass the
        private const string Import = "agur";
    #endif

    [DllImport(Import)]
    private static unsafe extern void sphericalRelativePoseSolveFromFile(
        string filePathStatic,
        string filePathDynamic,
        string solverConfigJson,
        out string[] solverSolutionJson);

    [DllImport(Import)]
    private static unsafe extern void sphericalRelativePoseSolve(
        string rawImageStatic,
        string rawImageDynamic,
        int imageWidth,
        int imageHeight,
        string solverConfigJson,
        out string[] solverSolutionJson);

    void Start()
    {
        try
        {
            var filePathStatic = "/Alignment/Theta_R0010732_2048.jpg";
            var filePathDynamic = "/Alignment/Theta_R0010733_2048.jpg";
            var solverConfigJson =
                "{" +
                    "\"acRansacMaxThreshold\": 4.0," +
                    "\"outputEssentialGeometry\": true," +
                    "\"outputFeatures\": true," +
                    "\"outputInliers\": true," +
                    "\"outputMatches\": false," +
                    "\"unstableSolutionInlierThreshold\": 60," +
                    "\"useUprightRelativePoseSolver\": true" +
                "}";


            var solverSolutionJson = new string[] { };
            sphericalRelativePoseSolveFromFile(filePathStatic, filePathDynamic, solverConfigJson, out solverSolutionJson);
            Console.WriteLine(solverSolutionJson[0]);
        }
        catch (Exception e)
        {
            _status = e.ToString();
        }
    }

    // Update is called once per frame
    void Update()
    {
        
    }

    private string UnpackAsset(string path)
    {

#if !UNITY_ANDROID
        return Path.Combine(Application.streamingAssetsPath, path);
#endif

        var inputFilePath = Path.Combine(Application.streamingAssetsPath, path);
        var outputFilePath = Path.Combine(Application.persistentDataPath, path);

        var loadingRequest = UnityWebRequest.Get(inputFilePath);
        loadingRequest.SendWebRequest();

        while (!loadingRequest.isDone)
        {
            if (loadingRequest.isNetworkError || loadingRequest.isHttpError)
            {
                break;
            }
        }

        if (!loadingRequest.isNetworkError && !loadingRequest.isHttpError)
        {
            File.WriteAllBytes(outputFilePath, loadingRequest.downloadHandler.data);
            return outputFilePath;
        }

        string errorMessage = $"Unpacking asset failed: {path}, reason: {loadingRequest.error}";
        throw new Exception(errorMessage);
    }
}

But I am getting nothing out of the solverSolutionJson variable. Simply, this is not working - can someone advise are to how this should be done from Unity/Mono please?

  • Should your output be a single `out` string rather than an array? – Alan Birtles May 20 '22 at 12:59
  • Things that come to mind: You need `CallingConvention = CDecl`. The `out` value should not be an array and should be `out StringBuilder`. You would need to pass through a preinitialized `StringBuilder` of a certain size (pass that through also), you **cannot** return `std::string` from C++. To be honest, C# has great JSON capabilities, why don't you just use those? – Charlieface May 20 '22 at 13:05
  • So @Charlieface this is different that calling with .NET Framework - why do we need to work with StringBuilder here? The Json that comes back is the results form a very complex C++ solver. – CipherShark May 20 '22 at 13:50
  • Not sure what you mean? Does the Mono marshaller understand `std::string`? I don't think so. See also https://stackoverflow.com/questions/43873902/how-do-i-marshal-a-wstring-in-c and https://stackoverflow.com/questions/44464552/how-to-properly-marshal-strings-from-unity-to-c-c and https://stackoverflow.com/questions/30998810/how-to-correctly-marshal-net-strings-to-stdwstrings-for-native-code. Your only other option is to allocate a BSTR and pass that as a pointer, and on the C# side you can then take the pointer, marshal it and *free it*. I think that works. – Charlieface May 20 '22 at 14:01
  • Basically, any memory allocated on C++ side must be freed there also, except for BStr. So the easiest is to just use a `StringBuilder` buffer – Charlieface May 20 '22 at 14:02

0 Answers0