Monthly Archives: June 2010

When Debug.Print Doesn’t Work Anymore

4
Filed under .NET, Troubleshooting, VB Feng Shui

This is one of those “back to basics” posts that, none-the-less, tripped me up for a few minutes, just because it’d been so long since I’d even looking at the setting involved.

I’d been working on a project for a while, when, one day a few weeks back, debug.print just stopped working. Even weirder, when I’d debug, the debugging would completely skip over the Debug.print statement.

When it first happened, I was right in the middle of working through some other problem and didn’t want to get sidetracked. But it hit me again a few weeks later, and I decided to start looking around.

It didn’t take long before I realized this setting was amiss.

cap1

It’s checked in the image above, but in my project, somehow it’d become UNCHECKED, and in the Debug project configuration!?

Still not sure how that happened, other than probably got a little click happy one day with the mouse and didn’t realize it.

Amazing how the little things can still get ya’ sometimes…

(and nobody say anything about my dll base address <g>! )

Printing a Range of Pages from a PDF file, On the Cheap

2
Filed under PDF, Utilities

Lets say you need to print some pages from a PDF file. But you don’t need to print the whole file, just a specific range, say pages 3 to the end of the document.

And let’s say you DON’T have any of those handy (and expensive) PDF libraries available. But of course you do have Acrobat Reader.

And let’s say you’ve got a whole pile of these PDFs that you need to get printed.

What to do?

First with the Printing

A little digging turned up that the AcroRd32.exe application (that actual Adobe Reader app, at least for version 9.x) can be invoked with command line parameters to print a file and exit:

AcroRd32.exe /p /h “filenamehere.pdf”

It’ll print to the current default printer, but that’s good enough for me for this purpose.

Simple enough, but you can’t specify which pages to print.

The Secret Ingredient

I’d almost given up on this when I came across a very handy utility called the PDFToolkit, by Sid Steward. PDFTK is a very small, free, command line driven utility. It won’t print, but it can slice and dice a PDF (or multiple PDFs) just about any which way you can dream up.

Say you have a PDF called input.pdf

You want to print pages 3 to the end of the file.

So, invoke PDFTK like so:

PDFTK.exe A=input.pdf cat A3-end output scratchfile.pdf

and then invoke the AcroRd32.exe line from above on the resulting scratchfile.pdf, and presto, your pdf is printed minus the first two pages!

Granted, if you’re developing a commercial product (or really even an inhouse utility), you’ll be much better served shelling out (no pun intended) a few bucks for a decent PDF library.

But in a pinch, PDFTK and AcroRd32 can be a pretty powerful little combination.

Configuring Log4Net in a .net VSTO Word Addin

3
Filed under Code Garage, Installations, log4net, VB Feng Shui

VSTO (Visual Studio Tools for Office) is a great way to put together addins for the various MS Office applications (especially Word, Excel, and Outlook).

And Log4Net is a fantastic and unbelievably flexible logging framework for .net applications.

So naturally, I wanted to use them together.

And doing so is not bad at all, till it came to configuration…

A Disclaimer (with a note of Encouragement)

Log4Net is not the most straightforward package out there. It’s extremely flexible, and very easy to work with once you’ve gotten used to it, but getting into the Tao of the thing took a few days, for me, anyway.

Don’t let that discourage you. It really is a spectacular framework for handling virtually any aspect of logging in your applications. And it really doesn’t take much “setup code” at all to get it operational.

The Super Highway

The easiest way to configure log4net in a .net application (VSTO addins included) is to simply call Configure on the XMLConfigurator object:

log4net.Config.XmlConfigurator.Configure()

That’ll work, but unfortunately, since your VSTO addin is a DLL, log4net will, by default, look in the current app.config file, which, if you’re running in Word, for instance, will be WinWord.exe.config in the folder where WinWord.exe lives.

Since WinWord.exe.config is Word’s config file, it’s probably not the best idea in the world to go shoe-horning your own (or log4net’s) config stuff in there as well. Not to mention how do you get your config information easily into that file during installation (or properly remove it during an uninstall).

The Scenic Byway

What you really want is for your VSTO addin DLL to have it’s own config file. Something that lives in the same folder as your DLL itself, and can easily be installed and removed.

Sure enough, that Configure method has an overload that accepts a FileInfo structure for an arbitrary XML Config file. So you can just do this:

Dim MyConfigFile = Me.GetType.Assembly.ManifestModule.Name & ".config"
If My.Computer.FileSystem.FileExists(MyConfigFile) Then
    Dim fi = My.Computer.FileSystem.GetFileInfo(MyConfigFile)
    log4net.Config.XmlConfigurator.Configure(fi)
End If

What this does effectively is construct a filename based on the name of whatever assembly the current code is defined within, but with a “.config” extension.

