Getting address of a struct
Asked Answered
O

2

5

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);

Overage answered 12/10, 2017 at 10:24 Comment(1)
You can't just copy/paste the C declaration and hope it will work. The function is difficult to call from a C program, that never gets better when you have to pinvoke it. It looks like it is trying to return a reference to an array. But to use that you have to know the number of elements in the array, maybe the int return value tells you that. You can't tell whether you are supposed to release the memory for the array or not. The only hope you have is out IntPtr and Marshal.PtrToStructure on the elements. But you'd better ask for the programming manual first.Premillennialism
S
6

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.

Sportswoman answered 12/10, 2017 at 11:37 Comment(0)
C
2

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..

Chignon answered 12/10, 2017 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.