Viveport SDK and Unity's IL2CPP Build Option

andyman404
Visitor

Viveport SDK and Unity's IL2CPP Build Option

Has anyone had any success in getting the Viveport SDK for Unity to work with Unity's IL2CPP build option?

 

I'm trying to integrate the Viveport SDK with my game, but when I build with the IL2CPP option and try to run it, I see this exception which prevents the Viveport API from trying to initialize:

NotSupportedException: IL2CPP does not support marshaling delegates that point to instance methods to native code.
  at Viveport.Internal.Api.Init (Viveport.Internal.StatusCallback initCallback, System.String appId) [0x00000] in <00000000000000000000000000000000>:0

 

I don't get this issue when running within the editor, or if I run a build of game with the Mono build option - the SDK works fine for both of those. This issue only happens for IL2CPP, which is unfortunate, because the game has better performance when built with IL2CPP. I'm using Windows 10 Pro, 64-bit, Unity 2018.2.13f1, and Viveport SDK 1.7.10 (via the Unity package in the SDK).

 

My code is pretty much just the example code in the SDK docs (but with my own VIVEPORT_ID obviously).  This issue appears to be happening inside the internal API.Init method when it is trying to make the call to the DLL.

 

Does anyone have any solutions/ideas, or is the Viveport SDK for Unity simple not compatible with the IL2CPP build option? If not, then how do we request for the Viveport SDK for Unity to support IL2CPP?

 

@yakingkuo

Vive Staff

Re: Viveport SDK and Unity's IL2CPP Build Option

Hi @andyman404:

 

Please check below link. 
https://answers.unity.com/questions/1229036/callbacks-from-c-to-c-are-not-working-in-540f3.html

 

Hope it can help you to fix this issue. 

Chiranjeev
Visitor

Re: Viveport SDK and Unity's IL2CPP Build Option

Have you found a solution of it? i am stucked with the same problem

@yakingkuo 

Vive Staff

Re: Viveport SDK and Unity's IL2CPP Build Option

Hi Chiranjeev: 

 

Your issue can not be solved by the link? 
https://answers.unity.com/questions/1229036/callbacks-from-c-to-c-are-not-working-in-540f3.html

@Chiranjeev 

 

Regards. 

Yaking

asdfqwer
Visitor

Re: Viveport SDK and Unity's IL2CPP Build Option

The provided link did not help

I pasted the original Viveport for Unity code which runs fine in editor checking the license, but compiled to IL2CPP I also get

NotSupportedException IL2CPP does not support marshaling delegates that point to instance methods to native code
>ViveportApiInit (Viveport.StatusCallback callback, SYstem.Strng appId) (at <0000...0>:))
ViveportLic.Start () (at <000...000>:0)

callback.PNG

The  only thing I see that link does is adding 

[MonoPInvokeCallback(typeof(CallbackDelegate))]

and that callback is already static.

 

public class ViveportLic : MonoBehaviour {
// ID and Key
   private delegate void CallbackDelegate();

    void Start()
    {
        Api.Init(InitStatusHandler, VIVEPORT_ID);
    }

    [MonoPInvokeCallback(typeof(CallbackDelegate))]
    static void InitStatusHandler(int nResult)
    {
        Viveport.Core.Logger.Log("Init(): " + nResult);
        if (nResult != 0)
        {
            Viveport.Core.Logger.Log("Init setup error ...");
        }
        UserStats.IsReady(IsReadyHandler);
    }
omkar_patil
Visitor

Re: Viveport SDK and Unity's IL2CPP Build Option

If some one is still stuck. I managed to create new script for few api operations for il2cpp support. 

using LitJson;
using PublicKeyConvert;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
using System.Text;
using UnityEngine;

public class ViveportIl2cpp : MonoBehaviour
{
    private static readonly List<GetLicenseCallback> InternalGetLicenseCallbacks = new List<GetLicenseCallback>();
    private static readonly List<StatusCallback> InternalStatusCallbacks = new List<StatusCallback>();
    internal static readonly List<LicenseChecker> InternalLicenseCheckers = new List<LicenseChecker>();


    public static bool bInit = true, bIsReady = false, bArcadeIsReady = false, bTokenIsReady = false;

    static string APP_ID = "your app id";
    static string APP_KEY = "Your app key";


    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void StatusCallback(int nResult);

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    private delegate void GetLicenseCallback([MarshalAs(UnmanagedType.LPStr)] string message, [MarshalAs(UnmanagedType.LPStr)] string signature);



