1

Hi I am trying to convert the C/C++ Strcut to C# and how to fill the structure member with address of another structure in C#?

C/C++ Struct looks like:

         typedef struct _NDISUIO_QUERY_OID
         {
           NDIS_OID        Oid;
           PTCHAR          ptcDeviceName;  
           UCHAR           Data[sizeof(ULONG)];
         } NDISUIO_QUERY_OID, *PNDISUIO_QUERY_OID;

         typedef struct My_Struct
         {
           //les have 2 variables...  
            UINT    a;
            UINT    b;
         }My_STATS, *PMy_STATS;

         PNDISUIO_QUERY_OID     pQueryOid = NULL;

         pQueryOid = (PNDISUIO_QUERY_OID)malloc(sizeof(NDISUIO_QUERY_OID)+ sizeof(My_STATS)) ;

         PMy_STATS   Statistics;
          pQueryOid->Oid = ulOIDCode;//Required OID
      pQueryOid->ptcDeviceName = AUB_NAME;//REquired STRING

         memcpy(pQueryOid->Data, Statistics, sizeof(My_STATS));

My C# Struct is:

    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

    public struct _NDISUIO_QUERY_OID
    {
        public uint        Oid;
        [MarshalAs(UnmanagedType.LPWStr)]
        public string          ptcDeviceName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = sizeof(uint))]
        public string Data;
    };

Problem: How to copy the Statistics structure to Data array in C#??

Thanks :)

