質問

I've been working on porting an old C++ app to C# and I'm not sure if my solution is actually feasible.

Most of the headache is that the app has a file format that is essentially just some (rather large) structures dumped to the file. The fun part is the format requires integers to be in big endian order regardless of the system. So, I threw together a utility method to flip byte orders, and constructed structures replicating the C++ layout using lots of fixed-size buffers. At this point importing is as simple as copying the file into memory and using Marshal.PtrToStructure, which is much easier than reading it in manually but doesn't let me flip byte orders as it's constructed.

So, the format is rather heavily dependent on short variables, both individually and in fixed buffers. While in the process of putting together the many, many fixed statements to pin the buffers for flipping, I had a disturbing thought.

Unless I'm mistaken, pinning just one of the buffers would cause the whole structure to be pinned (it wouldn't make sense to relocate part of a structure away from the rest). So, I've tried something like this:

[StructLayout(LayoutKind.Sequential, Pack = 1)]
public unsafe struct Example
{
    short value1;
    fixed short value2[10];
    short value3;
    fixed short value4[20];

    public void FlipEndianness()
    {
        fixed (short* ptr = &value1)
        {
            Utility.FlipShorts(ptr, 32);
        }
    }
}

The idea is that the utility method works on a pointer and a count, iterating through the pointer as if it were an array - I originally wrote it for flipping the buffers but I saw the value here. In theory, it seems to work properly - all of the variables are flipped in just one call with not so much fuss. What I need to know is if my concept is actually safe. Manually pinning everything would be a huge pain - have I mentioned the structures are massive?

Does pinning a variable pin the whole structure and make this kind of trickery possible, or am I just lucky so far that this hasn't kludged up memory? So far my tests say yes, but I can't say for certain. I'd rather not have this blow up on me.

役に立ちましたか?

解決

There isn't any resource that I know of that is going to tell you that this is safe to do in C#. But you'll get away with it, there's no mechanism in the GC that's going to keep a field of struct pinned and still allow the other fields in the struct to move. A fixed statement like this creates a pinned interior pointer, the GC is smart enough to discover the object root from that. Interior pointers only ever are mentioned in literature for C++/CLI, a language that allows declaring one directly with the interior_ptr<> keyword. Do beware that this won't necessary work well on another CLR implementation, like Mono. YMMV. C++/CLI is otherwise the better weapon, at least you can use the original C or C++ declaration without having to rewrite it in C#. And you'll have a stable pointer to the native struct.

It shouldn't be an issue in the first place, actual object pinning is only required if the struct is part of a reference type that's allocated on the GC heap. No idea what your code looks like, but a sane approach is to keep such a struct as a local variable in the method that reads and converts the data. Such value types are allocated on the stack and have a stable address.

Do note that there are gobs of unsafe in this code, neither the compiler nor the runtime can do anything about you using the magic 32 value incorrectly. Optimizing file data conversion code is very rarely useful, the cost of reading the data is orders of magnitude higher than interpreting it. So much so that using Reflection to find the fields back is reasonable, you can't ever get it wrong that way. Jon Skeet's EndianBinaryReader ought to be mentioned too.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top