Linker Error after Unity (4.6.0f3) and Xcode (6.1.1) Upgrades

I upgraded to Unity 4.6.0f3 and Xcode 6.1.1. After doing this, I get a linker error when trying to build my Xcode project.

I should point out the following up front:

  • Yes, I have scoured the forums and SO (and Google for that matter), and tried every recommended fix I could find. None of them have worked.
  • I can build and run the project in Xcode 6.1.1 that was exported from Unity 4.4 without seeing this issue
  • The Android project still exports, builds and runs perfectly after the Unity upgrade
  • I have not made any changes to the Unity or Xcode projects, other than editing my
    CMVideoSampling class to use <OpenGLES/ES2/glext.h> instead of <OpenGLES/ES2/glext.h>

Here is the error:

Undefined symbols for architecture armv7:
“_SetSceneData”, referenced from:
RegisterMonoModules() in RegisterMonoModules.o
ld: symbol(s) not found for architecture armv7
clang: error: linker command failed with exit code 1 (use -v to see invocation)

the “SetSceneData” function is the gateway between my native (in this case Objective C) code and the Unity C code:

Here’s the method (I removed the rest of the class for brevity but can share upon request):

using UnityEngine;
using System.Collections.Generic;
using System.Runtime.InteropServices;

[System.Serializable]
public class IOSCommunicator : MonoBehaviour, SceneStateCaching
{
    //This is how we define messages that we will send to iOS.  iOS will implement this method signature in C.
    #if UNITY_IPHONE || UNITY_ANDROID
    [DllImport("__Internal")]
    private static extern void SetSceneData(string[] sliceTitles, int sliceCount, string[] videoURLs, int videoCount, string description);
    #endif.....

If anyone else has dealt with this issue please let me know. I’m a newbie to Unity, and fairly new to Xcode, so any assistance will be greatly appreciated. Thanks in advance.

Looks like Objective-C (well, C) half of your native plugin is missing, but I can’t explain why it would work from Unity 4.4 and not 4.6…

I would’ve thought it would still work as well. I only posted the top portion of the C class to keep the post short. Here’s the class in its entirety:

using UnityEngine;
using System.Collections.Generic;
using System.Runtime.InteropServices;

[System.Serializable]
public class IOSCommunicator : MonoBehaviour, SceneStateCaching
{
    //This is how we define messages that we will send to iOS.  iOS will implement this method signature in C.
    #if UNITY_IPHONE || UNITY_ANDROID
    [DllImport("__Internal")]
    private static extern void SetSceneData(string[] sliceTitles, int sliceCount, string[] videoURLs, int videoCount, string description);
    #endif

    public DefectType defectType;
    public ModelType modelType;
    public List<SlicePlane> slicePlanes;
    public TextAsset summaryHTMLAsset;
    public string[] videoURLs;

    private bool interestPointsEnabled;
    private SlicePlane currentSlice;
   
    //Script references
    private ModelSlice sliceScript;
    private InteractiveObject interactiveObject;
    private InteractiveCamera interactiveCamera;

    void Start ()
    {
        if (SceneState.Instance().isFirstScene == false)
        {
            sliceScript = GameObject.FindWithTag("BaseModel").GetComponent<ModelSlice>();
            interactiveCamera  = Camera.main.GetComponent<InteractiveCamera>();
            interactiveObject = GameObject.FindWithTag("BaseModel").GetComponent<InteractiveObject>();

            sendSceneSpecificInfoToIOS();
            if (SceneState.Instance().isFirstHeartScene == false)
            {
                loadStateForNewScene();
            }
        }
    }

    void Update()
    {
        #if UNITY_ANDROID
        if (Input.GetKeyDown (KeyCode.Escape)) {
            AndroidJavaClass jc = new AndroidJavaClass ("com.unity3d.player.UnityPlayer");
            AndroidJavaObject jo = jc.GetStatic<AndroidJavaObject> ("currentActivity");
            jo.Call ("closeActivity");
        }
        #endif
    }

    /** SceneStateCaching **/
    public void saveStateForNewScene()
    {
        SceneState.Instance().interestPointsEnabled = interestPointsEnabled;
        if (currentSlice != null)
            SceneState.Instance().savedSliceName = currentSlice.title;       
    }
    public void loadStateForNewScene()
    {
        interestPointsEnabled = SceneState.Instance().interestPointsEnabled;
        string savedSliceName = SceneState.Instance().savedSliceName;
        if (savedSliceName != null)
        {
            setInterestPointsActive(false);  //disable old points
            SlicePlane slice = slicePlaneWithName(savedSliceName);
            if (slice != null)
            {
                currentSlice = slice;
                setInterestPointsActive(interestPointsEnabled);
                if (slice.isNoSlice == true)
                {
                    sliceScript.reset();
                }
                else
                {
                    sliceScript.sliceModelWithPlane(new Plane(slice.normal, slice.distance), false);
                }
            }
        }
    }


   
    public List<GameObject> getCurrentInterstPoints()
    {
        if (interestPointsEnabled == true && currentSlice != null)
            return currentSlice.interestPoints;
        else
            return null;
    }