It then checks for the existence of that file. Since there’s no path on the file, it only looks in the current directory, but that’s fine, since the config file will always be located in the same folder as it’s DLL.

And finally, if the file is found, it retrieves a FILEINFO object for it and configures Log4Net with that file.

Bumps along the Road

Unfortunately, that will get you farther, but not by much.

There are two problems.

  1. In debug mode in the IDE, the “current directory” is, indeed, the same folder as the one with your addin DLL in it. But, when running in release mode, in production, with Visual Studio completely out of the picture, the current directory is very likely the folder where WinWord.exe is located. Not good.
  2. Worse, in production, VSTO addins are generally copied to a “shadow cache” folder by the .net framework, so that the original files can be upgraded in place easily while the application is in use.

Unfortunately, your config will will not get copied to the shadow cache.

This means that for determining where to look for your config file, using something like:

  • Me.GetType.Assembly.CodeBase or
  • Me.GetType.Assembly.Location

won’t work, because often times, they’ll point you off into the wilds of the assembly cache folder and not  the \Program Files\MyCompany\MyProduct\ folder where you installed your addin and where you most likely would prefer your MyProduct.dll.config file to live.

Happy Trails

In the end, I found the best solution to be:

  1. Look in the “current directory” for your config file.
  2. If you find it, use it from there. This accommodated easy debugging while in the IDE because you can easily get to and edit your config file.
  3. If you don’t find it, construct the path to the app’s \Program Files\ folder and check there.
  4. If it’s not there either, just fall back to the default and call XMLConfigurator.Configure() with no parameters and let it default everything.

A routine that puts all that together looks like this:

Private Sub ConfigureLog4Net()
        Dim MyConfig = Me.GetType.Assembly.ManifestModule.Name & ".config"
        If Not My.Computer.FileSystem.FileExists(MyConfig) Then
            '---- not in current dir, so check in our Program Files folder
            Dim pth = Path.Combine(My.Computer.FileSystem.SpecialDirectories.ProgramFiles, My.Application.Info.CompanyName)
            pth = Path.Combinepth, My.Application.Info.ProductName)
            MyConfig = System.IO.Path.Combine(pth, MyConfig)
        End If
        If My.Computer.FileSystem.FileExists(MyConfig) Then
            Dim fi = My.Computer.FileSystem.GetFileInfo(MyConfig)
            log4net.Config.XmlConfigurator.Configure(fi)
        Else
            log4net.Config.XmlConfigurator.Configure()
        End If
End Sub

To use this, you’ll need to make sure that your installation package installs your addin DLL and it’s config file into the \Program Files\Company Name\Product Name folder.

This is the pretty typical case, though, so that shouldn’t be a worry.

Later on Down the Road

This obviously begs the next question. What about other application settings? Log4Net reads stuff out of an arbtrary config file  you specify, but the My.Settings object does not. It’ll still end up looking in the default place, which is the host application’s config file (again, WinWord.exe.config, or Excel.exe.config, etc).

I hope to cover a decent solution to that in a later post.

Implementing Document.SaveCopyAs in Word

2
Filed under Office, VB Feng Shui

If you’ve used the Excel object model, youmay have discovered how incredibly handy the SaveCopyAs function is. Essentially, it allows you to save a currently loaded Excel spreadsheet into some arbitrary file, without altering the state of the loaded copy. In other words, if the user has altered the spreadsheet loaded into Excel, but hasn’t saved it, after a “SaveCopyAs”, that copy of the sheet is still considered dirty, and the saved file on disk does not have the user’s changes saved within it.

This is a very wonderful thing!

Basically, it means you can save off the spreadsheet as it currently exists in memory, complete with all the users edits up to this point, perform any operation on that saved copy that you want, and then either keep that copy or throw it away, and the copy that the user is currently working on is not affected in anyway whatsoever!

Good stuff.

But Word has never had it!

I’d rectified this long ago, in VB6, but recently had a need for this capability again, in VB.net this time.

Turns out, it’s incredibly simple to implement with VB.net, and much more intuitive to.

With .net 3.5, you can even create the function as an Extension Method, and directly add it to the Word Document object interface! Very cool!

The thing is, I knew I’d done this before, but couldn’t remember exactly how. After quite some time googling this, I came across a post where it was theorized it might be possible to implement SaveCopyAs by using the Compare function in Word.

Hmm, I’d never even tried the Compare, but it sounded interesting. After a few false starts, I ended up with this:

Imports System.Runtime.CompilerServices

<System.Runtime.CompilerServices.Extension()> _ Public Sub SaveCopyAs(ByVal Document As Word.Document, ByVal FileName As String) Document.Compare(Name:=Document.FullName, CompareTarget:=Microsoft.Office.Interop.Word.WdCompareTarget.wdCompareTargetNew) With Document.Application.ActiveDocument If .Revisions.Count > 0 Then .Revisions.RejectAll() .SaveAs(FileName, AddToRecentFiles:=False) .Close() End If End With Document.Activate() End Sub

