Microsoft .NET Development for Microsoft Office

 

This article is an excerpt from Microsoft .NET Development for Microsoft Office, from Microsoft Press (ISBN 0-7356-2132-2, copyright Microsoft Press 2005, all rights reserved).

The author, Andrew Whitechapel, spent several years on the .NET Enterprise Applications Development team at Microsoft Consulting Services in the United Kingdom. He has extensive experience developing Microsoft Officebased applications with Microsoft .NET managed code. Andrew is now a technical program manager on the Microsoft Visual Studio Tools for Office team.

No part of this chapter may be reproduced, stored in a retrieval system, or transmitted in any form or by any meanselectronic, electrostatic, mechanical, photocopying, recording, or otherwisewithout the prior written permission of the publisher, except in the case of brief quotations embodied in critical articles or reviews.

Download sample code.

Chapter 2: Basics of Office Interoperability (Part 3 of 3)

Contents

2.6 Visual Basic .NET vs. C#
    Optional Parameters and Word Reference Parameters
    Excel Parameterized Properties and Word Accessors
    Accessing Late-Bound Members
2.7 Debugging Interop Solutions
    Use Structured Exception Handling
    Check Assembly Dependencies
Summary

2.6 Visual Basic .NET vs. C#

Note   This section applies to Office 97, Office 2000, Office XP and Office 2003.

All .NET languages are equal citizens, in the sense that they all conform to the Common Language Specification (CLS) and that all will compile to the same intermediate language code. However, each language might provide additional features over and above the CLS and might be optimized to suit a particular development scenario.

We have seen that the Office applications can interop with managed code to a very sophisticated degree. Nonetheless, they were historically never built with managed code and were designed to support programmability internally through Microsoft Visual Basic for Applications (VBA). Even unmanaged languages such as C and C++ that can be used to automate the Office applications (and for add-ins) are often less convenient for programming Office applications than VBA.

Consequently, while there are many advantages to developing for Office in managed code, it is worth noting that some VBA features do not correspond directly to features in managed languages. It is also worth noting that Visual Basic .NET as a language is closer to VBA than C# is.

This is not to suggest that Visual Basic .NET is better for Office interop than C# overall, just that in certain contexts it is slightly more convenient to code in Visual Basic .NET, as summarized here:

  • Passing ref parameters to Word methods
  • Optional parameters (all Office applications)
  • Parameterized properties in Excel
  • Hidden accessor methods in Word
  • Accessing late-bound members

We will explore all these contexts by developing a managed application to automate both Word and Excel. The sample solution contains both Visual Basic .NET and C# versions, and relevant code snippets follow in both languages. Simple .xls and .doc files are supplied with the sample code for use in this exercise, although you can use any .xls and .doc files.

