2

I am trying to conver a C SDK to C# and am running into a "Illegal Parameter" error on the conversion of a C function.

The details of the C SDK function are listed below

#ifndef LLONG
#ifdef WIN32
#define LLONG LONG
#else //WIN64 
#define LLONG INT64
#endif
#endif

#ifndef CLIENT_API
#define CLIENT_API  __declspec(dllexport) 
#endif

#else

#ifndef CLIENT_API
#define CLIENT_API  __declspec(dllimport)
#endif

#endif

#define CALLBACK __stdcall
#define CALL_METHOD  __stdcall  //__cdecl


// Configuration type,corresponding to CLIENT_GetDevConfig and CLIENT_SetDevConfig
#define DH_DEV_DEVICECFG            0x0001      // Device property setup 
#define DH_DEV_NETCFG               0x0002      // Network setup 
#define DH_DEV_CHANNELCFG           0x0003      // Video channel setup
#define DH_DEV_PREVIEWCFG           0x0004      // Preview parameter setup
#define DH_DEV_RECORDCFG            0x0005      // Record setup
#define DH_DEV_COMMCFG              0x0006      // COM property setup 
#define DH_DEV_ALARMCFG             0x0007      // Alarm property setup
#define DH_DEV_TIMECFG              0x0008      // DVR time setup 
#define DH_DEV_TALKCFG              0x0009      // Audio talk parameter setup 
#define DH_DEV_AUTOMTCFG            0x000A      // Auto matrix setup
#define DH_DEV_VEDIO_MARTIX         0x000B      // Local matrix control strategy setup
#define DH_DEV_MULTI_DDNS           0x000C      //  Multiple ddns setup 
#define DH_DEV_SNAP_CFG             0x000D      // Snapshot corresponding setup 
#define DH_DEV_WEB_URL_CFG          0x000E      // HTTP path setup 
#define DH_DEV_FTP_PROTO_CFG        0x000F      // FTP upload setup 
#define DH_DEV_INTERVIDEO_CFG       0x0010      // Plaform embedded setup. Now the channel parameter represents the platform type. 



// Search configuration information 
CLIENT_API BOOL  CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);

The C# info is as follows:

//  [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.StdCall)]
       [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
   //  [DllImport("dhnetsdk.dll")]

      public static extern bool CLIENT_GetDevConfig(long lLoginID, 
           uint dwCommand, 
           long lChannel, 
            IntPtr lpBuffer,
           uint dwOutBufferSize, 
           uint lpBytesReturned, 
           int waittime = 500);

And I am calling the method as follows:

int t = 500;
            uint BytesReturned = 0;
            uint c = 8;

            var lpOutBuffer = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(NET_TIME)));
            if (CLIENT_GetDevConfig(lLogin, c, 0, lpOutBuffer, (uint)Marshal.SizeOf(typeof(NET_TIME)),  BytesReturned, t) == false)
            {
            Console.WriteLine("GetDevConfig FAILED");
            }


[StructLayout(LayoutKind.Sequential)]
    public  struct NET_TIME
    {
     //   [FieldOffset(0)]  
        uint  dwYear;
     //   [FieldOffset(4)]
        uint  dwMonth;
     //   [FieldOffset(4)]
        uint  dwDay;
      //  [FieldOffset(4)]
        uint  dwHour;
      //  [FieldOffset(4)]
        uint  dwMinute;
      //  [FieldOffset(4)]
        uint  dwSecond;
    }

I am positive the lLogin is correct since I successfully logged into the device using it. But when I check GetLastError immediately after the call to GetDevConfig fails, it indicates a illegal parameter. So, can anybody point out the illegal parameter in the above code?

The following is my C# code with the illegal parameter issues...

