Ironpython Interacting with COM Objects

0
485

An Overview of COM Access Differences with Python

COM access is an area where IronPython and Python take completely different approaches. In fact, it’s safe to say that any Python code you want to use definitely won’t work in IronPython. Python developers normally rely on a library such as Python for Windows Extensions (http://sourceforge.net/ projects/pywin32/). This is a library originally created by Mark Hammond (http://starship .python.net/crew/mhammond/win32/) that includes not only the COM support but also a really nice Python editor. You can see a basic example of using this library to access COM at http://www .boddie.org.uk/python/COM.html. Even if you download the required library and try to follow the tutorial, you won’t get past step 1. The tutorial works fine with standard Python, but doesn’t work at all with IronPython.

It’s important to remember that IronPython is a constantly moving target. The developers who support IronPython constantly come out with new features and functionality, as do the third parties that support it. You may find at some point that there’s a COM interoperability solution that does work for both Python and IronPython. The solution doesn’t exist today, but there’s always hope for tomorrow. If you do encounter such a solution, please be sure to contact me at [email protected]

Fortunately, IronPython developers aren’t left out in the cold. COM support is built right into IronPython in the form of the .NET Framework. An IronPython developer uses the same techniques as a C# or a Visual Basic.NET developer uses to access COM — at least at a code level.

When you work with COM in Visual Studio in either a C# or Visual Basic.NET project, the IDE does a lot of the work for you. If you want to use a COM component in your application, you right-click References in Solution Explorer and choose Add Reference from the context menu. At this point, you see the Add Reference dialog box where you choose the COM tab shown in Figure 9-1.

When you highlight an item, such as the Windows Media Player, and click OK, the IDE adds the COM component to the References folder of Solution Explorer, as shown in Figure 9-2. The IDE writes code for you in the background that adds the COM component and makes it accessible. You’ll find this code in the .CSProj file and it looks something like this:

[code]
<COMReference Include=”MediaPlayer”>
<Guid>{22D6F304-B0F6-11D0-94AB-0080C74C7E95}</Guid>
<VersionMajor>1</VersionMajor>
<VersionMinor>0</VersionMinor>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>False</Isolated>
<EmbedInteropTypes>True</EmbedInteropTypes>
</COMReference>
[/code]

Fi gure 9-1: The Add Reference dialog box provides you with a list of COM components you can use.

In addition, the IDE creates Interop.MediaPlayer.DLL, which resides in the project’s objx86Debug or objx86Release folder. This interoperability (interop for short) assembly makes it easy for you to access the COM component features.

Any reference you add appears in the References folder of Solution Explorer.

Of course, if the COM component you want to use is actually a control, you right-click the Toolbox instead and select Choose Items from the context menu. The COM Components tab looks much like the one shown in Figure 9-3.

In this case, check the controls you want to use and click OK. Again, the IDE does some work for you in the background to make the control accessible and usable. For example, it creates the same interop assembly as it would for a reference. You’ll see the control in the Toolbox, as shown in Figure 9-4.

The tasks that the IDE performs for you as part of adding a reference or Toolbox item when working with C# or Visual Basic.NET are manual tasks when working with IronPython. As you might imagine, all of this manual labor makes IronPython harder to use with COM than when you work with Python. While a Python developer simply imports a module and then writes a little specialized code, you’re saddled with creating interop assemblies and jumping through coding hoops.

COM components and controls can also appear in the Choose Toolbox Items dialog box.

The control or controls you selected appear in the Toolbox.

You do get something for the extra work, though. IronPython provides considerably more flexibility than Python does and you can use IronPython in more places. For example, you might find it hard to access Word directly in Python. The bottom line is that IronPython and Python are incompatible when it comes to COM support, so you can’t use all the online Python sources of information you normally rely on when performing a new task.

Choosing a Binding Technique

Before you can use a COM component, you must bind to it (create a connection to it). The act of binding gives you access to an instance of the component. You use binding to work with COM because, in actuality, you’re taking over another application. For example, you can use COM to create a copy of Word, do some work with it, save the resulting file, and then close Word — all without user interaction. A mistake that many developers make is thinking of COM as just another sort of class, but it works differently and you need to think about it differently. For the purposes of working with COM in IronPython, the act of binding properly is one of the more important issues. The following sections describe binding in further detail.

Understanding Early and Late Binding

When you work with a class, you create an instance of the class, set the resulting object’s properties, and then use methods to perform a particular task. COM lets you perform essentially the same set of steps in a process called early binding. When you work with early binding, you define how to access the COM object during design time. In order to do this, you instantiate an object based on the COM class.

These sections provide an extremely simplified view of COM. You can easily become mired in all kinds of details when working with COM because COM has been around for so long. For example, COM supports multiple interface types, which in turn determines the kind of binding you can perform. This chapter looks at just the information you need to work with COM from IronPython. If you want a better overview of COM, check the site at http://msdn.microsoft.com/ library/ms809980.aspx. In fact, you can find an entire list of COM topics at http://msdn.microsoft.com/library/ms877981.aspx.

The COM approach relies on a technique called a virtual table (vtable) — essentially a list of interfaces that you can access, with IUnknown as the interface that’s common to all COM components. Your application gains access to the IUnknown interface and then calls the queryinterface() method to obtain a list of other interfaces that the component supports (you can read more about this method at http://msdn.microsoft.com/library/ms682521.aspx). Using this approach means that your application can understand a component without really knowing anything about it at the outset.

It’s also possible to tell COM to create an instance of an object after the application is already running. This kind of access is called late binding because you bind after the application starts. In order to support late binding, a COM component must support the IDispatch interface. This interface lets you create the object using CreateObject(). Visual Basic was the first language product to rely on late binding. You can read more about IDispatch at http://msdn.microsoft.com/library/ ms221608.aspx.

Late binding also offers the opportunity to gain access to a running copy of a COM component. For example, if the system currently has a copy of Excel running, you can access that copy, rather than create a new Excel object. In this case, you use GetObject() instead of CreateObject() to work with the object. If you call GetObject() where there isn’t any copy of the component already executing, you get an error message — Windows doesn’t automatically start a new copy of the application for you.

If a COM component supports both the vtable and IDispatch technologies, then it has a dual interface that works with any current application language. Most COM components today are dual interface because adding both technologies is relatively easy and developers want to provide the greatest exposure for their components. However, it’s always a good idea to consider the kind of binding that your component supports. You can read more about dual interfaces at http://msdn.microsoft.com/library/ ekfyh289.aspx.

Using Early Binding

As previously mentioned, using early binding means creating a reference to the COM component and then using that reference to interact with the component. IronPython doesn’t support the standard methods of early binding that you might have used in other languages. What you do instead is create an interoperability DLL and then import that DLL into your application. The “Defining an Interop DLL” section of the chapter describes this process in considerably more detail. Early binding provides the following benefits:

  • Faster execution: Generally, your application will execute faster if you use early binding because you rely on compiled code for the interop assembly. However, you won’t get the large benefits in speed that you see when working with C# or Visual Basic.NET because IronPython itself is interpreted.
  • Easier debugging: In most cases, using early binding reduces the complexity of your application, making it easier to debug. In addition, because much of the access code for the COM component resides in the interop assembly, you won’t have to worry about debugging it.
  • Fuller component access: Even though both early and late binding provide access to the component interfaces, trying to work through those interfaces in IronPython is hard. Using early binding provides you with tools that you can use to explore the interop assembly, and therefore discover more about the component before you use it.
  • Better access to enumerations and constants: Using early binding provides you with access to features that you might not be able to access when using late binding. In some cases, IronPython will actually hide features such as enumerations or constants when using late binding.

Using Late Binding

When using late binding, you create a connection to the COM component at run time by creating a new object or reusing a running object. Some developers prefer this kind of access because it’s less error prone than early binding where you might not know about runtime issues during design time. Here are some other reasons that you might use late binding.

  • More connectivity options: You can use late binding to create a connection to a new instance of a COM component (see the “Performing Late Binding Using Activator.CreateInstance()” section of this chapter) or a running instance of the COM component
  • Fewer modules: When you use late binding, you don’t need an interop assembly for each of the COM components you want to use, which decreases the size and complexity of your application.
  • Better version independence: Late binding relies on registry entries to make the connection. Consequently, when Windows looks up the string you use to specify the application, it looks for any application that satisfies that string. If you specify the Microsoft Excel 9.0 Object Library COM component (Office 2000 specific), Windows will substitute any newer version of Office on the system for the component you requested.
  • Fewer potential compatibility issues: Some environments don’t work well with interop assemblies. For example, you might be using IronPython within a Web-based application. In this case, the client machine would already have to have the interop assembly, too, and it probably doesn’t. In this case, using late binding allows your application to continue working when early binding would fail.

Defining an Interop DLL

Before you can do much with COM, you need to provide some means for .NET (managed code) and the component (native code) to talk. The wrapper code that marshals data from one environment to another, and that translates calls from one language to the other, is an interoperability (interop) assembly, which always appears as a DLL. Fortunately, you don’t have to write this code by hand because the task is somewhat mundane. Microsoft was able to automate the process required to create an interop DLL.

Of course, Microsoft couldn’t make the decision straightforward or simple. You use different utilities for controls and components. The Type Library Import (TLbImp) utility produces a DLL suitable for component work, while the ActiveX Import (AxImp) utility produces a pair of DLLs suitable for control work. In many cases, the decision is easy — a COM component that supports a visual interface should use AxImp. However, some COM components, such as Windows Media Player (WMP.DLL) are useful as either controls or components. The example in this chapter uses the control form because that’s the way you’ll use Windows Media Player most often, but it’s important to make the decision. The following sections describe how to use both the TLbImp and AxImp utilities.

Accessing the Visual Studio .NET Utilities

You want to create an interop assembly in the folder that you’ll use for your sample application. However, you also need access to the .NET utilities. The best way to gain this access is to open a Visual Studio command prompt by choosing Start ➪ Programs ➪ Microsoft Visual Studio 2010 ➪ Visual Studio Tools ➪ Visual Studio Command Prompt (2010). If you’re working with Vista or Windows 7, right-click the Visual Studio Command Prompt (2010) entry and choose Run As Administrator from the context menu to ensure you have the rights required to use the utilities. Windows will open a command prompt that provides the required access to the .NET utilities.

Understanding the Type Library Import Utility

Remember that you always use Type Library Import (TLbImp) for components, not for controls. Before you can use TLbImp, you need to know a bit more about it. Here’s the command line syntax for the tool:

[code]
TlbImp TypeLibName [Options]
[/code]

The TypeLibName argument is simply the filename of the COM component that you want to use to create an interop assembly. A COM component can have a number of file extensions, but the most common extensions are .DLL, .EXE, and .OCX.

The TypeLibName argument can specify a resource identifier when the library contains more than one resource. Simply follow the filename with a backslash and the resource number. For example, the command line TLbImp MyModule .DLL1 would create an output assembly that contains only resource 1 in the MyModule.DLL file.

You can also include one or more options that modify the behavior of TLbImp. The following list describes these options.

  • /out:FileName: Provides the name of the file you want to produce as output. If you don’t provide this argument, the default is to add Lib to the end of the filename for the type library. For example, WMP.DLL becomes WMPLib.DLL.
  • /namespace:Namespace: Defines the namespace of the classes within the interop assembly. The default is to add Lib to the filename of the type library. For example, if the file has a name of WMP.DLL, the namespace is WMPLib.
  • /asmversion:Version: Specifies the file version number of the output assembly. This information appears on the Version tab of the file Properties dialog box shown in Figure 9-5. The default version number is 1.0.0.0.

You must specify a version number using dotted syntax. The four version number elements are: major version, minor version, build number, and revision number. For example, 1.2.3.4 would specify a major version number of 1, minor version number of 2, a build number of 3, and a revision number of 4.

  • /reference:FileName: Determines the name of the assembly that TLbImp uses to resolve references. There’s no default value. You may use this command line switch as many times as needed to provide a complete list of assemblies.
  • /tlbreference:FileName: Determines the name of the type library that TLbImp uses to resolve references. There’s no default value. You may use this command line switch as many times as needed to provide a complete list of assemblies.
  • /publickey:FileName: Specifies the name of a file containing a strong name public key used to sign the assembly. There’s no default value.
  • /keyfile:FileName: Specifies the name of a file containing a strong name key pair used to sign the assembly. There’s no default value.
  • /keycontainer:FileName: Specifies the name of a key container containing a strong name key pair used to sign the assembly. There’s no default value.
  • /delaysign: Sets the assembly to force a delay in signing. Use this option when you want to use the assembly for experimentation only.
Include version information for the assembly so others know about it.
Fi gure 9-5: Include version information for the assembly so others know about it.
  • /product:Product: Defines the name of the product that contains this assembly. This information appears on the Version tab of the file Properties dialog box shown in Figure 9-5. The default is to say that the assembly is imported from a specific type library.
  • /productversion:Version: Defines the product version number of the output assembly. This information appears on the Version tab of the file Properties dialog box shown in Figure 9-5. The default version number is 1.0.0.0.
  • /company:Company: Defines the name of the company that produced the output assembly. This information appears on the Version tab of the file Properties dialog box shown in Figure 9-5. There’s no default value.
  • /copyright:Copyright: Defines the copyright information that applies to the output assembly. This information appears on the Version tab of the file Properties dialog box shown in Figure 9-5. There’s no default value.
  • /trademark:Trademark: Defines the trademark and registered trademark information that applies to the output assembly. This information appears on the Version tab of the file Properties dialog box shown in Figure 9-5. There’s no default value.
  • /unsafe: Creates an output assembly that lacks runtime security checks. Using this option will make the assembly execute faster and reduce its size. However, you shouldn’t use this option for production systems because it does reduce the security features that the assembly would normally possess.
  • /noclassmembers: Creates an output assembly that has classes, but the classes have no members.
  • /nologo: Prevents the TLbImp utility from displaying a logo when it starts execution. This option is useful when performing batch processing.
  • /silent: Prevents the TLbImp utility from displaying any output, except error information. This option is useful when performing batch processing.
  • /silence:WarningNumber: Prevents the TLbImp utility from displaying output for the specified warning number. This option is useful when an assembly contains a number of warnings that you already know about and you want to see only the warnings that you don’t know about. You can’t use this option with the /silent command line switch.
  • /verbose: Tells the TLbImp utility to display every available piece of information about the process used to create the output assembly. This option is useful when you need to verify the assembly before placing it in a production environment or when you suspect a subtle error is causing application problems (or you’re simply curious).
  • /primary: Creates a Primary Interop Assembly (PIO). A COM component may use only one PIO and you must sign the PIO (use the /publickey, /keyfile, or /keycontainer switches to sign the assembly). See http://msdn.microsoft.com/library/aax7sdch.aspx for additional information.
  • /sysarray: Specifies that the assembly should use SAFEARRAY in place of the standard System.Array.
  • /machine:MachineType: Creates an assembly for the specified machine type. The valid inputs for this command line switch are:
    • X86
    • X64
    • Itanium
    • Agnostic
  • /transform:TransformName: Performs the specified transformations on the assembly. You may use any of these values as a transformation.
    • SerializableValueClasses: Forces TLbImp to mark all of the classes as serializable.
    • DispRet: Applies the [out, retval] attribute to methods that have a dispatch-only interface.
  • /strictref: Forces TLbImp to use only the assemblies that you specify using the /reference command line switch, along with PIAs, to produce the output assembly, even if the source file contains other references. The output assembly might not work properly when you use this option.
  • /strictref:nopia: Forces TLbImp to use only the assemblies that you specify using the /reference command line switch to produce the output assembly, even if the source file contains other references. This command line switch ignores PIAs. The output assembly might not work properly when you use this option.
  • /VariantBoolFieldToBool: Converts all VARIANT_BOOL fields in structures to bool.
  • /? or /help: Displays a help message containing a list of command line options for the version of TLbImp that you’re using.

Understanding the ActiveX Import Utility

The example in this chapter relies on the ActiveX Import (AxImp) utility because it produces the files you need to create a control (with a visual interface) rather than a component. When you use this utility, you obtain two files as output. The first contains the same information you receive when using the TLbImp utility. The second, the one with the Ax prefix, contains the code for a control. Before you can use AxImp, you need to know a bit more about it. Here’s the command line syntax for the tool:

[code]
AxImp OcxName [Options]
[/code]

The OcxName argument is simply the filename of the COM component that you want to use to create a control version of an interop assembly. A COM component can have a number of file extensions, but the most common extensions are .DLL, .EXE, and .OCX. It’s uncommon for an OLE Control eXtension (OCX), a COM component with a visual interface, to have a .EXE file extension.

You can also include one or more options that modify the behavior of AxImp. The following list describes these options.

  • /out:FileName: Provides the name of the ActiveX library file you want to produce as output. If you don’t provide this argument, the default is to add Lib to the end of the filename for the type library. For example, WMP.DLL becomes WMPLib.DLL and AxWMPLib.DLL. Using this command line switch changes the name of the AxWMPLib.DLL file. For example, if you type AxImp WMP .DLL /out:WMPOut.DLL and press Enter, the utility now outputs WMPLib.DLL and WMPOut.DLL.
  • /publickey:FileName: Specifies the name of a file containing a strong name public key used to sign the assembly. There’s no default value.
  • /keyfile:FileName: Specifies the name of a file containing a strong name key pair used to sign the assembly. There’s no default value.
  • /keycontainer:FileName: Specifies the name of a key container containing a strong name key pair used to sign the assembly. There’s no default value.
  • /delaysign: Sets the assembly to force a delay in signing. Use this option when you want to use the assembly for experimentation only.
  • /source: Generates the C# source code for a Windows Forms wrapper. You don’t need to use this option when working in IronPython because the code doesn’t show how to use the wrapper — it simply shows the wrapper code itself.
  • /rcw:FileName: Specifies an assembly to use for Runtime Callable Wrapper (RCW) rather than generating a new one. In most cases, you want to generate a new RCW when working with IronPython.
  • /nologo: Prevents the AxImp utility from displaying a logo when it starts execution. This option is useful when performing batch processing.
  • /silent: Prevents the AxImp utility from displaying any output, except error information. This option is useful when performing batch processing.
  • /verbose: Tells the AxImp utility to display every available piece of information about the process used to create the output assembly. This option is useful when you need to verify the assembly before placing it in a production environment or when you suspect a subtle error is causing application problems (or you’re simply curious).
  • /? or /help: Displays a help message containing a list of command line options for the version of AxImp that you’re using.

Creating the Windows Media Player Interop DLL

Now that you have an idea of how to use the AxImp utility, it’s time to see the utility in action. The following command line creates an interop assembly for the Windows Media Player.

[code]
AxImp %SystemRoot%System32WMP.DLL
[/code]

This command line switch doesn’t specify any options. It does include %SystemRoot%, which points to the Windows directory on your machine (making it possible to use the command line on more than one system, even if those systems have slightly different configurations). When you execute this command line, you see the AxImp utility logo. After a few minutes work, you’ll see one or more warning or error messages if the AxImp utility encounters problems. Eventually, you see a success message, as shown in Figure 9-6.

The AxImp tells you that it has generated the two DLLs needed for a control.
Fi gure 9-6: The AxImp tells you that it has generated the two DLLs needed for a control.

Exploring the Windows Media Player Interop DLL

When working with imported Python modules, you use the dir() function to see what those modules contain. In fact, you often use dir() when working with .NET assemblies as well, even though you have the MSDN documentation at hand. Theoretically, you can also use dir() when working with imported COM components as well, but things turn quite messy when you do. The “Using the Windows Media Player Interop DLL” section of this chapter describes how to import and use an interop assembly, but for now, let’s just look at WMPLib.DLL using dir(). Figure 9-7 shows typical results.

Using dir() won’t work well with interop assemblies in many cases.
Fi gure 9-7: Using dir() won’t work well with interop assemblies in many cases.

The list goes on and on. Unfortunately, this is only the top level. You still need to drill down into the interop assembly, so things can become confusing and complex. Figuring out what you want to use is nearly impossible. Making things worse is the fact that any documentation you obtain for the interop assembly probably won’t work because the documentation will take the COM perspective of working with the classes and you need the IronPython perspective. Using dir() won’t be very helpful in this situation.

Fortunately, you have another alternative in the form of the Intermediate Language Disassembler (ILDasm) utility. This utility looks into the interop assembly and creates a graphic picture of it for you. Using this utility, you can easily drill down into the interop assembly and, with the help of the COM documentation, normally figure out how to work with the COM component — even complex COM components such as the Windows Media Player.

To gain access to ILDasm, you use the same process you use for TLbImp to create a Visual Studio Command Prompt. At the command prompt, type ILDasm WMPLib.DLL and press Enter (see more of the command line options in the “Using the ILDasm Command Line” section of the chapter). The ILDasm utility will start and show entries similar to those shown in Figure 9-8.

ILDasm is an important tool for the IronPython developer who wants to work with COM. With this in mind, the following sections provide a good overview of ILDasm and many of its usage details. Most important, these sections describe how to delve into the innermost parts of any interop assembly.

Use ILDASM to explore WMPLib.DLL.
Fi gure 9-8: Use ILDASM to explore WMPLib.DLL.

Using the ILDasm Command Line

The ILDasm utility usually works fine when you run it and provide the filename of the interop assembly you want to view. However, sometimes an interop assembly is so complex that you really do want to optimize the ILDasm view. Consequently, you use command line options to change the way ILDasm works. ILDasm has the following command line syntax.

[code]
ildasm [options] <file_name> [options]
[/code]

Even though this section shows the full name of all the command line switches, you can use just the first three letters. For example, you can abbreviate /BYTES as /BYT. In addition, ILDasm accepts both the dash (-) and slash (/) as command line switch prefixes, so /BYTES and -BYTES work equally well.

The options can appear either before or after the filename. You can divide the options into those that affect output redirection (sending the output to a location other than the display) and those that change the way the file/console output appears. ILDasm further divides the file/console options into those that work with EXE and DLL files, and those that work with EXE, DLL, OBJ, and LIB files. Here are the options for output redirection.

  • /OUT=Filename: Redirects the output to the specified file rather than to a GUI.
  • /TEXT: Redirects the output to a console window rather than to a GUI. This option isn’t very useful for anything but the smallest files because the entire content of the interop assembly simply scrolls by. Of course, you can always use a pipe (|) to send the output to the More utility to view the output one page at a time.
  • /HTML: Creates the file in HTML format (valid with the /OUT option only). This option is handy for making the ILDasm available for a group of developers on a Web site. For example, if you type ILDasm /OUT=WMPLib.HTML /HTML WMPLib.DLL and press Enter, you obtain WMPLib.HTML. The resulting file is huge — 7.53 MB for WMPLib.HTML. Figure 9-9 shows how this file will appear.
  • /RTF: Creates the file in RTF format (valid with the /OUT option only). This option is handy for making the ILDasm available for a group of developers on a local network using an application such as Word. For example, if you type ILDasm /OUT=WMPLib.RTF /RTF WMPLib.DLL and press Enter, you obtain WMPLib.RTF. The resulting file is huge — 5.2 MB for WMPLib.RTF, and may cause Word to freeze.

Of course, you might not want to redirect the output to a file, but may want to change the way the console appears instead. The following options change the GUI or file/console output for EXE and DLL files only.

  • /BYTES: Displays actual bytes (in hex) as instruction comments. Generally, this information isn’t useful unless you want to get into the low-level details of the interop assembly. For example, you might see a series of hex bytes such as // SIG: 20 01 01 08, which won’t be helpful to most developers. (In this case, you’re looking at the signature for the WMPLib .IAppDispatch.adjustLeft() method.)
HTML output is useful for viewing ILDasm output in a browser
Fi gure 9-9: HTML output is useful for viewing ILDasm output in a browser
  • /RAWEH: Shows the exception handling clauses in raw form. This isn’t a useful command line switch for interop assemblies because interop assemblies don’t require exception handlers in most cases.
  • /TOKENS: Displays the metadata tokens of classes and members as comments in the source code, as shown in Figure 9-10 for the WMPLib.IAppDispatch.adjustLeft() method. For example, the metadata token for mscorlib is /*23000001*/. Most developers won’t require this information.
The metadata tokens appear as comments beside the coded text.
Fi gure 9-10: The metadata tokens appear as comments beside the coded text.
  • /SOURCE: Shows the original source lines as comments when available. Unfortunately, when working with an interop assembly, there aren’t any original source lines to show, so you won’t need to use this command line switch.
  • /LINENUM: Shows the original source code line numbers as comments when available. Again, when working with an interop assembly, there aren’t any original source code line numbers to show so you won’t need to use this command line switch.
  • /VISIBILITY=Vis[+Vis…]: Outputs only the items with specified visibility. The valid inputs for this argument are:
    • PUB: Public
    • PRI: Private
    • FAM: Family
    • ASM: Assembly
    • FAA: Family and assembly
    • FOA: Family or assembly
    • PSC: Private scope
  • /PUBONLY: Outputs only the items with public visibility (same as /VIS=PUB).
  • /QUOTEALLNAMES: Places single quotes around all names. For example, instead of seeing mscorlib, you’d see ‘mscorlib‘. In some cases, using this approach makes it easier to see or find specific names in the code.
  • /NOCA: Suppresses the output of custom attributes.
  • /CAVERBAL: Displays all of the Custom Attribute (CA) blobs in verbal form. The default setting outputs the CA blobs in binary form. Using this command line switch can make the code more readable, but also makes it more verbose (larger).
  • /NOBAR: Tells ILDasm not to display the progress bar as it redirects the interop assembly output to another location (such as a file).

ILDasm includes a number of command line switches that affect file and console output only. The following command line switches work for EXE and DLL files.

  • /UTF8: Forces ILDasm to use UTF-8 encoding for output in place of the default ANSI encoding.
  • /UNICODE: Forces ILDasm to use Unicode encoding for output in place of the default ANSI encoding.
  • /NOIL: Suppresses Intermediate Language (IL) assembler code output. Unfortunately, this option isn’t particularly useful because it creates a file that contains just the disassembly comments, not any of the class or method information. You do get the resource (.RES) file containing the resource information for the interop assembly (such as the version number). To use this command line switch, you must include redirection such as ILDasm /OUT=WMPLib.HTML / HTML /NOIL WMPLib.DLL to produce WMPLib.HTML as output.
  • /FORWARD: Forces ILDasm to use forward class declaration. In some cases, this command line switch can reduce the size of the disassembly.
  • /TYPELIST: Outputs a full list of types. Using this command line switch can help preserve type ordering.
  • /HEADERS: Outputs the file header information in the output.
  • /ITEM=Class[::Method[(Signature)]]: Disassembles only the specified item. Using this command line switch can greatly reduce the confusion of looking over an entire interop assembly.
  • /STATS: Provides statistical information about the image. The statistics appear at the beginning of the file in comments. Here’s a small segment of the statistics you might see (telling you about the use of space in the file).
    [code]
    // File size : 331776
    // PE header size : 4096 (496 used) ( 1.23%)
    // PE additional info : 1015 ( 0.31%)
    // Num.of PE sections : 3
    // CLR header size : 72 ( 0.02%)
    // CLR meta-data size : 256668 (77.36%)
    // CLR additional info : 0 ( 0.00%)
    // CLR method headers : 9086 ( 2.74%)
    // Managed code : 51182 (15.43%)
    // Data : 8192 ( 2.47%)
    // Unaccounted : 1465 ( 0.44%)
    [/code]
  • /CLASSLIST: Outputs a list of the classes defined in the module. The class list appears as a series of comments at the beginning of the file. Here’s an example of the class list output for WMPLib.DLL
    [code]
    // Classes defined in this module:
    //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    // Interface IWMPEvents (public) (abstract) (auto) (ansi) (import)
    // Class WMPPlaylistChangeEventType (public) (auto) (ansi) (sealed)
    // Interface IWMPEvents2 (public) (abstract) (auto) (ansi) (import)
    // Interface IWMPSyncDevice (public) (abstract) (auto) (ansi) (import)
    // Class WMPDeviceStatus (public) (auto) (ansi) (sealed)
    // Class WMPSyncState (public) (auto) (ansi) (sealed)
    // Interface IWMPEvents3 (public) (abstract) (auto) (ansi) (import)
    // Interface IWMPCdromRip (public) (abstract) (auto) (ansi) (import)
    [/code]
  • /ALL: Performs the combination of the /HEADER, /BYTES, /STATS, /CLASSLIST, and /TOKENS command line switches.

This set of command line switches also affects just file and console output. However, you can use them for EXE, DLL, OBJ, and LIB files.

  • /METADATA[=Specifier]: Shows the interop assembly metadata for the elements defined by Specifier. Here are the values you can use for Specifier.
    • MDHEADER: MetaData header information and sizes
    • HEX: More data in hex as well as words
    • CSV: Record counts and heap sizes
    • UNREX: Unresolved externals
    • SCHEMA: MetaData header and schema information
    • RAW: Raw MetaData tables
    • HEAPS: Raw heaps
    • VALIDATE: MetaData consistency validation

The final set of command line switches affects file and console output for LIB files only.

  • /OBJECTFILE=Obj_Filename: Shows the MetaData of a single object file in library.

Working with ILDasm Symbols

When working with ILDasm, you see a number of special symbols. Unfortunately, the utility often leaves you wondering what the symbols mean. Here are some of the most common symbols you encounter when working with COM components.

  • Interface: Represents an interface with which you can interact.
  • Private Class: Represents an abstract or sealed class in most cases.
  • Enumeration: Contains a list of enumerated items you use to provide values for method calls and other tasks.
  • Attribute: Provides access to the attributes that describe a COM component. Common attributes and attribute containers include:
    • Manifest (and its associated attributes)
    • Extends (defines a class that the class extends)
    • Implements (defines an interface that the class implements)
    • ClassInterface (see http://msdn.microsoft.com/library/system .runtime.interopservices.classinterfaceattribute.aspx for details)
    • GuidAttribute (see http://msdn.microsoft.com/library/system .runtime.interopservices.guidattribute.aspx for details)
    • TypeLibTypeAttribute (see http://msdn.microsoft.com/library/ system.runtime.interopservices.typelibtypeattribute.aspx for details)
    • InterfaceTypeAttribute (see http://msdn.microsoft.com/library/ system.runtime.interopservices.interfacetypeattribute.aspx for details)
  • Method: Describes a method that you can use within an interface or private class.
  • Property: Describes a property that you can use within an interface or private class.
  • Variable: Defines a variable of some type within an interface or private class. The variable could be an interface, such as IConnectionPoint, or an array, such as ArrayList, or anything else that the developer wanted to include.
  • Event: Specifies an event that occurs within the interface or private class.

Exploring ILDasm entries

It’s important to remember that interop assemblies simply provide a reference to the actual code found in the COM component. Even so, you can use ILDasm to find out all kinds of interesting information about the component. At the top level, you can see a list of all of the interfaces, classes, and enumerations, as shown in Figure 9-8. The next level is to drill down into specific methods and properties, as shown in Figure 9-11.

Opening an interface displays all the methods it contains.
Fi gure 9-11: Opening an interface displays all the methods it contains.

The information shown in this figure is actually the most valuable information that ILDasm provides because you can use it to discover the names of methods and properties you want to use in your application. In addition, these entries often provide clues about where to look for additional information in the vendor help files. Sometimes these help files are a little disorganized and you might not understand how methods are related until you see this visual presentation of them.

It’s possible to explore the interop assembly at one more level. Double-click any of the methods, properties, or attributes and you’ll see a dialog box like the one shown in Figure 9-12. The amount of information you receive may seem paltry at first. However, look closer and you’ll discover that this display often tells you about calling requirements. For example, you can discover the data types you need to rely on to work with the COM component (something that COM documentation can’t tell you because the vendor doesn’t know that you’re using the component from .NET).

Discover the calling requirements for methods by reviewing the methods’ underlying code.
Fi gure 9-12: Discover the calling requirements for methods by reviewing the methods’ underlying code.

Using the Windows Media Player Interop DLL

It’s finally time to use early binding to create a connection to the Windows Media Player. This example uses the Windows Media Player as a control. You might find a number of online sources that say it’s impossible to use the Windows Media Player as a control, but it’s actually quite doable. Of course, you need assistance from yet another one of Microsoft’s handy utilities, Resource Generator (ResGen) to do it. The example itself relies on the normal combination of a form file and associated application file. The following sections provide everything needed to create the example.

Working with ResGen

Whenever you drop a control based on a COM component onto a Windows Forms dialog box, the IDE creates an entry for it in the .RESX file for the application. This entry contains binary data that describes the properties for the COM component. You may not know it, but most COM components have a Properties dialog box that you access by right-clicking the control and choosing Properties from the context menu. These properties are normally different from those shown in the Properties window for the managed control. Figure 9-13 shows the Properties dialog box for the Windows Media Player.

The COM component has properties that differ from the managed control.
Fi gure 9-13: The COM component has properties that differ from the managed control.

It’s essential to remember that the managed control is separate from the COM component in a Windows Forms application. The COM component properties appear in a separate location and the managed environment works with them differently. If you look in the .RESX file, you see something like this:

[code]
<data name=”MP.OcxState” mimetype=”application/x-microsoft.net.object.binary.base64”>
<value>
AAEAAAD/////AQAAAAAAAAAMAgAAAFdTeXN0ZW0uV2luZG93cy5Gb3JtcywgVmVyc2lvbj00LjAuMC4w
LCBDdWx0dXJlPW5ldXRyYWwsIFB1YmxpY0tleVRva2VuPWI3N2E1YzU2MTkzNGUwODkFAQAAACFTeXN0
ZW0uV2luZG93cy5Gb3Jtcy5BeEhvc3QrU3RhdGUBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAywAAAAIB
AAAAAQAAAAAAAAAAAAAAALYAAAAAAwAACAAUAAAAQgBlAGwAbABzAC4AdwBhAHYAAAAFAAAAAAAAAPA/
AwAAAAAABQAAAAAAAAAAAAgAAgAAAAAAAwABAAAACwD//wMAAAAAAAsA//8IAAIAAAAAAAMAMgAAAAsA
AAAIAAoAAABmAHUAbABsAAAACwAAAAsAAAALAP//CwD//wsAAAAIAAIAAAAAAAgAAgAAAAAACAACAAAA
AAAIAAIAAAAAAAsAAAAuHgAAfhsAAAs=
</value>
[/code]

This binary data contains the information needed to configure the COM aspects of the component. When the application creates the form, the binary data is added to the component using the OcxState property like this:

[code]
this.MP.OcxState =
((System.Windows.Forms.AxHost.State)(resources.GetObject(“MP.OcxState”)));
[/code]

Because of the managed code/COM component duality of a Windows Forms application, you can’t simply embed the COM component into an IronPython application using techniques such as the one shown at http://msdn.microsoft.com/library/dd564350.aspx. You must provide the binary data to the COM component using the OcxState property. Unfortunately, IronPython developers have an added twist to consider. The C# code shown previously won’t work because you don’t have access to a ComponentResourceManager for the IronPython form. Instead, you must read the resource from disk using code like this

[code]
self.resources = System.ComponentModel.ComponentResourceManager.
CreateFileBasedResourceManager(
‘frmUseWMP’, ‘C:/0255 – Source Code/Chapter09’, None)
[/code]

Now, here’s where the tricky part begins (you might have thought we were there already, but we weren’t). The CreateFileBasedResourceManager() method doesn’t support .RESX files. Instead, it supports .RESOURCES files. The ResGen utility can create .RESOURCES files. You might be tempted to think that you can duplicate the binary data from the .RESX file using .TXT files as suggested by the ResGen documentation. Unfortunately, .TXT files can only help you create string data in .RESOURCES files.

So your first step is to create a Windows Forms application, add the component to it, perform any required COM component configuration (no need to perform the managed part), save the result, and then take the resulting .RESX file for your IronPython application. You can then use ResGen to create the .RESOURCES file using a command line like this:

[code]
ResGen frmUseWMP.RESX
[/code]

ResGen outputs a .RESOURCES file you can use within your application. Of course, like every Microsoft utility, ResGen offers a little more than simple conversion. Here’s the command line syntax for ResGen:

[code]
ResGen inputFile.ext [outputFile.ext] [/str:lang[,namespace[,class[,file]]]]
ResGen [options] /compile inputFile1.ext[,outputFile1.resources] […]
[/code]

Here are the options you can use.

  • /compile: Performs a bulk conversion of files from one format to another format. Typically, you use this feature with a response file where you provide a list of files to convert.
  • /str:language[, namespace[, classname[, filename]]]: Defines a strongly typed resource class using the specified programming language that relies on Code Document Object Model (CodeDOM) (see http://msdn.microsoft.com/library/y2k85ax6.aspx for details). To ensure that the strongly typed resource class works properly, the name of your output file, without the .RESOURCES extension, must match the [namespace.]classname of your strongly typed resource class. You may need to rename your output file before using it or embedding it into an assembly.
  • /useSourcePath: Specifies that ResGen uses each source file’s directory as the current directory for resolving relative file paths.
  • /publicClass: Creates the strongly typed resource class as a public class. You must use this command line switch with the /str command line switch.
  • /r:assembly: Tells ResGen to load types from the assemblies that you specify. A .RESX file automatically uses newer assembly types when you specify this command line switch. You can’t form the .RESX file to rely on older assembly types.
  • /define:A[,B]: Provides a means for performing optional conversions specified by #ifdef structures within a .RESTEXT (text) file.
  • @file: Specifies the name of a response file to use for additional command line options. You can only provide one response file for any given session.

Creating the Media Player Form Code

As normal, the example relies on two files to hold the form and the client code. Because we’re using a COM component for this example, the form requires a number of special configuration steps. Listing 9-1 shows the form code.

Listin g 9-1: Creating a Windows Forms application with a COM component

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
clr.AddReference(‘System.Drawing.DLL’)
clr.AddReference(‘AxWMPLib.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
import System.Drawing.Point
import AxWMPLib
class frmUseWMP(System.Windows.Forms.Form):
# This function performs all of the required initialization.
def InitializeComponent(self):
# Create a Component Resource Manager
self.resources = System.ComponentModel.ComponentResourceManager.
CreateFileBasedResourceManager(
‘frmUseWMP’, ‘C:/0255 – Source Code/Chapter09’, None)
# Configure Windows Media Player
self.MP = AxWMPLib.AxWindowsMediaPlayer()
self.MP.Dock = System.Windows.Forms.DockStyle.Fill
self.MP.Enabled = True
self.MP.Location = System.Drawing.Point(0, 0)
self.MP.Name = “MP”
self.MP.Size = System.Drawing.Size(292, 266)
self.MP.OcxState = self.resources.GetObject(“MP.OcxState”)
# Configure the form.
self.ClientSize = System.Drawing.Size(350, 200)
self.Text = ‘Simple Windows Media Player Example’
# Add the controls to the form.
self.Controls.Add(self.MP)
[/code]

The code begins with the normal steps of adding the .NET Framework path, making clr accessible, importing the required DLLs, and importing the required assemblies. Notice that the example uses the AxWMPLib.DLL file and AxWMPLib assembly. Remember that the Ax versions of the files provide wrapping around the ActiveX controls to make them usable as a managed control.

The code begins by creating a ComponentResourceManager from a file, using the CreateFileBasedResourceManager() method. Normally, a managed application would create the ComponentResourceManager directly from the data stored as part of the form. This is a special step for IronPython that could cause you grief later if you forget about it.

Even though Listing 9-1 shows the CreateFileBasedResourceManager() method call on multiple lines, it appears on a single line in the actual source code. The IronPython call won’t work if you place it on multiple lines because IronPython lacks a line continuation character (or methodology).

Media Player (MP) configuration comes next. You must instantiate the control from the AxWMPLib .AxWindowsMediaPlayer() constructor, rather than using the COM component constructor. The Ax constructor provides a wrapper with additional features you need within the Windows Forms environment. Like most controls, you need to specify control position and size on the form. However, because of the nature of the Windows Media Player, you want it to fill the client area of the form, so you set the Dock property to System.Windows.Forms.DockStyle.Fill.

The one configuration item that you must perform correctly is setting the COM component values using the MP.OcxState property. The ComponentResourceManager, resources, contains this value. You simply set the MP.OcxState property to resources.GetObject(“MP.OcxState”) — this technique is also different from what you’d use in a C# or Visual Basic.NET application. The rest of the form code isn’t anything special — you’ve seen it in all of the Windows Forms examples so far.

Creating the Media Player Application Code

Some COM components require a lot of tinkering by the host application, despite being self-contained for the most part. However, the Windows Media Player is an exception to the rule. Normally, you want to tinker with it as little as possible to meet your programming requirements. In some cases, you won’t want to tinker at all, as shown in Listing 9-2.

Listin g 9-2: Interacting with the COM component

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
# import the form.
from frmUseWMP import *
# Define the Windows Form and the elements of this specific instance.
WMPForm = frmUseWMP()
WMPForm.InitializeComponent()
# Run the application.
System.Windows.Forms.Application.Run(WMPForm)
[/code]

This code does the minimum possible for a Windows Forms application. It contains no event handlers or anything of that nature. In fact, the code simply displays the forms. Believe it or not, the actual settings for the application appear as part of the .RESOURCES file. What you see when you run this application appears in Figure 9-14.

This is a fully functional Windows Media Player. You can adjust the volume, set the starting position, pause the play, or do anything else you normally do with the Windows Media Player. It’s even possible to right-click the Windows Media Player to see the standard context menu. The context menu contains options to do things like slow the play time, see properties, and change options. Play with the example a bit to see just how fully functional it is.

The example application shows a form with Windows Media Player on it.
Figure 9-14: The example application shows a form with Windows Media Player on it.

A Quick View of the Windows Media Player Component Form

You may encounter times when you really don’t want to display the Windows Media Player as a control — you simply want it to work in the background. In this case, you can use the Windows Media Player as a component. The following code snippet shows the fastest way to perform this task in IronPython (the sys.path.append() call should appear on a single line, even though it appears on two lines in the book). (You can find the entire source in the MPComponent example supplied with the book’s source code.)

[code]
# Set up the path to the .NET Framework.
import sys
sys.path.append(
‘C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727’)
# Make clr accessible.
import clr
# Add any required references.
clr.AddReference(‘System.Windows.Forms.DLL’)
clr.AddReference(‘WMPLib.DLL’)
# Import the .NET assemblies.
import System
import System.Windows.Forms
import WMPLib
# import the form.
from frmMPComponent import *
# Define the event handlers.
def btnPlay_Click(*args):
# Create the Media Player object.
MP = WMPLib.WindowsMediaPlayerClass()
# Assign the media player event.
MP.MediaError += PlayerError
# Assign a sound to the Media Player.
MP.URL = “Bells.WAV”
# Play the sound.
MP.controls.play()
[/code]

Notice that you start by adding a reference to WMPLib.DLL and importing WMPLib into IronPython, rather than using the Ax versions. The next step appears in the btnPlay_Click() event handler. After the code imports the required support, it instantiates an object (MP) of the WindowsMediaPlayerClass, not WindowsMediaPlayer (an interface) as many of the Microsoft examples show.

Now you can perform various tasks with the resulting component. The example is simple. All it does is assign a filename to the URL property, and then call on controls.play() to play the file. You can find additional information on using this technique at http://msdn.microsoft.com/library/dd562692.aspx.

Performing Late Binding Using Activator.CreateInstance()

The Activator.CreateInstance() method is one of the more powerful ways to work with objects of all kinds. In fact, this particular method can give your IronPython applications the same kind of support as the Windows scripting engines CScript and WScript.

When working with the Activator.CreateInstance() method, you describe the type of object you want to create. The object can be anything. In fact, if you look through the HKEY_CLASSES_ ROOT hive of the registry, you’ll find a number of objects to try on your system.

The example in this section does something a bit mundane, but also interesting — it demonstrates how to interact with the Shell objects. You can get a description of the Shell objects at http:// msdn.microsoft.com/library/bb774122.aspx. The main reason to look at the Shell objects is that every Windows machine has them and they’re pretty useful for detecting user preferences. Listing 9-3 shows the code used for this example.

Listin g 9-3: Working with Shell objects

[code]
# We only need the System assembly for this example.
from System import Activator, Type
# Import the time module to help with a pause.
import time
# Constants used for Shell settings.
from ShellSettings import *
# Create the Shell object.
ShObj = Activator.CreateInstance(Type.GetTypeFromProgID(‘Shell.Application’))
# Toggle the Desktop.
raw_input(‘Press Enter to show and then hide the Desktop’)
ShObj.ToggleDesktop()
time.sleep(2)
ShObj.ToggleDesktop()
# Show some of the settings.
print ‘nThe user wants to show file extensions:’,
print ShObj.GetSetting(SSF_SHOWEXTENSIONS)
print ‘The user wants to see system files:’,
print ShObj.GetSetting(SSF_SHOWSYSFILES)
print ‘The user also wants to see operating system files:’,
print ShObj.GetSetting(SSF_SHOWSUPERHIDDEN)
# Check Explorer policies.
print ‘nThe NoDriveTypeAutoRun policies are:’
# Obtain the bit values. These values are:
# 0 Unknown drives
# 1 No root directory
# 2 Removable drives (Floppy, ZIP)
# 3 Hard disk drives
# 4 Network drives
# 5 CD-ROM drives
# 6 RAM disk drives
# 7 Reserved
MyBits = ShObj.ExplorerPolicy(‘NoDriveTypeAutoRun’)
# Display the results.
if MyBits.__and__(0x01) == 0x01:
print(‘tAutorun Disabled for Unknown Drives’)
else:
print(‘tAutorun Enabled for Unknown Drives’)
if MyBits.__and__(0x02) == 0x02:
print(‘tAutorun Disabled for No Root Directory’)
else:
print(‘tAutorun Enabled for No Root Drives’)
if MyBits.__and__(0x04) == 0x04:
print(‘tAutorun Disabled for Removable (Floppy/ZIP) Drives’)
else:
print(‘tAutorun Enabled for Removable (Floppy/ZIP) Drives’)
if MyBits.__and__(0x08) == 0x08:
print(‘tAutorun Disabled for Hard Disk Drives’)
else:
print(‘tAutorun Enabled for Hard Disk Drives’)
if MyBits.__and__(0x10) == 0x10:
print(‘tAutorun Disabled for Network Drives’)
else:
print(‘tAutorun Enabled for Network Drives’)
if MyBits.__and__(0x20) == 0x20:
print(‘tAutorun Disabled for CD-ROM Drives’)
else:
print(‘tAutorun Enabled for CD-ROM Drives’)
if MyBits.__and__(0x40) == 0x40:
print(‘tAutorun Disabled for RAM Disk Drives’)
else:
print(‘tAutorun Enabled for RAM Disk Drives’)
# Pause after the debug session.
raw_input(‘Press any key to continue…’)
[/code]

This example starts by showing a different kind of import call. In this case, the import retrieves only the Activator and Type classes from the System assembly. Using this approach reduces environmental clutter. In addition, using this technique reduces the memory requirements for your application and could mean the application runs faster. The example also imports the time module.

The first step in this application can seem a little complicated so it pays to break it down into two pieces. First, you must get the type of a particular object by using its identifier within the registry with the Type.GetTypeFromProgID() method. As previously mentioned, the object used in this example is Shell.Application. After the code obtains the type, it can create an instance of the object using Activator.CreateInstance().

The Shell.Application object, ShObj, provides several interesting methods and this example works with three of them. The first method, ToggleDesktop(), provides the same service as clicking the Show Desktop icon in the Quick Launch toolbar. Calling ToggleDesktop() the first time shows the desktop, while the second call restores the application windows to their former appearance. Notice the call to time.sleep(2), which provides a 2-second pause between the two calls.

The second method, GetSetting(), accepts a constant value as input. Listing 9-4 shows common settings you can query using GetSetting(). The example shows the results of three queries about Windows Explorer settings for file display. You can see these results (as well as the results for the third method) in Figure 9-15.

Listin g 9-4: Queryable information for GetSetting()

[code]
SSF_SHOWALLOBJECTS = 0x00000001
SSF_SHOWEXTENSIONS = 0x00000002
SSF_HIDDENFILEEXTS = 0x00000004
SSF_SERVERADMINUI = 0x00000004
SSF_SHOWCOMPCOLOR = 0x00000008
SSF_SORTCOLUMNS = 0x00000010
SSF_SHOWSYSFILES = 0x00000020
SSF_DOUBLECLICKINWEBVIEW = 0x00000080
SSF_SHOWATTRIBCOL = 0x00000100
SSF_DESKTOPHTML = 0x00000200
SSF_WIN95CLASSIC = 0x00000400
SSF_DONTPRETTYPATH = 0x00000800
SSF_SHOWINFOTIP = 0x00002000
SSF_MAPNETDRVBUTTON = 0x00001000
SSF_NOCONFIRMRECYCLE = 0x00008000
SSF_HIDEICONS = 0x00004000
SSF_FILTER = 0x00010000
SSF_WEBVIEW = 0x00020000
SSF_SHOWSUPERHIDDEN = 0x00040000
SSF_SEPPROCESS = 0x00080000
SSF_NONETCRAWLING = 0x00100000
SSF_STARTPANELON = 0x00200000
SSF_SHOWSTARTPAGE = 0x00400000
[/code]

The shell objects provide access to all sorts of useful information.
Fi gure 9-15: The shell objects provide access to all sorts of useful information.

The third method, ExplorerPolicy(), is a registry-based query that relies on bit positions to define a value. You find these values in the HKEY_CURRENT_USERSoftwareMicrosoft WindowsCurrentVersionPoliciesExplorer registry key. The two most common policies are NoDriveAutorun and NoDriveTypeAutoRun. When working with the NoDriveAutorun policy, Windows enables or disables autorun on a drive letter basis where bit 0 is drive A and bit 25 is drive Z. Listing 9-3 shows how to work with the bits for the NoDriveTypeAutoRun policy, while Figure 9-15 shows the results for the host machine.

You can find a number of other examples of this kind of late binding for IronPython on the Internet. For example, you can see a Word late binding example at http://www.ironpython.info/index .php/Extremely_Late_Binding. This particular example would possibly be the next step for many developers in working with Activator.CreateInstance(). The important thing to remember is that this method is extremely flexible and that you need to think of the impossible, as well as the possible, when using it.

Performing Late Binding Using Marshal.GetActiveObject()

Sometimes you need to interact with an application that’s already running. In this case, you don’t want to create a new object; you want to gain access to an existing object. The technique used to perform this type of late binding is to call Marshal.GetActiveObject() with the type of object you want to access. Typically, you use this technique with application objects, such as a running copy of Word. Listing 9-5 shows an example of how to use Marshal.GetActiveObject() to gain access to a running Word application.

Listin g 9-5: Working with a running copy of Word

[code]
# Import only the required classes from System.
from System.Runtime.InteropServices import Marshal
# Obtain a pointer to the running Word application.
# Word must be running or this call will fail.
WordObj = Marshal.GetActiveObject(‘Word.Application’)
# Add a new document to the running copy of Word.
MyDoc = WordObj.Documents.Add()
# Get the Application object.
App = MyDoc.Application
# Type some text in the document.
App.Selection.TypeText(‘Hello World’)
App.Selection.TypeParagraph()
App.Selection.TypeText(‘Goodbye!’)
[/code]

The import statement differs from normal in this example. Notice that you can drill down into the namespace or class you want, and then import just the class you need. In this case, the example requires only the Marshal class from System.Runtime.InteropServices.

The first step is to get the running application. You must have a copy of Word running for this step to work; otherwise, you get an error. The call to Marshal.GetActiveObject() with Word.Application returns a Word object, WordObj. This object is the same object you get when working with Visual Basic for Applications (VBA). In fact, if you can do it with VBA, you can do it with IronPython.

After gaining access to Word, the application adds a new document using WordObj.Documents.Add(). It then creates an Application object, App. Using the App.Selection.TypeText() method, the application types some text into Word, as shown in Figure 9-16. Of course, you can perform any task required — the example does something simple for demonstration purposes.

 You can control Word using IronPython as easily as you can using VBA.
Fi gure 9-16: You can control Word using IronPython as easily as you can using VBA.