Note   The sample solution for this topic can be found in the sample code at <install location>\Code\Office<n>\ManagedLanguages.

  1. Create a new Windows application (in Visual Basic .NET or C#, whichever you prefer) called InteropCSharp or InteropVB. Add references to the appropriate Excel and Word IAs for the version of Office you're targeting.

  2. Put six buttons on the form, as shown in the following figure, and get a Click event handler for each one. Note that only the first Excel button and the first Word button are initially enabled—we don't want to try getting a range until we know we've opened a workbook or document. Similarly, a lot of Excel and Word dialog boxes are not available unless a workbook or document is open. See Figure 2-15.

    Visual Basic .NET vs. C# open document sample

    Figure 2-15. Visual Basic .NET vs. C# open document sample

  3. In the form class, declare field variables for the Excel and Word Application objects, and the Document and Workbook objects. The C# code is shown here:

        private Excel.Application excelApp; 
        private Word.Application wordApp; 
        private Excel.Workbook book; 
        private Word.Document doc;
    

    The Visual Basic .NET equivalent is shown here:

    Private excelApp As Excel.Application 
    Private wordApp As Word.Application 
    Private book As Excel.Workbook 
    Private doc As Word.Document
    

Optional Parameters and Word Reference Parameters

Now to implement the button Click handlers. Our example will illustrate the use of optional parameters and Word's insistence on passing parameters by reference instead of value. We'll start with the button to open an Excel workbook. Excel's Open method takes a number of parameters, only the first of which is mandatory—the others are optional, but we still need to pass a parameter. In this situation, we can use either a System.Type.Missing or a System.Reflection.Missing.Value, as shown in the following C# code. Note that you'll need to change the path for Products.xls to wherever it actually is on your machine.

    private void btnOpenXls_Click(object sender, System.EventArgs e) 
    { 
        excelApp = new Excel.ApplicationClass(); 
        excelApp.Visible = true; 
        string fileName = @"C:\Temp\ManagedLanguages\Products.xls"; 
        //object missing = System.Type.Missing; 
        object missing = System.Reflection.Missing.Value; 
        book = excelApp.Workbooks.Open(fileName,  
            missing, missing, missing, missing, missing, missing, missing, 
            missing, missing, missing, missing, missing, missing, missing); 
        btnExcelRange.Enabled = true; 
        btnExcelDialog.Enabled = true; 
    }

Note that when we've successfully opened a workbook, we'll enable the remaining Excel-specific buttons. Also note that the code listed here is correct for Office 2003. For earlier versions, there are very slight differences—specifically in the number of parameters to Excel and Word's Open methods.

The Visual Basic .NET equivalent follows. As you can see, we don't need to pass any of the optional parameters that we're not interested in:

Private Sub btnOpenXls_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles btnOpenXls.Click 
excelApp = New Excel.ApplicationClass() 
excelApp.Visible = True 
Dim fileName As String 
fileName = "C:\Temp\ManagedLanguages\Products.xls" 
book = excelApp.Workbooks.Open(fileName) 
btnExcelRange.Enabled = True 
btnExcelDialog.Enabled = True 
End Sub

In this example, we can omit all parameters after the first one. If we want to pass some parameters at arbitrary positions in the parameter list, we can do so by specifying the parameter name. For example, the third parameter governs whether we open the file as read-only; we can specify this (using ":="), omitting the second parameter and all parameters after the third:

book = excelApp.Workbooks.Open(fileName, ReadOnly:=True)

As an aside, note that in this line of code, the ReadOnly parameter is a parameter identifier for the Open function. It's also a keyword in Visual Basic .NET, which is why it is in blue. However, the Visual Basic .NET compiler is smart enough to parse this correctly, so despite the editor's coloring, this line does in fact compile correctly.

To open a Word document, the Open method also takes optional parameters, but the additional point here is that Word requires all these parameters to be passed by reference, not by value:

    private void btnOpenDoc_Click(object sender, System.EventArgs e) 
    { 
        wordApp = new Word.ApplicationClass(); 
        wordApp.Visible = true; 
        object fileName = @"C:\Temp\ManagedLanguages\Jabberwocky.doc"; 
        object missing = Missing.Value; 
        doc = wordApp.Documents.Open(ref fileName,  
            ref missing, ref missing, ref missing, ref missing, ref missing,  
            ref missing, ref missing, ref missing, ref missing, ref missing,  
            ref missing, ref missing, ref missing, ref missing, ref missing); 
        btnWordRange.Enabled = true; 
        btnWordDialog.Enabled = true; 
    }

In Visual Basic .NET, we don't need to specify pass by reference because the compiler will do the extra work and silently translate for us. So, to implement the first Word-specific button on our form to open a document, the Visual Basic .NET equivalent is more streamlined:

Private Sub btnOpenDoc_Click(ByVal sender As System.Object,_
ByVal e As System.EventArgs) Handles btnOpenDoc.Click 
wordApp = New Word.ApplicationClass() 
wordApp.Visible = True 
Dim fileName As String 
fileName = "C:\Temp\oic\ManagedLanguages\Jabberwocky.doc" 
doc = wordApp.Documents.Open(fileName) 
btnWordRange.Enabled = True 
btnWordDialog.Enabled = True 
End Sub

Excel Parameterized Properties and Word Accessors

Excel exposes several parameterized properties—that is, properties that allow you to pass more than one parameter to them. Visual Basic .NET supports passing multiple parameters to properties, but C# does not. The Excel PIA provides a workaround for this limitation by providing get_ and set_ accessor methods as an alternative means of working with the properties.

For example, we'll implement the second Excel button to get an arbitrary range of cells and format them in bold. Note that while the Range object is exposed as a property of the Worksheet object, it is not accessible directly in C#. In the Object Browser, Range is not listed. However, the get_Range and set_Range methods are listed and also show up in IntelliSense because we need to specify more than one value to define a range (such as C2 to C13):

    private void btnExcelRange_Click(object sender, System.EventArgs e) 
    { 
        Excel.Range range = ((Excel.Worksheet)book.ActiveSheet).get_Range("C2", "C13"); 
        range.Font.Bold = true; 
    }

Visual Basic .NET supports passing multiple parameters to properties, so we can implement our second Excel button in Visual Basic .NET to pass both delimiters of the range to the Range property directly:

Private Sub btnExcelRange_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles btnExcelRange.Click 
Dim range As Excel.Range 
range = CType(book.ActiveSheet, Excel.Worksheet).Range("C2", "C13") 
range.Font.Bold = True 
End Sub

Note that C# developers generally know that properties are actually implemented by the compiler as get_ and set_ accessor methods behind the scenes, so this explicit use of get_ and set_ methods won't come as a huge surprise.

Word does not expose parameterized properties. On the other hand, it does still expose accessor methods because many properties in Word are defined to use Variant values. Visual Basic .NET supports Variant values, but C# does not, so to work with a property that uses Variant values in C# you must use the accessor methods.

As an aside, if you were to examine one of these parameterized properties using ILDASM, you'd see that they're attributed with MethodImpl(MethodImplOptions.InternalCall). This attribute can't be applied to a property in C#, but it can be applied to the get_ and set_ accessor methods that implement a property.

In our exercise, we'll implement the second Word button to set the style of the document content. Both the Object Browser and IntelliSense expose the Style property of the Range object in Word. However, if you try to use this property, you'll get a compiler error:

    private void btnWordRange_Click(object sender, System.EventArgs e) 
    { 
        Word.Range range = doc.Content; 
        range.Style = Word.WdStyleType.wdStyleEmphasis; 
    }

Specifically, the compiler will complain with this error message: "Property 'Style' is not supported by the language; try directly calling accessor methods 'get_Style' or 'set_Style'."

It's fortunate that the compiler is so explicit here because these accessor methods are hidden. They are not shown in the Object Browser or in IntelliSense. Although this is a little inconvenient, if you change the code to use the appropriate accessor, all will be well:

    private void btnWordRange_Click(object sender, System.EventArgs e) 
    { 
        Word.Range range = doc.Content; 
        //range.Style = Word.WdStyleType.wdStyleEmphasis; 
        object style = Word.WdBuiltinStyle.wdStyleEmphasis; 
        range.set_Style(ref style); 
    }

In Visual Basic .NET, which is aware of Variant values—we can access the property directly, without using hidden accessors:

Private Sub btnWordRange_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles btnWordRange.Click 
Dim range As Word.Range 
range = doc.Content 
range.Style = Word.WdBuiltinStyle.wdStyleEmphasis 
End Sub

Accessing Late-Bound Members

Visual Basic .NET supports late binding in a way that's almost transparent—much like in VBA. However, this only works if you disable Option Strict. If you don't disable Option Strict (and it is generally recommended that you keep it enabled), coding for late binding in Visual Basic .NET is the same as in C#. This requires you to use a technique that is essentially a subset of the reflection features of .NET. Now, you could use reflection to implement late binding for everything, as demonstrated in Section 3.3, "Using COM by Reflection." However, there's no point in using late binding unless you're forced to.

One situation in which you're forced to use late binding is for Word and Excel built-in dialog boxes. Both Word and Excel offer a large number of internal dialog boxes, none of which are strongly typed. That is, you must use a generic Excel.Dialog interface and a generic Word.Dialog interface for all built-in dialog boxes. There are a couple of reasons for this. First, internal dialog boxes are triggered by a specific sequence of operations (usually a user's menu selection), which can itself be programmed instead of trying to directly invoke the dialog box. Second, the Excel and Word object models have evolved over many years, and implementing operations that are largely internal through late binding is a good strategy for keeping changes to the object model to a minimum.

