-1

I have a C# form. I am calling unmanaged functions from a C++ dll.

I have a callback called FUNDownDevCBEx this returns variables int nType, IntPtr pData.

So pointing to the struct _tagGPSMDVRInfo p = (_tagGPSMDVRInfo)Marshal.PtrToStructure(pData, typeof(_tagGPSMDVRInfo)); I can get the pointer variables.

However when I point to szIDNO I only get the last character of the string and I don't know why..

I expected szIDNO to show names incrementally but instead i only get the last char.

expected:

00091
00001
01211
01222
01504

what I got:

4
2
1
1
1

FORM IMAGE4

void FUNDownDevCBEx(int nType, IntPtr pData, Form1 form1) is the callback loop returning szIDNO

Any suggestions will be appreciated.

C++ typedef:

 typedef struct _tagGPSInfo
 {
     int nID;
     char szIDNO[32];               
     char szName[32];               
     char szSIMCard[16];                
     union
     {
         GPSInfo_S gDVRInfo;
         GPSMobileInfo_S gMobileInfo;
         GPSDVSInfo_S DVSInfo;
     };
 }GPSInfo_S, *LPGPSInfo_S;

C++ Callback looks like this:

void (CALLBACK * FUNDownDevCBEx)(int nType, void* pData, void * pUsr)

My C# conversion code:

 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
    public struct _tagGPSInfo
    {
        [MarshalAs(UnmanagedType.I4)]
        public int nID;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szIDNO;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
        public string szName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 16)]
        public string szSIMCard;
    }



 private void devlist_Click(object sender, EventArgs e)
        {
            try
            {
                IntPtr _lHandle = IntPtr.Zero;
                NETClass.NETCLIENT_DEVOpenDevDownEx(ref _lHandle);
                NETClass.NETCLIENT_DEVSetCharEx( _lHandle);
                FUNDownDevCBEx _1callback = new FUNDownDevCBEx(FUNDownDevCBEx);
                NETClass.NETCLIENT_DEVRegDevDownCBEx( _lHandle, this, _1callback);
                NETClass.NETCLIENT_DEVStartDevDownEx(_lHandle, 0,0);
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "List Exception");
                return;
            }
        }



      static void FUNDownDevCBEx(int nType, IntPtr pData, Form1 form1)
    {
        try
        {
            _tagGPSInfo p = (_tagGPSMDVRInfo)Marshal.PtrToStructure(pData, typeof(_tagGPSInfo));
            int nID = p.nID;
            string szIDNO = p.szIDNO;
            switch (nType)
            {
                case 0:
                    form1.Invoke((MethodInvoker)(() => form1.memoBox.AppendText(" DATA =" + pData + " nID=" + nID + " szIDNO=" + szIDNO + Environment.NewLine)));
                    break;
                case 1:
                    //MessageBox.Show("GPS_DEV_DOWN_GROUP" + Environment.NewLine + " DATA =" + pData + " nID=" + nID);
                    break;
                case 2:
                    //MessageBox.Show("GPS_DEV_DOWN_FAILED" + Environment.NewLine + " DATA =" + pData );
                    break;
                case 3:
                    //MessageBox.Show("GPS_DEV_DOWN_SUC" + Environment.NewLine + " DATA =" + pData);
                    break;
                case 4:
                    //MessageBox.Show("GPS_DEV_DOWN_RELATION" + Environment.NewLine + " DATA =" + pData + " nID=" + nID);
                    break;
                default:
                    //MessageBox.Show("DEFAULT");
                    break;
            }
            //MessageBox.Show("nType= " + nType + " pData= " + pData);
            NETClass.NETCLIENT_DEVStopDevDownEx(IntPtr.Zero);
            NETClass.NETCLIENT_DEVCloseDevDownEx(IntPtr.Zero);
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.ToString(), "FUNDownDevCBEx Exception");
            return;
        }
    }

my C# NETClass:

namespace ConversionTest
{

    [UnmanagedFunctionPointer(CallingConvention.StdCall)]
    public delegate void FUNDownDevCBEx(int nType, IntPtr data, Form1 form1);


    class NETClass
    {       
        [DllImport("libnetclient.dll", CallingConvention = CallingConvention.StdCall,SetLastError = true)]
        public static extern int NETCLIENT_DEVOpenDevDownEx(ref IntPtr lpHandle);