    [DllImport("viveport_api", EntryPoint = "IViveportAPI_Init", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private static extern int Init(StatusCallback initCallback, string appId);

    [DllImport("viveport_api", EntryPoint = "IViveportAPI_GetLicense", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private static extern void GetLicense(GetLicenseCallback callback, string appId, string appKey);

    [DllImport("viveport_api", EntryPoint = "IViveportUserStats_IsReady", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private static extern int IsReady(StatusCallback IsReadyCallback);

    [DllImport("viveport_api", EntryPoint = "IViveportAPI_Shutdown", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    private static extern int Shutdown(StatusCallback initCallback);



    [DllImport("viveport_api", EntryPoint = "IViveportUser_GetUserID", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    internal static extern int GetUserID(StringBuilder userId, int size);

    [DllImport("viveport_api", EntryPoint = "IViveportUser_GetUserName", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    internal static extern int GetUserName(StringBuilder userName, int size);

    [DllImport("viveport_api", EntryPoint = "IViveportUser_GetUserAvatarUrl", CharSet = CharSet.Ansi, CallingConvention = CallingConvention.Cdecl)]
    internal static extern int GetUserAvatarUrl(StringBuilder userAvatarUrl, int size);



    [AOT.MonoPInvokeCallback(typeof(StatusCallback))]
    static void InitHandler(int nResult)
    {
        if (nResult == 0)
        {
            bInit = true;
            bIsReady = false;
            bArcadeIsReady = false;
            Viveport.Core.Logger.Log("InitStatusHandler is successful");
        }
        else
        {
            // Init error, close your app and make sure your app ID is correct or not.
            bInit = false;
            Viveport.Core.Logger.Log("InitStatusHandler error : " + nResult);
        }
    }

    [AOT.MonoPInvokeCallback(typeof(StatusCallback))]
    static void ShutdownHandler(int nResult)
    {
        if (nResult == 0)
        {
            bInit = false;
            bIsReady = false;
            Viveport.Core.Logger.Log("ShutdownHandler is successful");
        }
        else
        {
            Viveport.Core.Logger.Log("ShutdownHandler error: " + nResult);
        }
    }

    [AOT.MonoPInvokeCallback(typeof(StatusCallback))]
    static void IsReadyHandler(int nResult)
    {
        if (nResult == 0)
        {
            bIsReady = true;
            bArcadeIsReady = false;
            Viveport.Core.Logger.Log("IsReadyHandler is successful");
        }
        else
        {
            // IsReady error, close your app and make sure the viveport is launched normally.
            bIsReady = false;
            Viveport.Core.Logger.Log("IsReadyHandler error: " + nResult);
        }
    }

    [AOT.MonoPInvokeCallback(typeof(GetLicenseCallback))]
    static void GetLicenseHandler(
               [MarshalAs(UnmanagedType.LPStr)] string message,
               [MarshalAs(UnmanagedType.LPStr)] string signature)
    {
        // Logger.Log("Raw Message: " + message);
        // Logger.Log("Raw Signature: " + signature);

        var isVerified = !string.IsNullOrEmpty(message);
        if (!isVerified)
        {
            for (var i = InternalLicenseCheckers.Count - 1; i >= 0; i--)
            {
                var checker = InternalLicenseCheckers[i];
                checker.OnFailure(90003, "License message is empty");
                InternalLicenseCheckers.Remove(checker);
            }
            return;
        }

        isVerified = !string.IsNullOrEmpty(signature);
        if (!isVerified) // signature is empty - error code mode
        {
            var jsonData = JsonMapper.ToObject(message);
            var errorCode = 99999;
            var errorMessage = "";

            try
            {
                errorCode = int.Parse((string)jsonData["code"]);
            }
            catch
            {
                // ignored
            }
            try
            {
                errorMessage = (string)jsonData["message"];
            }
            catch
            {
                // ignored
            }

            for (var i = InternalLicenseCheckers.Count - 1; i >= 0; i--)
            {
                var checker = InternalLicenseCheckers[i];
                checker.OnFailure(errorCode, errorMessage);
                InternalLicenseCheckers.Remove(checker);
            }
            return;
        }

        isVerified = VerifyMessage(APP_ID, APP_KEY, message, signature);
        if (!isVerified)
        {
            for (var i = InternalLicenseCheckers.Count - 1; i >= 0; i--)
            {
                var checker = InternalLicenseCheckers[i];
                checker.OnFailure(90001, "License verification failed");
                InternalLicenseCheckers.Remove(checker);
            }
            return;
        }

        var decodedLicense = Encoding.UTF8.GetString(
                Convert.FromBase64String(
                        message.Substring(message.IndexOf("\n", StringComparison.Ordinal) + 1)
                )
        );
        var jsonData2 = JsonMapper.ToObject(decodedLicense);
        Viveport.Core.Logger.Log("License: " + decodedLicense);

        var issueTime = -1L;
        var expirationTime = -1L;
        var latestVersion = -1;
        var updateRequired = false;

        try
        {
            issueTime = (long)jsonData2["issueTime"];
        }
        catch
        {
            // ignored
        }
        try
        {
            expirationTime = (long)jsonData2["expirationTime"];
        }
        catch
        {
            // ignored
        }
        try
        {
            latestVersion = (int)jsonData2["latestVersion"];
        }
        catch
        {
            // ignored
        }
        try
        {
            updateRequired = (bool)jsonData2["updateRequired"];
        }
        catch
        {
            // ignored
        }

        for (var i = InternalLicenseCheckers.Count - 1; i >= 0; i--)
        {
            var checker = InternalLicenseCheckers[i];
            checker.OnSuccess(issueTime, expirationTime, latestVersion, updateRequired);
            InternalLicenseCheckers.Remove(checker);
        }
    }


    private static bool VerifyMessage(
                string appId,
                string appKey,
                string message,
                string signature)
    {
        try
        {
            var provider = PEMKeyLoader.CryptoServiceProviderFromPublicKeyInfo(appKey);
            var decodedSignature = Convert.FromBase64String(signature);
            var sha = new SHA1Managed();
            var data = Encoding.UTF8.GetBytes(appId + "\n" + message);

            return provider.VerifyData(data, sha, decodedSignature);
        }
        catch (Exception e)
        {
            Viveport.Core.Logger.Log(e.ToString());
        }
        return false;
    }

    public void SendLicenceStatus(bool isPassed, string message)
    {
        if (isPassed)
        {
            Debug.LogError("_________________passed__________________");
        }
        else
        {
            Debug.LogError("_________________failed__________________");
        }
    }


    // function to initialise
    public static void init_VivePort()
    {
        Init(InitHandler, APP_ID);
        if(bInit)
        {
            IsReady(IsReadyHandler);
// after is ready you can fetch usern details from User.GetUserName(), User.GetUserId() } } // function to shutdown public static void ShutDown() { Shutdown(ShutdownHandler); } // function to get licence public static void GetViveLicenceStatus() { if(bIsReady && bInit) { MyLicenseChecker checker = new MyLicenseChecker(); InternalLicenseCheckers.Add(checker); GetLicense(GetLicenseHandler, APP_ID, APP_KEY); } } } public class MyLicenseChecker : LicenseChecker { public delegate void LicenceCheck(bool passed, string message); public static event LicenceCheck OnCheck; public override void OnSuccess(long issueTime, long expirationTime, int latestVersion, bool updateRequired) { Viveport.Core.Logger.Log("[MyLicenseChecker] issueTime: " + issueTime); Viveport.Core.Logger.Log("[MyLicenseChecker] expirationTime: " + expirationTime); Viveport.Core.Logger.Log("[MyLicenseChecker] latestVersion: " + latestVersion); Viveport.Core.Logger.Log("[MyLicenseChecker] updateRequired: " + updateRequired); OnCheck(true, "Passed : (issueTime: " + issueTime + ")"); } public override void OnFailure(int errorCode, string errorMessage) { Viveport.Core.Logger.Log("[MyLicenseChecker] errorCode: " + errorCode); Viveport.Core.Logger.Log("[MyLicenseChecker] errorMessage: " + errorMessage); OnCheck(false, "Failed : (errorMessage: " + errorMessage + ")"); } } public abstract class LicenseChecker { public abstract void OnSuccess( long issueTime, long expirationTime, int latestVersion, bool updateRequired ); public abstract void OnFailure( int errorCode, string errorMessage ); } public partial class User { #if !UNITY_ANDROID private const int MaxIdLength = 256; private const int MaxNameLength = 256; private const int MaxUrlLength = 512; #endif public static string GetUserId() { #if !UNITY_ANDROID var userId = new StringBuilder(MaxIdLength); ViveportIl2cpp.GetUserID(userId, MaxIdLength); return userId.ToString(); #else return Internal.User.GetUserId().ToString(); #endif } public static string GetUserName() { #if !UNITY_ANDROID var userName = new StringBuilder(MaxNameLength); ViveportIl2cpp.GetUserName(userName, MaxNameLength); return userName.ToString(); #else return Internal.User.GetUserName().ToString(); #endif } public static string GetUserAvatarUrl() { #if !UNITY_ANDROID var userAvatarUrl = new StringBuilder(MaxUrlLength); ViveportIl2cpp.GetUserAvatarUrl(userAvatarUrl, MaxUrlLength); return userAvatarUrl.ToString(); #else return Internal.User.GetUserAvatarUrl().ToString(); #endif } }

Hope this helps!