Excel and Word take different approaches to working with these dialog boxes. The Excel.Dialog interface and the Word.Dialog interface are not the same. The Excel.Dialog interface is very minimal, exposing only three properties and one method. The Show method takes 30 optional parameters, named Arg1 through Arg30. Each specific dialog box takes zero or more actual parameters. So if you want to specify how an Excel dialog box should look and behave, you call the Show method with whatever parameters you can that will modify the dialog box. The full list is supplied in the Excel VBA help under the section Built-In Dialog Box Argument Lists.

We'll implement our third Excel button to display the Style dialog box, and we'll specify that the Bold and Italic check boxes should be checked, by passing true for the first two parameters. This dialog box takes only two parameters, so you cannot modify the appearance of any of the other check boxes, lists, and so forth:

    private void btnExcelDialog_Click(object sender, System.EventArgs e) 
    { 
        Excel.Dialog dlg = excelApp.Dialogs[Excel.XlBuiltInDialog.xlDialogStyle]; 
        object missing = Missing.Value; 
        dlg.Show(true, true, missing, missing, missing, missing, missing, 
            missing, missing, missing, missing, missing, missing, missing, 
            missing, missing, missing, missing, missing, missing, missing, 
            missing, missing, missing, missing, missing, missing, missing, 
            missing, missing); 
    }

