简体   繁体   中英

Synchronising C++ (.dll) with a C# Unity script

I am trying to synchronise a native code (c++ project) as a.dll with a C# unity script. Native code:

Audio.h:

#pragma once
#include <iostream>
#define EXPORT __declspec(dllexport)

extern "C" {    
    EXPORT void initialize(std::string soundFilePaths[]);
    EXPORT void setSourcePosition(std::string soundFilePath, float x, float y, float z);
    EXPORT void play();
    EXPORT void stop();
    EXPORT void setListenerRotation(float x, float y, float z);
}

class Audio {

public:

    static Audio& instance() {  // Singleton
        static Audio INSTANCE;
        return INSTANCE;
    }

    void initialize(std::string soundFilePaths[]);
    void setSourcePosition(std::string soundFilePath, float x, float y, float z);
    void play();
    void stop();
    void setListenerRotation(float x, float y, float z);
    ~Audio();

private:    
    Audio();
};

Audio.cpp:

#include "Audio.h"

extern "C" {    

    void initialize(std::string soundFilePaths[]) {
        Audio::instance().initialize(soundFilePaths); 
    }

    void setSourcePosition(std::string soundFilePath, float x, float y, float z) {
        Audio::instance().setSourcePosition(soundFilePath, x, y, z);
    }

    void play() {
        Audio::instance().play();
    }

    void stop() {
        Audio::instance().stop();
    }

    void setListenerRotation(float x, float y, float z) {
        Audio::instance().setListenerRotation(x, y, z);
    }
}

Audio::Audio()
{
}

Audio::~Audio()
{
}

void Audio::initialize(std::string soundFilePaths[])
{   
}

void Audio::setSourcePosition(std::string soundFilePath, float x, float y, float z)
{

}

void Audio::play()
{
}

void Audio::stop()
{
}

void Audio::setListenerRotation(float x, float y, float z)
{
}

The Unity C# script is the following:

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

public class AudioPlugin : MonoBehaviour
{
    public AudioContainer[] audioContainers;
    //public AudioClip[] audioFiles;
    //public Transform[] audioSources;
    public Transform headGeometry;
    public float rotationSpeed = 50;
    public float maxYRotation = 90;
    public float minYRotation = -90;
    float _currentYRotation;

    void Start()
    {
        _currentYRotation = headGeometry.transform.rotation.eulerAngles.y;
        string[] filePaths = GetAllFilePathsFromClips();
        AudioPluginConnection.Initialize(filePaths);
        AudioPluginConnection.Play();
    }

    void Update()
    {
        TurnHeadWithInput();
        UpdateListenerRotation();
        UpdateSoundPositions();
    }

    void OnDestroy()
    {
        AudioPluginConnection.Stop();
    }

    void TurnHeadWithInput()
    {
        float horizontal = Input.GetAxis("Horizontal");
        horizontal *= Time.deltaTime * rotationSpeed;
        _currentYRotation = Mathf.Clamp(_currentYRotation + horizontal, minYRotation, maxYRotation);
        Vector3 eulerAngles = headGeometry.rotation.eulerAngles;
        eulerAngles.y = _currentYRotation;
        headGeometry.rotation = Quaternion.Euler(eulerAngles);
    }

    void UpdateListenerRotation()
    {
        Vector3 eulerAngles = headGeometry.rotation.eulerAngles;
        AudioPluginConnection.SetListenerRotation(eulerAngles.x, eulerAngles.y, eulerAngles.y);
    }

    void UpdateSoundPositions()
    {
        foreach (AudioContainer container in audioContainers)
        {
            Vector3 position = container.source.position;
            AudioPluginConnection.SetSourcePosition(container.filePath, position.x, position.y, position.z);
        }
    }

    string[] GetAllFilePathsFromClips()
    {
        List<string> audioFilePaths = new List<string>();
        foreach (AudioContainer container in audioContainers)
        {
            audioFilePaths.Add(container.filePath);
        }
        return audioFilePaths.ToArray();
    }
}

[System.Serializable]
public class AudioContainer
{
    public AudioClip clip;
    public Transform source;
    public string filePath { get { return Application.dataPath + "/Audio/" + clip.name + ".wav"; } }
}

public class AudioPluginConnection
{
    [DllImport("AudioPlugin", EntryPoint = "test")]
    public static extern int Test();
    [DllImport("AudioPlugin", EntryPoint = "initialize")]
    public static extern void Initialize(string[] soundFilePaths);
    [DllImport("AudioPlugin", EntryPoint = "setSourcePosition")]
    public static extern void SetSourcePosition(string soundFilePath, float x, float y, float z);
    [DllImport("AudioPlugin", EntryPoint = "play")]
    public static extern void Play();
    [DllImport("AudioPlugin", EntryPoint = "stop")]
    public static extern void Stop();
    [DllImport("AudioPlugin", EntryPoint = "setListenerRotation")]
    public static extern void SetListenerRotation(float x, float y, float z);
}

Every time I try to run the unity scene, the Unity program crashes. I think the problem is in the native code because I don't really know how to export classes and methods to a.dll. My assumption is that maybe string (I have used it as an argument of two methods) doesn't work because the program also crashed when I made a test method with string return value. But I am not sure.

I would like to know why my program is crashing and how to synchronise both codes (c++ and c#).

Thank you in advance

Take a look at my answer to this question on the same subject of C# <-> C++ interop. I described how to use the MarshalAsAttribute and some of the differences in System.String and std::string.

One issue I immediately see is that you wrote C++ functions like this:

EXPORT void initialize(std::string soundFilePaths[]);

Your C++ function should take a parameter of the type const char* instead of an std::string object. You'll also need to add [MarshalAs(UnmanagedType.LPStr)] to the string parameter of the extern C# method so the interop layer knows how to handle the marshaling to the native code. There are tons of different ways to do this, but that's one of the easier and more straightforward ways I can think of.

Also, it can often make things simpler to use wchar_t* and wstring* in your C++ code whenever you have the option. C++'s std::string is totally different and uses 8-bit ASCII and UTF-8 characters, whereas C# is using 16-bit Unicode characters. In C++, wchar_t is an implementation-defined 16-bit character type. On Windows, you're basically dealing with Unicode characters encoded as UTF-16LE (most of the time), so using wchar_t and wstring on the native side makes interop situations a lot simpler for you in many cases (which can get far more complex than this, believe me). And the extern method taking an array of type string[] is simply not going to work. You're going to have to totally rethink that one and what you're trying to do, my friend!

Arrays in C/C++ are just pointers to a buffer of data. myArray[2] is just like taking the base address int* ptr = &myArray[0] and then adding an offset of 0x02 to that address to perform pointer arithmetic: int n = *(ptr + 2); Here is some basic info about pointer arithmetic and arrays I found right quick.

So what you're essentially dealing with in the C/C++ code is going to be a pointer to a buffer of pointers, rather than a managed array of strings. So your C++ function is going to take a const char** str_array parameter. And you'll have to marshal your string[] array over to it properly. Here is some Microsoft documentation about Marshaling Managed Arrays to/from native code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM