3

I need to convert the following struct to a byte array:

[Serializable]
public struct newLeads
{
    public string id;
    public string first_name;
    public string last_name;
}

I'm trying to convert to the byte array with the following code:

public class ConvertStruct
{
    public static byte[] StructureToByteArray(object obj)
    {
        int Length = Marshal.SizeOf(obj);
        byte[] bytearray = new byte[Length];
        IntPtr ptr = Marshal.AllocHGlobal(Length);
        Marshal.StructureToPtr(obj, ptr, false);
        Marshal.Copy(ptr, bytearray, 0, Length);
        Marshal.FreeHGlobal(ptr);
        return bytearray;
    }
}

I'm getting the exception on line:

IntPtr ptr = Marshal.AllocHGlobal(Length);

Exception: Attempt by security transparent method 'Classes.ConvertStruct.ConvertStruct.StructureToByteArray(System.Object)' to access security critical method 'System.Runtime.InteropServices.Marshal.AllocHGlobal(Int32)' failed."}

My Question is? How can I fix this to avoid the exception and convert my simple struct into a byte[]?

Thanks in advance!

UPDATE: I tried this in a console application and it works. I'm calling this from an asp.net page code-behind so that must have something to do with it, but I can't figure out what!

4

3 に答える 3

3
  1. 構造体のサイズを確認し、ボックス化しないでください。
  2. 文字列に適切なマーシャリングを設定します(goricによる回答から取得)。それにもかかわらず、得られるのはバイト配列内の文字列のメモリアドレスです(良いことではありません)。

    [Serializable]
    public struct newLeads
    {
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
        public string id;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
        public string first_name;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
        public string last_name;
    }
    
    [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
    public static byte[] ToByteArray(newLeads value)
    {
        int length = Marshal.SizeOf(typeof(newLeads));
        var result = new byte[length];
        IntPtr sourcePtr = Marshal.AllocHGlobal(length);
        Marshal.StructureToPtr(value, sourcePtr, false);
        Marshal.Copy(sourcePtr, result, 0, length);
        Marshal.FreeHGlobal(sourcePtr);
        return result;
    }
    

あなたのコメントでは、このコードはすぐに失敗すると言いました。それは、メソッドが呼び出されるたびに特定の権限をチェックする際に、(.NET 4 で推奨されているように) Security Permission Demand を使用します。それなしでそれを実行しようとするかもしれません。期待される結果は、最初に得たものです。


本当の答え

おそらくある種のサンドボックスまたはポインターをサポートしていないプラットフォームなど、制約のある環境で実行している必要があります。その場合、別の方法で変換を行う必要がある場合があります。

あなたはASP.NETと言いましたか?それだけです。

ポインターを使用せずに変換するには、次の手法を試してください。

//You will have to decide an encoding.
//If nto ASCII, try UTF8Encoding, UnicodeEncoding or (Hopefully not) UTF7Encoding
void Main()
{
    Encoding encoding = new ASCIIEncoding();
    newLeads target = GetNewLeads();
    byte[] id = EncodeString(target.id, encoding);
    byte[] first_name = EncodeString(target.first_name, encoding);
    byte[] last_name = EncodeString(target.last_name, encoding);
}

byte[] EncodeString(string str, Encoding encoding)
{
    byte[] data;
    if (ReferenceEquals(str, null))
    {
        data = new byte[0];
    }
    else
    {
        data = encoding.GetBytes(str);
    }
    return data;
}

この時点で、より良い解決策を提供するために、あなたの状況についてもう少し知る必要があります。特に、誰が、または何がそのバイト配列を読み取るのでしょうか?. とにかく、次のように文字列の長さをエンコードできます (null の場合は -1 を予約します):

byte[] EncodeString(string str, Encoding encoding)
{
    byte[] data;
    byte[] data_length;
    Union union = new Union();
    if (ReferenceEquals(str, null))
    {
        data = new byte[0];
        union.data = -1;
    }
    else
    {
        data = encoding.GetBytes(str);
        union.data = str.Length;
    }
    data_length = new byte[]{union.a, union.b, union.c, union.c};
    int length = data.Length;
    byte[] result = new byte[4 + data.Length];
    System.Buffer.BlockCopy(data_length, 0, result, 0, 4);
    System.Buffer.BlockCopy(data, 0, result, 4, length);
    return result;
}

//I hope endianess doesn't bite
[StructLayout(LayoutKind.Explicit)] 
struct Union
{
    [FieldOffset(0)]
    public int data;
    [FieldOffset(0)] 
    public byte a;
    [FieldOffset(1)] 
    public byte b;
    [FieldOffset(2)] 
    public byte c;
    [FieldOffset(3)] 
    public byte d;
}

最後に、これらの配列を結合する必要があります。私はこれがもっと最適化できることを知っています... (MemoryStream と StreamWriter を使用するための良い考えです) とにかく、これは私の最初の実装です [テスト済み]:

byte[] ToByteArray(newLeads value)
{
    Encoding encoding = new ASCIIEncoding(); //Choose some encoding
    byte[] id = EncodeString(value.id, encoding);
    byte[] first_name = EncodeString(value.first_name, encoding);
    byte[] last_name = EncodeString(value.last_name, encoding);
    byte[] result = new byte[id.Length + first_name.Length + last_name.Length];
    System.Buffer.BlockCopy(id, 0, result, 0, id.Length);
    System.Buffer.BlockCopy
        (
            first_name,
            0,
            result,
            id.Length,
            first_name.Length
        );
    System.Buffer.BlockCopy
        (
            last_name,
            0,
            result,
            id.Length + first_name.Length,
            last_name.Length
        );
    return result;
}

byte[] EncodeString(string str, Encoding encoding)
{
    byte[] data;
    byte[] data_length;
    Union union = new Union();
    if (ReferenceEquals(str, null))
    {
        data = new byte[0];
        union.data = -1;
    }
    else
    {
        data = encoding.GetBytes(str);
        union.data = str.Length;
    }
    data_length = new byte[]{union.a, union.b, union.c, union.c};
    int length = data.Length;
    byte[] result = new byte[4 + data.Length];
    System.Buffer.BlockCopy(data_length, 0, result, 0, 4);
    System.Buffer.BlockCopy(data, 0, result, 4, length);
    return result;
}

//I hope endianess doesn't bite
[StructLayout(LayoutKind.Explicit)] 
struct Union
{
    [FieldOffset(0)]
    public int data;
    [FieldOffset(0)] 
    public byte a;
    [FieldOffset(1)] 
    public byte b;
    [FieldOffset(2)] 
    public byte c;
    [FieldOffset(3)] 
    public byte d;
}

注: 最終的にどのエンコーディングを使用するかわからないため、null で終了する文字列は使用しませんでした。

最適化

同じロジックですが、ストリームで実装されています (Union は変更されていません)[テスト済み]。

byte[] ToByteArray(newLeads value)
{
    Encoding encoding = new ASCIIEncoding(); //Choose some encoding
    var stream = new MemoryStream();
    EncodeString(value.id, stream, encoding);
    EncodeString(value.first_name, stream, encoding);
    EncodeString(value.last_name, stream, encoding);
    int length = (int)stream.Length;
    byte[] result = new byte[(int)stream.Length];
    System.Buffer.BlockCopy(stream.GetBuffer(), 0, result, 0, length);
    stream.Close();
    return result;
}

void EncodeString(string str, Stream stream, Encoding encoding)
{
    Union union = new Union();
    if (ReferenceEquals(str, null))
    {
        union.data = -1;
        stream.Write(new byte[]{union.a, union.b, union.c, union.c}, 0, 4);
    }
    else
    {
        union.data = str.Length;
        stream.Write(new byte[]{union.a, union.b, union.c, union.c}, 0, 4);
        var tmp = encoding.GetBytes(str);
        stream.Write(tmp, 0, tmp.Length);
    }
}

弦を張り直す

データを取得するには、文字列の長さを読み取ることから始めます (同じタイプの Union を使用)。

var newLeads = GetNewLeads();
var z= ToByteArray(newLeads); //we have the byteArray in z

var data = new MemoryStream(z); //Create an stream for convenience
//Use the union to get the length
var union = new Union()
    {
        a = (byte)data.ReadByte(),
        b = (byte)data.ReadByte(),
        c = (byte)data.ReadByte(),
        d = (byte)data.ReadByte()
    };
Console.WriteLine(union.data); //the length of the first string

次のステップは、多くの文字を読み取ることです。そのために、StreamReader を使用します。

var newLeads = GetNewLeads();
var z = ToByteArray(newLeads);

var data = new MemoryStream(z);
var union = new Union()
    {
        a = (byte)data.ReadByte(),
        b = (byte)data.ReadByte(),
        c = (byte)data.ReadByte(),
        d = (byte)data.ReadByte()
    };
var encoding = new ASCIIEncoding();
string result = null;
if (union.data != -1)
{
    char[] finalChars = new char[union.data];
    var reader = new StreamReader(data, encoding);
    reader.Read(finalChars, 0, union.data);
    result = new string(finalChars);
}
Console.WriteLine(result);

これにより、文字列をデコードするメソッドを構築します。

string DecodeString(Stream data, Encoding encoding)
{   
    //TODO: You may want to validate that data and encoding are not null
    //or make this private
    var union = new Union()
    {
        a = (byte)data.ReadByte(),
        b = (byte)data.ReadByte(),
        c = (byte)data.ReadByte(),
        d = (byte)data.ReadByte()
    };
    string result = null;
    if (union.data != -1)
    {
        char[] finalChars = new char[union.data];
        var reader = new StreamReader(data, encoding);
        reader.Read(finalChars, 0, union.data);
        result = new string(finalChars);
    }
    return result;
}

//Convenience method, not needed:

string DecodeString(byte[] data, Encoding encoding)
{
    //TODO: You may want to validate that data and encoding are not null
    //or make this private
    return DecodeString(new MemoryStream(data), encoding);
}

そして最後に newLeads を回復する方法 (再び同じ Union タイプ)[テスト済み]:

newLeads FromByteArray(byte[] data)
{
    //TODO: Validate that data is not null
    Encoding encoding = new ASCIIEncoding(); //Choose the same encoding
    newLeads result = new newLeads();
    var reader = new StreamReader(new MemoryStream(data), encoding);
    result.id = DecodeString(reader);
    result.first_name = DecodeString(reader);
    result.last_name = DecodeString(reader);
    reader.Close();
    return result;
}


//Changed to reuse StreamReader...
//Because closing it will close the underlying stream
string DecodeString(StreamReader reader)
{
    //TODO: You may want to validate that reader is not null
    //or make this private
    var data = reader.BaseStream;
    var union = new Union()
    {
        a = (byte)data.ReadByte(),
        b = (byte)data.ReadByte(),
        c = (byte)data.ReadByte(),
        d = (byte)data.ReadByte()
    };
    string result = null;
    if (union.data != -1)
    {
        char[] finalChars = new char[union.data];
        reader.Read(finalChars, 0, union.data);
        result = new string(finalChars);
    }
    return result;
}

//Convenience method, not needed:

string DecodeString(byte[] data, Encoding encoding)
{
    //TODO: You may want to validate that data and encoding are not null
    //or make this private
    return DecodeString(new StreamReader(new MemoryStream(data), encoding));
}
于 2012-06-01T01:57:10.193 に答える
0

メソッドを次のようにマークできるようですSecurityCritical

http://msdn.microsoft.com/en-us/library/bb264475.aspx

于 2012-06-01T01:48:47.510 に答える
0

MarshalAs構造体で、文字列を適切な属性でマークします( MSDN リファレンス)。

[Serializable]
public struct newLeads
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
    public string id;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
    public string first_name;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 5000)]
    public string last_name;
}

SizeConst値が、使用するデータの最大長をカバーするのに十分な大きさであることを確認してください。

于 2012-06-01T01:33:25.327 に答える