        [DllImport("libnetclient.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int NETCLIENT_DEVRegDevDownCBEx( IntPtr lHandle, Form1 form1, FUNDownDevCBEx _callback);

        [DllImport("libnetclient.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int NETCLIENT_DEVStartDevDownEx( IntPtr lHandle, int nMgrType, int nDevType);

        [DllImport("libnetclient.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int NETCLIENT_DEVStopDevDownEx( IntPtr lHandle);

        [DllImport("libnetclient.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int NETCLIENT_DEVCloseDevDownEx( IntPtr lHandle);

        [DllImport("libnetclient.dll", CallingConvention = CallingConvention.StdCall, SetLastError = true)]
        public static extern int NETCLIENT_DEVSetCharEx( IntPtr lHandle, bool bUtf8 = true);
    }
}
Ðаn
  • 10,934
  • 11
  • 59
  • 95
MALKAVIAN
  • 131
  • 14
  • 1
    The problem is probably in the code we don't see here. Create a [mcve] – NineBerry Jan 22 '20 at 19:36
  • @NineBerry I added my net class and the button event. – MALKAVIAN Jan 22 '20 at 19:47
  • If your app is 64bit, there's probably an alignment issue, so c# expects `szIDNO` start at offset 8 while the dll puts it at offset 4. So with 5-character strings you only get the last character. – Alex Skalozub Jan 22 '20 at 19:49
  • @AlexSkalozub could be but there's other longer strings of different lengths in other accounts I tested with the same result.. I think it's a byte array or something and it's overwriting until the last byte, a bit frustrating. I'm running it on 32 bit settings. – MALKAVIAN Jan 22 '20 at 19:51
  • @MALKAVIAN just try adding `Pack = 4` to your `StructLayout` attribute and see if it solves the issue. Also try if it works with `char[]` instead of `string`. – Alex Skalozub Jan 22 '20 at 19:54
  • @AlexSkalozub no change so far.. – MALKAVIAN Jan 22 '20 at 19:58
  • @AlexSkalozub no luck, char[] gives invalid combination errors – MALKAVIAN Jan 22 '20 at 20:03
  • @MALKAVIAN for `char[]` you need to change to `UnmanagedType.ByValArray` – Alex Skalozub Jan 22 '20 at 20:05
  • @AlexSkalozub thanks was just searching the errors. it just prints `System.Char[]` so no values. – MALKAVIAN Jan 22 '20 at 20:10
  • @MALKAVIAN just look at the contents in the debugger. Or allocate a string from those chars before printing: `string szIDNO = new string(p.szIDNO);` – Alex Skalozub Jan 22 '20 at 20:13
  • @AlexSkalozub Still no luck..I am beginning to think there may be an unknown issue with the C++ dlls. It should just work with string no problem.... – MALKAVIAN Jan 22 '20 at 20:19
  • @MALKAVIAN yeah, at this point I'm starting to doubt your native dll is filling the buffer correctly. Check that if you have the source code for it. – Alex Skalozub Jan 22 '20 at 20:21
  • @AlexSkalozub such a pain, unfortunately I only have the dlls,libs and some header files but no source for the dlls.. – MALKAVIAN Jan 22 '20 at 20:45
  • @MALKAVIAN I would try writing a simple c++ app (where you can easily cast pointer to a structure type and observe raw values in debugger) and see what exactly the dll returns. – Alex Skalozub Jan 22 '20 at 20:50
  • The above suggestions from @Alex are all incorrect and misleading. Apart from the final one. That's a solid suggestion. – David Heffernan Jan 23 '20 at 05:54
  • @David to be fair, my very first comment turned out to be correct, just for the different reason. I’ve been pretty confident about c# reading the wrong offset, but was driven away by "there's other longer strings of different lengths in other accounts I tested with the same result" claim :) – Alex Skalozub Jan 23 '20 at 09:17
  • 1
    @alex I don't think so. I think we are still in the dark as to what is going on here. – David Heffernan Jan 23 '20 at 09:49

1 Answers1

-1

Thanks for the comments. The solution was a good nights rest and a cup of coffee in the morning..

Since [StructLayout(LayoutKind.Sequential)] is Sequential each variable in the struct has to be sequential. Since my original struct was extremely long I missed one int variable before the szIDNO and this caused the pointer to ""miss"" 4 characters from my string...

This is how I understand it feel free to comment...

[StructLayout(LayoutKind.Sequential)]
public struct _tagGPSMDVRInfo
{
    //struct now in proper sequence 
    [MarshalAs(UnmanagedType.I4)]
    public int nID;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string szName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
    public string szIDNO;// <--- Now in correct position.... 
    [MarshalAs(UnmanagedType.I4)]
    public int nJingDu;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 7)]
    public string strReserve;
}



static void FUNDownDevCBEx(int nType, IntPtr pData, Form1 form1)
{
    try
    {
        switch (nType)
        {
            case 0:
                _tagGPSMDVRInfo p = new _tagGPSMDVRInfo(); // Create the managed struct
                p = Marshal.PtrToStructure<_tagGPSMDVRInfo>(pData);//simplify
                int nID = p.nID;
                string szIDNO = p.szIDNO;
                form1.Invoke((MethodInvoker)(() => form1.textBox4.AppendText(" DATA =" + pData + " nID=" + nID + " szIDNO=" + szIDNO + Environment.NewLine)));
                ;
                break;
            case 1:
                //MessageBox.Show("GPS_DEV_DOWN_GROUP" + Environment.NewLine + " DATA =" + pData + " nID=" + nID);
                break;
            case 2:
                //MessageBox.Show("GPS_DEV_DOWN_FAILED" + Environment.NewLine + " DATA =" + pData );
                break;
            case 3:
                //MessageBox.Show("GPS_DEV_DOWN_SUC" + Environment.NewLine + " DATA =" + pData);
                break;
            case 4:
                //MessageBox.Show("GPS_DEV_DOWN_RELATION" + Environment.NewLine + " DATA =" + pData + " nID=" + nID);
                break;
            default:
                //MessageBox.Show("DEFAULT");
                break;
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.ToString(), "FUNDownDevCBEx Exception");
        return;
    }
}
MALKAVIAN
  • 131
  • 14
  • 1
    No, this struct definition can't be correct. There surely is a problem but this can't be the right solution. – David Heffernan Jan 23 '20 at 09:48
  • It makes sense that missing a field of the struct produces the problem seen here. But the code given in your answer makes no sense because the relevant beginning of the struct is unchanged compared to the original question. Also, specifiying "Size = 16" makes not sense for the struct because it is much longer. – NineBerry Jan 23 '20 at 11:01