arya2arya
  • 291
  • 2
  • 21
  • Can you give some more details. It seems that on the native side you have a 4 byte buffer, Data, into which you push 8 bytes. How can that work? Who allocates what? – David Heffernan Mar 24 '14 at 13:52
  • @DavidHeffernan Hi sry for the late response :( I ll update the Qn.. – arya2arya Mar 24 '14 at 14:01
  • @DavidHeffernan In similar fashion as in C++, Is it possible in C#?? I am trying by converting the structure to ptr and then using Marshal.copy() – arya2arya Mar 24 '14 at 14:05

3 Answers3

3

Here's my implementation (FYI, the SDF contains all of this code and a lot more)

internal class NDISQueryOid
{
    protected const int NDISUIO_QUERY_OID_SIZE = 12;

    protected byte[] m_data;
    public int Size { get; private set; }

    public NDISQueryOid(byte[] data)
    {
        int extrasize = data.Length;
        Size = 8 + extrasize;
        m_data = new byte[Size];
        Buffer.BlockCopy(data, 0, m_data, DataOffset, data.Length);
    }

    public NDISQueryOid(int extrasize)
    {
       Size = NDISUIO_QUERY_OID_SIZE + extrasize;
        m_data = new byte[Size];
    }

    protected const int OidOffset = 0;
    public uint Oid
    {
        get { return BitConverter.ToUInt32(m_data, OidOffset); }
        set
        {
            byte[] bytes = BitConverter.GetBytes(value);
            Buffer.BlockCopy(bytes, 0, m_data, OidOffset, 4);
        }
    }

    protected const int ptcDeviceNameOffset = OidOffset + 4;
    public unsafe byte* ptcDeviceName
    {
        get
        {
            return (byte*)BitConverter.ToUInt32(m_data, ptcDeviceNameOffset);
        }
        set
        {
            byte[] bytes = BitConverter.GetBytes((UInt32)value);
            Buffer.BlockCopy(bytes, 0, m_data, ptcDeviceNameOffset, 4);
        }
    }

    protected const int DataOffset = ptcDeviceNameOffset + 4;
    public byte[] Data
    {
        get
        {
            byte[] b = new byte[Size - DataOffset];
            Array.Copy(m_data, DataOffset, b, 0, Size - DataOffset);
            return b;
        }
        set
        {
            Size = 8 + value.Length;
            m_data = new byte[Size];
            Buffer.BlockCopy(value, 0, m_data, DataOffset, value.Length);
        }
    }

    public byte[] getBytes()
    {
        return m_data;
    }

    public static implicit operator byte[](NDISQueryOid qoid)
    {
        return qoid.m_data;
    }
}

Note that in my usage, the NDIS IOCT takes in a pointer (most of my NDIS work is all done as unsafe) so you'd have to do some adjustment there.

So if, for example, you're querying the BSSID, I know the BSSID data is 36 bytes, so I'd create something like this:

var queryOID = new NDISQueryOid(36);

then allocate the name and call NDIS (the production code has a lot more checking than this):

byte[] nameBytes = System.Text.Encoding.Unicode.GetBytes(adapterName + '\0');

fixed (byte* pName = &nameBytes[0])
{
    queryOID.ptcDeviceName = pName;
    queryOID.Oid = (uint)oid;

    var bytes = queryOID.getBytes();
    ndis.DeviceIoControl(IOCTL_NDISUIO_QUERY_OID_VALUE, bytes, bytes);

    var result = new byte[queryOID.Data.Length];
    Buffer.BlockCopy(queryOID.Data, 0, result, 0, result.Length);
}

EDIT

So the result member above is a byte array of the "result" of the query. What it means and how you interpret it depends on what the OID you queried was. For example, if you were querying the currently connected SSID (i.e. NDIS_OID.SSID), then that comes back as a 4-byte length followed by the ASCII-encoded name, so you'd decipher it like this:

int len = BitConverter.ToInt32(data, 0);
if (len > 0)
{
    ssid = System.Text.Encoding.ASCII.GetString(data, 4, len);
}

But again, this is only for one specific OID. You have to handle every return case for every incoming OID you decide to support.

ctacke
  • 66,480
  • 18
  • 94
  • 155
  • Hi Sir Can we use this structure in C#? `[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct _NDISUIO_QUERY_OID { public uint Oid; [MarshalAs(UnmanagedType.LPWStr)] public string ptcDeviceName; public IntPtr Data; (or) [MarshalAs(UnmanagedType.ByValArray,SizeConst = sizeof(uint))] public char[] Data; };` – arya2arya Mar 24 '14 at 14:24
  • 1
    It depends on the data you're passing. For example for a BSSID, no you can't, because the data is 36 bytes. My implementation allows any data length (which is why I use it). – ctacke Mar 24 '14 at 15:26
  • Hi Sir, With the above implementation I was bit confused.. Can you please clarify me Is it possible to copy the second structure into Data member of 1st struct? – arya2arya Mar 25 '14 at 07:07
  • Not sure what you're asking. In my example, you create a QueryOID struct with the proper length for the data you're querying (from 4-2k bytes in my code). The result of the query comes back and is then in the `result` member in the last block of my code. How you decipher that resulting byte array depends on what the query OID you passed in was. It might be an RSSI, Auth mode, SSID or even a full SSID list for the adapter. It's up to the caller to interpret the results based on calling context. – ctacke Mar 25 '14 at 17:15
3

First you have the wrong translation of your C++ code: the C# equivalent of a C++ char[] is not a string, it's a byte[]. Once you have that, you just need to know, in general, how to copy a structure into a byte array. Here's a compilable example:

using System;
using System.Runtime.InteropServices;


    struct Dest
    {
        public byte[] Data;
    }

    struct Src
    {
        public GCHandle StringHandle;
        public long A;
        public long B;
    }

    class Program
    {
        static void Main()
        {
            Copy();
        }

        static void Copy()
        {
            var str = "Hello";
            var src = new Src { 
                A = 3, 
                B = 4, 
                StringHandle = GCHandle.Alloc(str, GCHandleType.Normal) 
            };
            var dst = new Dest();

            unsafe
            {
                Src* srcPtr = &src;
                dst.Data = new byte[sizeof(Src)];
                Marshal.Copy((IntPtr)srcPtr, dst.Data, 0, sizeof(Src));
            }

            // When you're sure no one can reference the string anymore
            // (Including by accessing the data you put in dst.Data!)
            src.StringHandle.Free();
        }

EDIT: added example of how to deal with reference types such as strings.

Asik
  • 21,506
  • 6
  • 72
  • 131
  • Hi Sir.. Its not allowing me to create a pointer for the structure. Src* srcPtr = &src; Getting Error: Cannot take the address of, get the size of, or declare a pointer to a managed type – arya2arya Mar 26 '14 at 06:20
  • 1
    You cannot take the address of a struct that has reference type members in it. I suppose you tried adding a string to Src. I edited the example to show how to deal with reference types. – Asik Mar 26 '14 at 15:02
  • @@asik But how you are doing this? `Src* srcPtr = &src;` – arya2arya Mar 27 '14 at 04:40
  • The above code compiles (with /unsafe), so you must be doing something different. – Asik Mar 27 '14 at 14:26
2

Safely, you can't. .NET enforces type safety, which means that you simply can't force a string to be a structure. However, you can look at the data instead of doing unsafe type casts (why are you storing two uints in a string in the first place? And marshalling it as unicode?

First, you'll have to make Data a byte array. It might be possible to do this with a string as well, but that's just adding encoding issues to the mix; if you can, use byte[] instead. Also, if you don't need to have different kinds of data inside (it seems so), you could simply put the two uint fields right inside the struct and it should work just fine:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _NDISUIO_QUERY_OID
{
    public uint Oid;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string ptcDeviceName;
    public uint DataA;
    public uint DataB;
};

The second approach would use a const-sized byte array, long enough to hold the two uints:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct _NDISUIO_QUERY_OID
{
    public uint Oid;
    [MarshalAs(UnmanagedType.LPWStr)]
    public string ptcDeviceName;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = sizeof(ulong))]
    public byte[] Data;
};

The first four bytes will be the first uint, the next will be the second.

And of course, you could also use a .NET struct the same way as in the original code - just make sure you use the correct datatype in _NDISUIO_QUERY_OID and it should work automagically.

One point to note though, it seems that the data returned isn't actually necessarily fixed-length. That is quite tricky and it basically means you'd have to deserialize the structure manually based on the pointer and length you get.

Luaan
  • 62,244
  • 7
  • 97
  • 116
  • my task is to copy the structure "Statistics" address to the "Data" in _NDISUIO_QUERY_OID structure. in byte[] how can we copy another struct address? – arya2arya Mar 24 '14 at 08:31
  • @arya2arya You can't. However, that doesn't really matter, since you can serialize the struct into it - basically, rather than doing an unsafe `memcpy`, you'll explicitly say "put the first field in the first four bytes, and the second in the next four bytes". Reading is easy, you can use eg. `BitConverter.ToUInt32(Data, 0);`, and for writing, `BitConverter.GetBytes(fieldA).CopyTo(Data, 0);` will do quite well. It's not the fastest way, but it's safe. Just read / write the first field with `startIndex = 0`, and the second with `4`. – Luaan Mar 24 '14 at 08:42
  • Thanks for your answer.. :) but my requirement is to copy the structure to the "Data" in _NDISUIO_QUERY_OID structure :( – arya2arya Mar 24 '14 at 08:57
  • @arya2arya Yes, so change the structure the way I said (use `byte[]` instead of `string`) and you can use the code snippets I've shown you to copy the structure inside the data field. Native code doesn't care about *definitions* at all - the struct is one big byte array for the native code, it doesn't care about fields or stuff like that. The other side just interprets the first four bytes as `UINT Oid`, but that's just an interpretation. If you pass a four-byte string instead, it will simply interpret it as a `UINT`, as simple as that. – Luaan Mar 24 '14 at 09:03