views:

198

answers:

2

For the purpose of learning I recently looked at an existing assembly (using Reflector) that uses Win32 WriteFile. The implementation is:

Write(IntPtr handleFile, void* bufferData, uint length){
void* buffer = bufferData
while (length > 0)
{
  uint wrtn;
  if (!WriteFile(handle, buffer, len, out wrtn, IntPtr.Zero))
  {
     // Do some error handling
  }
  // This does not compile, because of the cast but also simply because void* does not have += operators (it is unknown size).
  buffer += (void*)wrtn;
  len -= wrtn;
}

}

It is actually the last 2 lines that are problematic... For one, the compiler complains that you cannot cast uint to void*. Additionally, it is just not possible to use += or even + on void* because it is not of known size.

Write(IntPtr handleFile, void* bufferData, uint length){
    byte* buffer = (byte*)bufferData
    while (length > 0)
    {
      uint wrtn;
      if (!WriteFile(handle, (void*)buffer, len, out wrtn, IntPtr.Zero))
      {
         // Do some error handling
      }
      // This works! I can add to a byte*
      buffer = buffer + wrtn; // I could also have used buffer += wrtn
      len -= wrtn;
    }
}

The above code does work but still the last few lines will compile to:

buffer += (byte*)wrtn;

I do not understand why and very much would like to know why the compiler behaves in this way:

  1. Why does it generate the cast like this (and why is it not accepted to do this in user written code)?
  2. What is up with the += operators on void* in the first example? What original code code have generated buffer += (void*)wrtn where buffer is also void* ????
+1  A: 

Well for your second point, void* has no size information so the compiler doesn't know how much to increment the pointer by. Should it increment by sizeof(double)? Only with type information does it know what to expect.

Edit: Actually this applies to your first point as well. The compiler needs to know the size of the data type its incrementing. Void* has no size information present so by casting to a byte* you let the compiler know that it needs to increment the pointer by sizeof(byte) * wrtn.

Edit2: With your clarification, it appears you're asking why the Reflector emits code as a void* instead of as it's properly casted type (byte *). Most likely this is due to the type information being extracted from the parameter type and somewhat naively being used in the method. This is probably an issue with Reflector more than the compiler.

It's also possible that this pointer code loses it's type information in the IL, I haven't tested it yet, but it may not carry typing information (besides the size of the data) into the IL for the Reflector to properly emit (normal 'safe' IL should always have this type information). If this was the case Reflector may default to void* or the nearest inferred type.

Ron Warholic
Thanks for the reply, but as I already point out in my original post, I know about the whole "unknown" size thing. What surprises me is that the compiler still somehow generates void* somepointer += (void*) uint wrtn (I added the type for clarity). It should not be possible (and certainly does not compile!) so I wonder what the original code was that generated this...
Kris
So it most likely is a Reflector thing. After all, Reflectors task probably is not an easy one. I could understand how reflector spits out the casts based on the IL it finds, so that part of the question I consider solved. However, I have been trying and trying to generate reflector output (for the sake of gaining insight) exactly like the one in my very first code block (so without usage of byte*) and I just cannot get there... You cannot do much with void* so I am really curious what the original code that generated this would have looked like!
Kris
Most certainly it looked like your 'fixed' copy (within some tolerance). Have you tried dumping the IL and sorting through that manually? It's not hard to see what Reflector has to work with so you can see where it's inferences come from.
Ron Warholic
Hi sid, The IL starts with: .locals init ( [0] void* buffer, [1] uint32 wrtn, [2] bool result) Then a lot of other stuff and then almost at the end: L_003e: ldloc.0 L_003f: ldloc.1 L_0040: add So to me it looks like somehow it permits addition of void* to uint32... can this be? Do you need more info?
Kris
Ok... I figured it out:buffer = (void*)((byte*)buffer + wrtn);Generates the exact IL that I was looking at and that eventually makes Reflector generate buffer += (void*)wrtn;Is it me or would this be very strange to use in production code?
Kris
In IL, adding a pointer and an integer is permitted for any pointer type, and shifts the pointer by a given amount of _bytes_, regardless of its type. In fact, CLR stack doesn't even have typed pointers as such - when you push a pointer on the stack, it's just "pointer" aka ` which is why you have to specify type in `ldind` and `stind` instructions (which do pointer indirection).
Pavel Minaev
@Kris: also note that you don't need that cast to `void*` in the end, because any pointer type is implicitly castable to `void*`. And no, it's not particularly strange to use it in production code, if you get a `void*` pointer (say, as method argument) and want to poke bytes inside the data pointed to by it.
Pavel Minaev
A: 

For the purpose of learning I recently looked at an existing assembly (using Reflector)

The only problem here is using Reflector - apparently, it's not so good at deducing the original C# code from IL. The IL itself is correct, and has no casts (none are needed - in IL, you push a pointer and an integer argument on the stack, and do an add/subtract). Reflector is wrong.

Pavel Minaev
So what code could have generated the buffer += (void*)wrtn???? I can imagine reflector to be wrong but I cannot write code that would generate the line just mentioned in reflector... And thus my question remains unanswered :)
Kris
Try `buffer = (byte*)buffer + wrtn`.
Pavel Minaev