Uzair Raza
Uzair Raza

Reputation: 55

Passing C# struct to C++ unmanaged DLL returning incorrect result

I have a simple C++ win32 DLL developed in visual studio 2017 and compiled in 64 bit environment having the following code:

typedef struct sum {
    struct  {
        int num1;
        int num2;
    } nums;
} sum1;

extern "C" {

__declspec(dllexport) int initialize(sum1 *summing)
{
    int res;
    res = summing->nums.num1 + summing->nums.num2;
    return res;
}

}

The above code contains a method which returns the sum of two integers by taking a typedef struct as an argument.

I have a C# client application which consumes this Win32 C++ DLL using PInvoke. Following is the code of my C# client application:

[StructLayout(LayoutKind.Sequential)]
public struct nums
{
    public int a;
    public int b;
}

[StructLayout(LayoutKind.Sequential)]
public struct mydef
{
    public IntPtr sum;
}

public class LibWrap
{    
    [DllImport("C++.dll", EntryPoint = "initialize")]
    public static extern int Initialize(ref mydef mydef);
}

class Program
{
    static void Main(string[] args)
    {
        mydef mydef = new mydef();
        nums nums;
        nums.a = 6;
        nums.b = 6;

        IntPtr buffer1 = Marshal.AllocCoTaskMem(Marshal.SizeOf(nums));
        Marshal.StructureToPtr(nums, buffer1, false);
        mydef.sum = buffer1;

        int res = LibWrap.Initialize(ref mydef);

        Console.WriteLine(res);
    }
}

With the above code, I am expecting '12' as output, but instead I am getting '-1504178328' as output.

I am a C# developer with no experience in C++ at all. Please help me to solve this problem.

Upvotes: 1

Views: 1403

Answers (2)

Remy Lebeau
Remy Lebeau

Reputation: 595762

On the C# side, you are not handling the nested struct correctly. Try this instead:

[StructLayout(LayoutKind.Sequential)]
public struct mynums {
    public int num1;
    public int num2;
}

[StructLayout(LayoutKind.Sequential)]
public struct sum1 {
    public mynums nums;
}

public class LibWrap {
    [DllImport("C++.dll", EntryPoint = "initialize")]
    public static extern int Initialize(ref sum1 summing);
}

class Program {
    static void Main(string[] args) {
        sum1 mysum;
        mysum.nums.num1 = 6;
        mysum.nums.num2 = 6;
        int res = LibWrap.Initialize(ref mysum);
        Console.WriteLine(res);
    }
}

That being said, having a struct whose sole data member is another struct is redundant and unnecessary. You should remove the outer struct altogether, eg:

struct nums {
    int num1;
    int num2;
};

extern "C" {

__declspec(dllexport) int initialize(nums *summing) {
    return summing->num1 + summing->num2;
}

}
[StructLayout(LayoutKind.Sequential)]
public struct nums {
    public int num1;
    public int num2;
}

public class LibWrap {
    [DllImport("C++.dll", EntryPoint = "initialize")]
    public static extern int Initialize(ref nums summing);
}

class Program {
    static void Main(string[] args) {
        nums mynums;
        mynums.num1 = 6;
        mynums.num2 = 6;
        int res = LibWrap.Initialize(ref mynums);
        Console.WriteLine(res);
    }
}

Upvotes: 0

felix-b
felix-b

Reputation: 8498

Use a simpler P/Invoke wrapper:

public static class LibWrap
{
    [DllImport("C++.dll", EntryPoint = "initialize")]
    public static extern int Initialize(ref Nums nums);

    [StructLayout(LayoutKind.Sequential)]
    public struct Nums
    {
        public int a;
        public int b;
    }
}

and use it like this:

void CSharpExample()
{
    LibWrap.Nums nums;
    nums.a = 6;
    nums.b = 7;
    int res = LibWrap.Initialize(ref nums);
    Console.WriteLine(res);
}

In your example, you don't need any memory allocation and marshaling, because:

  • LibWrap.Nums is a struct, thus local variable nums in CSharpExample() is allocated completely on stack.
  • passing managed struct LibWrap.Nums by ref to LibWrap.Initialize will pass the pointer to local variable nums on stack.
  • LibWrap.Initialize is called synchronously, so that the pointer you pass to it isn't used anywhere after LibWrap.Initialize function exits. This is important because the pointer becomes invalid as soon as CSharpExample() exits.

Upvotes: 2

Related Questions