using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    static public int lLogin;

    public delegate void fDisConnect(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser);
    public delegate void fHaveReConnect(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser);

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLIENT_Init(fDisConnect cbDisConnect, uint dwUser);

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void CLIENT_SetAutoReconnect(fHaveReConnect cbHaveReconnt, uint dwUser);

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern int CLIENT_Login(string pchDVRIP, ushort wDVRPort, string pchUserName, string pchPassword, NET_DEVICEINFO lpDeviceInfo, IntPtr error);

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    [return: MarshalAs(UnmanagedType.Bool)]
    public static extern bool CLIENT_GetDevConfig(
    int loginId,
    uint command,
    int channel,
    out NET_TIME buffer,
    out uint bufferSize,
    IntPtr lpBytesReturned,
    int waittime = 500);

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern bool CLIENT_Logout(long lID);

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern void CLIENT_Cleanup();

    [DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
    public static extern uint CLIENT_GetLastError();


    public static void fDisConnectMethod(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser)
    {
        System.Console.WriteLine("Disconnect");
        return;
    }

    public static void fHaveReConnectMethod(long lLoginID, IntPtr pchDVRIP, long nDVRPort, uint dwUser)
    {
        System.Console.WriteLine("Reconnect success");
        return;
    }

    [StructLayout(LayoutKind.Sequential)]
    public class NET_DEVICEINFO
    {
        [MarshalAs(UnmanagedType.ByValArray, SizeConst = 48)]
        public byte[] sSerialNumber;
        public byte byAlarmInPortNum;
        public byte byAlarmOutPortNum;
        public byte byDiskNum;
        public byte byDVRType;
        public byte byChanNum;
    }

    [StructLayout(LayoutKind.Sequential)]
    public struct NET_TIME
    {
        uint dwYear;
        uint dwMonth;
        uint dwDay;
        uint dwHour;
        uint dwMinute;
        uint dwSecond;
    }

    public static void Main()
    {
        fDisConnect fDisConnecthandler = fDisConnectMethod;
        fHaveReConnect fHaveReConnecthandler = fHaveReConnectMethod;
        NET_DEVICEINFO deviceinfo = new NET_DEVICEINFO();
        IntPtr iRet = new IntPtr(0);
        CLIENT_Init(fDisConnectMethod, 0);
        CLIENT_SetAutoReconnect(fHaveReConnecthandler, 0);
        lLogin = CLIENT_Login("192.168.1.198", 31111, "user", "password", deviceinfo, iRet);

        if (lLogin <= 0)
            Console.WriteLine("Login device failed");
        else
        {
            Console.WriteLine("Login device successful");
            byte[] byteout = new byte[20];
            const int t = 500;
            IntPtr BytesReturned;
            BytesReturned = IntPtr.Zero;
            const uint c = 8;
            NET_TIME nt;
            uint sizeofnt = (uint)Marshal.SizeOf(typeof(NET_TIME));
            if (CLIENT_GetDevConfig(lLogin, c, 0, out  nt, out sizeofnt, BytesReturned, t) == false)
            {
                uint gle = CLIENT_GetLastError();
                Console.WriteLine("getDevConfig failed");
            }
            CLIENT_Logout(lLogin);
            CLIENT_Cleanup();
        }
    }
}

And here is my C code that I'm trying to port to C#. It works without any issues..

#pragma comment(lib,"dhnetsdk.lib")

#include <Windows.h>
#include <stdio.h>
#include <conio.h>
#include "dhnetsdk.h"

void CALLBACK DisConnectFunc(LONG lLoginID, char *pchDVRIP, LONG nDVRPort, DWORD dwUser)
{
    printf("Disconnect.");
    return;
}

void CALLBACK AutoConnectFunc(LONG lLoginID,char *pchDVRIP,LONG nDVRPort,DWORD dwUser)
{
    printf("Reconnect success.");
    return;
}

int main(void)
{
    NET_TIME nt = {0};
    NET_DEVICEINFO deviceInfo = {0};
    unsigned long lLogin = 0;
    LPVOID OutBuffer;
    int iRet = 0;
    DWORD dwRet = 0;

    //Initialize the SDK, set the disconnection callback functions
    CLIENT_Init(DisConnectFunc,0);     
    //Setting disconnection reconnection success of callback functions. If don't call this interface, the SDK will not break reconnection.                                  
    CLIENT_SetAutoReconnect(AutoConnectFunc,0);                      
    lLogin = CLIENT_Login("192.168.1.108",31111,"user","password",&deviceInfo, &iRet);
    if(lLogin <= 0)
    {
        printf("Login device failed");
    }
    else
    {
        OutBuffer = (LPVOID)malloc(sizeof(NET_TIME));
        memset(OutBuffer, 0, sizeof(NET_TIME));
        if(CLIENT_GetDevConfig( lLogin, 8 /* DH_DEV_TIMECFG */, 0, OutBuffer, sizeof(NET_TIME), &dwRet, 500) == FALSE)
        {
            printf("Failed\n");
        }
        else
        {
            memcpy(&nt, OutBuffer, sizeof(nt));




    printf("Time %d %d %d %d %d %d\n", nt.dwYear,nt.dwMonth,nt.dwDay, nt.dwHour,nt.dwMinute, nt.dwSecond);
        }
        _getch();
    }
    CLIENT_Logout(lLogin);
    CLIENT_Cleanup();
    return 0;
}
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
Bob S
  • 23
  • 3

