Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Array handling when using C++ with Unity

Discussion in 'Scripting' started by nishinami, Jan 5, 2023.

  1. nishinami

    nishinami

    Joined:
    Jan 5, 2023
    Posts:
    5
    Hello.
    I would like to convert a program I previously created in C++ into a DLL file and use it in conjunction with Unity.
    I would like to send an array of type double from the Unity side to the DLL file, and return the result of the calculation in C++ to Unity as a variable of type double.

    [1] I cannot send a variable x of type double to the DLL file and "return x" as it is. (x = 1.1 but the return value is x = 2939499.
    [2] I can't send the array to the DLL file and return it to Unity either. (array x = {1.1, 1.2, 1.3} passed to DLL. I set the return value to x[2], but x = 31049950.)
    [3] I was able to send variables of type int to the DLL file and return them to Unity. (I sent int a = 1, int b = 2 to the DLL file and set the return value as a+b. 3 was returned to the Unity side.)

    Is it not possible to send double type variables and arrays between Unity and a DLL file created in C++?
    (By the way, there is no error in the program or in Unity.)
     
  2. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,653
    Yes, it is possible. It would help if you'd shown the code how you do it.
     
    angrypenguin and Bunny83 like this.
  3. nishinami

    nishinami

    Joined:
    Jan 5, 2023
    Posts:
    5
    Thank you for your reply.
    The code is very long, so I will cut out the part to be sent.

    //Unity(C#)
    Code (CSharp):
    1. static class DLL
    2. {
    3.     [DllImport("add", CallingConvention = CallingConvention.StdCall)]
    4.     public static extern int add_function(double[] x0, double Kp, double Kd, double targetAngle);
    5. }
    6.  
    7. public class MPC : MonoBehaviour
    8. {
    9.           public double Kp = 11.0;
    10.           public double Kd = 1.0;
    11.           double targetAngle = 30.0;
    12.           double[] x0 = new double[4];
    13.  
    14.    private void FixedUpdate()
    15.        {
    16. //.....
    17.         x0[0] = legAngle;//  legAngle = 30.0
    18.         x0[1] = angleZ;
    19.         x0[2] = legAngle_dot;
    20.         x0[3] = angularVelocityZ;
    21.  
    22.         //torque
    23.         tau = DLL.add_function(x0, Kp, Kd, targetAngle);
    24.         Debug.Log(tau);
    25.        }
    26. }

    //C++(DLL)
    extern "C" __declspec(dllexport) int __stdcall add_function(
    double x0[4], double Kp, double Kd, targetAngle)
    {
    return x0[0];
    }

    My expected answer:30.0
    The actual answer:78095568
     
  4. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,653
    Well, your C++ function returns double, while C# one returns int. That's the first thing to fix I think.
     
    Bunny83 likes this.
  5. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    Well, both return an int, but that's of course equally wrong when you return a double as an int. So the signature of the method(s) should be adjusted to actually return what you want.

    So according to that it should return a double value, so the signature of the C++ method should look like this instead:
    Code (CSharp):
    1. //C++(DLL)
    2. extern "C" __declspec(dllexport) double__stdcall add_function(double x0[4], double Kp, double Kd, targetAngle)
    3. {
    4.     return x0[0];
    5. }
    6.  
    and the C# signature would look like this:

    Code (CSharp):
    1. static class DLL
    2. {
    3.     [DllImport("add", CallingConvention = CallingConvention.StdCall)]
    4.     public static extern double add_function(double[] x0, double Kp, double Kd, double targetAngle);
    5. }
    If for some reason your method actually should turn an integer, you should rethink about what your method actually does. Because if the return type is an integer, this would not make much sense:
    Code (CSharp):
    1.     return x0[0];
    So whatever you actually want to do, make sure it actually makes sense and is consistent. What you've shown up to this point is neither :)
     
    Kurt-Dekker and Yoreki like this.
  6. nishinami

    nishinami

    Joined:
    Jan 5, 2023
    Posts:
    5
    Thanks for the advice.

    I made this change.
    As a result, Kp (Kp=11.1) is returned, even though I want x0[3] returned.

    Code (CSharp):
    1. //C++(DLL)
    2. extern "C" __declspec(dllexport) double__stdcall add_function(double x0[4], double Kp)
    3. {
    4.     return x0[3];
    5. }
    Code (CSharp):
    1. static class DLL
    2. {
    3.     [DllImport("add", CallingConvention = CallingConvention.StdCall)]
    4.     public static extern double add_function(double[] x0, double Kp);
    5. }
    Code (CSharp):
    1. public class MPC : MonoBehaviour
    2. {
    3.           public double Kp = 11.1;
    4.           public double Kd = 1.1;
    5.           double[] x0 = new double[4];
    6.    private void FixedUpdate()
    7.        {
    8. //.....
    9.         x0[0] = 0.1;
    10.         x0[1] = 1.1;
    11.         x0[2] = 2.2;
    12.         x0[3] = 3.3;
    13.         //torque
    14.         tau = DLL.add_function(x0, Kp);
    15.         Debug.Log(tau);
    16.        }
    17. }
    However, when I send only x0 to the DLL and x0[3] as the return value, 3.3 is returned.

    Code (CSharp):
    1. //C++(DLL)
    2. extern "C" __declspec(dllexport) double__stdcall add_function(double x0[4])
    3. {
    4.     return x0[3];
    5. }
    6.  
    Code (CSharp):
    1. static class DLL
    2. {
    3.     [DllImport("add", CallingConvention = CallingConvention.StdCall)]
    4.     public static extern double add_function(double[] x0);
    5. }
    Why is this?
    Please tell me.
     
  7. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,653
  8. nishinami

    nishinami

    Joined:
    Jan 5, 2023
    Posts:
    5
    Thank you.
    I will look at the site and give it a try.
     
  9. nishinami

    nishinami

    Joined:
    Jan 5, 2023
    Posts:
    5
    Hello.

    I asked this question before.
    I have marshalled the array by looking at the website you advised and other sites as well.

    However, I am having trouble getting it to work, so I am asking the question again.

    Here is the program I am currently working on.
    Code (CSharp):
    1. public class MPC : MonoBehaviour
    2. {
    3.   [DllImport("add19.dll", CallingConvention = CallingConvention.Cdecl)]
    4.     static extern int add_function(System.IntPtr array, int length);
    5.     double[] array = new double[] { 0.1, 1.1, 2.2, 3.3, 4.4 };
    6.         double tau;
    7.  
    8.   private void FixedUpdate()
    9.     {
    10.             int length = array.Length;
    11.             int size = Marshal.SizeOf(typeof(double)) * length;
    12.              System.IntPtr ptr = Marshal.AllocCoTaskMem(size);
    13.             Marshal.Copy(array, 0, ptr, length);
    14.             tau = add_function(ptr, length);
    15.             Marshal.FreeCoTaskMem(ptr);
    16.             Debug.Log(tau);
    17.  
    18.         }
    19.  
    20. }
    Code (CSharp):
    1. //C++
    2. #include "stdafx.h"
    3. #include <iostream>
    4. DllExport double add_function(double *array, int length);
    5.  
    6. double add_function(double *array, int length)
    7. {
    8.    return array[0];
    9. }
    10.  
    11.  

    My ultimate goal is to send several arrays of type double to C++ and perform the calculation in C++. Then I want to return the result of the calculation (one variable of type double) to C#.
    Currently, I'm trying to do this with a single array first, but it's not working.

    If there is anything wrong in my program, please correct me.
    Also, if anyone knows a solution to this, I would appreciate it if you could make a sample program.
     
  10. Aurimas-Cernius

    Aurimas-Cernius

    Unity Technologies

    Joined:
    Jul 31, 2013
    Posts:
    3,653
  11. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,572
    So if that's the case, why have you again declared your extern method to have the return type int? You really need to get your types correct. Int and double do not even have the same size in memory (int is 32 bit, double is 64 bit). So on a little endian system you would just get the lower 32 bits of the double value interpreted as integer. Which is probably complete nonsense :)

    Note that just out of curiosity I tried the attempt you made on Jan, 6. I build a C++ dll (x64) in release mode, put it into Unity, added the extern declaration, changed the name of the DllImport attribute to match the name of my dll, I copied your code to my test project and run it. For me the method is returning 3.3 as expected.

    Warning: Native DLLs can not be unloaded. You see this as a warning on the DLL file inside the inspector when you select it in your project. That means when you update your dll file on disk, you have to restart Unity in order to get the updated behaviour. Maybe you played around too much without properly reloading the correct DLL?

    I have used some native code plugins in the past and never had any marshalling issues. Where I had the most issues were when it comes to WebGL native javascript plugins and the managed heap craziness that stems from C#, Mono and IL2CPP. Though such a trivial case as a double array is just as straight forward as you have shown. It does work as expected.

    Of course what's important is that both sides use the same calling convention. Stdcall and cdecl both use a RTL scheme to push the arguments onto the stack. So the last argument is actually pushed first onto the stack while the first argument is pushed last. that's because doing it this way the stack pointer points to the first argument, since it was pushed last. The difference between those two conventions is who is responsible for cleaning up the pushed arguments. Stdcall means that the "callee", so the method itself has to get rid of the allocated variables on the stack. Cdecl means that the caller, so the side which actually calls the method and originally pushed the value onto the stack has to clean up the stack. The exact details are irrelevant. However it's important that both sides use the same convention, otherwise you end up either at a stack overflow or in the worst case in a fatal crash.
     
    Last edited: Jan 18, 2023