1

嗨,我正在尝试将 C/C++ Strcut 转换为 C#,以及如何用 C# 中另一个结构的地址填充结构成员?

C/C++ 结构看起来像:

         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));

我的 C# 结构是:

    [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;
    };

问题:如何在 C# 中将统计结构复制到数据数组?

谢谢 :)

4

3 回答 3

3

这是我的实现(仅供参考,SDF 包含所有这些代码等等)

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;
    }
}

请注意,在我的使用中,NDIS IOCT 接收一个指针(我的大部分 NDIS 工作都是以不安全的方式完成的),因此您必须在那里进行一些调整。

因此,例如,如果您要查询 BSSID,我知道 BSSID 数据是 36 个字节,所以我会创建如下内容:

var queryOID = new NDISQueryOid(36);

然后分配名称并调用 NDIS(生产代码有比这更多的检查):

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);
}

编辑

所以result上面的成员是查询“结果”的字节数组。它的含义以及您如何解释它取决于您查询的 OID 是什么。例如,如果您正在查询当前连接的 SSID(即NDIS_OID.SSID),则返回为 4 字节长度,后跟 ASCII 编码名称,因此您可以像这样对其进行解密:

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

但同样,这仅适用于一个特定的 OID。您必须为您决定支持的每个传入 OID 处理每个返回案例。

于 2014-03-24T14:06:14.913 回答
3

首先,您的 C++ 代码翻译错误:C++ 的 C# 等价物char[]不是字符串,而是byte[]. 一旦你有了它,你通常只需要知道如何将结构复制到字节数组中。这是一个可编译的示例:

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();
        }

编辑:添加了如何处理字符串等引用类型的示例。

于 2014-03-25T17:40:07.360 回答
2

安全,你不能。.NET 强制执行类型安全,这意味着您根本无法将字符串强制为结构。但是,您可以查看数据而不是进行不安全的类型转换(为什么首先将两个uints 存储在字符串中?并将其编组为 unicode?

首先,您必须创建Data一个字节数组。也可以使用字符串来执行此操作,但这只是增加了编码问题;如果可以,请byte[]改用。此外,如果您不需要在其中包含不同类型的数据(看起来是这样),您可以简单地将两个uint字段放在结构中,它应该可以正常工作:

[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;
};

第二种方法将使用 const 大小的字节数组,其长度足以容纳两个 uint:

[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;
};

前四个字节将是第一个uint,下一个将是第二个。

当然,您也可以像在原始代码中一样使用 .NET 结构——只要确保使用正确的数据类型_NDISUIO_QUERY_OID,它就会自动工作。

需要注意的一点是,返回的数据似乎不一定是固定长度的。这非常棘手,它基本上意味着您必须根据获得的指针和长度手动反序列化结构。

于 2014-03-24T08:24:06.810 回答