Accessing COM Objects from Visual Basic .NET

By far, one of the most common and important forms of COM interop is the consumption of COM objects from inside of managed applications. Runtime Callable Wrappers enable such consumption. The following sections describe how to create and consume Runtime Callable Wrappers from your managed applications.

Creating a Runtime Callable Wrapper Automatically

In order to access COM objects from managed code, you need to have a Runtime Callable Wrapper. You can obtain RCWs for COM objects in two ways. The first is to create them yourself, and the second is to use the vendor's PIAs (Primary Interoperability Assemblies). In this section, you will see how you can create RCWs for virtually any COM object, leaving a discussion on PIAs for later in this chapter.

You might expect that Microsoft would make it easy to reference COM objects from your .NET applications and, in fact, they have. There are two ways for you to create an RCW—using the command-line utility TBLImp or automatically using Visual Studio .NET. Let's start with using Visual Studio .NET, the more traditional method. Perform the following steps to refer to the Microsoft CDO 1.21 library:

1.
Create or open a VB.NET project.

2.
Select Add Reference from the Project menu in Visual Studio.NET to display the Add References dialog box.

3.
Click the COM tab of the Add Reference dialog box.

4.
Select Microsoft CDO 1.21 Library from the list of registered COM objects and click the Select button, and then click OK.

If you are familiar with adding COM references in Visual Basic 6.0, you should see that this process is virtually identical. After performing the previous steps, you will see a new entry under the References section of your project named MAPI, which refers to the name of the COM object you just made a reference to.

Let's take a closer look at what Visual Studio .NET just did for you. The COM DLL that you referred to is normally installed in C:Program FilesCommon FilesSystem MSMAPI1033CDO.dll on most systems. As stated, Visual Studio needed to bridge the gap between the CDO COM object and your .NET application. To do this, it automatically created an RCW for you to consume from your Visual Studio project. Right-click and view the properties of the MAPI in the References section of your project. The first thing that you should notice is that the Copy Local property is set to TRUE. Note also that the path listed for the selected COM reference is pointing to a DLL in your project's OBJ directory called Interop.CDO.DLL, which isn't the COM library just referenced. It's instead the Runtime Callable Wrapper that Visual Studio automatically created for you. The Copy Local property simply instructs Visual Studio to copy the new RCW into a local directory. When you want to work with CDO from Visual Studio .NET, you will actually be working directly with the RCW, and the wrapper in turn is responsible for the instantiation and ongoing communication with the underlying CDO COM library. It is important to note that if you are to distribute your application to other computers, you must also ship the wrapper that Visual Studio .NET created for you.

MAILING FROM .NET

CDO stands for Collaborative Data Objects. This library allows you to access data programmatically on any MAPI-compliant provider such as Microsoft Exchange. In fact, CDO is a COM wrapper around Microsoft's Messaging API called MAPI. You can use CDO to access and send email, examine the contents of folders, and refer to the Exchange Address Book. In general, if your users are using a Microsoft email solution, CDO is a fairly simple way to send mail and interact with your mail server.

If you can't count on CDO being installed and working on a user's machine, your next best bet is to use the basic SMTP protocols. Sending email through SMTP is even provided in the .NET Framework through the System.Web.Mail namespace. The Framework email code requires CDOSYS (a scaled down, SMTP-based set of messaging code similar to CDO), which must be installed for the System.Web.Mail code to work.

If you are looking for a dependency free method for sending email messages, don't despair! There are many good samples of sending SMTP email messages using just the networking classes in the .NET Framework, and links to several are provided on the Chapter 9 section of http://www.duncanmackenzie.net/kickstartvb.


Listing 9.1 demonstrates some code that will consume the CDO object through the RCW.

Listing 9.1. Declare and Use the RCW Just as You Would the COM Object Directly
Dim oMAPISession As New MAPI.Session
Try
    oMAPISession.Logon()
    MessageBox.Show("Logon Successful for " _
        & oMAPISession.CurrentUser, _
        "Logon Result")
Catch
    MessageBox.Show("Logon Failed", "Logon Results")
End Try

For readers familiar with the programming of CDO from Visual Basic 6.0, much of the code in Listing 9.1 should be familiar if not identical to what you are used to. Notice that the code calls the RCW as if you were calling the COM object directly, because the RCW proxies all of your calls to the specific COM object it was built to wrap. As you can see on the first line, you instantiate the RCW in the same way you do the COM object. Calling methods of your RCW is identical to calling the methods of the CDO object as you can see in the calls to Logon and CurrentUser. In fact, you can also use the Object Browser in Visual Studio .NET to view the classes and interfaces automatically created for you in the RCW that map to COM interfaces, methods, and properties of the COM object it wraps, as depicted in Figure 9.2. You can now use the COM object as if you were calling it directly from your code.

