Question

I have a problem with marshaling an array member of a class. The input class marshals fine (I can see the values in the C code), but the output class has 0s everywhere. My C header file is the following:

#pragma pack(push)
#pragma pack(4)

#define MAX_PERIODS 241
#define MAX_REGIONS 60

typedef enum { FNMA, FHLMC }agency_model_type;

typedef struct {
  int periods_count;
  double test;
  double timeline[MAX_REGIONS];
} agency_model_input;

typedef struct {
  double wal_years;
  double dd_90_rate[MAX_PERIODS];
  int test;
} agency_model_output;

#pragma pack(pop)

#ifdef __cplusplus
extern "C" {
#endif

  LIB_API void __stdcall run_agency_model(agency_model_input *model_input, agency_model_output *model_output);

#ifdef __cplusplus
}
#endif

The C# part is following:

public class Model
    {
        public const int MAX_PERIODS = 241;
        public const int MAX_REGIONS = 60;

        public enum AgencyModelType { FNMA, FHLMC };

        [StructLayout(LayoutKind.Sequential, Pack=4)]
        public class AgencyModelInput
        {
            public int PeriodsCount;
            public double test;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_REGIONS)]
            public double[] Timeline = new double[MAX_REGIONS];
        }

        [StructLayout(LayoutKind.Sequential, Pack=4)]
        public class AgencyModelOutput
        {
            public double WAL;
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = MAX_PERIODS)]
            public double[] DD90Rate = new double[MAX_PERIODS];
            public int test;
        }

        [DllImport("my-lib.dll", EntryPoint="run_agency_model")]
        public static extern void RunAgencyModel(AgencyModelInput input, AgencyModelOutput output);
    }

I wrote a unit test to check if that works. The C function simply increases the inputs by 1 and stores them in the output. Here's the test:

        Model.AgencyModelInput input = new Model.AgencyModelInput();
        input.PeriodsCount = 10;
        input.test = 99;
        input.Timeline[0] = 100;
        input.Timeline[1] = 200;

        Model.AgencyModelOutput output = new Model.AgencyModelOutput();

        Model.RunAgencyModel(input, output);

        Assert.AreEqual(11, output.WAL);
        Assert.AreEqual(100, output.test);
        Assert.AreEqual(101, output.DD90Rate[0]);
        Assert.AreEqual(201, output.DD90Rate[1]);

The test fails on the first assertion, output.WAL is 0. The other members are 0s as well. I verified and the input values are marshaled fine and visible in C.

I tried removing the pack directives, but the result was the same.

What's interesting, if I remove the DD90Rate array from the output class and the C struct, the output.WAL and the other primitive field are set correctly and the test passes after removing the assertions on the arrays.

If possible, I'd like to keep using classes instead of structs in my C#, because they are more flexible. I'm assuming the memory layout of classes and structs is the same and this shouldn't cause any issues.

Best regards, Michal

Was it helpful?

Solution

The solution was to add the [Out] attribute to the extern method:

[DllImport("my-lib.dll", EntryPoint="run_agency_model")]
public static extern void RunAgencyModel(AgencyModelInput input, [Out] AgencyModelOutput output);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top