Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting address of a struct

Here is my code:

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

public class CarMove : MonoBehaviour
{
public unsafe struct TrafficRoadSystem { };
public unsafe struct CarsSimulation { };
public unsafe struct CarPostion
{
    double position_x;
    double position_y;
    double position_z;
};

// Use this for initialization
[DllImport("Assets/traffic.dll", CharSet = CharSet.Ansi)]
public unsafe static extern int traffic_import_osm([MarshalAs(UnmanagedType.LPStr)] string osm_pbf_path, TrafficRoadSystem** out_road_system);
[DllImport("Assets/traffic.dll")]
public unsafe static extern int create_simulation(TrafficRoadSystem* road_system, CarsSimulation** out_simulation, double speed, int cars_count);
[DllImport("Assets/traffic.dll")]
public static extern void update_simulation(ref CarsSimulation simulation, double time);
[DllImport("Assets/traffic.dll")]
public static extern CarPostion get_car_postion(ref CarsSimulation simulation, int car_number);
unsafe CarsSimulation* carSimulation;
unsafe TrafficRoadSystem* trafficRoadSystem;
void Start()
{
    unsafe
    {
        string osmPath = "Assets/Resources/map.osm.pbf";
        int results;
        results = traffic_import_osm(osmPath, &trafficRoadSystem);
    }
}

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

}
}

This is from dll C library. Function int traffic_import_osm() works on TrafficRoadSystem* trafficRoadSystem; object as reference and I want to have in access to object in void Update(). This works well in one function but I cannot get address of a class variable and I get error

Error CS0212 You can only take the address of an unfixed expression inside of a fixed statement initializer

in line results = traffic_import_osm(osmPath, &trafficRoadSystem);

I try to use this solution https://msdn.microsoft.com/en-us/library/29ak9b70(v=vs.90).aspx

I wrote this:

TrafficRoadSystem trafficRoadSystem;
void Start()
{
    unsafe
    {
        string osmPath = "Assets/Resources/map.osm.pbf";
        CarMove carMove = new CarMove();
        int results;
        fixed( TrafficRoadSystem* ptr = &carMove.trafficRoadSystem)
        {
            results = traffic_import_osm(osmPath, &ptr);
        }
    }
}

and I get error CS0459 Cannot take the address of a read-only local variable in line results = traffic_import_osm(osmPath, &ptr);

like image 462
Macko.lysy Avatar asked Oct 12 '17 10:10

Macko.lysy


2 Answers

Making a C or C++ plugin in Unity requires extensive knowledge of these languages. This means you should spend time learning pointers before attempting to use raw pointer in Unity. Because even if you get it to compile, you may also run into hard to fix crashes.

You have:

unsafe TrafficRoadSystem* trafficRoadSystem;

and want to pass it to the function below:

public unsafe static extern int traffic_import_osm(..., TrafficRoadSystem** out_road_system);

1.The trafficRoadSystem variable is a pointer and you will need to make another pointer that points to trafficRoadSystem. This is called "pointers to pointer"

TrafficRoadSystem** addressOfTrafficRoadSystem = &trafficRoadSystem;

Notice the double "**".

2.You must use fixed keyword to do what I mentione in #1.

fixed (TrafficRoadSystem** addressOfTrafficRoadSystem = &trafficRoadSystem)
{

}

3.You can pass that pointer to pointer address to the traffic_import_osm function.

The whole new Start function:

void Start()
{
    unsafe
    {
        fixed (TrafficRoadSystem** addressOfTrafficRoadSystem = &trafficRoadSystem)
        {
            string osmPath = "Assets/Resources/map.osm.pbf";
            int results;
            results = traffic_import_osm(osmPath, addressOfTrafficRoadSystem);
        }
    }
}

Unrelated possible future issues:

1.I noticed that you did CarMove carMove = new CarMove(); in your updated question. Don't use the new keyword here because CarMove inherits from MonoBehaviour. See this answer for what to do.

2.I also noticed you used "Assets/Resources/map.osm.pbf";. This path will be invalid when you build your project. Consider using the StreamingAssets folder instead of the Resources folder which only works with the Resources API. You use Application.streamingAssetsPath with the StreamingAssets folder.

like image 111
Programmer Avatar answered Nov 12 '22 15:11

Programmer


An alternative to using raw Pointers is to use Marshalling. Marshalling allows you to take C# data structures and transform them to C-understandable data without the need of any unsafe-keywords.

Mono has a document describing how to properly interact with native libraries using PInvoke (which is the search term you're looking for).

Here, here and here you can also find an introduction into PInvoke and Marshalling.

Basically you replace the pointer signatures with the IntPtr-type, which is a managed version of a pointer. To transform your managed struct variable you'll first need to allocate a memory region and use it for your struct

IntPtr ptr;
TrafficRoadSystem _struct;
try {
    ptr = Marshal.AllocHGlobal(Marshal.SizeOf(_struct));
    Marshal.StructureToPtr(_struct, ptr, false);
} finally {
    Marshal.FreeHGlobal(ptr);
}

I don't know how to properly deal with double pointers though..

like image 24
CShark Avatar answered Nov 12 '22 16:11

CShark