Figure 9.2. Use the Object Browser to view the properties, methods, and events of the RCW that wraps an underlying COM object.


Creating a Runtime Callable Wrapper with TLBImp

You just used Visual Studio .NET to do all the dirty work to create an RCW. However, what happens if the underlying COM object changes its CLSID (Class ID) or adds new interfaces? The short answer is that the wrapper will break. It knows about your COM object by its CLSID; the CLSID determines where the COM object is physically located and how it can be used.

SHOP TALK: KEEPING IT COMPATIBLE

As a long-time Visual Basic developer, and therefore a long-time COM developer, talking about the Class ID (CLSID) of a component brings up a lot of frustrating memories. A COM object is defined by its interfaces, which are represented in a structure called a COM type library. Type libraries can either be included with the binary of the object or stored in a separate file with a .TLB extension. CLSIDs and IIDs are GUIDs (128-bit values, designed to be unique across all systems) that help to uniquely identify a COM object and the interfaces that it exposes. When a type library is created, a CLSID is created for that library, which is how other applications reference your library (using early binding). If, at some point in the future, you change the Class ID of your library, any application that was referencing your component will break. You can help prevent this in Visual Basic 6.0 by configuring your projects to be Binary Compatible. This setting forces Visual Basic to reuse the CLSID of an existing reference library (usually an earlier version of your own code) reference object that you can choose. There are other techniques to reduce the impact of changing an already referenced COM library, but this is more of a Visual Basic 6.0 and COM topic. I am bringing this topic up here, in a Visual Basic .NET book, for two reasons. The first reason is to make you appreciate that many of these issues (such as registering type libraries on the user's machine) go away when you are dealing with managed code. The second reason is to ensure that you keep these COM issues in mind when adding COM references to your .NET code; the Runtime Callable Wrapper is performing early binding and is just as susceptible to its target COM library changing its CLSID.


The quick and dirty answer to this problem is to regenerate the RCW. In order to do this using Visual Studio .NET, you need to delete the existing RCW on the file system, and re-add the reference to the COM object. If you find yourself doing this often, you might want to use the command-line utility called TLBImp.exe, which ships with the .NET Framework. The TLBimp.exe utility creates RCWs for most COM objects in the same way that Visual Studio did for you automatically. It does this by reading meta information stored about the COM object from a type library. Type libraries can be stored in a separate file (a .TLB file), or can be embedded directly into the COM object binary.

Tlbimp.exe CDO.dll

Using TLBImp to create RCWs is straightforward. The command line shown previously generates an RCW DLL with the same name as the type library found in CDO.dll (as opposed to the name of the physical filename) with a .DLL extension. In this case, TLBImp will create a new assembly called MAPI.DLL in the same directory as CDO.DLL. You can now move this assembly to a location in your project directory where you can reference it directly from your project. This time, however, when you are setting up the references you must refer to it like a normal .NET assembly instead of a COM object as you did when you made a reference to the CDO.DLL COM library directly. Perform the following steps to refer to RCW that you generated with TLBImp.exe in your project:

1.
Copy the MAPI.DLL assembly to some location in your project, such as the bin directory from the location it was created in. Optionally, you can use the /Out command-line parameter of TLBImp to specify the location of the resulting RCW. Refer to Table 9.1, which contains the common command-line parameters, for more information.

2.
Add a new reference to your project by choosing Add Reference from the Project menu in Visual Studio .NET.

3.
From the Add References dialog box, select Browse to choose the RCW that you just placed in the project directory.

4.
Select the MAPI.DLL file from the location you placed it in step 1, and then click OK.

As you would expect, using TLBImp.exe from the command line to create your RCWs does give you more options than having Visual Studio create them for you. For example, you can use some of the following command-line switches in Table 9.1 to customize how you can generate an RCW.

Table 9.1. Command-Line Options for TLBImp.exe
COMMAND-LINE SWITCHDESCRIPTION
/asmversion:versionNumberAllows you to specify the version number of the assembly to produce using the format major.minor.build.revision.
/help or /?Displays command syntax and options for TLBImp.
/keyfile:filenameSigns the resulting assembly with a strong name using the publisher's official public/private keypair found in filename.
/namespace:namespaceSpecifies the namespace in which to produce the assembly.
/out:filenameSpecifies the name of the output file, assembly, and namespace in which to write the metadata definitions.
/primaryProduces a primary interop assembly for the specified type library. Additional information is added to this assembly detailing the publisher of the type library produced the assembly. You should use the /primary option only if you are the publisher of the type library that you are importing with Tlbimp.exe. Note that you must also sign a primary interop assembly with a strong name.
/silentSuppresses the display of success messages.
/strictrefDoes not import a type library if the tool cannot resolve all references within the current assembly or the assemblies specified with the /reference option.
/verboseSpecifies verbose mode; displays additional information about the imported type library.

In fact, using TLBImpl.exe is extremely useful if you want to produce your own primary interop assemblies or RCWs that have a strong name such that they can be placed in the Global Assembly Cache (GAC).

Inheriting from COM Objects

Once you have successfully created your RCW, you can use it almost exactly as you do other classes. In fact, you can even create classes that inherit from a COM object's RCW. Perform the following steps to create a class that inherits from the CDO object you just wrapped:

1.
Add a new class to your project by selecting the Project menu and selecting Add Class. Name the file CDO.vb and click Open.

2.
Instruct your new class to inherit from the MAPI.Session class, which is denied by the CDO COM object and wrapped through the RCW, as shown in Listing 9.2.

Listing 9.2. Writing Code that Inherits from a COM Object
Public Class CDO
    Inherits MAPI.SessionClass

End Class

Once you have inherited the class, you can begin by providing implementations for the public functions you can override. To view these methods, choose (Overrides) from the left code window drop-down box. Then use the right code window drop-down box to view the methods that can be overridden. For example, Listing 9.3 demonstrates how you can override the CDO Logon method with custom code that will first require confirmation to continue the logon operation from the users.

Listing 9.3. Provide an Implementation for the Overridden Logon Method
Public Overrides Function Logon( _
          Optional ByVal ProfileName As Object = Nothing, _
   Optional ByVal ProfilePassword As Object = Nothing,
   Optional ByVal ShowDialog As Object = Nothing, _
   Optional ByVal NewSession As Object = Nothing, _
   Optional ByVal ParentWindow As Object = Nothing, _
   Optional ByVal NoMail As Object = Nothing, _
   Optional ByVal ProfileInfo As Object = Nothing) As Object

    Dim result As DialogResult _
                = MessageBox.Show("Are you sure you want to logon?", _
                   "Please Confirm", _
                    MessageBoxButtons.YesNo, _
                    MessageBoxIcon.Question, _
                    MessageBoxDefaultButton.Button1)
    If result = vbYes Then
        Try
            MyBase.Logon()
            MessageBox.Show("Successful Logon", _
                "Success", _
                MessageBoxButtons.OK, _
                MessageBoxIcon.Information)
        Catch
                MessageBox.Show("Logon Failed", _
                "Error", _
                MessageBoxButtons.OK, _
                MessageBoxIcon.Asterisk, _
                MessageBoxDefaultButton.Button1)
       End Try
    End If

End Function
					

Note that when you create classes that inherit from your COM object, it works like implementing an interface; you must provide complete implementation for all methods of the COM object.

Capturing COM Events

It's important to know that the RCW that you created will also propagate events generated by the underlying COM object, provided you want to receive them. Capture COM events as you would events from other classes using WithEvents in the declaration and Handles for the event handler. As an example, Listing 9.4 demonstrates how to catch the NewMail event from Microsoft Outlook.

Listing 9.4. You Handle a COM-Generated Event as You Would Any Other Event
 Public Class Form1
     Inherits System.Windows.Forms.Form
...
     Private WithEvents oOutlook As New Outlook.Application

    Private Sub oOutlook_NewMail() Handles oOutlook.NewMail MessageBox.Show("You have new
 mail")
    End Sub
 End Class
					

COM Interop Late Binding

All of the examples given so far have demonstrated how to use a COM object through early binding. That is, at compile time you already know the type of the object you are calling. In many cases, you might not have that luxury and must use late binding. Late binding forces your applications to wait until runtime to determine the type of object called. Listings 9.5 and 9.6 demonstrate how to perform late binding in both Visual Basic 6.0 and Visual Basic .NET. As you can see, the code is virtually identical.

Listing 9.5. Visual Basic 6.0 Code for Late Binding
Dim oMAPI as Object Set oMAPI = CreateObject("MAPI.Session") oMAPI.Logon

For those of you who might be confused about language choice, should you use C# or Visual Basic .NET, I consider late binding to be a clear example of how Visual Basic .NET's heritage works in your favor. As an existing Visual Basic/COM developer, interoperability with COM objects will be a major part of your development tasks, and Visual Basic .NET makes interoperability easier than it is in C#. The simplicity of late binding is one such example, and the other example would be the difference between C# and Visual Basic .NET in their support for calling COM functions with optional parameters. In Visual Basic .NET, you can just omit optional parameters when you are calling a procedure, without any special code required. In general, if you have to work with a great deal of legacy Visual Basic code or other COM objects, you will find it easier to do this work in Visual Basic .NET.

Listing 9.6. Visual Basic .NET Code for Late Binding Dim
oMAPI as Object oMAPI = CreateObject("MAPI.Session") oMAPI.Logon

In the previous example, oMAPI is declared as an object and the CreateObject method creates an object of the type specified in its string literal parameter, in this case a MAPI session object. MAPI.Session is referred to as the ProgID and is associated with the object's CLSID in the system Registry. At runtime, this application will dynamically determine the CLSID of the COM object and bind the results to the oMAPI variable.

Optionally, you can use reflection to perform late binding to COM objects in Visual Basic. NET. Reflection is the .NET way to late-bind to objects and is not limited to COM objects. Listing 9.7 uses reflection to late-bind to CDO and call the Logon method.

Listing 9.7. Use Reflection to Late-Bind to a COM Object and Call its Methods and Properties
Dim oCDO As Object
Dim oCDOType As Type

Try
    oCDOType = Type.GetTypeFromProgID("mapi.session")
    oCDO = Activator.CreateInstance(oCDOType)    oCDOType.InvokeMember("Logon", _
             Reflection.BindingFlags.Default _
             Or Reflection.BindingFlags.InvokeMethod, _
             Nothing, _
             oCDO, _
             Nothing)
Catch
     MessageBox.Show("Can not call CDO")
End Try

As you can see, CreateObject is a much cleaner implementation of late binding and without a doubt you should use it whenever possible. Reflection, however, is a very powerful feature of .NET; I encourage you to leverage it within your applications.

Using and Creating Primary Interop Assemblies

If you develop software that uses COM objects supplied by third-party vendors, you should make every attempt to use the Primary Interop Assembly (PIA) for those COM objects. A Primary Interop Assembly is a unique, vendor-supplied, RCW for a specific COM type library. It's important to use these RCWs because Visual Studio .NET and TLBImp do not always create the optimum wrapper for the underlying COM object. For example, if you attempt to automatically generate the RCWs for the Microsoft Office COM type libraries, such as Microsoft Office XP, you will find that the RCW you create does not work quite right.

Microsoft provides the PIAs for Microsoft Office XP that have been specifically generated and tested for use by those who want to automate Microsoft products from .NET. In addition, if different developers generate and sign their own interop assemblies for the same COM type library, they create a set of unique types that are incompatible. For that reason there can be exactly one vendor-supplied PIA for any COM type library, which must be signed with a strong name. PIAs are generally installed into the Global Assembly Cache so that they are readily accessible by any .NET application that wants to use the COM type libraries these PIAs represent.

Of course, you can create PIAs for your own COM objects, as demonstrated in Listing 9.8. Typically, you use the TLBImp utility with the /primary command-line switch to create PIAs. You will need to provide a strong name for the PIA, which essentially means you need to provide a cryptographic key to digitally sign the assembly.

Listing 9.8. Using TLBimp to Generate Primary Interop Assemblies
tlbimp MyCOMObj.dll /primary /keyfile:MyCompany.snk /namespace:MyCompany.MyCOMObj /out
:MyCompany.MyCOMObj.dll

Notice the use of the /keyfile switch; it references a .SNK file that contains the cryptographic information required to provide a strong name for the resulting assembly, MyCompany.MyCOMObj.dll, as specified with the /out switch. By using the /namespace switch, you can provide a .NET namespace that can be used to reference this object.

In order for other developers to use the PIA you just created, you must install and register your PIA. Typically, vendors provide a setup program to install and register the PIAs they provide. Note that PIAs do not need to be registered if you do not plan to use them with Visual Studio .NET. To register a PIA, use the Assembly Registration Tool (Regasm.exe) utility provided with the .NET Framework. The syntax for registration will look similar to the following command line.

Regasm MyCompany.MyCOMObj.dll

If you are simply distributing your PIAs to computers that will not be using them for development, you need to install the PIAs only in the Global Assembly Cache of the computer using the GACUtil command-line utility provided with the framework.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.223.169