ifefer
ifefer

Reputation: 101

PInvoke DLL in C#

I want to pass a structure to C function and I write the following code.

When I run it, the first function - Foo1 is working and then function Foo gets an exception. Can you help me to understand what the problem is?...

The C code:

typedef struct 
{
    int Size; 
    //char *Array;
}TTest;

__declspec(dllexport) void Foo(void  *Test);
__declspec(dllexport) int  Foo1();

void Foo(void  *Test)
{
    TTest *X = (TTest *)Test;
    int i = X->Size;
    /*for(int i=0;i<Test->Size;Test++)
    {
        Test->Array[i] = 127;
    }*/
}

int Foo1()
{
    return 10;
}

The C# code:

using System;
using System.Runtime.InteropServices;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication1
{
    [StructLayout(LayoutKind.Sequential)]
    public class TTest 
    {
        public int Size; 
    }

    class Program
    {
        [DllImport(@"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto)]
        public static extern void Foo(
            [MarshalAs(UnmanagedType.LPStruct)]
            TTest lplf   // characteristics
        );

        [DllImport(@"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto)]
        public static extern int Foo1();

        static void Main(string[] args)
        {
            TTest Test = new TTest();
            Test.Size = 25;

            int XX = Program.Foo1();
            Program.Foo(Test);
        }
    }
}

Upvotes: 5

Views: 18016

Answers (2)

Abdurrahim
Abdurrahim

Reputation: 2184

You are using C call so you need to specify CallingConvention.Cdecl

[DllImport(@"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]

By default its stdcall in C# pinvoke as i remember; You can also do change C code instead and leave your C# code as is like in below

__declspec(dllexport) void __stdcall  Foo(void  *Test);

But for me best is to both declare __cdecl (or stdcall) in your C export and CallingConvention.Cdecl (or stdcall) in your C# code to keep convenience. You can check https://learn.microsoft.com/en-gb/cpp/cpp/argument-passing-and-naming-conventions?view=vs-2017 and https://learn.microsoft.com/en-gb/dotnet/api/system.runtime.interopservices.callingconvention?view=netframework-4.7.2 for further info

Upvotes: 1

Robert Rouhani
Robert Rouhani

Reputation: 14688

To the downvoters: This answer solves two issues: the immediate issue of the calling convention/the MarhsalAs attribute, and the issue he will soon find where his TTest parameter won't work if he takes my suggestion of turning TTest into a struct.

Your native code is asking for a void*, which in C# is an IntPtr. First you should define TTest as a struct and not a class. Second, you should change the declaration of Foo to:

[DllImport(@"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void Foo(IntPtr lplf);

And third, you should pin the TTest using the fixed keyword and pass it's pointer to Foo. If you're using a class, you can use Marhsal.StructureToPtr to get an IntPtr from your TTest.

This provides the same functionality on both sides, where a pointer to any type can be passed in. You can also write overloads with all the class types that you want to use since they all equate to void* on the native side. With a struct, your parameters would be prepended with a ref.

What I'm curious about is why your native code wants a void* instead of a TTest* when the first thing you do in the unmanaged code is cast to a TTest*. If you switched the parameter to a TTest*, then providing identical functionality becomes simpler. You declaration would become:

[DllImport(@"C:\.net course\unmanaged1\unmanaged3\Debug\unmanaged3.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl)]
public static extern void Foo(ref TTest lplf);

And you would call the function as Program.Foo(ref Test);

If you're using the class, the ref isn't necessary as classes are reference types.

Upvotes: 7

Related Questions