Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1303
Chapter 28: Using Business Objects
Runtime
Callable
Wrapper
(RCW)
.NET Code
(C# or
VB.NET)
ActiveX
DLL or OCX
(COM
Component)
Your New .NET
Code
.NETs Built-In
Interoperability
Technology
Your existing
ActiveX Component
Code
Managed Code Unmanaged Code
Figure 28-6
The Runtime Callable Wrapper
The Runtime Callable Wrapper, or RCW, is the magic piece of code that enables interaction to occur
between .NET and COM. One RCW is created for each COM component in your project. To create an
RCW for a COM component, you can use Visual Studio 2008.
To add an ActiveX DLL to the References section of your project, choose Website ➪ Add Reference or
choose the Add Reference menu item that appears when you right-click the root node of your project in
the Solution Explorer.
The Add Reference dialog box appears with five tabs: .NET, COM, Projects, Browse, and Recent, as
shown in Figure 28-7. For this example, select the COM tab and locate the component that you want to
add to your .NET project. After you have located the component, highlight the item and click OK to add
a reference to the component to your project. The newly added component will then be found inside a
newly created
Bin
folder in your project.
Your Interop library is automatically created for you from the ActiveX DLL that you told Visual Studio
2008 to use. This Interop library is the RCW component customized for your ActiveX control, as shown
previously in Figure 28-6. The name of the Interop file is simply
Interop.OriginalName.DLL
.
It is also possible to create the RCW files manually instead of doing it through Visual Studio 2008. In the
.NET Framework, you will find a method to create RCW Interop files for controls manually through a
command-line tool called the Type Library Importer. You invoke the Type Library Importer by using the
tlbimp.exe
executable.
For example, to create the Interop library for the SQLDMO object used earlier, start up a Visual Studio
2008 Command Prompt from the Microsoft Visual Studio 2008 ➪ Visual Studio Tools group within your
Start Menu. From the comment prompt, type
tlbimp sqldmo.dll /out:sqldmoex.dll
In this example, the
/out:
parameter specifies the name of the RCW Interop library to be created. If you
omit this parameter, you get the same name that Visual Studio would generate for you.
1303
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1304
Chapter 28: Using Business Objects
Figure 28-7
The Type Library Importer is useful when you are not using Visual Studio 2008 as your development
environment, if you want to have more control over the assemblies that get created for you, or if you are
automating the process of connecting to COM components.
The Type Library Importer is a wrapper application around the
TypeLibConvertor
class of the
Sys-
tem.Runtime.InteropServices
namespace.
Using COM Objects in ASP.NET Code
To continue working through some additional examples, you next take a look at a simple example of
using a COM object written in Visual Basic 6 within an ASP.NET page.
In the first step, you create an ActiveX DLL that you can use for the upcoming examples. Add the Visual
Basic 6 code shown in Listing 28-1 to a class called
NameFunctionsClass
and compile it as an ActiveX
DLL called
NameComponent.dll
.
Listing 28-1: VB6 code for ActiveX DLL, NameComponent.DLL
Option Explicit
Private m_sFirstName As String
Private m_sLastName As String
Public Property Let FirstName(Value As String)
m_sFirstName = Value
1304
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1305
Chapter 28: Using Business Objects
End Property
Public Property Get FirstName() As String
FirstName = m_sFirstName
End Property
Public Property Let LastName(Value As String)
m_sLastName = Value
End Property
Public Property Get LastName() As String
LastName = m_sLastName
End Property
Public Property Let FullName(Value As String)
m_sFirstName = Split(Value, " ")(0)
If (InStr(Value, " ") > 0) Then
m_sLastName = Split(Value, " ")(1)
Else
m_sLastName = ""
End If
End Property
Public Property Get FullName() As String
FullName = m_sFirstName + " " + m_sLastName
End Property
Public Property Get FullNameLength() As Long
FullNameLength = Len(Me.FullName)
End Property
Now that you have created an ActiveX DLL to use in your ASP.NET pages, the next step is to create a
new ASP.NET project using Visual Studio 2008. Replace the HTML code in the
Default.aspx
file with
the HTML code illustrated in Listing 28-2. This adds a number of text boxes and labels to the HTML page,
as well as the Visual Basic or C# code for the functionality.
Listing 28-2: Using NameComponent.dll
VB
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub AnalyzeName_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim Name As New NameComponent.NameFunctionsClass()
If (FirstName.Text.Length > 0) Then
Name.FirstName = FirstName.Text
End If
If (LastName.Text.Length > 0) Then
Continued
1305
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1306
Chapter 28: Using Business Objects
Name.LastName = LastName.Text
End If
If (FullName.Text.Length > 0) Then
Name.FullName = FullName.Text
End If
FirstName.Text = Name.FirstName
LastName.Text = Name.LastName
FullName.Text = Name.FullName
FullNameLength.Text = Name.FullNameLength.ToString
End Sub
</script>
<html xmlns=" >
<head runat="server">
<title>Using COM Components</title>
</head>
<body>
<form id="form1" runat="server">
<p>
<asp:Label ID="Label1" runat="server">First Name:</asp:Label>
<asp:TextBox ID="FirstName" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label ID="Label2" runat="server">Last Name:</asp:Label>
<asp:TextBox ID="LastName" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label ID="Label3" runat="server">Full Name:</asp:Label>
<asp:TextBox ID="FullName" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label ID="Label4" runat="server">Full Name Length:</asp:Label>
<asp:Label ID="FullNameLength" runat="server"
Font-Bold="True">0</asp:Label>
</p>
<p>
<asp:Button ID="AnalyzeName" runat="server"
OnClick="AnalyzeName_Click" Text="Analyze Name"></asp:Button>
</p>
</form>
</body>
</html>
C#
<%@ Page Language="C#" %>
<script runat="server">
1306
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1307
Chapter 28: Using Business Objects
protected void AnalyzeName_Click(object sender, System.EventArgs e)
{
NameComponent.NameFunctionsClass Name =
new NameComponent.NameFunctionsClass();
if (FirstName.Text.Length > 0)
{
string firstName = FirstName.Text.ToString();
Name.set_FirstName(ref firstName);
}
if (LastName.Text.Length > 0)
{
string lastName = LastName.Text.ToString();
Name.set_LastName(ref lastName);
}
if (FullName.Text.Length > 0)
{
string fullName = FullName.Text.ToString();
Name.set_FullName(ref fullName);
}
FirstName.Text = Name.get_FirstName();
LastName.Text = Name.get_LastName();
FullName.Text = Name.get_FullName();
FullNameLength.Text = Name.FullNameLength.ToString();
}
</script>
Now you need to add the reference to the ActiveX DLL that you created in the previous step. To do so,
follow these steps:
1. Right-click your project in the Solution Explorer dialog.
2. Select the Add Reference menu item.
3. In the Add Reference dialog box, select the fourth tab, Browse.
4. Locate the
NameComponent.dll
object by browsing to its location.
5. Click OK to add
NameComponent.dll
to the list of selected components and close the dialog
box.
If you are not using Visual Studio 2008 or code-behind pages, you can still add a reference to your COM
control by creating the RCW manually using the Type Library Converter and then placing an
Imports
statement (VB) or
using
statement (C#) in the page.
After you have selected your component using the Add Reference dialog, an RCW file is created for the
component and added to your application.
That’s all there is to it! Simply run the application to see the COM interoperability layer in action.
Figure 28-8 shows the ASP.NET page that you created. When the Analyze Name button is clicked, the
fields in the First Name, Last Name, and Full Name text boxes are sent to the RCW to be passed to
1307
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1308
Chapter 28: Using Business Objects
the
NameComponent.DLL
ActiveX component. Data is retrieved in the same manner to repopulate the text
boxes and to indicate the length of the full name.
Figure 28-8
Accessing Tricky COM Members in C#
Sometimes, some members of COM objects do not expose themselves properly to C#. In the
preceding examples, the
String
properties did not expose themselves, but the
Long
property
(
FullNameLength
)did.
You know when there is a problem because, although you can see the property, you cannot compile the
application. For instance, instead of the code shown in Listing 28-2 for C#, use the following piece of code
to set the
FirstName
property of the
NameComponent.dll
ActiveX component:
if (FirstName.Text.Length > 0)
Name.FirstName = FirstName.Text.ToString();
When you try to compile this code, you get the following error:
c:
\
inetpub
\
wwwroot
\
wrox
\
Default.aspx.cs(67): Property, indexer, or event ’FirstName’
is not supported by the language; try directly calling accessor methods ’NameComponent
.NameFunctionsClass.get_FirstName()’ or ’NameComponent.NameFunctionsClass
.set_FirstName(ref string)’
The
FirstName
property seems to be fine. It shows up in IntelliSense, but you can’t use it. Instead, you
must use
set_FirstName
(and
get_FirstName
to read). These methods do not show up in IntelliSense,
but rest assured, they exist.
1308
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1309
Chapter 28: Using Business Objects
Furthermore, these methods expect a
ref string
parameter rather than a
String
.Intheexamplefrom
Listing 28-2, two steps are used to do this properly. First,
String
is assigned to a local variable, and then
the variable is passed to the method using
ref
.
Releasing COM Objects Manually
One of the great things about .NET is that it has its own garbage collection — it can clean up after itself.
This is not always the case when using COM interoperability, however. .NET has no way of knowing
when to release a COM object from memory because it does not have the built-in garbage collection
mechanism that .NET relies on.
Because of this limitation, you should release COM objects from memory as soon as possible using the
ReleaseComObject
class of the
System.Runtime.InteropServices.Marshal
class:
C#
System.Runtime.InteropServices.Marshal.ReleaseComObject(Object);
It should be noted that if you attempt to use this object again before it goes out of scope, you would raise
an exception.
Error Handling
Error handling in .NET uses exceptions instead of the HRESULT values used by Visual Basic 6 applica-
tions. Luckily, the RCW does most of the work to convert between the two.
Take, for instance, the code shown in Listing 28-3. In this example, a user-defined error is raised if the
numerator or the denominator is greater than 1000. Also notice that we are not capturing a divide-by-zero
error. Notice what happens when the ActiveX component raises the error on its own.
Begin this example by compiling the code listed in Listing 28-3 into a class named
DivideClass
within
an ActiveX component called
DivideComponent.dll
.
Listing 28-3: Raising errors in VB6
Public Function DivideNumber(Numerator As Double, _
Denominator As Double) As Double
If ((Numerator > 1000) Or (Denominator > 1000)) Then
Err.Raise vbObjectError + 1, _
"DivideComponent:Divide.DivideNumber", _
"Numerator and denominator both have to " + _
"be less than or equal to 1000."
End If
DivideNumber = Numerator / Denominator
End Function
Next, create a new ASP.NET project; add a reference to the
DivideComponent.dll
(invoking Visual
Studio 2008 to create its own copy of the RCW). Remember, you can also do this manually by using the
tlbimp
executable.
1309
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1310
Chapter 28: Using Business Objects
Now add the code shown in Listing 28-4 to an ASP.NET page.
Listing 28-4: Error handling in .NET
VB
<%@ Page Language="VB" %>
<script runat="server">
Protected Sub Calculate_Click(ByVal sender As Object, _
ByVal e As System.EventArgs)
Dim Divide As New DivideComponent.DivideClass()
Try
Answer.Text = Divide.DivideNumber(Numerator.Text, Denominator.Text)
Catch ex As Exception
Answer.Text = ex.Message.ToString()
End Try
System.Runtime.InteropServices.Marshal.ReleaseComObject(Divide)
End Sub
</script>
<html xmlns=" /><head runat="server">
<title>Using COM Components</title>
</head>
<body>
<form id="form1" runat="server">
<p>
<asp:Label ID="Label1" runat="server">Numerator:</asp:Label>
<asp:TextBox ID="Numerator" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label ID="Label2" runat="server">Denominator:</asp:Label>
<asp:TextBox ID="Denominator" runat="server"></asp:TextBox>
</p>
<p>
<asp:Label ID="Label3" runat="server">
Numerator divided by Denominator:</asp:Label>
<asp:Label ID="Answer" runat="server" Font-Bold="True">0</asp:Label>
</p>
<p>
<asp:Button ID="Calculate"
runat="server"
OnClick="Calculate_Click"
Text="Calculate">
</asp:Button>
</p>
1310
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1311
Chapter 28: Using Business Objects
</form>
</body>
</html>
C#
<%@ Page Language="C#" %>
<script runat="server">
protected void Calculate_Click(object sender, System.EventArgs e)
{
DivideComponent.DivideClass myDivide = new DivideComponent.DivideClass();
try
{
double numerator = double.Parse(Numerator.Text);
double denominator = double.Parse(Denominator.Text);
Answer.Text = myDivide.DivideNumber(ref numerator,
ref denominator).ToString();
}
catch (Exception ex)
{
Answer.Text = ex.Message.ToString();
}
System.Runtime.InteropServices.Marshal.ReleaseComObject(myDivide);
}
</script>
The code in Listing 28-4 passes the user-entered values for the
Numerator
and
Denominator
to the
DivideComponent.dll
ActiveX component for it to divide. Running the application with invalid data
gives the result shown in Figure 28-9.
Figure 28-9
1311
Evjen c28.tex V2 - 01/28/2008 3:52pm Page 1312
Chapter 28: Using Business Objects
Depending on the language that you are using to run the ASP.NET application, you will see different
values for different sets of data. For valid inputs, you will always see the correct result, of course, and
for any input that is over 1000, you see the Visual Basic 6 appointed error description of
Numerator and
denominator both have to be less than or equal to 1000
.
However, for invalid Strings, Visual Basic 2008 reports
Cast from string "abc" to type ‘Double’ is not
valid.
whereas C# reports
Input string was not in a correct format.
. For a divide by zero, they both
report
Divide by Zero
because the error is coming directly from the Visual Basic 6 runtime.
Deploying COM Components with .NET Applications
Deploying COM components with your .NET applications is very easy; especially when compared to just
deploying ActiveX controls. Two scenarios are possible when deploying .NET applications with COM
components:
❑ Using private assemblies
❑ Using shared assemblies
Private Assemblies
Installing all or parts of the ActiveX component local to the .NET application is considered installing
private assemblies. In this scenario, each installation of your .NET application on the same machine has,
at least, its own copy of the Interop library for the ActiveX component you are referencing, as shown in
Figure 28-10.
Interop.MyCOM.dllMyApp.exe
C:\Program Files\First Application Location\
Interop.MyCOM.dllMyApp.exe
C:\Program Files\Second Application Location\
MyCOM.DLL
C:\Program Files\Third Party COM Controls\
(Managed Code) (RCW)
(Managed Code) (RCW)
(ActiveX DLL)
Figure 28-10
It is up to you whether you decide to install the ActiveX component as local to the application or in a
shared directory for all calling applications.
It was once considered proper practice to separate ActiveX components into their own directory so that if
these components were referenced again by other applications, you did not have to register or install the
1312