LIU
LIU

Reputation: 315

Use C++ DLL in C#

My program need control a hardware. Vendor provide a DLL which is design for C/C++ language. Most of functions passed my test beside below function:

int32 ni845xSpiWriteRead(NiHandle DeviceHandle,
                         NiHandle ConfigurationHandle,
                         uInt32   WriteSize,
                         uInt8  * pWriteData,
                         uInt32 * pReadSize,
                         uInt8  * pReadData);

Document of this function is here:

My code in C# is here:

[DllImport("Ni845x", CallingConvention = CallingConvention.Cdecl)]
public static extern int ni845xSpiWriteRead(
                                                long DeviceHandle, //In 
                                                long ConfigurationHandle, //In
                                                int WriteSize, //In
                                                IntPtr pWriteData, //In
                                                IntPtr pReadSize, //Out
                                                IntPtr pReadData //Out
                                                );

I always got AccessViolationException exception. I am guessing that is caused by the pointer input/output parameter.

The code of invoke Swrapper.ni845xSpiWriteRead() i here:

public void WriteData(int length, int[] writeArray)
        {
            byte[] writeDate = new byte[8];

            int writeSize = writeDate.Length;

            try
            {
                //Define pointers
                IntPtr writeDataPointer = Marshal.AllocHGlobal(writeDate.Length);
                IntPtr readDataSizePointer = Marshal.AllocHGlobal(writeDate.Length);
                IntPtr readDataPointer = Marshal.AllocHGlobal(writeDate.Length);

                //Copy value to write data pointer
                Marshal.Copy(writeDate, 0, writeDataPointer, writeDate.Length);

                int state = Ni845xNativeMethods.ni845xSpiWriteRead(_niHandle, _niConfigrationHandle, writeSize, writeDataPointer,readDataSizePointer,readDataPointer);

                this.CheckStatus(state);

            }
            catch (Exception)
            {
                throw;
            }
        }

Upvotes: 0

Views: 879

Answers (4)

user20097559
user20097559

Reputation: 1

Change your calling convention from Cdecl to stdCall and it will work now. [DllImport("Ni845x", CallingConvention = CallingConvention.Cdecl)]

Upvotes: 0

David Heffernan
David Heffernan

Reputation: 612794

Most likely the p/invoke should be:

[DllImport("Ni845x", CallingConvention = CallingConvention.Cdecl)]
public static extern int ni845xSpiWriteRead(
    IntPtr DeviceHandle,
    IntPtr ConfigurationHandle,
    uint WriteSize,
    [In] byte[] WriteData,
    out uint ReadSize,
    [Out] byte[] ReadData
);

You'll need to allocate a byte[] of sufficient length to use as the ReadData parameter. Presumably you know how to do this.

I've taken at face value the statement that ReadSize is an output parameter. However, if it is both in and out, then declare it as ref.

Upvotes: 2

OldFart
OldFart

Reputation: 2479

I think both of the existing answers are somewhat incorrect, so I will add another (probably also incorrect) answer.

If DeviceHandle and ConfigurationHandle are truly HANDLE types (or other pointer types) then you should use IntPtr as the type for P/Invoke.

Given the C function declaration, the ReadData buffer must be allocated by the caller. Also, it is likely that ReadSize must be initialized to the size of the buffer and will be set to the actual number of bytes read during the function call.

I think this may come close to working:

class Program
{
    [DllImport("Ni845x", CallingConvention = CallingConvention.Cdecl)]
    public static extern int ni845xSpiWriteRead(
        IntPtr DeviceHandle,
        IntPtr ConfigurationHandle,
        UInt32 WriteSize,
        [MarshalAs(UnmanagedType.LPArray)][In] Byte[] pWriteData,
        ref UInt32 ReadSize,
        [MarshalAs(UnmanagedType.LPArray)][Out] Byte[] pReadData);

    static void Main(string[] args)
    {
        IntPtr DeviceHandle = (IntPtr)123;
        IntPtr ConfigurationHandle = (IntPtr)456;
        Byte[] WriteData = new Byte[2324];
        Byte[] ReadData = new Byte[8274];
        UInt32 ReadSize = (UInt32)ReadData.Length;

        int result = ni845xSpiWriteRead(DeviceHandle,
                                        ConfigurationHandle,
                                        (UInt32) WriteData.Length,
                                        WriteData,
                                        ref ReadSize,
                                        ReadData);
    }
}

Upvotes: 0

david.pfx
david.pfx

Reputation: 10868

The most likely thing is it's just the Out size parameter. So:

[DllImport("Ni845x", CallingConvention = CallingConvention.Cdecl)]
public static extern int ni845xSpiWriteRead(
                                            long DeviceHandle, //In 
                                            long ConfigurationHandle, //In
                                            int WriteSize, //In
                                            IntPtr pWriteData, //In
                                            out uint ReadSize, //Out
                                            IntPtr pReadData //Out
                                            );

This is similar to @Marius, but the handles should be fine as integers. The trick is to get the size returned as an integer, so either 'out uint' or 'ref uint' to do that.

Much better if we could see the calling code.


Having seen the code, you could also try something like this.

[DllImport("Ni845x", CallingConvention = CallingConvention.Cdecl)]
public static extern int ni845xSpiWriteRead(
  long DeviceHandle, //In 
  long ConfigurationHandle, //In
  int WriteSize, //In
  [MarshalAs(UnmanagedType.LPArray)] long [] writeData, //In
  out uint ReadSize, //Out
  [MarshalAs(UnmanagedType.LPArray), Out] readData //Out
);

This avoids all that Marshal.Copy stuff. Make sure the readData buffer is big enough.

Upvotes: 0

Related Questions