DirectInput exposes an effect interface for dealing with force feedback effects. Each effect has a set of additional type-specific parameters, which turn out to be rather difficult to encapsulate correctly for consumption by managed code. Just as a quick primer, in native DirectInput IDirectInputEffect exposes a GetParameters method to get the effect parameters. The returned parameters structure contains a pointer to the extra type-specific parameters, along with a byte size. It is assumed that the C++ programmer will know the correct type for this data and then cast the pointer appropriately. In the managed world, this isn't so easy.
Ideally, the managed wrapper would construct the appropriate type and then return that to the user, as playing with pointers isn't quite kosher in the managed world. Unfortunately, the returned parameters contain no identifier to specify which type of data is being represented. I scratched my head at this one for a bit, and then took a look at how MDX handled the situation. I was quite surprised to find this little snippet of code hiding in the bowels of MDX:
internal static unsafe void ToManaged(ref Effect ret, DIEFFECT modopt(IsConstModifier)* src){ if ((ret == 0) || (src == null)) { throw new ArgumentNullException(); } ret.m_Flags = *((EffectFlags*) (src + 4)); ret.m_Duration = *((int*) (src + 8)); ret.m_SamplePeriod = *((int*) (src + 12)); ret.m_Gain = *((int*) (src + 0x10)); ret.m_TriggerButton = *((int*) (src + 20)); ret.m_TriggerRepeatInterval = *((int*) (src + 0x18)); ret.m_StartDelay = *((int*) (src + 0x34)); if (*(((int*) (src + 0x20))) != 0) { int[] numArray2 = new int[*((int*) (src + 0x1c))]; numArray2.Initialize(); ret.m_Axes = numArray2; volatile ref int pinned numRef2 = (volatile ref int) &(ret.m_Axes[0]); memcpy(numRef2, *(((int*) (src + 0x20))), (*(((int*) (src + 0x1c))) << 2)); numRef2 = 0; } if (*(((int*) (src + 0x24))) != 0) { int[] numArray = new int[*((int*) (src + 0x1c))]; numArray.Initialize(); ret.m_Direction = numArray; volatile ref int pinned numRef = (volatile ref int) &(ret.m_Direction[0]); memcpy(numRef, *(((int*) (src + 0x24))), (*(((int*) (src + 0x1c))) << 2)); numRef = 0; } uint num2 = *((uint*) (src + 40)); if ((num2 != 0) && (((num2[4] != 0) || (num2[8] != 0)) || ((num2[12] != 0) || (num2[0x10] != 0)))) { ret.m_UsesEnvelope = true; Envelope.ToManaged(ref ret.EnvelopeStruct, *((DIENVELOPE**) (src + 40))); } if (0x10 == *(((int*) (src + 0x2c)))) { DICUSTOMFORCE* dicustomforcePtr = *((DICUSTOMFORCE**) (src + 0x30)); int num6 = *(((int*) (dicustomforcePtr + 8))) << 2; if (((IsBadReadPtr(*((void modopt(IsConstModifier)**) (dicustomforcePtr + 12)), (uint) num6) == 0) && (IsBadWritePtr(*((void**) (dicustomforcePtr + 12)), (uint) num6) == 0)) && (*(((int*) (dicustomforcePtr + 8))) > 0)) { CustomForce.ToManaged(ref ret.CustomStruct, dicustomforcePtr); ret.m_EffType = EffectType.CustomForce; return; } } uint num = *((uint*) (src + 0x2c)); if (0x10 == num) { Periodic.ToManaged(ref ret.Periodic, *((DIPERIODIC**) (src + 0x30))); ret.m_EffType = EffectType.Periodic; } else if (4 == num) { ConstantForce.ToManaged(ref ret.Constant, *((DICONSTANTFORCE**) (src + 0x30))); ret.m_EffType = EffectType.ConstantForce; } else if (8 == num) { RampForce.ToManaged(ref ret.RampStruct, *((DIRAMPFORCE**) (src + 0x30))); ret.m_EffType = EffectType.RampForce; } else if ((num != 0) && ((num % 0x18) == 0)) { int num5 = (int) (num / 0x18); Condition[] conditionArray = new Condition[num5]; conditionArray.Initialize(); ret.ConditionStruct = conditionArray; ret.m_EffType = EffectType.Condition; int index = 0; if (0 < num5) { DIEFFECT modopt(IsConstModifier)* dieffectPtr = src + 0x30; int num4 = 0; do { Condition.ToManaged(ref ret.ConditionStruct[index], (DICONDITION*) (num4 + *(((int*) dieffectPtr)))); index++; num4 += 0x18; } while (index < num5); } }}
Yes, it's ugly, but a lot of that is due to the compiler having stripped out constant and structure accesses. That's not really the point here though. What's really crazy is how this snippet of code is determining the correct type for the extra type-specific parameters. Let's look more closely at the last portion of this method, cleaned up to remove compiler funkiness.
if (effect.Size == 16){ DICUSTOMFORCE* ptr = (DICUSTOMFORCE*)effect.TypeSpecificParams; if (!IsBadReadPtr(ptr->ForceData) && !IsBadWritePtr(ptr->ForceData)) return CustomForce.ToManaged(ptr);}if (effect.Size == 16) return PeriodicForce.ToManaged(effect.TypeSpecificParams);else if (effect.Size == 4) return ConstantForce.ToManaged(effect.TypeSpecificParams);else if (effect.Size == 8) return RampForce.ToManaged(effect.TypeSpecificParams);else if (effect.Size != 0 && (effect.Size % 24) == 0) return ConditionArray.ToManaged(effect.TypeSpecificParams);
In case you don't quite understand the hilariousness that is this code, let me give you a little guidance. First, the function checks the number of bytes returned, and tries to match it up against the size of possible type-specific structures. As you can see, there are four possible force types. If the size doesn't match any of those, it checks to see if it's aligned on a Condition array boundary, in which case it interprets the data as an array of Condition structures. That's fairly hackish on it's own, but as you can see by that first if block, it gets better.
Unfortunately for the original MDX developers, both DIPERIODIC and DICUSTOMFORCE both have a size of 16 bytes, so how would you know which structure was being returned? Simple! Since DIPERIODIC contains four integers, and DICUSTOMFORCE contains three integers and a pointer, simply take a guess to see if the data at the correct offset could possibly be considered a valid pointer. If so, we'll pretend that we have a DICUSTOMFORCE and everything will be hunky dory!
The only question is, how do you know if a given set of bytes represents a pointer? Simple! Use the handy-dandy IsBadReadPtr and IsBadWritePtr functions to figure it out for you. For those of you in the know, IsBadXxxPtr should really be called CrashProgramRandomly or perhaps CorruptMemoryIfPossible. I haven't done much research on these two little functions, but before I'd even read the aforementioned article, I had already guessed that they weren't quite standard C++, if you catch my drift.
Now, this little... what did I call it... gem? that I've found here is quite out of scope for most normal applications, but I can't help but wonder if anyone had ever attempted to use this bit of functionality and found it horribly broken.