Reputation: 101
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
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
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