swemaniac Posted February 25, 2015 Report Posted February 25, 2015 Hi Paul, Is there a chance you can expose the FSUIPC.IOffset interface or perhaps enclose all offsets in another (marker) interface? The reason I'm asking is right now it's impossible to abstract an instance of Offset<T> (say you want to push offsets into a list or a dictionary for caching purposes, or instantiate offsets at runtime). Right now I'm casting object:s and doing some serious generic type juggling (good fun but not really pretty). Cheers /Johan
Paul Henty Posted February 25, 2015 Report Posted February 25, 2015 Hi Johan, Yes it's possible to make IOffset public. Before I do, I want to point out that the 'Value' property of IOffset is a generic 'object', so you'll likely have to cast the value if you want to do anything with it apart from ToString(). The data type can be accessed from a property on IOffset. I just want to check if that's okay for what you're doing, or will it just move the problem to elsewhere in the code? Paul
swemaniac Posted February 25, 2015 Author Report Posted February 25, 2015 Hi, Thank you I appreciate your input and you got a valid point, and I can explain why making IOffset public would make life easier for me if it interests you: Consider a simple dictionary of offset addresses and offsets. Right now it has to be declared as such: readonly IDictionary<int, object> Offsets = new Dictionary<int, object>(); (As there are no common public denominator). Could be done with dynamic as well. Then consider the method needed to retrieve an Offset<T> value from that dictionary. The easy implementation is if T is known (compile time): T GetOffsetValue<T>(int address) { object offset; if (Offsets.TryGetValue(address, out offset)) { if (offset is Offset<T>) return ((Offset<T>)offset).Value; else throw new InvalidOperationException(string.Format("Value of requested offset {0} does not match the expected type {1}", address, typeof(T))); } Offsets.Add(address, new Offset<T>(address)); return default(T); } But if you don't know T (as in getting an Offset from a Type), the code becomes a little messier (note that the generic type creation isn't strictly needed to see if the value is of the correct type, but it enforces the dictionary type to be Offset<>): object GetInternalOffsetValue(int address, Type type) { object tempOffset; var offsetBaseType = typeof(Offset<>); var offsetType = offsetBaseType.MakeGenericType(type); if (Offsets.TryGetValue(address, out tempOffset)) { if (offsetType.IsInstanceOfType(tempOffset)) return tempOffset.GetType().GetProperty("Value").GetGetMethod().Invoke(tempOffset, null); else throw new InvalidOperationException(string.Format("Value of requested offset {0} does not match the expected type {1}", address, type)); } var newOffset = Activator.CreateInstance(offsetType); Offsets.Add(address, newOffset); if (type.IsValueType) return Activator.CreateInstance(type); return null; } I am especially unfond of the magic string needed to get the value :) If IOffset was public, the dictionary can be strongly typed, and the second method can be trivialized by comparing the data type directly instead of having to create a generic type on the fly, and the offset value can be accessed directly via IOffset.Value rather than relying on a reflected magic string "Value".
Paul Henty Posted February 25, 2015 Report Posted February 25, 2015 return tempOffset.GetType().GetProperty("Value").GetGetMethod().Invoke(tempOffset, null); Ouch! Yes, I see the problem now. New version attached with public IOffset. Thanks for the explanation, it's always interesting to hear how people are using the DLL, especially if it's beyond the basic usage. Thinking more about your requirements, I could create a non-generics version of Offset and let you specify the address and length then just get the raw bytes back. I could put methods like GetInt32(), GetDouble() etc to convert the raw bytes to a proper type. A bit like the DataReader for database connections if you're familiar with that. Would that make it even easier for you? The generics are great for the normal usage where you know the offsets at design time, but I can see that they get in the way if you're creating offsets at runtime. Paul FSUIPCClient3.0_BETA.zip
swemaniac Posted February 25, 2015 Author Report Posted February 25, 2015 Thanks Paul! I like the idea of a more "raw" offset object that's untyped. How about "Offset(address, length)" with "T Get<T>" and "object Get(Type)" or something to accommodate both generics and non-generics users? I think it's pretty common to see overloads for "Type" methods wherever there's a generic method/class. You could still have the helper methods GetInt32() etc, but I prefer giving a type (again, easier in runtime). Then we could do: var offset = new Offset(0x345, 10); byte[] raw = offset.Value; string val = offset.Get<string>(); string val2 = (string)offset.Get(typeof(string));
Paul Henty Posted February 26, 2015 Report Posted February 26, 2015 string val = offset.Get<string>(); Yes, I like that better; it's much cleaner. To do this properly the plain non-generic offset should be the base class to the generic offsets (so a replacement for IOffset). This will need a bit of refactoring work in the DLL, so I'll need a week or so to do this. Just giving you fair warning as it'll be a breaking change if you decide to continue down the IOffset route for now. Thanks, Paul
swemaniac Posted February 26, 2015 Author Report Posted February 26, 2015 Yes sure, please go for it :) Thanks again.
Paul Henty Posted March 3, 2015 Report Posted March 3, 2015 Hi Johan, Attached is a new version with a non-typed Offset class. IOffset has been removed. The declaration is the same for Offset<T>, except that all constructors require the length to be specified. Instead of the strongly-typed Value property that Offset<T> has, you get and set the value via two methods: GetValue<T>() Gets the value from the offset, converting the raw bytes into type T. The only types allowed here are those allowed for Offset<T>. Note that if you want access to the raw byte array you can pass Byte[] as T. SetValue(object NewValue) Creates the raw bytes from whatever object is passed in. Again the types are limited to those allowed for Offset<T>. You can just pass a raw byte array if you need to. Writing works the same way as before; after calling SetValue() the offset will be in Write mode for the next process(). Below is some sample code showing two uses of the new class. private Offset avionics = new myOffset(0x2E80, 4); private Offset aircraftName = new Offset(0x3D00, 256); private void read_Click(object sender, EventArgs e) { FSUIPCConnection.Process(); this.checkBox1.Checked = (avionics.GetValue<int>() == 1); this.label1.Text = aircraftName.GetValue<string>(); } private void write_Click(object sender, EventArgs e) { avionics.SetValue(this.checkBox1.Checked ? 1 : 0); FSUIPCConnection.Process(); } I haven't 'sealed' the Offset class so if you need to store your own information about the offsets, or add your own methods, you can inherit it and make your own offset class. Paul
swemaniac Posted March 3, 2015 Author Report Posted March 3, 2015 Thank you for your work Paul, I'll try it out tonight! Looks great.
swemaniac Posted March 3, 2015 Author Report Posted March 3, 2015 Paul, I'm missing the thing that was going to help me access offset values at runtime without knowing the underlying type. I.e. I know the type I need to access, but i can't use `T` at runtime. I need to be able to get an `object` (as explained above in my examples) so that I can convert it to a type. E.g. `Convert.ChangeType(offset.GetValue(), type)` (where type is for instance, typeof(int)). Could you perhaps include a non-generic version of the `GetValue` method that returns the value T as an `object`?
Paul Henty Posted March 3, 2015 Report Posted March 3, 2015 Could you perhaps include a non-generic version of the `GetValue` method that returns the value T as an `object`? Okay - from your previous post you have this: string val2 = (string)offset.Get(typeof(string)); Is this what you want? object Offset.GetValue(Type returnAsType); Paul
swemaniac Posted March 3, 2015 Author Report Posted March 3, 2015 Sorry, yes. The scenario I'm referring to is basically this: // creating the offset var offset = new Offset(123, 4); // or var offset = new Offset<int>(123); // somewhere else, getting the value back of a type that's determined at runtime internal object GetInternalOffsetValue(int address, Type type) return offset.GetValue(type); Does it make sense to you to add that method? The GetValue<T> is still very useful to me, it's just I need the non-generic method as well to make this work.
Paul Henty Posted March 3, 2015 Report Posted March 3, 2015 Okay I get it now. :-) New version attached with the non generics GetValue. Paul FSUIPCClient3.0_BETA.zip
swemaniac Posted March 3, 2015 Author Report Posted March 3, 2015 Hey thanks Paul, I really appreciate it!
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now