0

I need to invoke a native DLL from C# code. As I am not very familiar with C/C++, I can't figure out how a structure defined in C should be declared in C# so it can be invoked. The problem is that two parameters seems to be an array of structs, which I don't know how to declare this in C# (see last code block):

c++ header file:

typedef enum
{   
    OK = 0,
    //others
} RES

typedef struct
{
    unsigned char* pData;
    unsigned int length;
} Buffer;

RES SendReceive(uint32 deviceIndex
    Buffer* pReq,
    Buffer* pResp,
    unsigned int* pReceivedLen,
    unsigned int* pStatus);

c# declaration:

enum
{   
    OK = 0,
    //others
} RES

struct Buffer
{
    public uint Length;
    public ??? Data; // <-- I guess it's byte[]
}

[DllImport("somemodule.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern uint SendReceive(
    uint hsmIndex,
    uint originatorId,
    ushort fmNumber,
    ??? pReq,  // <-- should this be ref Buffer[] ?
    uint reserved,
    ??? pResp, // <-- should this be ref Buffer[] ?
    ref uint pReceivedLen,
    ref uint pFmStatus);

in an equivalent java client, i found that the parameter is not just one Buffer but an array of Buffers. In C# it would look like this:

 var pReq = new Buffer[] 
{
    new Buffer { Data = new byte[] { 1, 0 }, Length = (uint)2 }, 
    new Buffer {Data = requestStream.ToArray(), Length = (uint)requestStream.ToArray().Length },
    //according to the header file, the last item must be {NULL, 0}
    new Buffer { Data = null, Length = 0 }
};

var pResp = new Buffer[] 
{
    new Buffer { Data = new byte[0x1000], Length = 0x1000 }, 
    //according to the header file, the last item must be {NULL, 0}
    new Buffer { Data = null, Length = 0x0 }
};

This seems strange to me because the extern C method does have a pointer to a Buffer struct (Buffer*) and not a pointer to a Buffer array (Buffer[]*). How do I need to define the Struct in C# and the parameter types of the extern method?

Any help appreciated, Thanks.

Thomas Zweifel
  • 627
  • 1
  • 6
  • 19

3 Answers3

2

Firstly your struct has the parameters in the wrong order. And the byte array needs to be declared as IntPtr with manual marshalling:

struct Buffer
{
    public IntPtr Data;
    public uint Length;
}

The p/invoke should be:

[DllImport("MyNativeDll.dll", CallingConvention=CallingConvention.Cdecl)]
static extern RES SendReceive(
    uint deviceIndex, 
    [In] Buffer[] pReq, 
    [In, Out] Buffer[] pResp, 
    out uint pReceivedLen, 
    out uint pStatus
);

The byte array needs to be IntPtr so that the struct is blittable. And that's needed so that the array parameters can be declared as Buffer[].

It's going to be a bit of a pain doing the marshalling of the byte arrays. You'll want to use GCHandle to pin the managed byte arrays, and call AddrOfPinnedObject() to get the address of the pinned array for each struct in your arrays of structs. It will be worth your while writing some helper functions to make that task less painful.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thanks David. What confuses me is the comment in the Header file: **Array of buffers to send. The final element in the array * must be {NULL, 0}.**. As my last code block example shows, the pReq and pResp Parameters seems to be an array of Buffer. How should I declare this in the P/invoke? – Thomas Zweifel Apr 25 '13 at 08:03
  • OK, I caught up now. Declare them to be `Buffer[]`. – David Heffernan Apr 25 '13 at 08:05
  • 1
    @ThomasZweifel OK, using `Buffer[]` works just fine, I tested. But you do need the struct to be blittable. So that means `IntPtr` and pinning. – David Heffernan Apr 25 '13 at 08:22
  • Thanks David. I'm working on it now, will take me a while. Thanks for your great help! – Thomas Zweifel Apr 25 '13 at 08:56
0

Your method signature in c# should be something like:

[DllImport("MyNativeDll.dll")]
public static extern RES SendReceive (uint32 deviceIndex, ref Buffer pReq, ref Buffer pResp, ref uint pReceivedLen, ref uint pStatus);

See this project, it might hel you in the future so generate native calls from .net http://clrinterop.codeplex.com/releases/view/14120

Oscar
  • 13,594
  • 8
  • 47
  • 75
  • Hi. Thanks. The library I have is not COM or Managed C++. So I guess the PInvoke Interop Assistant won' work with my Header file. – Thomas Zweifel Apr 25 '13 at 07:43
  • This project is intended to work with native C or C++ code, not with managed. In fact, you don't need interop to work with libraries written in managed C++, as they are .Net code already, you can call them directly. – Oscar Apr 25 '13 at 07:56
0

Based on the C++ header but without testing, have a look at the following code:

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace WindowsFormsApplication1
{
    public class Class1
    {
        public struct Buffer
        {
            [MarshalAs(UnmanagedType.LPStr)]
            public StringBuilder pData;

            public uint length;
        }

        [DllImport("kernel32.dll", EntryPoint = "LoadLibrary")]
        static extern int LoadLibrary(string lpLibFileName);

        [DllImport("kernel32.dll", EntryPoint = "GetProcAddress")]
        static extern IntPtr GetProcAddress(int hModule, string lpProcName);

        [DllImport("kernel32.dll", EntryPoint = "FreeLibrary")]
        static extern bool FreeLibrary(int hModule);

        [UnmanagedFunctionPointer(CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        internal delegate IntPtr SendReceive(
            uint deviceIndex,
            ref Buffer pReq,
            ref Buffer pResp,
            uint pReceivedLen,
            uint pStatus);

        public void ExecuteExternalDllFunction()
        {
            int dll = 0;

            try
            {
                dll = LoadLibrary(@"somemodule.dll");
                IntPtr address = GetProcAddress(dll, "SendReceive");

                uint deviceIndex = 0;
                Buffer pReq = new Buffer() { length = 0, pData = new StringBuilder() };
                Buffer pResp = new Buffer() { length = 0, pData = new StringBuilder() };
                uint pReceivedLen = 0;
                uint pStatus = 0;

                if (address != IntPtr.Zero)
                {
                    SendReceive sendReceive = (SendReceive)Marshal.GetDelegateForFunctionPointer(address, typeof(SendReceive));

                    IntPtr ret = sendReceive(deviceIndex, ref pReq, ref pResp, pReceivedLen, pStatus);
                }
            }
            catch (Exception Ex)
            {
                //handle exception...
            }
            finally
            {
                if (dll > 0)
                {
                    FreeLibrary(dll);
                }
            }
        }
    }
}
mtsiakiris
  • 190
  • 9
  • 1
    pData is a byte array and not a null terminated string – David Heffernan Apr 25 '13 at 07:58
  • As far as I have used the above code in many projects, I didn't have any problems. Also, check at this too: http://stackoverflow.com/questions/6063428/dllimport-char-and-stringbuilder-c-c I'm sure, there are other ways to achive the same result depending on the nature and the functionality of the managed library side. – mtsiakiris Apr 25 '13 at 08:07
  • It's not going to work here. Since these are struct arrays you are going to need to make the struct blittable. – David Heffernan Apr 25 '13 at 08:14
  • Also, what's all that manual `LoadLibrary`, `GetProcAddress`, `FreeLibrary` meant to achieve? – David Heffernan Apr 25 '13 at 08:42
  • I think we are getting away from answering the question. But, I used code samples from a project built looong time ago and there was a need to know if the DLL was present to the folder the app was looking at. The case with the stuct is missing to me because there was no need of any struct. Nevertheless check the following and I think it should be better to keep low now. Thank you David. http://stackoverflow.com/questions/548382/dllimport-vs-loadlibrary-what-is-the-best-way – mtsiakiris Apr 25 '13 at 08:52
  • You are right that the `LoadLibrary` is a side issue. However, its presence in your code is distracting from the big picture. If you want to concentrate on the question I think it should be excised. That's my point. – David Heffernan Apr 25 '13 at 09:02