Notice that I’m using the CompilerServices.Extension attribute to flag this as an extension method. This is what adds this method directly to the Word Document object and makes it so much more intuitive to use.

Essentially, the idea here is to:

  1. Compare the version of the document loaded in memory to the last saved version out on disk.
  2. Write the comparison result to a new document in memory (i.e. what becomes the active document)
  3. Check the revision count.
  4. If there are revisions, you know there’s been changes made to the document since it was last saved, so Reject all the revisions, save the active document and close it.
  5. If there were no revisions, the user hasn’t made any changes to the document since the last time they saved, so there’s really nothing else to do.
  6. Reactivate the user’s originally active document

The real trick here was step 4. Originally, I was accepting the revisions and then saving, and it wasn’t working at all. Eventually I traced the problem back to the comparison order. It turns out, when you perform a compare to another file from a loaded Document object in Word, the results of the comparison are the changes required to convert from the version of the document you currently have open in Word to the other document.

So, if the user has added the word “Nostradamus” in a paragraph, then the comparison would yield a revision that says, essentially, “remove the word Nostradamus from this paragraph”. This is exactly backward from the behavior you want. Turns out that all I had to do was “Reject all changes” instead.

Believe it or not, this works a dream.

But…

I can’t imagine that performing a compare, especially on a large document, is going to be a particularly speedy process and I knew I’d done this before using a alternate interface that the Word Document object implemented, albeit undocumentedly so. I just couldn’t remember how…

Suffice it to say, eventually I stumbled across the long forgotten interface. That interface is the COM IPersistFile. It’s a basic interface COM typically uses when saving Structured Storage documents, which are what Word files usually are (though, fortunately, it works with Word 2007 for saving DOCX style files).

Turns out the Word Document object implements that interface. They just don’t make that fact common knowledge.

Using it is trivially easy, especially given that it’s a COM interface that the .net framework happens to define natively (though do note, you’ll need to Import the InteropServices.ComTypes Namespace as show below.

Imports System.Runtime.CompilerServices
Imports System.Runtime.InteropServices.ComTypes

<System.Runtime.CompilerServices.Extension()> _ Public Sub SaveCopyAs(ByVal Document As Word.Document, ByVal FileName As String) Dim pf = DirectCast(Document, IPersistFile)  
pf.Save(FileName, False) 'pf.SaveCompleted(FileName) End Sub

About that last commented line, that calls the SaveCompleted function. From the docs, it sounds like that is necessary, but I’ve never found a situation where it made a difference calling it or not.

Maybe someone can illuminate that aspect of the interface better than me?

But at any rate, the IPersistFile method is likely to be many times faster than the Compare method, especially for large files or files that the user has made many changes to without saving. Still, I thought the Compare method was interesting enough to warrant a mention.

So there you have it. SaveCopyAs for Word, implemented as an extension method that directly extends the Word Document object.

Yet more VB.net sweetness!

VB.net Instance Variable Initializers vs the Constructor

0
Filed under VB Feng Shui

I ran into a very peculiar problem several days ago. Essentially, I had a class, with a few properties. One of those properties had some initialization code for a backing variable, like so:

   Public Property Count() As Integer
      Get
         Return _count
      End Get
      Set(ByVal Value As Integer)
         _count = Value
      End Set
   End Property
   Private _count As Integer = _collection.Count

Now, granted, this is a little contrived. But the idea is that this particular property is initialized with a value from a private object (the _collection variable), that is set up during the constructor.

The problem was that the app was throwing an object not initialized when try to initially set the _count variable’s value.

This completely threw me. How on earth was my constructor not being called? After some debugging, I quickly discovered that the constructor is called after all these private variable initialization lines are executed! Now, that doesn’t really make a whole lot of sense, but, it’s what’s happening.

Only later did I come across this post by Patrick Steele. He’s got a great explanation and description of what’s happening there, so I won’t repeat that.

But the key to the whole issue is a quote by him from the Visual Basic Language Specification:

When a constructor’s first statement is of the form MyBase.New(…), the constructor implicitly performs the initializations specified by the variable initializers of the instance variables declared in the type. This corresponds to a sequence of assignments that are executed immediately after invoking the direct base type constructor. Such ordering ensures that all base instance variables are initialized by their variable initializers before any statements that have access to the instance are executed.

(The emphasis is Patrick’s)

He goes on to quote the relevant part of the C# spec, which explicitly states exactly the opposite behavior for it’s initializers and constructors!

I haven’t been able to find any quote as to why there’s a difference in behavior, or more specifically, why on earth VB’s behavior in this case seems so wrong.

If anyone knows, please comment!