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.
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. | |
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 .NETCDO 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.
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.
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.
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.
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).
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. | |
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. |
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.
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.
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.
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.
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.
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.
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.
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.
tlbimp MyCOMObj.dll /primary /keyfile:MyCompany.snk /namespace:MyCompany.MyCOMObj /out |
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.
3.137.223.169