|
|
CF GAC, Loader, Interop, etc...
-
I knew it'd been a while since I'd updated my blog, but the realization that it'd had been nearly a year since my last post shocked me some. I mean, I've told people it'd been nearly a year, but didn't really believe it until I saw the date myself. Well, it's time to rectify that problem and re-emerge onto the blogging scene.
I've been working on a project over the last several months called "Play". Those of you who attended MEDC 2005 or Gamefest 2005 may have seen demos built on Play. It's a Peer to Peer Gaming Infrastructure, written on the Compact Framework, which allows for pluggable games and transports. It maintains your buddy lists and lets you play multiple games (both 2D Winforms Games and Managed Direct3D Mobile games). A lot of interesting things have come out of the Play work, and I plan to start blogging regularly both on Play, and another project of mine (Aquarium.NET) which you may have seen at MEDC 2004 or PDC 2003.
So, without further adieu, my first blog post from Play is a simple ResourceHelper class (http://blogs.msdn.com/jehance/archive/2005/07/01/434716.aspx). Retrieving resources can be a pain on CF, so I developed this class in Play to make things a little easier. It scans the resources from the calling assembly and will find case insensitive full and partial matches, and can even return both streams and byte arrays.
My next post will be for playing sound, so stay tuned.
|
-
using System; using System.Reflection; using System.IO; using System.Runtime.CompilerServices;
/// <summary> /// A simple helper class for extracting Resources from the /// calling assembly /// </summary>
public class ResourceHelper { private static Stream GetStream(Assembly assembly, string ResourceName) { if (ResourceName != null && ResourceName.Trim() != string.Empty) { ResourceName = ResourceName.Trim(); foreach (string name in assembly.GetManifestResourceNames()) if (string.Compare(ResourceName, name, true) == 0) return assembly.GetManifestResourceStream(name);
ResourceName = ResourceName.ToUpper(); foreach (string name in assembly.GetManifestResourceNames()) if (name.ToUpper().IndexOf(ResourceName) >= 0; return assembly.GetManifestResourceStream(name); }
return new MemoryStream(new byte[0], false); }
/// <summary> /// Returns the requested resource as a Stream /// </summary> /// <param name="ResourceName"> /// The name of the resource to retrieve /// </param> /// <returns> /// A stream for the requested resource on success. /// An empty stream on failure /// </returns> /// <remarks> /// GetStream first searches for an exact, case insensitive /// match to ResourceName. If the initial search fails,| /// GetStream will search again and return the first /// resource for which ResourceName is a case insensitive /// sub-string /// </remarks> [MethodImpl(MethodImplOptions.NoInlining)] public static Stream GetStream(string ResourceName) { return GetStream(Assembly.GetCallingAssembly(), ResourceName); }
/// <summary> /// Returns the requested resource as a byte[] /// </summary> /// <param name="ResourceName"> /// The name of the resource to retrieve /// </param> /// <returns> /// A byte[] for the requested resource on success. /// An empty byte[] on failure</returns> /// <remarks> /// GetBytes first searches for an exact, case insensitive /// match to ResourceName. If the initial search fails, /// GetBytes will search again and return the first /// resource for which ResourceName is a case insensitive /// sub-string /// </remarks> [MethodImpl(MethodImplOptions.NoInlining)] public static byte[] GetBytes(string ResourceName) { Stream s = GetStream(Assembly.GetCallingAssembly(), ResourceName); byte[] bytes = new byte[s.Length]; s.Read(bytes, 0, bytes.Length); s.Close(); return bytes; } }
--- jeh This post is provided “As Is”, with no warranties, and confers no rights.
|
-
New to the .NET Compact Framework in Whidbey Beta 1 is a feature known as COM Interop. Among other things, this feature allows simple managed access to a number of Native APIs, including the Pocket Outlook Object Model (POOM). Steven has posted a great article on getting POOM into your Whidbey project (I highly recommend reading it here.) I thought I'd take some time to focus on Step 4 of Steven's article, "Write your POOM application". After all, once you've gotten the PocketOutlook Interop Assembly references, it's time to do something with it. Two resources I've found to be invaluable are the Pocket Outlook Object Model Documentation at MSDN, and the Object Browser within Whidbey itself. If you're not familiar with the Object Browser, take some time to get acquainted with it once you've referenced the Pocket Outlook Interop Assembly. All assemblies and their namespaces are listed in the browser.  By expanding the namespace, you can look at individual classes and interfaces, and see not only their methods, by also the signatures as they have been imported by TLBIMP. And, of course, through the magic that is intellisense, you get full intellisense for the POOM classes and methods. For my application, I chose to write a simple managed Task Viewer. One drawback of the task viewer that ships with Windows Mobile is that there's no way to both view and edit tasks at the same time. With approximately 2 hours of coding, I was able to produce the application pictured to the left. The ListView shows all the tasks on the system (the same tasks that will show in the task viewer that ships with Windows Mobile). The fields at the bottom will allow the user to add new tasks, or edit existing tasks after selecting them in the ListView. Using the context menu on the ListView, the user can mark tasks as complete (which highlights them with a dark grey), or delete them. Changes made to the tasks via this application are reflected in the task viewer integrated into Windows Mobile. The app stores no information internally, but rather makes all updates, and retrieves all displayed items, directly with POOM. The code for this program (not counting that code automatically generated by the Windows Designer) is well under 300 lines (including white space for readability), and as you can well imagine, the majority of the code I wrote manipulates the UI. Less than 40 lines of C# code were required to manipulate POOM in the ways I've described. The code required to activate the POOM Application object, logon, and retrieve the the Task Folder is as simple as: // Get the application class and the tasksFolder ApplicationClass outlookApp = new ApplicationClass(); outlookApp.Logon(0); IFolder tasksFolder = outlookApp.GetDefaultFolder(OlDefaultFolders.olFolderTasks); You can find TaskViewer.cs here. Please feel free to send me any questions regarding this application, or using COM Interop with POOM.
|
-
It is my intention to keep this blog parimarily technical, but I feel the need to make an off topic post today.
For those that do not know, today is the 35th Anniversary of Neil Armstrong's famous first step out onto the moon, one of a few truly monumental events in the history of our world. If you turn on the TV or listen to the radio today, you're certain to hear at least some snippet about the anniversary, as well you should.
I've known about the upcoming anniversary for days, but I was reminded in the car this morning while driving my step-daughter to the airport. I was again reminded while watching the news at the airport (during the inevitable delay before boarding). I was amazed, and frankly somewhat dismayed, when the CNN Anchor asked people to email in to discuss whether or not we should continue to push to the moon and, possibly, beyond.
And, I must admit, I was quite shocked to see the numbers of people that responded crying an emphatic “no”, citing cost, risk, technology, and 'lack of need' as the barriers to our continued push into this vast frontier.
There are certainly numerous 'reasons' to continue our efforts to push into space:
- The human race continues to grow, with no end in sight. With our growth, we expand, and push into those areas that were once our farm lands and our ranches, our wild territory that belongs to the other inhabitants of our planet. Our need for room to live will eventually overwhelm our tiny planet, stifle, and kill it, unless we find other places which can support us and those things we need.
- As we grow, we consume natural resources at ever greater rates. Even our renewable resources need space in which to renew, and we consume that space. We must eventually find new wealth springs of these resources, or at the very least room in which these resources may renew.
- The discoveries and technologies that have come both directly and indirectly from our push into the unknown reaches of space are many, and significant. Not an aspect of our lives is untouched by these technologies, materials and gadgets, and very few of us can honestly claim we don't consider these advancements fundamental to our way of life... and we have only scratched the surface.
And, yet, the most compelling argument to continue our drive into unknown space is one beyond reason and logic; it is at the very core of what makes us human; it is, quite simply, “because it's there.”
Why do we celebrate Neil Armstrong's titanic first steps onto a new world? Why is he considered a true hero? Why was it a “giant leap for mankind”? The cost of those steps was enourmous, the risks extreme, the technology insufficient, and the need small. Why, then, are men such as he - the Columbus's, the Lewis and Clark's, and the Magellan's of the world, so celebrated, so revered, so well remembered, that their stories are told even centuries after their accomplishments.
Why do small boys and girls dream of one day being astronauts, being the first to step upon the surface of a strange new world?
It is because facing the risks, paying the costs, and looking fear in the eye to discover the unknown are at the very heart of what makes us human... they are what define the human spirit... they are what set us apart from the other animals that share our home. It is these singular men and women that give us hope and fuel our dreams. It is these people that prove we haven't given up, that life is something which, even in the darkest hour, is still worth fighting for.
I, for one, support our exploration of space whole heartedly, and support those men and women that give me, and future generations, a reason to dream..
|
-
Is there anything better than working from home?
I'm sitting on my back porch today, wireless laptop at the ready, 10 month old Chocolate Lab at my feet, staring out at the beautiful rarity known as a “Sunny Seattle Day”. Today, it was my step-daughter's doctor appointment that prompted the day at home. Tomorrow it will be the garage door repairmen.
It's not that I don't work when I stay home. I actually got quite a bit of important work done today. There's just something rejuvenating about a relaxing day on the back porch, with the dog, staring out at the blue between the coding and the emails.
It recharges the batteries in a way that a hectic weekend just can't touch.
But then, I'm weird like that.
|
-
I've been asked more than once how to go about getting the “right” Device ID from managed code. There's a myth floating around (to which, for a short time, I myself subscribed) that a device has multiple Device IDs, or that there's some trick to getting the one “true” Device ID.
Getting the device ID isn't particularly difficult. There are a number of samples floating around for retrieving the ID from native and managed code (my own modest offering can be found here). And, believe it or not, there is only one ID, and really only one way to get at it. Why then all the confusion?
Windows CE specifies that a Device ID is a 2 part critter, where one part is the Platform ID, and the other part is the Preset ID. The Platform ID is suppose to be an identifier that uniquely identifies the type of hardware the device is running on. So, two identical devices should have the same Platform ID.
The Preset ID is suppose to be a number that uniquely identifies a specific unit of a given platform. So, 2 identical ACME PDAs should have the same Platform ID, but different Preset IDs. Two unrelated CE Devices might have (unlikely though it may be) the same Preset IDs, but certainly should not have the same Platform IDs.
On Windows CE, the Platform ID and Preset ID can be any length. The Pocket PC standards say that the 2 IDs together will have a length of 16 bytes, so that the DeviceID can be treated as a Guid. So, let's talk about the Gotchas.
Gotcha #1 Often times, when someone attempts to retrieve the DeviceID, they only pay attention to either the Platform ID or the Preset ID. However, neither ID is, by itself, unique. It's only the combination of IDs that is guaranteed to be unique.
Gotcha #2 Alright, the DeviceID has a length of 16 bytes on PPC, so it can be treated as a GUID. However, there are a number of differing definitions that describe exactly what is inside a GUID. Three common definitions are:
- byte[16];
- int a;
short b; short c; byte[8] d;
- DWORD a;
DWORD b; DWORD c; DWORD d;
All of these pseudo-code definitions are 16 bytes. However, the same data will not necessary be represented the same way in all 3 cases. It just so happens that most platforms are Little-Endian, storing the lowest order byte before the higher order bytes. What does that mean? Well, a DWORD with the value of 1000 (0x00000E38) will actually be stored in memory as 0x38, 0x0E, 0x00, 0x00. Therefore, someone who treats the 4-byte DWORD as a DWORD may get one sequence of bytes whereas someone treating it as a byte[] may get a different sequence of bytes.
As a real world example, I used 2 different utilities to get the Device ID from my iPaq 3760, and got the following results:
Utility 1: FE0CEA0D-000C-0116-0800-57454D325750 Utility 2: FE0CEA0D-0116-000C-4557-00085057324D
You may notice, if you're the kind of person that likes to rearrange things (and, gee, who isn't?) that Utility 2 gives a DeviceID that is simply a jumble of the ID given by Utility 1.
Gotcha #3 Some older PPC devices have a slightly different behavior when it comes to retrieving the ID. The method we all know and love for retrieving the ID (KernelIoControl, IOCTL_HAL_GET_DEVICEID, DEVICE_ID Structure, mutter magic incantation) doesn't work the same on some older devices.
Typically you create a DEVICE_ID structure, which is actually just a large buffer which we pretend is a structure, set its first field to its size, and pass it and its size in to KernelIoControl. Most of the time “viola”, you get a DeviceID (or the Kernel says it needs a bigger buffer).
Unfortunately, the mechanism is broken on some older devices. In fact, on these older devices, if you claim the size of the buffer is anything other than 16 bytes (the size of a GUID), the method fails and gives you nothing. Do not despair! Passing in 16 as the size of the buffer will return you the entire 16 byte Device ID in the buffer, which you can then use as returned.
Gotcha #4 Finally, there is the matter of emulators. Device IDs are one of those things that Windows CE specifies how it should work, and then it's up to the manufacturer of the device to actually implement. So, of course, it's not always available as you would like. In particular, a number of device emulators don't support any form of Device ID retrieval.
--- jeh This post is provided “As Is”, with no warranties, and confers no rights.
|
-
I've received a number of questions regarding how best to retrieve the Device ID. As a result, I've decided to provide the following class for your coding pleasure:
--- jeh This posting is provided “AS IS“, without warranties, and confers no rights.
using System; using System.Runtime.InteropServices;
namespace DeviceID { public sealed class DeviceIDException : Exception { public DeviceIDException() : base() {} public DeviceIDException(string message) : base(message) {} public DeviceIDException(string message, Exception innerException) : base(message, innerException) {} }
/// <summary> /// Summary description for DeviceID. /// </summary> public sealed class DeviceID { private const int GuidLength = 16; private const Int32 ERROR_NOT_SUPPORTED = 0x32; private const Int32 ERROR_INSUFFICIENT_BUFFER = 0x7A; private const Int32 IOCTL_HAL_GET_DEVICEID = 0x01010054;
private byte[] idBytes = null; private Guid idGuid = Guid.Empty; private string idString = String.Empty;
public DeviceID() { byte[] buffer = new byte[20]; bool idLoaded = false; uint bytesReturned;
while(!idLoaded) { Array.Copy(BitConverter.GetBytes(buffer.Length), 0, buffer, 0, 4); try { idLoaded = KernelIoControl(IOCTL_HAL_GET_DEVICEID, 0, 0, buffer, (uint)buffer.Length, out bytesReturned); } catch (Exception e) { throw new DeviceIDException("This platform may not support DeviceIDs", e); }
if(!idLoaded) { int error = Marshal.GetLastWin32Error();
if(error == ERROR_INSUFFICIENT_BUFFER) { buffer = new byte[BitConverter.ToUInt32(buffer, 0)]; } else { // Some older PPC devices only return the ID if the buffer // is exactly the size of a GUID, so attempt to retrieve // the ID this way before throwing an exception buffer = new byte[GuidLength]; idLoaded = KernelIoControl(IOCTL_HAL_GET_DEVICEID, 0, 0, buffer, GuidLength, out bytesReturned);
if(idLoaded) { InitializeFromBytes(buffer); return; } else { if(error == ERROR_NOT_SUPPORTED) { throw new DeviceIDException("This platform does not support DeviceIDs"); } else { throw new DeviceIDException(String.Format( "Error Encountered Retrieving ID (0x{0})", error.ToString("X8"))); } } } } }
int dwPresetIDOffset = BitConverter.ToInt32(buffer, 4); int dwPresetIDBytes = BitConverter.ToInt32(buffer, 8); int dwPlatformIDOffset = BitConverter.ToInt32(buffer, 12); int dwPlatformIDBytes = BitConverter.ToInt32(buffer, 16);
idBytes = new byte[dwPresetIDBytes + dwPlatformIDBytes];
Array.Copy(buffer, dwPresetIDOffset, idBytes, 0, dwPresetIDBytes); Array.Copy(buffer, dwPlatformIDOffset, idBytes, dwPresetIDBytes, dwPlatformIDBytes); }
public DeviceID(Guid g) { idBytes = g.ToByteArray(); idGuid = g; }
public DeviceID(byte[] bytes) { InitializeFromBytes(bytes); }
private void InitializeFromBytes(byte[] bytes) { idBytes = new byte[bytes.Length]; Array.Copy(bytes, 0, idBytes, 0, bytes.Length); }
/// <summary> /// DeviceIDs are only guaranteed to be Guids on PPC. On generic Windows CE, they can be /// any unspecified length /// </summary> public bool IsGuid { get { return (idBytes.Length == GuidLength); } }
public Guid Guid { get { if(IsGuid) { if(idGuid == Guid.Empty) { idGuid = new Guid(idBytes); }
return idGuid; } else { throw new DeviceIDException(String.Format("The DeviceID {0} is not a Guid", ToString())); } } }
public override bool Equals(object obj) { if(obj is DeviceID) { DeviceID rhs = (DeviceID)obj;
if(idBytes.Length == rhs.idBytes.Length) { for(int i = 0; i < idBytes.Length; i++) { if(idBytes[i] != rhs.idBytes[i]) { return false; } }
return true; } }
return false; }
public override int GetHashCode() { if(IsGuid) { return this.Guid.GetHashCode(); } else { // The default GetHashCode for object is guaranteed to // always be the same for a given object, but not for // multiple objects with the same value. We want a HashCode // that will always be the same for a given ID byte[] tempbytes = new byte[16]; if(idBytes.Length > 16) { Array.Copy(idBytes, 0, tempbytes, 0, 16); } else { Array.Clear(tempbytes, 0, 16); // Should be unneccessary Array.Copy(idBytes, 0, tempbytes, 0, idBytes.Length); }
return (new Guid(tempbytes)).GetHashCode(); } }
public override string ToString() { if(idString == String.Empty) { if(IsGuid) { idString = this.Guid.ToString(); } else { idString = "";
for(int i = 0; i < idBytes.Length; i++) { if(i == 4 || i == 6 || i == 8 || i == 10) { idString = String.Format("{0}-{1}", idString, idBytes[i].ToString("x2")); } else { idString = String.Format("{0}{1}", idString, idBytes[i].ToString("x2")); } } } }
return idString; }
public byte[] ToByteArray() { byte[] cpyBytes = new byte[idBytes.Length]; Array.Copy(idBytes, 0, cpyBytes, 0, idBytes.Length); return cpyBytes; }
[DllImport("coredll.dll", SetLastError=true)] private static extern bool KernelIoControl( uint dwIoControlCode, uint lpInBuf /* set to 0 */, uint nInBufSize /* set to 0 */, [In, Out] byte[] lpOutBuf, uint nOutBufSize, out uint lpBytesReturned); } }
|
-
The Desktop Framework and the CF Framework have different Public Key Tokens as part of their strong name. The Desktop framework commonly uses two different Key Tokens (b77a5c561934e089 and b03f5f7f11d50a3a), while CF currently uses one (969db8053d3322ac). CF does not map Desktop references to CF assemblies, so any application referencing Desktop assemblies should never work on the Compact Framework because the references can not be resolved at run time.
“Never?!?” you say? Well, okay, not exactly never. There are two exceptions.
The first exception is mscorlib.dll. Currently, all shipping frameworks (including the Desktop framework) treat any references to any mscorlib as a reference to their own mscorlib. There are multiple reasons for this, but the primary reason is that without mscorlib, an app can not even generate a managed exception, as all the basic types and exception types are housed in mscorlib. Therefore, any app which uses things housed entirely within mscorlib should work on any .NET Framework. That said, this is not a supported scenario and there are still cases where it will fail to work. Primarily, there are things that exist within the Desktop's mscorlib that are not supported on CF. A reference to something not supported on CF will still cause the app to fail when run against CF.
The second exception is a bug that existed both in V1, and V1SP1 of CF, but was fixed in V1SP2 (and will remain fixed moving forward). This bug allowed, under certain circumstances, Desktop references to be retargetted to CF assemblies if the CF assembly was already loaded. This would happen for instance if, in a DLL, you referenced the Desktop System.Windows.Forms, but loaded the CF System.Windows.Forms in the application before using the DLL. Unfortunately, the way that Visual Studio .NET 2003's “Add New Project” wizard is structured, it is very easy to add a Desktop Class Library to a CF Project, so several applications were broken by this bug fix. To add a CF Class Library to an existing CF Project, remember to select “Smart Device Application”, and then select “Class Library” on the first pane of the Wizard.
-- jeh
Disclaimer: This posting is provided “AS IS” with no warranties, and confers no rights
|
-
My name is Jeremy Hance, and I'm an SDET on the Compact Framework team. Officially, SDET = Software Development Engineer in Test. In my position, I write code used to test the functionality of the Compact Framework. Primarily, I work with the Global Assembly Cache (GAC), the Loader, and Native Interop (including COM Interop, which is new to V2).
My intentions for this blog are fairly straight forward. I'm looking for a place where I can post answers to questions I frequently find myself answering, and a place for posting code samples/snippets that I find myself repeatedly providing.
Of course, like most things on the web, blogs are an active and constantly evolving art form, so we'll just have to see where this blog goes.
|
|
|
|