Chris Peiris (Avanade
Australia, www.ChrisPeiris.com)
Shawn Cicoria (www.CedarLogic.com)
February 2007
Revised August 2007
Applies to:
Microsoft .NET Framework 3.0
Windows Vista
Microsoft Internet Information Services
Microsoft Visual Studio 2005
Summary: This article will detail step by
step instructions to consume COM+ application services from WCF clients. We
will also discuss how legacy applications can use applications that expose WCF
services built on .NET 3.0. The content for this article is based on Chapter 10
of
by .
This book is targeted towards beginner to intermediate readers and part of
Apress series that discusses WPF, WCF and WF. (30 printed pages)
Download QuickReturns case study description
Contents
Introduced in 1993, Component Object Model (COM) was the basis
for other emerging technologies from Microsoft such as Object Linking and
Embedding (OLE), ActiveX, and Distributed COM (DCOM). COM was initially
introduced to compete with Common Object Request Broker Architecture (CORBA), a
language-independent and cross-platform distributed system technology. They did
share some core principles, but they were not compatible. Concepts and
techniques such as Interface Definition Language (IDL) are present in both
technologies. However, binary interoperability didn’t exist.
COM+, introduced in 1998, was more of ancillary technology that was
built on COM but did not replace it. Key features of COM+ are transactional components,
queued components, role-based security, and object pooling. COM+ 1.5 added
features such as application pooling, SOAP services, services without components,
and some other features []. Not without faults, numerous
applications were developed based upon this component technology. As a result,
we can’t ignore the existing investment by enterprises in this technology. It
would damage Microsoft’s credibility and the marketability if Microsoft
introduced a new technology that would force an enterprise to scrap its
original investments. Therefore, Microsoft and the WCF team worked hard to
provide an evolutionary, as opposed to revolutionary, approach for bridging the
technological divide, never forgetting the famous quote “Those who cannot
remember the past are condemned to repeat it.” (Attributed to George Santayana,
19th-century philosopher)
Today, COM+ 1.5 is a core part of the Windows platform. Even with
the .NET base class library (BCL) COM
interoperability still occurs. Runtime callable wrappers (RCW) are used
throughout the .NET BCL—one prime example in .NET 3.0 is the web browser Control,
which is, as stated in the MSDN documentation, a managed wrapper for the web browser
ActiveX control. Additionally, serviced components use COM+ transactions.
Why Integrate with COM+?
Although the future of the Windows platform is destined to be managed
code, COM+ will still be around. COM exists in the core Windows platform in addition to the Win32 API (that has gone
legacy) but still exists and matures. However,
the key concern is the billions of lines of code that independent software
developers have written to produce solutions. A great number of solutions have
been built on COM+ by enterprises that have also spent billions of dollars on
them. Therefore, clearly WCF needs to work with legacy implementations in order
to justify an investment in extending existing applications.
Few applications built today are completely stand-alone. In fact,
the terms application and solution
need a little definition. Generally, an application
represents a stand-alone deployable set of components, functionality, and so on.
A solution, however, represents a combination of applications
coupled together (either tightly or loosely) to address numerous business-processing
requirements.
Therefore, today we need to build solutions. These solutions will
most likely require integration with existing data and processes that exist in
legacy technology, where some of that technology will be COM+. We should also
consider that the existing COM+ applications aren’t going to be thrown away and
rewritten in .NET and WCF. Since we’re building all these new applications
based upon .NET and they’ll of course offer fantastic services that you’ll just
need to share, you’ll need a way to provide legacy COM+ applications to call your
services, just as any other application would. Fortunately, the WCF team has
provided the core tools to facilitate both sides of the interoperability needs.
Note: We refer to QuickReturns Ltd
several times in this article. QuickReturns Ltd is a fictitious case study to
illustrate WCF business drivers and technical advancements. Please download it
from the links at the start and the article and familiarize yourself with the
business case.
Running a COM+ Application As a WCF Service
Let’s take a look at a scenario involving QuickReturns Ltd.’s Old
Horse position-tracking system (or a custody system). Old Horse was built in
the late 1990s using Visual Basic 6. Since other groups within QuickReturns
Ltd. leverage Old Horse, the Old Horse development team provided COM interfaces
allowing client applications to interoperate with Old Horse using COM+.
When an Asset Manager makes a trade, it’s necessary that the
trade information is posted to the custody system. Additionally, as you’ll see
later, it’s necessary that the custody system checks with the Asset Manager’s
system for pricing and pending trade information.An overview of what the solution architecture
is shown in figure 10-1 below.
Figure 10-1. QuickReturns Ltd.’s Old Horse system
Visual Basic 6 COM+ Component Sample Setup
The example we’ll use is a simple Visual Basic 6 COM (ActiveX
DLL) component that exposes two interfaces. The complete solution is contained
in accompanying sample code within the Example1 folder. The following code
snippets are just the method signatures representing the Visual Basic 6 COM
interface. This is an illustration of a Visual Basic 6 component and should not
be viewed as best Visual Basic 6 best practice.
When looking at Visual Basic 6 COM components, you’ll soon see that
some issues relate to how the WCF interfaces leverage type library information in
regard to COM+ application components. Visual Basic 6 COM packs both the
component logic and the type library information inside the COM DLL; doing so
adds some attributes that prevent some interfaces from being initially wrapped
in WCF COM+ integration.
Prior to working through this example, set up a virtual directory
in IIS called VB6ComSample that is set for ASP.NET 2.0
and has anonymous access enabled (you can run the script CreateVirtualDirs.bat
located in the Example1 samples directory to setup IIS for you).
The PositionManagement interface shown in
Listing 10-1 provides a set of simple methods that allow the retrieval of the
position for a given ticker, in addition to providing a method for updating the
persisted quantity associated with a ticker. One element that is not shown is a
constructor. COM+ objects don’t offer a constructor. They can provide information
in other ways, such as with an initialization method. Visual Basic 6 and COM
offer several ways of providing static configuration information such as COM+
Initialization strings on the configured component; however, that requires
implementing IObjectConstructString in Visual Basic 6
and using the ConstructionEnable attribute in .NET. For
the example code and to keep it simple, we’re just showing method interfaces.
The ability to provide a connection string on object construction would be
something that could be provided through COM+ initialization.
Listing 10-1. PositionManagement.cls
| PositionManagement.cls |
Copy Code |
|---|
'Simple interface that allows a nominal change in the quantity of a position
'ticker: Ticker symbol of security
'quantity: Amount (+/-) to shift existing position
'Throws an error if quantity is not sufficient to support the change
(overdrawn)
Public Function UpdatePosition(ByVal Ticker As String, _
ByVal Quantity As Long) As Long
Public Function GetQuantity(ByVal Ticker As String) As Long
|
The second component is the Position
component. This class represents mostly a data class with read/write
properties. In addition, it has two methods; one provides a retrieval of a
specific position for a ticker, and the other returns a concrete Position object for a specific ticker. Listing 10-2 shows the
abbreviated class, and the full class is part of the article code in \OldHorsePositionTracking\VB6\PositionManagement.
Listing 10-2. Visual Basic 6 Position Class: Position.cls
| Visual Basic 6 Position Class: Position.cls |
Copy Code |
|---|
Public Property Let Quantity(ByVal vData As Long)
...
Public Property Get Quantity() As Long
...
Public Property Let Ticker(ByVal vData As String)
...
Public Property Get Ticker() As String
...
Public Function GetQuantity(ByVal Ticker As String) As Long
...
Public Function GetPosition(ByVal Ticker As String) As Position
...
|
One additional aspect of the PositonManagement
class is that it’s configured in Visual Basic 6 to be an MTS component with a Required transaction setting. This setting is reflected in
the generated WCF service inside the configuration file and is handled
automatically by the COM+ Integration Wizard, which makes a call to the ComSvcConfig.exe utility. This allows flow from a WCF client
to your COM+ component, ultimately being managed by the Microsoft Distributed
Transaction Coordinator (MSDTC).
Once the project is built to an ActiveX DLL, it is ready to be
installed and configured as a COM+ application. Briefly, for a Visual Basic 6
COM component, you follow these steps to create the OldHorse
COM+ application: (We won’t go into too much depth on how to create Visual
Basic 6 COM+ applications. Note that these steps for COM+ applications are
programmable through the COM Administration type library. However, .NET offers
the RegSvcs.exe utility that provides a simple
command-line interface for this.)
- First, from Administrative Tools,
launch Component Services. Alternatively you can also get to this Microsoft
Management Console (MMC) via the dcomcnfg.exe
command. Then, expand the Component Services node until you are at the computer
you want to configure for your COM+ application. In this example, it’s the
local machine, or My Computer. Select the COM+ Applications object in the
console, right-click, and choose New Application, as shown in Figure 102.
.gif)
Figure 10-2. Creating a new COM+ application
- At this point you’re presented with the
COM+ Application Install Wizard. Click through the first page of the wizard. On
the second page, click the Create an Empty Application button, as shown in
Figure 10-3.
.gif)
Figure 10-3. Creating an empty COM+ application
- On the next page of the wizard, enter
the name of your application, and ensure you select Library Application as the activation
type (see Figure 10-4).
.gif)
Figure 10-4. OldHorse library activation
- Click through the last page of the
wizard. At this point, you should now have a COM+ application defined in Component
Services. However, this is an empty package and has no associated components.
- The next step is to add your compiled
ActiveX DLL into the package. Do that by first selecting the Components tree
from within the OldHorse COM+ application. Right-click
the Components folder under the OldHorse application,
and then choose New | Component, as shown in Figure 10-5.
.gif)
Figure 10-5. Adding a new component to the OldHorse application
- This opens the COM+ Component
Installation Wizard. Click Next in
the wizard, and then choose Install New Component(s). Then, navigate to where
your Visual Basic 6 COM component’s DLL resides (if you have extracted the
samples, it is located in the directory \OldHorsePositionTracking\VB6\PositionManagement).
Choose it, and then just click Next
until the wizard is dismissed.
At this point you should have a COM+ application with the components
shown in Figure 10-6.
.gif)
Figure 10-6. Configured OldHorse COM+ application
First, you’ll see two components each with a single interface
listed—the name manufactured by the Visual Basic 6 framework. Second, in the
right pane, notice the Required transaction attribute. (You can see this view by
clicking the detail view.) This attribute forces the activation of this
component within a COM+ transaction—either a new transaction or an inherited
transactional context from the caller.
COM+ Application WCF Service Wrapper
Once a COM+ application is configured, you’re ready to leverage
WCF’s utilities for creating the necessary resources for calling a COM+ component
from a WCF client. The primary utility for this is the ComSvcConfig.exe
utility. This is a command-line utility that is installed with the .NET 3.0
runtime. Additionally, the SvcConfigEditor.exe
utility provides a graphical interface with some additional features that help hide
the complexities of the command-line ComSvcConfig.exe
utility. One suggestion is to get used to the SvcConfigEditor.exe
utility; it facilitates the composition of proper configuration files for WCF
with configuration-time validation of many elements.
Using SvcConfigEditor.exe Utility
Before you proceed, it’s important to understand some caveats
related to COM+ interoperability with WCF. There are restrictions as to what
COM+ interfaces can be exposed as a web service through the COM+ Integration
layer. Those restrictions are listed in the SDK, but some of them are as
follows:
- Interfaces that pass object
references as parameters: This violates a core tenet of SOA in that
passing a reference across a service boundary is expensive.
- Interfaces that pass types that are not compatible with the .NET Framework COM Interop conversions: This is a general incompatibility
issue for types that won’t serialize between the interoperability layers.
- Interfaces for applications
that have application pooling enabled when hosted by COM+: This causes
multiple listeners on the same URI moniker issues because there will be more
than one application pool attempting to reserve the service endpoint address.
- Interfaces from managed
components that have not been added to the Global Assembly Cache: This
is a general limitation of how COM+ hosts configured managed components. There
are other means of using COM+ from managed applications (services without components
were introduced in COM+ 1.5 []), but they are not supported
with WCF COM+ integration.
The first item mentioned here is important because given that one
of the core tenets of SOA is that boundaries are explicit, it would be expensive
to share an interface pointer across the service boundary. Also, given that the
default WCF service behavior InstanceContext mode is PerCall, this is something your SOA implementation should
consider.
In addition to the previously listed limitations, you’ll soon see
some limitations with Visual Basic 6 components and, specifically, how Visual
Basic 6 components are implemented.
At this point, you’re ready to create a WCF interoperability
layer around your COM+ components. Start by launching the SvcConfigEditor.exe
utility, which is located in the Microsoft SDK’s Bin
directory. The easiest way is to launch the CMD shell shortcut that gets
installed on your Start menu under the Microsoft
Windows SDK program group or from within Visual Studio 2005 Tools menu as WCF
Service Configuration Editor.
Start with no configuration file, and have the utility generate the necessary parts; this
will allow you to call the OldHorse component from a WCF client.
From the menu bar of SvcConfigEditor.exe,
select File | Integrate | COM+ Application. At this point you should see
a listing of all the COM+ applications, including OldHorse,
that are present on the local machine.
.gif)
Figure 10-7. COM+ Integration Wizard
If you expand the OldHorse.PositionManagement
node until you are able to see the list of interfaces (which will only be one),
then select the _PostionManagement interface, and click
Next. At this point, you should
see the page shown in Figure 10-8.
.gif)
Figure 10-8. _PositionManagement interface
Keep all selected, and just click Next. This presents the Hosting
Mode options. Choose the web hosting in-process mode, which allows per-message
activation and hosting within the IIS/WAS worker process. The other hosting
options are not available for library-activated (in-process) applications and
are enabled when the activation type is Server Activated (out-of-process).
Ensure that the Add MEX endpoint option is enabled. This allows clients to
leverage WS-Metadata Exchange to query the interface for contract and service
information.
The next page of the wizard lists the IIS virtual directories on
the local machine. Make sure you choose an IIS virtual directory that is
configured for the relevant .NET Framework. For this example, we’ve preconfigured
a virtual directory called /localhost/VB6ComSample
(see Figure 10-9) that is configured for ASP.NET 2.0.
.gif)
Figure 10-9. Choosing an IIS virtual directory
At that point, click Next, and you’re presented with the summary
of options shown in Figure 10-10.
.gif)
Figure 10-10. COM+ integration summary page
Click Next again, and the SvcConfigEditor.exe
makes a call to the ComSvcConfig.exe utility with the
appropriate command-line options. This generates two files in the virtual
directory. If the SvcConfigEditor.exe utility cannot
find the ComSvcConfig.exe utility, you’ll be presented
with a message box asking you to specify where it can be located (by default
the ComSvcConfig.exe utility is located at %SystemRoot%
\Microsoft.NET\Framework\v3.0\Windows Communication Foundation directory)
The two resources that are generated provide the WCF wrapper
service resource file and a Web.config file. The WCF service file is generated
with the COM ProgID as the filename. For this example,
the component OldHorse.PositionManagement generates a
file OldHorse.PositionManagement.svc. The contents of
that file appear in Listing 10-3.
Listing 10-3. OldHorse.PositionManagement.svc
| [CodeLanguage table header] |
Copy Code |
|---|
<%@ServiceHost
Factory="System.ServiceModel.ComIntegration.WasHostedComPlusFactory"
Service="{f4612210-b755-4e17-87db-f82d9751d582},
{893ae5eb-6e13-4949-881f-5e923ebeb982}"
%>
|
The SVC file contains a single line that points to the service factory
that will provide the COM+ integration—WasHostedComPlusFactory.
The second parameter, Service, provides two
initialization parameters for the factory class. The first is the GUID for the
COM interface as specified by the type library for the COM component. If you
leverage a tool such as OleView (which comes with the
Windows SDK), view the type library for OldHorse, and
dump the IDL, you’ll see that the supplied GUID matches the UUID of the implementation class,
which is PositionManagement.
The second parameter represents the COM+ application
ID, which is visible by choosing the properties of the COM+ application from
the Component Services management console. So, the combination of the application
ID and the CLSID (ProgID reference from COM) is a
direct pointer that allows the WCF COM+ integration runtime to locate,
instantiate, and service the WCF client call.
If you check the properties of the OldHorse.PositionManagement
component from within Component Services, you’ll see that the CLSID GUID and application
GUID both match the generated GUIDs in the OldHorse.PositionManagement.svc
file as shown in the figure below, noting that the 2nd parameter, Application
GUID will be different on each machine.
.gif)
Figure 10-11. OldHorse.PositionManagement properties
Using ComSvcConfig.exe Utility
You can also use the stand-alone ComSvcConfig.exe
utility to generate the required resources COM+ application integration. The
primary difference is it doesn’t provide the up-front validation that the SvcConfigEditor.exe utility does for validating supported COM
interfaces prior to generation. Instead, it provides that information as error
messages at runtime.
Using the same COM+ application as an example, the following
command generates the required resources for wrapping your COM+ application’s PositionManagement interface in a WCF service and hosting
inside IIS/WAS (all on a single line).
ComSvcConfig.exe /install /application:OldHorse
/contract:OldHorse.PositionManagement,_PositionManagement
/hosting:was /webdirectory:VB6ComSample /mex
In addition to the /install option listed
here, there are two additional primary actions: /list
and /uninstall. The /list
option enumerates what WCF COM+ integration services currently exist on the
local machine. The /uninstall option removes the application
.svc file in addition to updating the Web.config (or
application configuration) file, removing all references to the identified application
and interface.
Client Proxy Generation
At this point, you’re ready to create the client proxy for your
WCF COM+ integration, using either the SvcUtil.exe utility
or the Visual Studio 2005 Add Service
Reference add-in, as described in Chapter 5 of our book. Before proceeding,
ensure that the IIS website that you will be using has anonymous access enabled
(accessed through the Directory Security tab in IIS Virtual Directory properties).
A completed solution appears in the sample code in \VB6ComClient directory.
In this section, you’ll
create a simple console application. Start Visual Studio 2005, and create a new
Windows console project. Once you have done this, right-click the project (or select
the Project menu), and choose Add Service Reference. You can find detailed
steps for generating service proxies in Chapter 4 of our book. The URI to
specify for the Add Service Reference dialog box looks like Figure 10-12.
.gif)
Figure 10-12. Adding a service reference to a COM+ WCF wrapper
Once you’ve generated the service reference, you can now provide
the client code. Inside the Main method, the example
code looks like Listing 10-4, which shows the completed client project’s program.cs class file.
Listing 10-4. WCF COM+ Integration Client
| Program.cs |
Copy Code |
|---|
namespace VB6ComClient
{
class Program
{
static void Main ( string[] args )
{
OldHorse._PositionManagementClient proxy =
new VB6ComClient.OldHorse._PositionManagementClient();
int q = proxy.GetQuantity( "MSFT" );
Console.WriteLine( "We have " + q + " of MSFT" );
q = proxy.UpdatePosition( "MSFT", 100 );
Console.WriteLine( "We now have " + q + " of MSFT" );
proxy.Close();
proxy = null;
Console.WriteLine("Press return to end..." );
Console.ReadLine();
}
}
}
|
As shown in Listing 10-4, you simply instantiate a proxy type
using the default constructor (which reads address, binding, and contract
information from the configuration file). Using the _PositionManagementClient
object (which was automatically generated from SvcUtil.exe)
you then make a call to the methods exposed on the interface.
Consuming the PositionManagement
interface from a WCF client is done just like with any other WCF-generated
proxy type. In this model, the call is handed from the client over HTTP, which
is then received by the IIS/Http.sys listener
framework, and finally onto the WCF framework inside the WasHostedComPlusFactory
type. The WCF framework does a runtime lookup of the COM+ information,
instantiates the Visual Basic 6 COM component, and services the call.
One thing to note is that given the default service InstanceContext behavior is PerCall,
the WCF COM+ integration framework will service each call with a new PositionManagement object. Therefore, if you require server-side
state, you must modify the service behavior. Please review Chapters 3 and 6 from
our book for details about service behavior.
Visual Basic 6 COM+ Hiding Interfaces
When COM was introduced, it provided a capable component
architecture that permitted developers to leverage binary compatibility and
reuse components across solutions. With this came the complexity of COM
(reference tracking especially) and the language of COM itself. A core
component of COM definitions are buried inside the type library for each COM
component. C/C++ programmers are used to seeing IDL, which describes the COM
interfaces of implementation components.
Visual Basic programmers are generally not accustomed to working
with IDL. This is because Visual Basic 6 hides the inner workings of COM.
However, it is possible to take a contract-first approach in working with
Visual Basic 6 and COM.
Generally, you can find good references on the Internet, and the
following link provides examples and shows how to provide a contract-first
approach to Visual Basic 6 COM development: .
During the generation of the WCF COM+ integration components for your
OldHorse Visual Basic 6 ActiveX DLL, the Position component, while visible in the component selection
page as shown in Figure 10-7 earlier in the article, offered no visible interfaces
for use with the WCF COM+ integration. This is because Visual Basic 6 generates
hidden interfaces for the type library information that is bundled with the COM
DLL for nonprimitive types. Generally, when using other COM+ languages,
specifically C/C++, generating the type library information, a critical aspect
of COM+ programming, is done using IDL and compiled into a type library (TLB)
that is then used by the implementation programmer to ensure adherence to the contract.
Any interfaces that have any hidden types as parameters or return values are
not available in the WCF COM+ integration framework.
For the OldHorse Visual Basic 6 COM
implementation, the reason the WCF COM+ Integration Wizard ignores the Position interface is because of the method GetPosition
that returns a Position object. Visual Basic 6 has
hidden the internally generated _Position (note the
underscore) interface from consumers of the type library; therefore, it’s not
possible to create a type of _Position by a caller—generally
that’s up to the COM component.
Using OleView.exe (which comes with the
Windows SDK), if you dump the IDL and inspect the _Position
interface, you can see it’s marked with a hidden
attribute (see Listing 10-5).
Listing 10-5. OldHorse Visual Basic 6 COM Position IDL
| Program.cs |
Copy Code |
|---|
[
odl,
uuid(7E22753A-CD1B-4620-A952-E3CDFD456431),
version(1.0),
hidden,
dual,
nonextensible,
oleautomation
]
interface _Position : IDispatch {
[id(0x68030001), propput] HRESULT Quantity([in] long );
[id(0x68030001), propget] HRESULT Quantity([out, retval] long* );
[id(0x68030000), propput] HRESULT Ticker([in] BSTR );
[id(0x68030000), propget] HRESULT Ticker([out, retval] BSTR* );
[id(0x60030002)] HRESULT GetQuantity(
[in] BSTR Ticker,
[out, retval] long* );
[id(0x60030003)] HRESULT GetPosition(
[in] BSTR Ticker,
[out, retval] _Position** );
};
[
uuid(E17BC5E8-0378-4775-88DE-BADB73C57F03),
version(1.0)
]
coclass Position {
[default] interface _Position;
};
|
Through the IDL you can see why the WCF COM+ Integration Wizard did
not display this interface and how you use interface names when you are using
the ComSvcConfig.exe utility. You don’t actually use
the class names as declared inside the Visual Basic 6 class files; you use the
generated interface names that Visual Basic 6 provides (prefixed with an
underscore, _).
So, again, if you require access to the Position object through the WCF service
boundary, you have a couple of workarounds (there may be more):
- Remediate Visual Basic 6 to leverage contract-first
COM+ development (see the MSDN article Building COM Components That Take Full
Advantage of Visual Basic and Scripting
(http://msdn2.microsoft.com/en-us/library/ms810017.aspx) for an approach.
- Provide a .NET wrapper that interacts directly
with Visual Basic 6 COM components and exposes .NET types on the service
boundary.
.NET Enterprise Services and COM+ Components
For another example, we’ve included a simple .NET 2.0 class
library that represents the OldHorse2 COM+ application
but written in .NET 2.0 using Enterprise Services and serviced components.
This solution file is located as part of the sample code in the
following directory:
OldHorsePositionTracking\DotNet\OldHorse2Sln
Prior to stepping through this example, set up a virtual
directory inside IIS called DotNetComSample that is
configured as ASP.NET 2.0 and has anonymous access enabled. The script CreateVirtualDirs.bat will create the IIS virtual directories
and set the .NET runtime to 2.0 for the sites.
The solution also contains
a couple of batch files (reg.bat and unreg.bat)
that handle the global assembly cache (GAC) installation and COM+ application
configuration. These batch files use the GacUtil.exe
utility and the RegSvcs.exe utility that handles GAC
and COM+ registration. As listed in the SDK requirements, a .NET component that
is also a serviced component (COM+) must be registered in the GAC, which
requires it to have a strong name.
The implementation of OldHorse2 is a
mirror image of the Visual Basic 6 COM example, except it uses attributes from
the Enterprise Services namespaces. Additionally, the Guid
attribute is applied to ensure you leverage a consistent CLSID and APPID
instead of relying on the framework to regenerate each time.
For the OldHorse2 project, Listing 10-6
shows the PositionManagement class. The code provides
the sample simple interface as the Visual Basic 6 version along with Transaction attributes and AutoComplete
attributes for transaction management.
Listing 10-6. OldHorse2 PositionManagement.cs
using System;
using System.EnterpriseServices;
using System.Runtime.InteropServices;
namespace OldHorse2
{
[Guid( "3B26F4CA-E839-4ab6-86D4-AADB0A8AADA5" )]
public interface IPositionManagement
{
long UpdatePosition( string ticker, long quantity );
long GetQuantity( string ticker );
}
[Guid( "08F01AD6-F3EB-4f41-A73A-270AA942881A" )]
[Transaction(TransactionOption.Required)]
public class PositionManagement : ServicedComponent, IPositionManagement
{
public PositionManagement() {}
#region IPositionManagement Members
[AutoComplete]
public long UpdatePosition( string ticker, long quantity )
{
IPosition pos = new Position();
pos = pos.GetPosition( ticker );
pos.Quantity += quantity;
return pos.Quantity;
}
[AutoComplete]
public long GetQuantity( string ticker )
{
IPosition pos = new Position();
pos = pos.GetPosition( ticker );
return pos.Quantity;
}
#endregion
}
}
As you can see in the code in Listing 10-6, we’ve specifically
provided the interface IPositionManagement that is
implemented in the class PositionManagement, which also
inherits from ServicedComponent. Additionally, the
class has the TransactionOption.Required setting with
each method having the AutoComplete attribute from
Enterprise Services. This will ensure that each instance and call through the PositionManagement type takes place within a COM+
transaction.
In the same project, we’ve also defined the Position
class. Listing 10-7 shows its contents. Notice that we’ve followed the same
approach of providing a specific interface and corresponding implementation
class.
Listing 10-7. OldHorse2 Position.cs
using System;
using System.Runtime.InteropServices;
using System.EnterpriseServices;
namespace OldHorse2
{
[Guid( "D428B97A-13C8-4591-8AC3-5E8622A8C8BE" )]
public interface IPosition
{
long Quantity
{ get; set; }
string Ticker
{ get; set; }
long GetQuantity( string ticker );
IPosition GetPosition( string ticker );
}
[Guid( "02FD3A3B-CFCE-4298-8766-438C596002B4" )]
public class Position : ServicedComponent, IPosition
{
...
#region IPosition Members
public long Quantity
...
public string Ticker
...
public long GetQuantity( string ticker )
...
public IPosition GetPosition( string ticker )
...
}
}
Once the project is compiled to a managed assembly, it’s
necessary to register it in the GAC using the GacUtil.exe
utility that comes with the .NET 3.0 Framework. The command to register is as
follows:
gacutil /i bin\debug\OldHorse2.dll
Once it’s registered in the GAC, you can then install it in COM+.
.NET offers a useful command-line utility that does all the work for you. The
following command creates the COM+ application along with registering the .NET assembly’s
components:
regsvcs bin\debug\OldHorse2.dll
You can attribute the assembly with Enterprise Services types
that control the COM+ registration, shown in Listing 10-8; therefore, you don’t
have to build the application first and install the components through the
wizard. If you want to script this outside of .NET or for non-.NET components,
you could leverage the COM+ administrative interfaces for controlling COM+
applications.
Listing 10-8. OldHorse2 Assembly
Attributes for COM+
[assembly: ComVisible( true )]
[assembly: Guid( "c41f4ee8-3475-47b6-b381-5e7774e4287d" )]
[assembly: ApplicationName("OldHorse2")]
[assembly: ApplicationActivation(ActivationOption.Library)]
[assembly: ApplicationAccessControl(false)]
These commands are best executed from the Windows SDK command prompt
or Visual Studio 2005 command prompt—located under the Tools folder for the
Windows SDK and Visual Studio 2005 program groups from your All Programs Start menu
Item.Additionally, the commands are
contained in the batch files previously mentioned. Once registered, you should
now see in Component Services the OldHorse2 application,
as shown in Figure 10-13.
.gif)
Figure 10-13. OldHorse2 .NET COM+ registration
Now, using OleView.exe (from the Windows
SDK), refer to the IDL that is generated by the .NET Framework. The full IDL
files are located as part of the sample code in the \OldHorsePositionTracking
directory. Listing 10-9 shows the IDL listing.
Listing 10-9. OldHorse2 .NET IDL
[
odl,
uuid(D428B97A-13C8-4591-8AC3-5E8622A8C8BE),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, OldHorse2.IPosition)
]
interface IPosition : IDispatch {
...
[
odl,
uuid(3B26F4CA-E839-4AB6-86D4-AADB0A8AADA5),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, OldHorse2.IPositionManagement)
]
interface IPositionManagement : IDispatch {
...
The code has been abbreviated here, but you can see that the declared
interfaces IPosition and IPositionManagement
both do not have the hidden attribute. Therefore, you should
have a different experience when you run the WCF COM+ Integration Wizard.
Start the SvcConfigEditor.exe utility,
and access the COM+ integration feature. You now see the OldHorse2
application along with both the IPosition and IPositionManagement
interfaces available for integration.
.gif)
Figure 10-14. OldHorse2 WCF COM+ Integration Wizard
Select the IPositionManagement interface and then click Next. You now see that
both methods, as with the Visual Basic 6 COM component, appear (see Figure 10-15).
.gif)
Figure 10-15. OldHorse2.PositionManagement interface methods
Click Next two times, and then click Finish. At this point you’ll have two resources generated in
the virtual directory root—a Web.config file along with
the Service Host file called OldHorse2.PositionManagement.svc.
Client Proxy Generation
Once again, create a Visual
Studio 2005 console application, and choose "Add Service Reference" to add
to the project using the following URI shown in Figure 10-16.
.gif)
Figure 10-16. Adding a service reference to the project
In the completed solution, Listing 10-10 shows the code that
performs the same call as the Visual Basic 6 COM client performed. The only
difference is the type name no longer is prefixed with an underscore (_). This
is because when authoring components in .NET, you have control over the
interface names, where in Visual Basic 6 it’s left up to the Visual Basic 6
framework, hidden from normal levels of control. Other than that, there’ no
discernable difference from the consumer side, as shown in Listing 10-10.
Listing 10-10. OldHorse2 Position Management Client
namespace DotNetComClient
{
class Program
{
static void Main ( string[] args )
{
OldHorse2.PositionManagementClient();
OldHorse2.PositionManagementClient proxy =
new OldHorse2.PositionManagementClient();
long q = proxy.GetQuantity("MSFT");
Console.WriteLine( "We have " + q + " of MSFT" );
q = proxy.UpdatePosition( "MSFT", 100 );
Console.WriteLine( "We now have " + q + " of MSFT" );
proxy.Close();
proxy = null;
Console.WriteLine( "Press return to end..." );
Console.ReadLine();
}
}
}
Consuming WCF Services from COM+
Up to now, we’ve focused on solutions that need to leverage
existing legacy application logic that is hosted in COM+. We’ve focused
primarily on Visual Basic 6 given its distinct ability to hide some things that
you need control over in order to fully leverage and reuse your application logic.
This section approaches the problem scenario from the perspective
that these legacy solutions are not stagnant. In fact, it has been estimated that
nearly 90 percent [] of IT budgets are focused on
maintaining and extending existing solutions—many of those built on Visual
Basic 6 and other legacy technologies.
So, those applications aren’t going away. In fact, they most
likely will need to be extended to support new functionality or just change the
way they interface with other applications.
For the examples in this article, you’ll look at how you can make
a WCF service look like a COM+ component. This allows your legacy clients that
understand COM+ to work with your new .NET 3.0–based applications that expose
service endpoints. Note that both the .NET 3.0 and .NET 2.0 runtimes are
required when calling from any client. This is a requirement as the dynamic
invocation framework is leveraged in process by the client process.
QuickReturns Ltd. Quote Service
The QuickReturns Ltd. system, built on .NET 3.0, provides a quote
service using WCF. All parts of the QuickReturns Ltd. application leverage this
service. Some of the Old Horse custody systems, however, require the ability to
reuse this application logic, and they’ve chosen to use WCF COM integration
capabilities. The new QuickReturns Ltd. quote service is hosted in ASP.NET and
IIS and exposes its services using WCF.
Alternatively, we’ll also discuss how you can leverage runtime
registration of the COM interface through the use of the WSDL and MEX service monikers.
Typed Contract Service Moniker
We’ll provide a quick walk-through for the first scenario,
consuming a WCF Service from COM clients. This example will provide both an
automation client (VBScript) and an early binding client, Visual Basic 6. The
Visual Studio 2005 solution file QuickReturnsQuotes.sln contains
the website and proxy projects, located in the Example2 directory.
The first part of the solution is the QuickReturnsQuotes WCF service,
which is hosted in IIS and ASP.NET. If you haven’t already run the
setup script, to set up this virtual directory in IIS, run the batch file
CreateVirtualDirs.bat. The requirements are that IIS is
installed along with .NET 2.0 and the .NET 3.0 runtime components.
Open the solution file QuickReturnsQuotes.sln.
The solution file contains two projects. The first is the website that was just
mapped using the scripts mentioned previously. If the project doesn’t load,
there’s a problem with the script on your machine, and you’ll have to map the
site manually and reload the project. Ensure that you have IIS and .NET 2.0
installed and ASP.NET registered with IIS (use the aspnet_regiis.exe
command in the Framework folder).
The second project represents the proxy that when compiled, with
a strong name, will be registered both in the GAC and as a COM interface using
the RegSvcs.exe utility that’s part of the .NET 3.0
Framework.
This project has several extra member files along with both pre-build
and post-build event command lines:
- makeProxy.bat: This is the batch file that calls SvcUtil.exe
to generate the proxy stub source files; this file is part of the project prebuild steps.
- reg.bat: This is the batch file that registers the assembly in the
GAC and for COM interoperability; this file is part of the project post-build steps.
- unreg.bat: This is the batch file that will remove the assembly from
the GAC and from COM interoperability.
Note: For the build steps and these batch files to work, Visual Studio 2005 must be
installed in the default path. If you chosen a different path or haven’t
installed Visual Studio 2005, you need to update the path to the utilities as
required.
If you build the solution and all is successful, then you should
have a GAC-installed assembly registered for COM interoperability and ready for
use by COM clients. To verify, you can open Windows Explorer to the C:\Windows\Assembly path and see the assembly TypedServiceProxy listed, as shown in Figure 10-17.
.gif)
Figure 10-17. QuickReturns Ltd. WCF proxy in the GAC
Note: If you
haven’t modified any of the project Guid attributes,
then the next two steps are not required for this project to work. This would
be a normal step in your solutions to validate the correct Interface GUIDs.
The next step is to both verify the registration for COM and
retrieve the interface ID that is stored in the registry. The best tool for
this is OleView.exe, which comes with the Windows SDK.
Start OleView.exe, and open the top-level node labeled Type Libraries. Scroll down until you
find TypedServiceProxy, as shown in Figure 10-18.
.gif)
Figure 10-18. TypedServiceProxy registered in COM
For the next step, you must retrieve the interface ID (the GUID)
for the IQuoteService interface. The OleView.exe
utility can view the IDL for any COM registered classes. Double-click the item TypedServiceProxy in the list to open the
ITypeLib Viewer, as shown in Figure 10-19.
.gif)
Figure 10-19. ITypeLib Viewer for TypedServiceProxy
Find in the right pane of the viewer the IDL definition for the IQuoteService interface (which inherits from IDispatch—implying it supports automation as well as early
bind COM clients). Now, just above it (like attributes in .NET) are a list of
IDL attributes for this interface. We’re looking for the universally
unique identifier (UUID)
just above it. For this component, its value is 058E1BEC-C44A-31FB-98C8-9FB223C46FAF.
Inside the project file TypedServiceProxy,
you’ll see a VB Script file that illustrates how to call from an automation
client. Since this is an early bound client, it requires the interface ID to be
part of the service moniker construction string for the GetObject
call. The call sequence is into the quote service through COM and then through
the WCF framework to the quote service .NET assembly hosted in IIS/ASP.NET.
Listing 10-11 is the source file for QuickReturnsScriptClient.vbs;
note the wrap on some lines.
Listing 10-11. QuickReturnsScriptClient.vbs Automation Client
| QuickReturnsScriptClient.vbs |
Copy Code |
|---|
Option Explicit
Dim quoteProxy, moniker, result
moniker = "service:address="
moniker = moniker + "http://localhost/QuickReturnsQuotes/service.svc"
moniker = moniker + ",binding=wsHttpBinding"
moniker = moniker + ",contract={058E1BEC-C44A-31FB-98C8-9FB223C46FAF}"
'... cut comments
Set quoteProxy = GetObject(moniker)
result = quoteProxy.GetQuote("MSFT")
WScript.Echo "MSFT's price is " + CStr(result)
|
Visual Basic 6 can use early binding. Early binding allows
the lookup and discovery of the interfaces in your COM component at design
time. So, at runtime the COM client is expecting that the same UUID of your
interface is registered (via type library registration). The type library that
needs to be registered and referenced is part of the reg.bat
batch file in the TypedServiceProxy project—QuickReturnsProxy.tlb. COM interfaces are to be considered
immutable. If they change, then the underlying IDL will change. Therefore, any
changes to your interfaces in the base WCF service class will require a regeneration
of the proxy and a regeneration of the type library for use by clients.
There are scenarios where registering the COM type library is not
feasible. An example is Microsoft Excel spreadsheets that require dynamic
discovery and invocation, through COM locally to WCF services. For this, the
WCF framework and the COM integration provides a dynamic model, or what’s known
as late binding.
What the WCF framework provides is the runtime construction of a
proxy and COM interface for the COM client at object construction time. By
first querying the service metadata, after being provided some initialization
parameters, the WCF framework generates both a WCF proxy and a COM callable
wrapper that the COM client interfaces with. You currently have two choices for
the service monikers: WS-MetatdataExchange (MEX) and WSDL. Given this is a non-typed
model, it is callable only by clients that support automation (IDispatch)
such as VBScript, Visual Basic 6, Excel, and so on.
WCF supports the WS-MetadataExchange protocol that provides the discovery
of services in addition to policy and schema information. Please see Chapter 4 of
our book for more information. The WCF COM integration framework uses this to
dynamically derive the service endpoint interfaces along with binding and
service behavior.
Starting with the scripting sample from the project file in Listing
10.2, there’s an additional VBScript file: QuickReturnsScriptClientMex.vbs.
Listing 10-13 shows its contents (note the line wrap).
From the code in Listing 10-13, you don’t have a local
configuration file or a strongly typed object (in COM or .NET). Therefore, you must
supply the “discovery” information to the GetObject
call. One part is the URI for where the MEX metadata is found. The others are
the URI of the service endpoint, binding, and contract information that will be
mapped into the MEX response.
Similar to how WCF works with the WS-MetadataExchange protocol to
dynamically derive the COM and WCF interfaces and types, the service moniker
can also work with a WSDL contract. Listing 10-14, contained in the file QuickReturnsScriptClientWsdl.vbs,
illustrates how to make a call using the service moniker for WSDL.
The first statement after the variable declarations makes a call
to the included function that invokes GetWsdlFromUrl.
This VBScript function just makes an HTTP get call to the URI to retrieve the HTTP
response, which for that URI is the WSDL document for the service interface.
The moniker initialization string is then composed of the WSDL
response along with the remaining service moniker attributes. The WSDL string
is an XML response that fully describes the IQuoteService
interface exposed at the endpoint address. It’s the same XML you would see if
you opened the URL http://localhost/QuickReturnsQuotes/service.svc?wsdl
directly from a browser.
Again, using the dynamic service moniker, the COM interface makes
a call into the WCF framework to dynamically construct the types necessary to
make a round-trip request into the WCF service that is hosted in IIS—all
without the COM client knowing the underlying workings of how to work with WCF
(other than the moniker construction). What the dynamic generation provides is the
generation of a fully configured proxy that matches the service endpoints
advertised metadata including policy, security, and contract information.
Briefly, let’s summarize what the high-level steps are required
to consume a WCF service as a COM interface leveraging a typed contract service
moniker:
[1] What’s new in COM+ 1.5” on .MSDN at .
[2] Services without Components in COM+ 1.5. Please refer to
[3]Erlikh,
L. (2000). "Leveraging legacy system dollars for E-business". (IEEE) IT Pro,
May/June 2000,
This article focused on interoperability with COM, both from a
consumer and from a service perspective. WCF and .NET framework 3.0 provides a strong
extensible starting point to help in the evolutionary model of moving solutions
into the SOA age.
Avanade is a global IT consultancy dedicated to using the Microsoft platform to help enterprises achieve profitable growth. Through proven solutions that extend Microsoft technologies, Avanade helps enterprises increase revenue, reduce costs, and reinvest in innovation to gain competitive advantage. Our consultants deliver value according to each customer's requirements, time line, and budget by combining insight, innovation, and the talent of our global workforce. Additional information can be found at www.avanade.com.