    public void updatePOIsFromModelRotate()
    {
        if (currentSlice != null)
        {
            Plane visibilityPlane;
            if (currentSlice.isNoSlice == true)
                visibilityPlane = new Plane(new Vector3(0f, 0f, 1f), interactiveCamera.target.position);
            else
                visibilityPlane = sliceScript.slicePlane;
            currentSlice.updateVisibility(visibilityPlane);
        }
    }
   
    private SlicePlane slicePlaneWithName(string name)
    {
        foreach (SlicePlane slicePlane in slicePlanes)
            if (slicePlane.title == name)
                return slicePlane;
        return null;
    }
   
    private void setInterestPointsActive(bool active)
    {
        if (currentSlice != null)
        {
            foreach (GameObject interestPoint in currentSlice.interestPoints)
                interestPoint.SetActive(active);
        }
    }


   
    //----- Messages sent to iOS -----


    /// <summary>
    /// Sends all plane names and description text to iOS.
    /// </summary>
    private void sendSceneSpecificInfoToIOS()
    {
        string[] names = null;
        int namesLength = 0;
        if (slicePlanes != null)
        {
            namesLength = slicePlanes.Count;
            names = new string[namesLength];
            for (int i = 0; i < slicePlanes.Count; i++)
                names[i] = slicePlanes[i].title;
        }
       
        int videoURLsLength = 0;
        if (videoURLs != null)
        {
            videoURLsLength = videoURLs.Length;
        }
        #if UNITY_IPHONE
        SetSceneData(names, namesLength, videoURLs, videoURLsLength, summaryHTMLAsset.text);
        #elif UNITY_ANDROID
        AndroidJavaClass mUnityPlayer = new AndroidJavaClass("com.unity3d.player.UnityPlayer");
        AndroidJavaObject activity = mUnityPlayer.GetStatic<AndroidJavaObject>("currentActivity");
        activity.Call("SetSceneData", names, namesLength, videoURLs, videoURLsLength, summaryHTMLAsset.text);
        #endif
    }

   

    //----- Handlers for messages received from IOS -----


    /// <summary>
    /// Loads a scene with the provided name.  Caches the current state of the camera
    /// so that switching between Normal, Defect, and Repair is seamless, even though
    /// these are different scenes.
    /// </summary>
    public void loadScene(string sceneName)
    {
        if (SceneState.Instance().isFirstScene == true)
        {
            SceneState.Instance().isFirstScene = false;
            SceneState.Instance().isFirstHeartScene = true;
        }
        else if (SceneState.Instance().isFirstHeartScene == true)
        {
            SceneState.Instance().isFirstHeartScene = false;
        }


        if (interactiveCamera != null && interactiveObject != null)
        {
            interactiveCamera.saveStateForNewScene();
            interactiveObject.saveStateForNewScene();
            saveStateForNewScene();
        }
        Application.LoadLevel(sceneName);
    }
   

    /// <summary>
    /// Slices to the plane named sliceName if there is one.  Also, all interest points
    /// from the current slice are deactivated, and interest points for the next slice are
    /// turned on (if interestPointsEnabled == true).
    /// </summary>
    public void slice(string sliceName)
    {
        setInterestPointsActive(false);  //disable old points
        SlicePlane slice = slicePlaneWithName(sliceName);
        if (slice != null)
        {
            currentSlice = slice;
            setInterestPointsActive(interestPointsEnabled);
            if (slice.isNoSlice == true)
            {
                sliceScript.reset();
                interactiveObject.reset(true);
            }
            else
            {
                sliceScript.sliceModelWithPlane(new Plane(slice.normal, slice.distance), true);
            }
        }
    }


    /// <summary>
    /// Performs the initial defect slice.  This is called when an overall defect is
    /// initially selected (like Tetralogy of Fallot).  It enables the current interest
    /// points, sets the selected slice to slice 0, and resets the slice and camera.
    /// </summary>
    public void performInitialDefectSlice()
    {
        currentSlice = slicePlanes[0];
        interestPointsEnabled = true;
        setInterestPointsActive(true);
        sliceScript.reset();
        interactiveCamera.reset();
        interactiveObject.reset(false);

        //calculate the initial visibility so they all don't appear visibile at start
        updatePOIsFromModelRotate();
    }


    /// <summary>
    /// Stores if interest points are on/off and activates/deactivates them.
    /// </summary>
    public void toggleInterestPoints()
    {
        interestPointsEnabled = !interestPointsEnabled;
        setInterestPointsActive(interestPointsEnabled);
    }
}

BUMP

Still can’t figure out why updating Unity and Xcode would cause this error. The Android project came through the Unity upgrade just fine. Anyone else dealing with this??

The C# half is fine, but Xcode is looking for a C definition/implementation for _SetSceneData. It should be in the .h/.m files in Plugins/iOS/. That’s what I think you’re missing. But again, not sure why those files would have disappeared on you.