2

I'm stuck by passing struct with string data from C# code to C++ dll.

c++ code

typedef struct 
{
    LPCSTR lpLibFileName;
    LPCSTR lpProcName;
    LPVOID pPointer1;
    LPVOID pPointer2;
} ENTITY, *PENTITY, *LPENTITY;
extern "C" __declspec(dllexport) int Test(LPENTITY entryList, int size);

int Test(LPENTITY entryList, int size)
{
    for (int i = 0; i < size; i++)
    {
        ENTITY e = entryList[i];
        // the char* value doesn't get passed correctly.
        cout << e.lpLibFileName << e.lpProcName << endl;
    }
    return 0;
}

c# code

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private class Entity
{
    public string lpLibFileName;
    public string lpProcName;
    public IntPtr pPointer1;
    public IntPtr pPointer2;
}

[DllImport("cpp.dll")]
private static extern int Test(
    [In, Out, MarshalAs(UnmanagedType.LPArray)]Entity[] entities,
    int size);

static void Main(string[] args)
{
    var entries = new[]
        {
            new Entity
                {
                    lpLibFileName = "comdlg32",
                    lpProcName = "PrintDlgExW",
                    pPointer1 = Marshal.GetFunctionPointerForDelegate(new PrintDlgEx(PrintDlgExCallback)),
                    pPointer2 = IntPtr.Zero,
                },
            new Entity
                {
                    lpLibFileName = "shell32",
                    lpProcName = "ShellAboutW",
                    pPointer1 = Marshal.GetFunctionPointerForDelegate(new ShellAbout(ShellAboutCallback)),
                    pPointer2 = IntPtr.Zero,
                },
        };
    var ret = Test(entries, entries.Length);
}

The PINVOKE was triggered, but the char* data like lpLibFileName and lpProcName cannot be passed correctly. Did I miss something? How to correct it?

Thanks.

2
  • You are missing the required CallingConvention property in your [DllImport] attribute. The default is Stdcall but your function is Cdecl. Commented Jun 29, 2013 at 12:50
  • Yes, you are right, I should have used stdcall instead of cdecl in c++ code. But what actually blocked me is that I defined my structure as 'class' in c# code instead of 'struct'. After changing it back to struct, everything worked fine. Thanks. Commented Jun 29, 2013 at 13:59

2 Answers 2

1

Your code maps a C# class onto a native struct. Because a C# class is a reference type then it will be marshalled as a reference. So your code passes an array of references that gets marshalled to an array of pointers on the native side.

But the native code expects a pointer to an array of structs which are value types. So the simplest solution is to change the declaration of Entity to be a struct rather than a class.

The other issues that I can see:

  1. The native code appears to be using the cdecl calling convention. You'll need to change the C# code to match.
  2. You are decorating the array parameter with Out. You cannot marshal modifications to the string fields back to the managed code.
  3. You will need to make sure that you keep alive the delegates that you pass to GetFunctionPointerForDelegate to stop them being collected.
Sign up to request clarification or add additional context in comments.

Comments

0

When passing parameter like array of custom structures, use 'struct' instead of 'class' when defining your own data structure. After I changing it back to struct, everything worked fine.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
private struct Entity
{
    public string lpLibFileName;
    public string lpProcName;
    public IntPtr pPointer1;
    public IntPtr pPointer2;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.