How to pass a pointer from C# to native function i

2019-07-02 02:07发布

This is the signature of my function in DLL:

int __stdcall myFun( void * const context, const char * const pszFileName, const unsigned int buffSize, void * const pWaveFormatex );

All parameters are [in]. The user should pass a pointer to a WAVEFORMATEX struct through the last parameter. Upon return, it will be filled. All that works very well in C++.

Now, I'm trying for days to use the same DLL from C# and it simply doesn't work. The problem is in the last parameter. Since I do not know C# at all, I would like to ask somebody if this is doable at all. If it is, I would appreciate an example.

One of my last attempts was this:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public struct WAVEFORMATEX
{
    public ushort wFormatTag;
    public ushort nChannels;
    public uint nSamplesPerSec;
    public uint nAvgBytesPerSec;
    public ushort nBlockAlign;
    public ushort wBitsPerSample;
    public ushort cbSize;
}

Note: I also built my DLL written in C++ with the Struct Member Alignment = 1. Maybe I'm stupid, but I thought that Pack = 1 above is related with that in C++, but I have no idea if it is...

[DllImport("myLib.dll", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Ansi)]
        public static extern int myFun( IntPtr context,
                                        [MarshalAs( UnmanagedType.LPStr )]
                                        string pszFileName,
                                        int bufferSize,
                                        ref IntPtr pWfx );

IntPtr unmanaged_pWfx = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(WAVEFORMATEX)));

result = DLLWrapper.myFun(context,
                          "C:\\video.wmv",
                          176400,
                          ref unmanaged_pWfx );

WAVEFORMATEX wfxFormat = (WAVEFORMATEX)Marshal.PtrToStructure( unmanaged_pWfx, typeof(WAVEFORMATEX));

The behavior is undefined. Sometimes it hangs, sometimes it terminates... Something is very wrong. However, the problem is not in the DLL, the problem is on the C# side. Is C# capable of working with pointers at all?

Thanks for any feedback you provide.

EDIT (working C++ code):

void * context;
WAVEFORMATEX wfx;
int success = getContext( &context );
success = myFun( context, "C:\\video.wmv", 176400, &wfx );

The equivalent in C# is:

IntPtr context;
WAVEFORMATEX wfx;

int success = getContext( out context );
success = myFun( context, "C:\\video.wmv", 176400, out wfx );


extern "C" __stdcall int getContext( void ** pContext );

[DllImport("myLib.dll", CallingConvention = CallingConvention.StdCall)]
public static extern int getContext(out IntPtr context);

标签: c# c++ dll pinvoke
2条回答
祖国的老花朵
2楼-- · 2019-07-02 02:13

Well, based on the information you have provided I would say that you need to declare the C# function like this:

[DllImport("myLib.dll")]
public static extern int myFun(
    IntPtr context,
    string fileName,
    uint bufferSize,
    out WAVEFORMATEX wfx
);

And call the function like this:

WAVEFORMATEX wfx;
int result = DLLWrapper.myFun(context, @"C:\video.wmv", 176400, out wfx);

There's really no need for manual marshalling of this struct. It's a very simple blittable struct and it is much cleaner to let the framework handle the marshalling.

I am assuming that you are accurate when you state that the final struct parameter does not need to be initialised and its members are filled out by the function.

Packing looks reasonable. I don't think you need to build your DLL in any special way. I hope that you are picking up WAVEFORMATEX from the Windows header files and they already specify packing for that struct.

If you are still stuck then you should show the successful C++ calling code.


Judging from the comments, you still have a bug somewhere in your code. In such a situation, especially when you doubt the interop, it pays to make a simple reproduction to determine whether the interop is the problem, or not. Here is mine:

C++ DLL

#include <Windows.h>
#include <mmsystem.h>
#include <iostream>

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

__declspec(dllexport) int __stdcall myFun(void * const context, 
    const char * const pszFileName, const unsigned int buffSize, 
    void * const pWaveFormatex)
{
    std::cout << context << std::endl
        << pszFileName << std::endl 
        << buffSize << std::endl;

    WAVEFORMATEX wfx;
    wfx.cbSize = 1;
    wfx.nAvgBytesPerSec = 2;
    wfx.nBlockAlign = 3;
    wfx.nChannels = 4;
    wfx.nSamplesPerSec = 5;
    wfx.wBitsPerSample = 6;
    wfx.wFormatTag = 7;
    CopyMemory(pWaveFormatex, &wfx, sizeof(wfx));

    return 666;
}

C# console app

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication13
{
    class Program
    {
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct WAVEFORMATEX
        {
            public ushort wFormatTag;
            public ushort nChannels;
            public uint nSamplesPerSec;
            public uint nAvgBytesPerSec;
            public ushort nBlockAlign;
            public ushort wBitsPerSample;
            public ushort cbSize;
        }

        [DllImport(@"Win32Project1.dll", EntryPoint = "?myFun@@YGHQAXQBDI0@Z")]
        public static extern int myFun(
            IntPtr context,
            string fileName,
            uint bufferSize,
            out WAVEFORMATEX wfx
        );

        static void Main(string[] args)
        {
            WAVEFORMATEX wfx;
            int result = myFun((IntPtr)42, @"C:\video.wmv", 176400, out wfx);
            Console.WriteLine(result);
            Console.WriteLine(wfx.cbSize);
            Console.WriteLine(wfx.nAvgBytesPerSec);
            Console.WriteLine(wfx.nBlockAlign);
            Console.WriteLine(wfx.nChannels);
            Console.WriteLine(wfx.nSamplesPerSec);
            Console.WriteLine(wfx.wBitsPerSample);
            Console.WriteLine(wfx.wFormatTag);

            Console.ReadLine();
        }
    }
}

Output

0000002A
C:\video.wmv
176400
666
1
2
3
4
5
6
7
查看更多
我命由我不由天
3楼-- · 2019-07-02 02:32

The problem is passing the IntPtr. You're passing stack allocated variable(the one that holds actual pointer) to your code, but you want to pass the pointer. Just remove the "ref" keyword from your code.

查看更多
登录 后发表回答