PInvoke DLL in C#
Asked Answered
P

2

5

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);
        }
    }
}
Playreader answered 20/2, 2012 at 22:19 Comment(2)
Get rid of [MarshalAs(UnmanagedType.LPStruct)] and add CallingConvention = CallingConvention.Cdecl to both of your DllImports and it should be fine.Robespierre
please add exception details to your question, and any other info that could help people answer itCondon
U
7

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.

Unalienable answered 20/2, 2012 at 22:30 Comment(13)
-1 All of these changes are completely unnecessary and still fail to address the actual error the OP is getting (stack imbalance is my guess).Robespierre
@Robespierre I added the CallingConvention and removed the MarshalAs attribute as part of my answer. That will solve the immediate issue. After the error gets fixed, he would hit another error where his TTest parameter wouldn't work as a void*.Unalienable
"After the error gets fixed, he would hit another error where his TTest parameter wouldn't work as a void*." No he wouldn't -- to C#, TTest* and void* are 100% identical (as TTest is currently defined).Robespierre
Getting rid of the MarhsalAs attribute turns it into a regular TTest and not a TTest*Unalienable
TTest is a class, so it is implicitly passed by reference. What you said would be true only if TTest were a struct (which it shouldn't be).Robespierre
And that's where the discrepancy is, I suggested turning TTest into a struct.Unalienable
Which, again, is completely unnecessary. ;-]Robespierre
Well, we can't see the rest of his code, but it would simplify things if he's passing a TTest by value a lot (omit the ref instead of adding a [Out, In]). If he's always passing by reference, then yes, making it a struct is unnecessary and useless.Unalienable
And even with struct, I believe, you can sill use ref Test instead of IntPtr. Because, .NET will make TTest* out of it - and it is C++ (not C#) to whom it is 100% identical whether it receives Test* or void* as an arg.Stopoff
Edited my answer to be more specific about the difference between classes and structs.Unalienable
So i change the code to use with TTest instead of void * and it is working but i gets massage box that tell me: Additional Information: A call to PInvoke function 'ConsoleApplication1!ConsoleApplication1.Program::Foo' has unbalanced the stack. This is likely because the managed PInvoke signature does not match the unmanaged target signature. Check that the calling convention and parameters of the PInvoke signature match the target unmanaged signature. is it ok?? and how i am soled this massgae\exeption.Playreader
Did you change the CallingConvention on the C# side? Did you change the C# declaration to pass in a ref TTest (unless you didn't change it to a struct)? On the C side, are you passing in a TTest* or just a TTest?Unalienable
you are rigth i forget to change the CallingConventionPlayreader
S
1

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

Scorn answered 16/1, 2019 at 13:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.