Note that the Dialogs collections in Word introduced a default property (allowing the collection to be indexed directly) from Word 2002 (Word XP). Prior to that, you had to use the Item method:

    Word.Dialog dlg = wordApp.Dialogs.Item(Word.WdWordDialog.wdDialogInsertSymbol);

Note also that there is no direct way to execute an Excel dialog box without displaying the dialog box to the user. That is, Excel doesn't encourage you to perform operations by programmatically invoking dialog boxes, making programmatic selections, and programmatically closing the dialog box. Having said that, there is a way you could do it—by using SendKeys to simulate user keyboard input to make choices in the dialog box and simulate the Return key to close the dialog box, and by turning the ScreenUpdating property to false so the dialog box is never visible to the user. This is mentioned for completeness, but it is strongly discouraged—simulating keystrokes leads to localization problems and version upgrade problems (not to mention timing, window focus, and user interface problems).

The Visual Basic .NET equivalent is simpler because, of course, we don't have to pass optional parameters that we don't need. However, regardless of language, we're still limited by the properties that Excel exposes for any given dialog box:

Private Sub btnExcelDialog_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles btnExcelDialog.Click 
Dim dlg As Excel.Dialog 
dlg = excelApp.Dialogs(Excel.XlBuiltInDialog.xlDialogStyle) 
dlg.Show(True, True) 
End Sub

The pre–Word 2002 access to a Dialog from the Dialogs collection in Visual Basic .NET is shown here:

dlg = wordApp.Dialogs.Item(Word.WdWordDialog.wdDialogInsertSymbol)

The Word Dialog interface is slightly more complex than Excel's—there are six properties and four methods. The Show method takes only one optional parameter—a timeout value in milliseconds for how long the dialog box should be displayed before closing. If this is omitted, the dialog box is displayed until the user closes it.

So, for example, if we just want to show a Word dialog box (in this example, the Insert Symbol dialog box), we can call the Show method:

    private void btnWordDialog_Click(object sender, System.EventArgs e) 
    { 
        Word.Dialog dlg = wordApp.Dialogs[Word.WdWordDialog.wdDialogInsertSymbol]; 
        object missing = Missing.Value; 
        dlg.Show(ref missing); 
    }

To modify the behavior of a Word dialog box, you must set dialog-specific properties on the dialog box before calling the Show or Execute method. To set dialog-specific properties when all we have is a generic Dialog interface, we must use reflection—specifically, the InvokeMember method of the Type class. (For the COM-heads among you, this behaves pretty much like IDispatch::Invoke.) So we'll implement the third Word button on our form to specify a particular font, tab, and character number in the dialog box, and then we'll execute it. We'll factor out the repetitive code into a new private method.

    private void btnWordDialog_Click(object sender, System.EventArgs e) 
    { 
        Word.Dialog dlg =  
                 wordApp.Dialogs[Word.WdWordDialog.wdDialogInsertSymbol]; 
        object missing = Missing.Value; 
        //dlg.Show(ref missing); 
        InvokeDialog(dlg, "Font", "Wingdings"); 
        InvokeDialog(dlg, "Tab", "Symbols"); 
        InvokeDialog(dlg, "CharNum", 74); 
        dlg.Execute(); 
    } 
 
    private void InvokeDialog(Word.Dialog dlg, string memberName,  
           object memberValue) 
    { 
        Type dlgType = dlg.GetType(); 
        dlgType.InvokeMember(memberName,  
            BindingFlags.SetProperty | BindingFlags.Public |   
                   BindingFlags.Instance, 
            null, dlg, new object[]{memberValue}); 
    }