2 Answers2

1

Your extern function is wrongly defined. Let's take your C call example.

// Search configuration information 
CLIENT_API BOOL  CALL_METHOD CLIENT_GetDevConfig(LLONG lLoginID, DWORD dwCommand, LONG lChannel, LPVOID lpOutBuffer, DWORD dwOutBufferSize, LPDWORD lpBytesReturned,int waittime=500);

As stated in comment of your post, the length of LONG is 32-bit in Win32, so you have to use an int. You can also use the keyword out to get your structure without manually use the Mashaller. I would define your function as that.

[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(int loginId, uint command, int channel, out NET_TIME buffer, out uint bufferSize, IntPtr lpBytesReturned, int waittime = 500);

Note the presence of the additional attribute MarshalAs. It indicates how the managed code should consider the return value of the pinvoke'd function.

Jämes
  • 6,945
  • 4
  • 40
  • 56
  • I've tried your suggestions but unfortunately they don't seem to work. So, I've added my complete .Net code to my original post along with my working C code that I'm trying to port to C# for your review. – Bob S Sep 21 '13 at 02:03
  • @bob Doesn't work is not good enough. Try harder to explain the failure mode. Also this is not a code review service. – David Heffernan Sep 21 '13 at 08:07
  • I thought I initially did explain the failure. Thus, I didn't think I had to re-explain the issue. But anyway, I am trying to convert a C SDK to C# and am running into a "Illegal Parameter" error on the conversion of a C function. I posted the C code for comparison to the C# code because there is an issue somewhere with the function/method signatures. I was hoping someone would see the difference (problem) because I can't see it. I really don't need a code critique but would rather have someone point out my incompatible signature conversion(s). – Bob S Sep 21 '13 at 13:43
  • I found it very hard to work out what the issue was. Your initial post mentioned `GetLastError`. But it now transpires that the function was `CLIENT_GetLastError`. I think that info would have helped. Anyway, my answer points out a clear difference between your C and C# code. – David Heffernan Sep 21 '13 at 13:52
0

Here are the differences that I can see:

The C++ code:

CLIENT_API BOOL CALL_METHOD CLIENT_GetDevConfig(
    LLONG lLoginID, 
    DWORD dwCommand, 
    LONG lChannel, 
    LPVOID lpOutBuffer, 
    DWORD dwOutBufferSize, 
    LPDWORD lpBytesReturned,
    int waittime
);

Now, LLONG is pointer sized, 32 bit under x86, 64 bit under x64. That translates to IntPtr for C#. I'd declare the p/invoke like this:

[DllImport("dhnetsdk.dll", CallingConvention = CallingConvention.Cdecl)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool CLIENT_GetDevConfig(
    IntPtr lLoginID,
    uint dwCommand,
    int lChannel,
    out NET_TIME lpOutBuffer,
    uint dwOutBufferSize,
    out uint lpBytesReturned,
    int waittime
);

The main problems that I can see are that:

  • You are translated dwOutBufferSize incorrectly. It's an IN parameter but you are passing it by reference. This is the most likely explanation for the failure.
  • In the C++ code you pass a pointer to a DWORD variable for lpBytesReturned. In your C# code you pass IntPtr.Zero which is the null pointer.

So, I'd have the call to CLIENT_GetDevConfig looking like this:

NET_TIME nt;
uint sizeofnt = (uint)Marshal.SizeOf(typeof(NET_TIME));
uint BytesReturned;
if (!CLIENT_GetDevConfig(lLogin, 8, 0, out nt, sizeofnt, out BytesReturned, 500))
    ....

It may be that you can indeed pass IntPtr.Zero to the BytesReturned parameter. Perhaps it's an optional parameter. But I cannot tell that from here. However, for sure the dwOutBufferSize mis-declaration is an error.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Well, I finally "stumbled" across a solution to my problem after trying about a gazillion different possible solutions over the last few days. I've posted the complete C# code in my original thread in case there are others searching the internet for info on porting this very obscure C SDK to C#. – Bob S Sep 21 '13 at 16:05
  • Er, your latest update just repeats what I said here. I feel you could simply accept my answer. It seems to me that you asked for help, I spent time solving your problem, and you somehow have not recognised that. – David Heffernan Sep 21 '13 at 16:37