Don't you just hate it when you have some code that works fine in your development environment but not when you hand it to testers?
Loyal readers might recall that I have been migrating a bunch of legacy Win32 code to .Net. In the case of our GUI editor, that people use to map out their business processes, I have been writing a C# COM component that gets called from the legacy Delphi.Win32 code. Gradually more and more functionality is migrating to C#.
Along the way we have been storing the user's business process map in an OpenXML package. This is basically a ZIP file containing images, the model XML and bits of XML specifying how things are connected. Then it's a case of using System.IO.Packaging to get at it all.
Testing discovered that some larger client models were failing to upgrade with the mysterious error message "Unable to determine the identity of domain". This turns out to have nothing to do with Windows domains or user permissions.
As with all things .Net, someone somewhere has no doubt hit this problem before. Kevin Rohrbaugh had a similar scenario
http://www.coderoni.com/2008/04/04/server-side-office-document-generation-bug/. His workaround was to ensure that they were running under an account that did not have a profile on the local machine (more below). That's not an option for us.
I'll spare you a gruesome recap of the full debugging which involves much "spelunking" with Reflector as Kevin so aptly calls it. Here are the broad strokes:
When you get a stream on a part in an OpenXML package (so that you can get at the uncompressed bits), you think you are getting something in-memory. However, if the package part is too big (more than 1.3Mb compressed), the framework decides to unzip the entire package part to disk and to give you a handle on that instead. This has unforeseen performance consequences, but we'll ignore them for now.
Where does it unzip them to? Isolated Storage. Exactly what kind of isolated storage depends on the user account you are running under. If you are running under a user account that doesn't have a profile on the local machine, the framework uses a machine-scoped location. If you are running under an account that does have a profile on the local machine (as we will be -- it'll be running under the account of the user of the GUI tool) it uses a user-scoped account.
If I add a reference to the assembly containing the code that accesses the package, and run it in the debugger all is well. But under COM all is far from well.
Running reflector on the Isolated Package class shows that when using user-scoped isolated storage, the framework examines the "evidence" of the AppDomain (where an AppDomain is a lightweight process). Under COM, we are running in a DefaultDomain that doesn't have any evidence.
You can't set the evidence for an AppDomain once it has been started and it's not possible to specify that the COM DLL should run with certain evidence or in a special AppDomain. Running the "Microsoft .NET Framework 2.0 Configuration" tool and granting Full Trust to our assembly doesn't solve the problem because there is still no "evidence" for the Framework code to examine. So there are two options:
1) In the Win32 code, host the CLR, create an AppDomain with the appropriate evidence and load the assembly. Use reflection to get at the methods.
2) (What I did in the interests of expediency). In the COM component, create a new AppDomain with the appropriate evidence. and execute the code in that. This works fine. There is a performance hit because we are now marshaling across AppDomains as well as marshaling across COM. We will see if the performance is acceptable. If not we will have to go with (1).
Doing (2) is similar to what you have to do for Office add-ins. For Office add-ins, the recommended strategy to satisfy the security model is to have an unmanaged shim. You sign the shim to make Office happy. Office talks to the shim, the shim acts as a proxy passing everything to your managed code.
In our case, the COM interface now loads up the AppDomain and proxies calls to an instance of our class running in that AppDomain.
The crucial (necessary and sufficient) piece of evidence is that we require the code to be running in the MyComputer zone.
First we need a simple AppDomainSetup:
AppDomainSetup setup = new AppDomainSetup();
setup.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory.ToString();
Then we need our evidence
Evidence evidence = new Evidence();
evidence.AddHost(new Zone(SecurityZone.MyComputer));
Now we can fire up an AppDomain running with that evidence.
AppDomain hostedAppDomain = AppDomain.CreateDomain("Demo", evidence, setup);
Now we get a handle on an instance of our class running in that AppDomain
ObjectHandle handle = hostedAppDomain.CreateInstance("MyStuff.Demo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=d0e8b069449d61a1", "MyStuff.Demo.DemoComponent");
To pull this off, DemoComponent has to inherit from MarshalByRefObject so now we have a little .Net remoting magic to do. We have to get a lease on the object and extend its lease if we are not done with it. A trivial class LicenseRenewer that implements ISponsor does the trick
lease = (ILease)handle.GetLifetimeService();
lease.Register(leaseRenewer);
Finally we can get a usable instance of the class. We access this locally and it transparently proxies calls to the other AppDomain.
demoComponent = (IDemoComponent)handle.Unwrap();
Now calls to our COM interface can just explicitly proxy to demoComponent e.g.
public bool Demo()
{
return demoComponent.Demo();
}
Then our COM interface just proxies things to demoComponent. Any types you want to marshall across AppDomains have to marked [Serializable()] of course.
Monday, 15 December 2008
Tuesday, 30 September 2008
Open XML
I am working on a report that a few customers are very keen on. We take the description of roles and business processes and produce a role/task matrix to show which roles can perform which operations in the business.
The HTML version of the output is a thing of beauty if I say so myself. It has lots of merged rows so that we can represent a process consisting of several stages then in each cell in the matrix we have a pretty icon that shows the effective permissions.
Now the task is to produce the same thing in Excel. We'll be targeting Excel 2007, which means using the Open XML package format. No problem there. As mentioned in earlier postings, I have been converting our file formats to that format anyway. But now it's time to grapple with the issue of images in Excel. When you think of a tabular representation of data like this in HTML, you think of cells containing (references to) images. In Excel, however, the cells contain data and somewhere else altogether is some DrawingML that specifies what the icons look like and where they go.
I have started doing some investigation of the DrawingML but quotes like this make me think I might have a steep learning curve:
http://openxmldeveloper.org/articles/2327.aspx
"The attribute “editAs” specifies how the drawing object is moved/resized when the rows and columns are resized. For the possible values and their details, refer to page 4765 of the ECMA Open XML specification document."
Ah yes, the infamous 6,000 page document submitted for standards approval. I sense some work ahead of me.
The HTML version of the output is a thing of beauty if I say so myself. It has lots of merged rows so that we can represent a process consisting of several stages then in each cell in the matrix we have a pretty icon that shows the effective permissions.
Now the task is to produce the same thing in Excel. We'll be targeting Excel 2007, which means using the Open XML package format. No problem there. As mentioned in earlier postings, I have been converting our file formats to that format anyway. But now it's time to grapple with the issue of images in Excel. When you think of a tabular representation of data like this in HTML, you think of cells containing (references to) images. In Excel, however, the cells contain data and somewhere else altogether is some DrawingML that specifies what the icons look like and where they go.
I have started doing some investigation of the DrawingML but quotes like this make me think I might have a steep learning curve:
http://openxmldeveloper.org/articles/2327.aspx
"The attribute “editAs” specifies how the drawing object is moved/resized when the rows and columns are resized. For the possible values and their details, refer to page 4765 of the ECMA Open XML specification document."
Ah yes, the infamous 6,000 page document submitted for standards approval. I sense some work ahead of me.
Thursday, 28 August 2008
Start 'em young
My 6.5 year old son was off school sick yesterday. I decided it's time to introduce him to the joys of coding. What better language than Logo to get someone started. I know, I know, it's really a symbolic processing language with a noble Lisp pedigree and so much more than turtle graphics, but turtle graphics seemed like a good place to start.
We used a browser-based Logo implementation at http://www.calormen.com/Logo. It's a site that won't win any beauty contests, but it seemed fairly functional. I explained the underlying metaphor of the turtle with a pen that can be raised or lowered and showed him how to draw a few simple things by a sequence of commands e.g. fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 fd 100. That seemed to make sense once I explained how 90 degrees meant going from vertical to horizontal. This is the kid who asked me when he was four if two dots could ever make a curve, so I figured he'd be good.
He immediately wanted to try a few things, like moving the turtle forward 1,000. I just let him try and see. Whoa! The turtle goes out of the visible region. We cleared the screen (cs) and tried some more things. What happens if you go forward a really large number like 100000000000000. I was expecting an error but no it just went out of the visible region. What happens if you turn right one degree and draw?
Interestingly he said he was "asking" the turtle to do things although after listening to me referring to "commands" he started to say he was "commanding" it to do things. I left him to experiment on his own for a while.
Perhaps next time we'll do some control structures. Perhaps something simple like repeat 4 [ rt 90 fd 100].
We used a browser-based Logo implementation at http://www.calormen.com/Logo. It's a site that won't win any beauty contests, but it seemed fairly functional. I explained the underlying metaphor of the turtle with a pen that can be raised or lowered and showed him how to draw a few simple things by a sequence of commands e.g. fd 100 rt 90 fd 100 rt 90 fd 100 rt 90 fd 100. That seemed to make sense once I explained how 90 degrees meant going from vertical to horizontal. This is the kid who asked me when he was four if two dots could ever make a curve, so I figured he'd be good.
He immediately wanted to try a few things, like moving the turtle forward 1,000. I just let him try and see. Whoa! The turtle goes out of the visible region. We cleared the screen (cs) and tried some more things. What happens if you go forward a really large number like 100000000000000. I was expecting an error but no it just went out of the visible region. What happens if you turn right one degree and draw?
Interestingly he said he was "asking" the turtle to do things although after listening to me referring to "commands" he started to say he was "commanding" it to do things. I left him to experiment on his own for a while.
Perhaps next time we'll do some control structures. Perhaps something simple like repeat 4 [ rt 90 fd 100].
Friday, 1 August 2008
F5 / F9
I am slowly but surely going mad. I am currently grafting some lovely shiny C# 3.5 code on to the side of our legacy Delphi.Win32 code. This means working in Visual Studio and the Delphi IDE simultaneously.
I doubt that I will ever love the Delphi IDE. OK, I am still annoyed that it was crashing 20 times per day (literally -- I took to obsessively recording them) until I applied the service pack, but at least it's stable now. The IDE lacks the most basic functionality: no moving the cursor back to a line before this one, no clicking on a point in the call stack and having it set the frame so you can inspect variables at that point, no examining the values of variables if an exception gets thrown. Still, I don't have to love it. Delphi is obviously circling the drain as a development environment. Their latest schedule of coming features makes it obvious they are a few years behind the curve. Coming real soon, Unicode strings! 64 bit will have to wait until the time of our children's children.
But that's not what's driving me mad. It's the F5/F9 thing. In Visual Studio F5 is run, F9 is set breakpoint. In Delphi it's exactly the opposite. For the past twenty odd years, I have used IDEs where F5 means run, so it's now burned into my brain. Having F5 mean "insert breakpoint" just seems profoundly wrong.
There doesn't seem to be a way to remap the keys in Delphi beyond choosing a Visual Studio emulation mode (it doesn't specify what version of VS). I could remap the keys in Visual Studio, but VS has things the way I like them.
In the interests of fair and balanced whining, I should point out that even Microsoft can't decide about the whole F5/F9 thing. I usually have a folder open on the Global Assembly Cache. I have to hit F5 to refresh it. In Outlook, refreshing (checking for new messages) is F9.
I doubt that I will ever love the Delphi IDE. OK, I am still annoyed that it was crashing 20 times per day (literally -- I took to obsessively recording them) until I applied the service pack, but at least it's stable now. The IDE lacks the most basic functionality: no moving the cursor back to a line before this one, no clicking on a point in the call stack and having it set the frame so you can inspect variables at that point, no examining the values of variables if an exception gets thrown. Still, I don't have to love it. Delphi is obviously circling the drain as a development environment. Their latest schedule of coming features makes it obvious they are a few years behind the curve. Coming real soon, Unicode strings! 64 bit will have to wait until the time of our children's children.
But that's not what's driving me mad. It's the F5/F9 thing. In Visual Studio F5 is run, F9 is set breakpoint. In Delphi it's exactly the opposite. For the past twenty odd years, I have used IDEs where F5 means run, so it's now burned into my brain. Having F5 mean "insert breakpoint" just seems profoundly wrong.
There doesn't seem to be a way to remap the keys in Delphi beyond choosing a Visual Studio emulation mode (it doesn't specify what version of VS). I could remap the keys in Visual Studio, but VS has things the way I like them.
In the interests of fair and balanced whining, I should point out that even Microsoft can't decide about the whole F5/F9 thing. I usually have a folder open on the Global Assembly Cache. I have to hit F5 to refresh it. In Outlook, refreshing (checking for new messages) is F9.
Saturday, 12 July 2008
It's the little things
Yesterday afternoon I was walking up the hill from work, headed to Starbucks to buy some coffee filters.
The area where I work is full of little boutiques that survive on a steady flow of tourists offloaded from package tour buses. One boutique that sells (as near as I can determine from glancing in the window) very expensive letter paper and body parts for Pinocchio dolls of various sizes had a sign in the window saying that it was open and "Back in 5 minutes". The door was ajar. The proprietor was nowhere to be seen. I was greatly amused that they had left the door open so customers could come in from the cold and look around.
Back to the coffee filters. At home we make American-style drip coffee, i.e. what they call "coffee" (with no modifiers) in the US. Coffee filters are something of a specialty item in New Zealand and surprisingly expensive in the supermarkets. At Starbucks the filters are about half the price of the supermarkets.
I picked two boxes of 100 filters and took them to the counter. The cashiers looked at the boxes as if they had never seen them before, turning them around hunting for the price sticker. They rang them up then asked if I wanted them to remove the stickers -- i.e. they thought these novelty items must be gifts. I just chuckled. I declined the special little carry bag too.
I guess we're not in Seattle any more.
The area where I work is full of little boutiques that survive on a steady flow of tourists offloaded from package tour buses. One boutique that sells (as near as I can determine from glancing in the window) very expensive letter paper and body parts for Pinocchio dolls of various sizes had a sign in the window saying that it was open and "Back in 5 minutes". The door was ajar. The proprietor was nowhere to be seen. I was greatly amused that they had left the door open so customers could come in from the cold and look around.
Back to the coffee filters. At home we make American-style drip coffee, i.e. what they call "coffee" (with no modifiers) in the US. Coffee filters are something of a specialty item in New Zealand and surprisingly expensive in the supermarkets. At Starbucks the filters are about half the price of the supermarkets.
I picked two boxes of 100 filters and took them to the counter. The cashiers looked at the boxes as if they had never seen them before, turning them around hunting for the price sticker. They rang them up then asked if I wanted them to remove the stickers -- i.e. they thought these novelty items must be gifts. I just chuckled. I declined the special little carry bag too.
I guess we're not in Seattle any more.
Tuesday, 8 July 2008
Anticipatory medicine
My 6.5 year old son was asking why doctors can't cure the common cold. I explained that one problem is that the various viruses keep mutating, so you can't just have one medicine to solve the problem. He suggested that the scientists should anticipate the way that the viruses will mutate and make the medicine for that scenario. Then when the viruses mutate the medicine will get them.
OpenXML Packages
I am taking the various files that users refer to when documenting the process flow in their organization and putting them all in an OpenXML package (using System.IO.Packaging).
Office provides an additional layer around these packages to make it a bit easier to produce Office documents (which are just packages with a particular internal structure). See here for the SDK.
The Office SDK makes some things a little easier, although some of the names had me a bit confused at first (CustomXmlPart for example has nothing XML-ish about it, it's just a nice way to put things in the place in a .docx file where Word expects the custom XML to go). But with that extra layer you lose some of the functionality of the underlying System.IO.Packaging. For example, the Office SDK provides some nice functions to add images but you don't get to specify the compression. One JPG file ended up going from 276,216 bytes to 403,084 bytes, i.e. actually getting bigger. Using the underlying System.IO.Packaging, I am able to specify no compression for JPGs -- they have such low entropy all you do is add the overhead of the ZIP housekeeping bits.
Curiously, with both the Office SDK and the underlying classes you have to specify the image type. I couldn't find anything in the DotNet foundation classes to tell me the image type given a stream or even given a file name, so I had to bake something trivial. Fortunately, a lot of the time I know exactly what the image type is anyway.
Office provides an additional layer around these packages to make it a bit easier to produce Office documents (which are just packages with a particular internal structure). See here for the SDK.
The Office SDK makes some things a little easier, although some of the names had me a bit confused at first (CustomXmlPart for example has nothing XML-ish about it, it's just a nice way to put things in the place in a .docx file where Word expects the custom XML to go). But with that extra layer you lose some of the functionality of the underlying System.IO.Packaging. For example, the Office SDK provides some nice functions to add images but you don't get to specify the compression. One JPG file ended up going from 276,216 bytes to 403,084 bytes, i.e. actually getting bigger. Using the underlying System.IO.Packaging, I am able to specify no compression for JPGs -- they have such low entropy all you do is add the overhead of the ZIP housekeeping bits.
Curiously, with both the Office SDK and the underlying classes you have to specify the image type. I couldn't find anything in the DotNet foundation classes to tell me the image type given a stream or even given a file name, so I had to bake something trivial. Fortunately, a lot of the time I know exactly what the image type is anyway.
Subscribe to:
Posts (Atom)