The result at run time is that the dialog box is silently invoked (with our chosen property values), executed, and closed. We will get character 74 from the Wingdings font inserted into our document, and the user will never see the dialog box. (As with most of the code in this book, this should all be done wrapped in try/catch exception handling, which has generally been omitted for brevity. This is particularly important when you're using late binding—by definition you won't get any help from the compiler to ensure you're doing the right things.)

The Visual Basic .NET code required if Option Strict is left enabled is essentially the same as for the C# version, using Type.InvokeMember. However, if Option Strict is turned off, Visual Basic .NET allows us to access late-bound members directly:

Private Sub btnWordDialog_Click(ByVal sender As System.Object, _ 
ByVal e As System.EventArgs) Handles btnWordDialog.Click 
Dim dlg As Word.Dialog 
dlg = wordApp.Dialogs(Word.WdWordDialog.wdDialogInsertSymbol) 
dlg.Font = "Wingdings" 
dlg.Tab = "Symbols" 
dlg.CharNum = 74 
dlg.Execute() 
End Sub

In conclusion, a few features of Visual Basic .NET make it superficially more convenient to develop for Office interop than C#. Most of these features involve the Visual Basic .NET compiler doing extra work for us. In the case of parameterized Excel properties, it actually involves the Visual Basic .NET compiler not doing something that the C# compiler does. This reliance on extra compiler work is a technique that's familiar to Visual Basic and VBA developers, but many developers insist on a finer degree of control over their code. It comes down to a question of personal taste. Of the five issues listed at the beginning of this section, only one—the ability to omit optional parameters—is really at all useful. C# developers tend to work around this by writing one-time wrapper methods that they can call and by passing only the parameters they're interested in.

To round out the picture here, it's worth noting that other differences exist between C# and Visual Basic .NET that are not directly related to Office interop. These are language differences and differences in usage. The language differences are listed in Table 2-3.

Table 2-3   Summary of Differences Between Visual Basic .NET and C#

Visual Basic .NET C#
Fully CLS compliant:

Case-insensitive

Unsigned integers only

No unsafe code; no pointers

Offers non-CLS-compliant extensions:

Case-sensitive

Supports signed byte, unsigned short, unsigned int, unsigned long

unsafe code, pointers, sizeof

  Automatic documentation through XML comments
  readonly member variables
  static constructors
  checked/unchecked code
Exponentiation operator: ^  
Redim: allows you to reallocate an array  
  Pre/post inc/decrement operators: ++ --
  Compound modulus operator: %=
  Compound bitwise AND, XOR, OR
operators:

&=    ^=    |=   

Visual Basic .NET 2003 does include
bitshifts:

<<    >>    

Bitwise shift and compound bitwise shift operators:

<<    >>    <<=    >>=

Structured exception handling (try, catch) and unstructured exception handling (On Error GoTo, On Error Resume Next) Structured exception handling (try, catch) only
Extra parentheses to force passing parameters by value (including reference types)  
Optional parameters  
Late binding supported by both implicit reflection helper and explicit reflection Late binding possible through explicit reflection
Handles: allows you to specify the events for which an event procedure may be called  
With: evaluates an object expression once to access multiple members  
Static: use Static for local variables and Shared for "static" class members static: for class members only
  volatile: allows an object to be modified asynchronously

Planned changes (at time of writing, next release of Visual Studio .NET 2005):

Visual Basic .NET C#
Generics: like C++ templates Generics: like C++ templates
  Iterators: like STL iterators
  Anonymous methods: dynamically defined delegates
Partial types: allows you to split class code across multiple files Partial types: allows you to split class code across multiple files

The main differences in terms of usage are as follows:

  • There is an ECMA/ISO standard for C# but not for Visual Basic .NET. An open standard is likely to encourage wider use, more support from third-party tool and library vendors, and more cross-platform use.
  • As the planned changes just shown seem to indicate, C# is likely to be the initial .NET target for any significant advancements in language design—from Microsoft, industry momentum, or ECMA/ISO contributors.
  • For some developers (Visual Basic 6.0 developers, scripting language developers, and new developers), the Visual Basic .NET learning curve is generally simpler due to:
    • .NET Framework Visual Basic compatibility "shortcuts"
    • The .NET Framework provides "classic" Visual Basic facade classes that provide shortcuts to underlying .NET behavior.
    • Additional support from the Visual Studio IDE
    • More prevalent IntelliSense (such as for type browsing in variables or method declarations), default "hiding" of automatically generated code, and greater use of autocomplete.
    • Verbose "English" grammar
    • Limited use of control/escape characters, delimiters, and special processing instructions.
    • For C/C++, Java, Perl, and other terse language developers, C# might be more immediately productive because of its structural similarity.
    • Many Visual Basic 6.0 developers move to C# when moving to .NET. C# offers the ease of development of Visual Basic 6.0 combined with the power of C++. Visual Basic 6.0 developers who had previously resisted moving to C++ or Java because of the inherent pitfalls of these languages don't see the same pitfalls with C#.

2.7 Debugging Interop Solutions

Note   This section applies to Office 97, Office 2000, Office XP and Office 2003.

For each of the specific technologies that you can use to develop Office solutions with managed code, there are specific debugging issues. Each of these will be discussed in the appropriate chapter throughout this book. That said, a few general debugging techniques—and debugging and code-analysis tools—can be applied across all technologies. These are discussed in this section.

Note   The sample solution for this topic can be found in the sample code at this location: <install location>\Code\Office<n>\SimpleBug.

Use Structured Exception Handling

To some developers, structured exception handling (SEH) comes naturally, but it depends on your background and the types of languages you are familiar with. In the context of Office development, many developers have a Visual Basic background, where the use of SEH may be relatively unfamiliar.

The SimpleBug solution is a copy of the TestExcelPIA project introduced as the first example in Section 2.2. The only modification is to the index into the Cells collection: we've deliberately used a column index of 257 (which is greater than the maximum number of columns supported by Excel):

    private void runExcel_Click(object sender, System.EventArgs e) 
    { 
        xl = new Microsoft.Office.Interop.Excel.Application(); 
        xl.Visible = true; 
        xl.DisplayAlerts = false; 
        Excel.Workbook book = xl.Workbooks.Add( 
Excel.XlSheetType.xlWorksheet); 
        Excel.Worksheet sheet = (Excel.Worksheet)book.ActiveSheet; 
 
        Excel.Range r = (Excel.Range)sheet.Cells[1,257]; 
        r.Value2 = "Hello World"; 
        quitExcel.Enabled = true; 
    }

When you run this application and click the Run Excel button, Excel will return the error HRESULT, which will be translated into a COMException. The error details are reasonably informative in this case and should allow you to identify the bug in your code. However, another issue is that if the bug appears inconsistently or is determined by something in the environment, it's far better to catch exceptions in your own code so that you can deal with them yourself:

    private void runExcel_Click(object sender, System.EventArgs e) 
    { 
        try 
        { 
            xl = new Microsoft.Office.Interop.Excel.Application(); 
            xl.Visible = true; 
            xl.DisplayAlerts = false; 
 
            Excel.Workbook book = xl.Workbooks.Add( 
Excel.XlSheetType.xlWorksheet); 
            Excel.Worksheet sheet = (Excel.Worksheet)book.ActiveSheet; 
 
            Excel.Range r = (Excel.Range)sheet.Cells[1,257]; 
            r.Value2 = "Hello World"; 
            quitExcel.Enabled = true; 
        } 
        catch (COMException ex) 
        { 
            MessageBox.Show(ex.StackTrace, ex.Message); 
        } 
    }

Using SEH, you can structure your code to deal with multiple anticipated exception types. You can ensure that you clean up memory if an unexpected exception is thrown. You can handle runtime errors in a manner that is useful to users and to operations support. In the example above, we've merely caught the exception and presented a message. From the user's perspective, the net result is not very different from the previous behavior. However, a more useful strategy would be to catch the exception and write details to a log—perhaps the event log or an external file, or some other event logging system.

Note   In many of the code snippets presented in this book, exception-handling code has been omitted. This is not an indication of recommended practice—quite the opposite. It is simply that I've tried to keep the code snippets focused on the main point of the technique under discussion, so exception-handling code has been removed for the sake of brevity.

Check Assembly Dependencies

A problem that often comes up during Office development is that your solution loads a managed assembly that is bound to the wrong dependent assemblies. This typically happens when porting between versions of Office. The interop assemblies (including the PIAs) for Office are version-specific. It is possible to use the IA from one version of Office with a different version, provided that the code is simple and doesn't involve anything that changed in the object model between versions. However, this usage is not supported—you should always use the interop assembly for the specific version of Office that you plan to use in production. An easy way to determine which assemblies any assembly is dependent on—and in fact to determine maximum information about the assembly itself—is to use ILDASM.

ILDASM, the Microsoft Intermediate Language Disassembler, is a tool that ships with Visual Studio .NET. ILDASM takes as input a portable executable (PE) file containing MSIL code (either a .NET module or a .NET assembly) and creates a text file as output. It also offers a GUI display of the assembly, showing the assembly manifest, the type metadata, and the intermediate language (IL) code.

When you're happy with the debugger, it would be worth your while to spend a few minutes getting familiar with ILDASM. You can run ILDASM from a Visual Studio .NET command window, like this:

ildasm SimpleBug.dll

The ILDASM main window displays a treelist of the assembly, as shown in Figure 2-16.

ILDASM main window (click to see larger image)

Figure 2-16. ILDASM main window (click to see larger image)

Of particular interest is the manifest. The manifest contains a list of the dependencies (in this case, Microsoft.Office.Interop.Excel.dll, System.Windows.Forms.dll, System.dll, etc.), as well as a description of the SimpleBug assembly itself.

Among other things, you can use the manifest dump to double-check or troubleshoot to find out which version of the Office PIAs your assembly has linked to. This is particularly useful if you have multiple PIA versions installed and want to be doubly sure. For example, the one shown in Figure 2-17 links to Office 2003 (that is, v11.0.0.0).

ILDASM manifest window (click to see larger image)

Figure 2-17. ILDASM manifest window (click to see larger image)

Many developers find it useful to add ILDASM to the Tools menu in Visual Studio .NET. To do this, from the Tools menu, select External Tools, click the Add button, and type ILDASM (this will be the text of the menu item). Click the Browse button at the end of the Command field to navigate to where ILDASM.exe resides (probably either C:\Program Files\Microsoft.Net\FrameworkSDK\Bin\ILDASM.exe or C:\Program Files\Microsoft.Net\FrameworkSDK\v1.1\Bin\ILDASM.exe). Finally, in the Arguments box, click the right-arrow to get a list of variables; select $(TargetPath) for the current executable. See Figure 2-18.

ILDASM tool

Figure 2-18. ILDASM tool

The Reflector tool, developed by Lutz Roeder, and available for download from www.aisto.com/roeder/dotnet, is a powerful alternative to ILDASM for viewing assembly contents.

Another tool to use in debugging is PerfMon. This offers a number of .NET-specific counters, as well as all the expected operating system and process counters. For example, you can add counters to track the number of times arguments and return values have been marshaled from managed to unmanaged code and vice versa, as shown in Figure 2-19.

The Add Counter feature of the PerfMon tool

Figure 2-19. The Add Counter feature of the PerfMon tool

The ProcessExplorer tool from www.sysinternals.com is another excellent debugging aid. You can use it to check all dependent DLLs (and other files) loaded for a given process, counts for AppDomains and assemblies, window and resource handles, and other useful runtime data:

Process Explorer tool (click to see larger image)

Figure 2-20. Process Explorer tool (click to see larger image)

Summary

In this chapter, we've introduced the basic concepts of interoperability between managed .NET code and Office. The Office applications are COM servers, so the basic mechanics of interoperation between managed code and Office are actually the same for interoperating between managed code and any COM servers. We've seen that during development we work with interop assemblies or primary interop assemblies. These are managed representations of the COM server's type library, and they allow us to develop against strongly typed classes, interfaces, and method signatures.

At run time, the .NET CLR places an RCW between our managed code and the Office components. This RCW acts as a proxy to the Office component so that when we make calls into the RCW, they are passed on to the underlying Office component. As part of this proxying behavior, the CLR marshals the parameters (both outgoing and incoming) and return values. This marshaling includes translation of types between the managed and unmanaged worlds.

For memory management and object lifetime, a basic mismatch exists between the expectations of COM components such as the Office applications and the expectations of .NET code. Several strategies are available for dealing with this mismatch, some more elegant than others. This is a topic that we'll revisit in later chapters because how you choose to deal with object lifetime depends to an extent on the context of your solution.

We've looked closely at some of the anomalies in the interop assemblies, and we've compared Visual Basic .NET with C# in the context of Office development. Both languages are very good for developing Office solutions, and the differences really come down to the historical evolution of the two languages. Visual Basic .NET does a little more for you during development, which makes it slightly more convenient. On the other hand, C# allows you to remain firmly and precisely in control of your own code.

Now that we've covered all the basic mechanics, we can begin to explore other simple techniques that build upon these basics.

Read Chapter 2, Part 1

Read Chapter 2, Part 2