Tài liệu COM and .NET Interoperability doc - Pdf 90



COM and .NET Interoperability
by Andrew Troelsen
ISBN: 1590590112
Apress © 2002 (816 pages)
For both new and seasoned developers, this reference provides you with all
you need to know about COM and .NET, and how to make them work
together for you.
Table of Contents

COM and .NET Interoperability

Introduction

Chapter 1 - Understanding Platform Invocation Services

Chapter 2 - The Anatomy of a COM Server

Chapter 3 - A Primer on COM Programming Frameworks

Chapter 4 - COM Type Information

Chapter 5 - The Anatomy of a .NET Server

Chapter 6 - .NET Types

Chapter 7 - .NET-to-COM Interoperability— The Basics

every occurrence of a trademarked name, we use the names only in an editorial fashion
and to the benefit of the trademark owner, with no intention of infringement of the
trademark.
Technical Reviewers: Habib Heydarian, Eric Gunnerson
Editorial Directors: Dan Appleman, Peter Blackburn, Gary Cornell, Jason Gilmore,
Karen Watterson, John Zukowski
Managing Editor: Grace Wong
Copy Editors: Anne Friedman, Ami Knox
Proofreaders: Nicole LeClerc, Sofia Marchant
Compositor: Diana Van Winkle, Van Winkle Design
Artist: Kurt Krames
Indexer: Valerie Robbins
Cover Designer: Tom Debolski
Marketing Manager: Stephanie Rodriguez
Distributed to the book trade in the United States by Springer-Verlag New York, Inc., 175
Fifth Avenue, New York, NY, 10010 and outside the United States by Springer-Verlag
GmbH & Co. KG, Tiergartenstr. 17, 69112 Heidelberg, Germany.
In the United States, phone 1-800-SPRINGER, email <>,
or visit .
Outside the United States, fax +49 6221 345229, email <>, or
visit .
For information on translations, please contact Apress directly at 2560 Ninth Street, Suite
219, Berkeley, CA 94710.
Phone: 510-549-5930, Fax: 510-549-5939, Email: <>, Web site:
.
The information in this book is distributed on an "as is" basis, without warranty. Although
every precaution has been taken in the preparation of this work, neither the author nor
Apress shall have any liability to any person or entity with respect to any loss or damage
caused or alleged to be caused directly or indirectly by the information contained in this
work.


Introduction
The funny thing about writing a book on COM and .NET interoperability is that one
author could craft a five- to ten-page article describing the basic details that you
must understand to get up and running with interop-related endeavors. At the same
time, another author could write volumes of material on the exact same subject. So,
you may be asking, how could this massive discrepancy between authors possibly
exist?
Well, stop and think for a moment about the number of COM-aware programming
languages and COM application frameworks that exist. Raw C++/IDL, ATL, MFC,
VB 6.0, and Object Pascal (Delphi) each have their own syntactic tokens that hide
the underbelly of COM from view in various ways. Thus, the first dilemma you face
as an interop author is choosing which language to use to build the COM sample
applications.
Next, ponder the number of .NET-aware programming languages that are either
currently supported or under development. C#, VB .NET, COBOL .NET, APL .NET,
PASCAL .NET, and so on, each have their own unique ways of exposing features of
the CTS to the software engineer. Therefore, the next dilemma is choosing which
language to use to build the .NET applications.
Even when you solve the first two dilemmas and choose the languages to use
during the course of the book, the final dilemma has to do with the assumptions
made regarding the readers themselves. Do they have a solid understanding of IDL
and the COM type system? Do they have a solid understanding of the .NET
platform, managed languages, and metadata? If not, how much time should be
spend pounding out such details?
Given the insane combinations of language preferences and reader backgrounds, I
have chosen to take a solid stance in the middle ground. If I have done my job
correctly, you will walk away from this text with the skills you need to tackle any
interop-centric challenge you may encounter. Also, I am almost certain you will
learn various tantalizing tidbits regarding the COM and .NET type systems.

Chapter 3: A Primer on COM Programming Frameworks
Given that you build a number of COM servers over the course of the book, this
(brief) chapter provides an overview of two very popular COM frameworks: the
Active Template Library (ATL) and Visual Basic 6.0. Knowledge mappings are
made between the raw C++ server created in Chapter 2 and the binaries produced
by the ATL/VB 6.0 COM frameworks. Along the way, you also explore the key COM
development tool, oleview.exe.
Chapter 4: COM Type Information
This chapter examines the gory details of the COM type system, including a number
of very useful (but not well-known) tasks such as constructing custom IDL attributes,
applying various IDL keywords such as [appobject], [noncreatable], and so forth.
More important, this chapter also illustrates how to read and write COM type
information programmatically using ICreateTypeLibrary, ICreateTypeInfo, and
related COM interfaces. This chapter wraps up by examining how to build a
managed C# application that can read COM type information using interop
primitives.
Chapter 5: The Anatomy of a .NET Server
The goals of this chapter are to examine the core aspect of a .NET code library,
including various deployment-related issues (for example, XML configuration files,
publisher policy, and the like). This chapter also provides a solid overview of a
seemingly unrelated topic: dynamically generating and compiling code using
System.CodeDOM. Using this namespace, developers are able to dynamically
generate code in memory and save it to a file (*.cs or *.vb) on the fly. Once you
have investigated the role of System.CodeDOM, you will have a deeper
understanding of how various interop-centric tools (such as aximp.exe) are able to
emit source code via command line flags.
Chapter 6: .NET Types
If you haven't heard by now, understand that the .NET type system is 100 percent
different than that of classic COM. Here, you solidify your understanding of the .NET
type system, including the use of custom .NET attributes. This chapter also

This chapter focuses on how COM clients (written in VB 6.0, C++, and VBScript)
can make use of .NET types using a COM Callable Wrapper (CCW). Here, I cover
class interfaces, the tlbexp.exe/regasm.exe command line tools, and various
registration and deployment issues. This chapter also examines how a COM client
can interact with the types contained in the core .NET assembly, mscorlib.dll.
Chapter 11: COM-to-.NET Interoperability—Intermediate Topics
This chapter builds on the materials presented in Chapter 10 by examining how
.NET enumerations, interface hierarchies, delegates, and collections are expressed
in terms of classic COM. You also learn how to expose custom .NET exceptions as
COM error objects, as well as about the process of exposing .NET interface
hierarchies to classic COM.
Chapter 12: COM-to-.NET Interoperability—Advanced Topics
This advanced COM-to-.NET-centric chapter examines how a .NET programmer is
able to build "binary-compatible" .NET types that integrate with classic COM. You
see how a .NET type can implement COM interfaces, and you also get a chance to
explore the details of manually defining COM types using managed code. This
chapter also examines how to interact with the registration process of an interop
assembly. The final topics of this chapter address the process of building a custom
host for the .NET runtime (using classic COM) and the construction of a custom
.NET-to-COM conversion utility.
Chapter 13: Building Serviced Components (COM+ Interop)
Despite the confusion, .NET programmers are able to build code libraries that can
be installed under COM+. In this final chapter, I begin by examining the role of the
COM+ runtime and reviewing how it fits into n-tier applications. The bulk of this
chapter is spent understanding the System.EnterpriseServices namespace and
numerous types of interest. You learn how to program for JITA, object pools,
construction strings, and transactional support using managed code. I wrap up by
constructing an n-tier application using managed code, serviced components,
Windows Forms, and ASP .NET.
Now that you have a better understanding about the scope of this book and the

exact function export. PInvoke also facilitates the marshalling of managed data (for
example, intrinsic data types, arrays, structures) to and from their unmanaged
counterparts.
In this chapter, you learn how to interact with unmanaged C DLLs using a small set of
types found within the System.Runtime.InteropServices namespace. As you will see,
PInvoke is basically composed of two key members. The DllImport attribute is a .NET
class type that wraps low-level LoadLibrary() and GetProcAddress() calls on your behalf.
System.Runtime.InteropServices.Marshal is the other key PInvoke-centric type, and it
allows you to transform various primitives (including COM types) from managed to
unmanaged equivalents and vice versa.
The Two Faces of Unmanaged Code
As I am sure you are aware, code built using a .NET-aware programming language (C#,
VB .NET, and so on) is termed managed code. Conversely, code that was compiled
without a .NET-aware compiler is termed unmanaged code. Unmanaged code really
comes in two flavors:
§ Traditional C-style Win32 DLLs/EXEs
§ COM-based DLLs/EXEs
Obviously, the majority of this book is concerned with interoperating with COM-based
binary images. However, the .NET platform does support the ability for managed code to
call methods exported from a traditional (non-COM) C-style DLL. Formally, this facility is
known as Platform Invocation, or simply PInvoke.
However, you will seldom be in a position where you absolutely need to directly call a
Win32 API function, given the very simple fact that the .NET class libraries will typically
provide the same functionality using a particular assembly. If you can find a .NET type
that satisfies your needs, make use of it! Not only will it require less work on your part,
but you can rest assured that as the .NET platform is ported to other operating systems,
your code base will not be contingent upon a Windows-centric DLL.
Nevertheless, PInvoke is still a useful technology. First of all, many shops make use of a
number of proprietary C-based DLLs in their current systems. Thus, if you have the best
bubble sort algorithm known to humankind contained in a C-style DLL, your shiny new

// The optional, but quite helpful, DllMain().
BOOL APIENTRY DllMain( HANDLE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: break;
case DLL_THREAD_ATTACH: break;
case DLL_THREAD_DETACH: break;
case DLL_PROCESS_DETACH: break;
}
return TRUE;
}
Obviously, what you do within the scope of DllMain() is contingent on the module you are
constructing. Possible tasks include assigning values to module-level data members,
allocating (and deallocating) memory, and so forth. Of course, a DLL that only defines
DllMain() is not very useful. You need custom content to make your DLL interesting to
the outside world.
Exporting Custom Members
A traditional C-style DLL is not constructed using the building blocks of COM and does
not have the same internal structure as a .NET binary. Rather, unmanaged DLLs contain
some set of global functions, user-defined types (UDTs), and data points that are
identified by a friendly string name and ordinal value. Typically, a *.def file is used to
identify the available exports. For example, assume you have written a C-based DLL that
exports four global functions. The corresponding *.def file might look something like the
following:
; MyCBasedDll.def : Declares the module parameters.
LIBRARY "MyCBasedDll.dll"


You would then expose MethodA() from a given DLL as shown here (note that the
prototype and member implementation both need to be qualified with the
MYCSTYLEDLL macro):
// Function prototype (in some header file).
extern "C" MYCSTYLEDLL_API int MethodA(void);

// Function implementation (in some *.cpp file).
extern "C" MYCSTYLEDLL_API int MethodA(void)
{return 1234;}
This same shortcut can be used when you wish to export a single point of data (such as
some fixed global constants) or an entire class module (not a COM class mind you, but a
vanilla-flavored C++ class). Building a Custom C-Based DLL
During the course of this chapter, you learn how to use the DllImport attribute to allow
your managed .NET code to call members contained in a traditional C-style DLL
(including Win32 DLLs). To be sure, DllImport is most commonly used to trigger Win32
API functions; however, this same .NET attribute can be used to interact with your
custom proprietary modules. Given this, let's build a simple Win32 DLL named
MyCustomDLL. If you wish to follow along, fire up Visual Studio 6.0 (or VS .NET if you
prefer) and select a Win32 DLL project workspace (Figure 1-1).

Figure 1-1: Creating your C-style DLL
From the resulting wizard, simply select "A simple DLL" project. The first order of
business is to establish the custom declspec macros, which will be used under two
circumstances. First, if the code base defines the MYCSTYLEDLL_EXPORTS symbol,
the macro will expand to __declspec(dllexport). On the other hand, if an external code
base #includes the files that define the custom members (and thus does not define the
MYCSTYLEDLL_EXP ORTS symbol), the macro will expand to __declspec(dllimport).

{
int ans = 0;
for(int i = 0; i < size; i++)
{
ans = ans + x[i];
}
return ans;
}
Functions Receiving Structures (and Structures Containing Structures)
The next two function exports allow the user to pass in a complex structure for
processing as well as return an array of structures to the caller. Before you see the
methods themselves, here are definitions of the CAR and CAR2 UDTs:
// A basic structure.
typedef struct _CAR
{
char* make;
char* color;
} CAR;

// A structure containing another structure.
typedef struct _CAR2
{
CAR theCar;
char* petName;
} CAR2;
As you can see, the basic CAR structure defines two fields that document the color and
make of a give automobile. CAR2 extends this basic information with a new field
(petName), which allows the user to assign a friendly name to the car in question. The
first structure-centric function, DisplayBetterCar(), takes a CAR2 type as an input
parameter that is displayed using a Win32 MessageBox() call:

pCurCar->color = carColors[i];
pCurCar->make = carMakes[i];
}
}
Functions Using Class Types
The final two function exports defined by your custom DLL allow the outside world to
obtain and destroy a (non-COM) C++ class type named CMiniVan:
// A class to be exported.
class MYCUSTOMDLL_API CMiniVan
{
public:
CMiniVan(){m_numbKids = 52;}
int DisplayNumberOfKids()
{ return m_numbKids;}
private:
int m_numbKids;
};
To interact with this class type, you provide the final two functions:
// Prototypes for class marshaling.
extern "C" MYCUSTOMDLL_API CMiniVan* CreateMiniVan();
extern "C" MYCUSTOMDLL_API void DeleteMiniVan(CMiniVan* obj);

// 5) Method to create a CMiniVan.
extern "C" MYCUSTOMDLL_API CMiniVan* CreateMiniVan()
{ return new CMiniVan(); }

// 6) Method to destroy a CMiniVan
extern "C" MYCUSTOMDLL_API void DeleteMiniVan(CMiniVan* obj)
{ delete obj; }
That's it! Go ahead and compile the project. Over the course of this chapter, you will

using
symbols
if present
in the file.
/exports This
option
Table 1-1: Common dumpbin.exe Flags
dumpbin.exe Flag Meaning
in Life
displays
all
definition
s
exported
from an
executabl
e file or
DLL.
/imports This
option
displays
all
definition
s
imported
to an
executabl
e file or
DLL.
/summary This


Figure 1-3: The exports of MyCustomDLL.dll
Notice that the CMiniVan class is internally represented using a common C++ complier
technique termed named mangling. Basically, name mangling is a way to assign a
unique internal name to a given class member. Typically, C++ developers do not need to
be concerned with the internal mangled representation of a given class member.
However, do be aware that when you wish to trigger a class method from managed
code, you will need to obtain this internal name. For example, later in this chapter when
you invoke CMiniVan::DisplayNumberOfKids(), you need to refer to this member as
?DisplayNumberOfKids@CMiniVan@@QAEHXZ Deploying Traditional DLLs
Now that you have created a custom DLL, you are ready to begin building a number of
client applications (both managed and unmanaged) that can access the exported
member set. Before you get to that point, you need to address a rather obvious question:
How will the runtime locate the custom C-based module?
As you may know (and will see in detail in Chapter 2), COM-based DLLs can be placed
anywhere within the host computer's directory structure, given that COM servers are
explicitly registered in the system registry. On the other hand, .NET-based DLLs are not
registered in the system registry at all, but are typically deployed in the same directory as
the launching client (that is, as a private assembly). As an alternative, .NET DLLs can be
shared by multiple client applications on a given machine by placing the assembly within
a well-known location called the Global Assembly Cache (GAC).
Traditional C-style DLLs are deployed much like a .NET DLL, given that they are not
registered within the system registry. The simplest approach to deploy your custom DLLs
is to place them directly in the directory of the calling client (typically called the
application directory).
This brings about a rather interesting side note, however. As you know, the Windows OS
defines a number of system-level DLLs that supply a number of core services such as

32-bit
base API
support
mpr.dll No, not
Minnesot
a Public
Radio,
but rather
Multiple
Provider
Router
Table 1-2: Core System-Level DLLs
Core Windows DLL Meaning
in Life
library
netapi32.dll 32-bit
Network
API
library
shell32.dll 32-bit
Shell API
library
user32.dll Library
for user
interface
routines
version.dll Version
library
winmm.dll Windows
multimedi

DllImport attribute mimics the same pattern found with the
LoadLibrary()/GetProcAddress() APIs.
To begin, assume you have a new Win32 console application named
MyCustomDLLCppClient (a "simple project" will be fine). First, place a copy of the
MyCustomDll.h file directly in the project directory (you do this because the file has the C
definitions of your custom UDTs). When you need to load a C-based DLL and invoke its
members dynamically, you must make use of three key Win32 API calls, which are
explained in Table 1-3.
Table 1-3: Library-Centric Win32 API Functions
Library-Centric API Function Meaning
in Life
Table 1-3: Library-Centric Win32 API Functions
Library-Centric API Function Meaning
in Life
FreeLibrary() This API
decrease
s the
*.dll's
internal
use
counter
by one
and
removes
the
binary
from
memory
when the
counter is

#include <iostream>
#include "MyCustomDLL.h"
using namespace std;
int main(int argc, char* argv[])
{
// A handle to the loaded library.
HINSTANCE dllHandle = NULL;

// Load the DLL and keep the handle to it.
// Assume this DLL is in the same folder as the
// client EXE or under \System32.
dllHandle = LoadLibrary("MyCustomDll.dll");

// If the handle is valid, try to call members.
if (NULL != dllHandle)
{

// Free the library when finished.
FreeLibrary(dllHandle);
}
return 0;
}
Invoking Members
Given that the example has not directly linked the DLL to its compilation cycle, you are
not currently able to directly resolve the names of the exported functions. What you need
is a generic way to represent the address of a given function. Lucky for you,
GetProcAddress() will return a pointer to a specific function upon successful completion.
So, how do you represent a generic function pointer? The standard approach is to build
a C-style type definition that represents a pointer to the method as well as its set of
arguments and return value. For example, if you craft such a pointer for the

CAR2 myCar;
myCar.petName = "JoJo";
myCar.theCar.make = "Viper";
myCar.theCar.color = "Red";

pfnDisplayBetterCar = (PFNDISPLAYBETTERCAR)
GetProcAddress(dllHandle, "DisplayBetterCar");

// If the function address is valid, call DisplayBetterCar().
if (NULL != pfnDisplayBetterCar)
{
pfnDisplayBetterCar(&myCar);
}

// Free the library.
FreeLibrary(dllHandle);
}
As you can see, GetProcAddress() requires you to specify the module to examine
(represented by the HINSTANCE returned from LoadLibrary()) and the name of the
member you wish to invoke. The result is a pointer to the correct function, which can be
invoked as if you had a direct function definition! When you run this application, you
should see the result of adding 100 and 100, followed by a series of message boxes
describing your new red Viper named JoJo.
CODE The MyCustomDLLCppClient application is found under the
Chapter 1 directory. The Atoms of PInvoke
Now that you have created a custom DLL (and checked out the process of dynamically
invoking members using the Win32 API), you will spend the rest of this chapter

rather a managed equivalent. Table 1-4 documents the mapping between Win32
typedefs (and their C representation) and the correct .NET data type.
Table 1-4: Data Type Representation
Unmanaged
Type in
wtypes.h
Unmanaged
C
Language
Type
Managed Type
Representati
on
Meaning
in Life
BOOL long System.Int32 32 bits
BYTE unsigned
char
System.Byte 8 bits
CHAR char System.Char ANSI
string
DOUBLE double System.Double 64 bits
DWORD unsigned
long
System.UInt32 32 bits
FLOAT float System.Single 32 bits
HANDLE void* System.IntPtr 32 bits
Table 1-4: Data Type Representation
Unmanaged
Type in

System.StringBuil
der
Unicode
string
SHORT short System.Int16 16 bits
UINT unsigned int System.UInt32 32 bits
ULONG unsigned
long
System.UInt32 32 bits
WORD unsigned
short
System.UInt16 16 bits
The Marshal Class
System.Runtime.InteropServices.Marshal is a key type that is used with all facets of
.NET interoperability. This sealed class defines a healthy dose of static (Shared in terms
of VB .NET) members that provides a bridge between managed and unmanaged
constructs. When you are working with PInvoke proper (meaning you are not interested
in communicating with COM-based DLLs), you really only need to access a very small
subset of its overall functionality. In fact, a majority of the members provided by the
Marshal type are most useful when dealing with COM/.NET interop issues.
Nevertheless, in this section, I outline the full functionality of Marshal, by grouping
members by related functionality. You will see additional aspects of Marshal during the
remainder of this text, so don't panic due to the sheer volume of members. Table 1-5
documents a number of members that allow you to interact with low-level COM primitives
such as IUnknown, VARIANT transformations, and moniker bindings (among other
things).
Table 1-5: COM-Centric Members of the Marshal Type
General COM-Centric Member of the Marshal
Type
Meaning

by the Type
Library
Exporter
(TlbExp.exe
)
GenerateProgIdForType() Returns a
ProgID for
the
specified
type
GetActiveObject() Obtains a
running
instance of
the
specified
object from
the Running
Object
Table
(ROT)
GetComInterfaceForObject() Returns an
IUnknown
pointer
representin
g the
specified
interface for
an object
GetIDispatchForObject() Returns an
IDispatch

whether a
specified
object
represents
an
unmanaged
COM object
IsTypeVisibleFromCom() Indicates
whether a
type is
visible to
COM
clients
QueryInterface() Requests a
pointer to a
specified
interface
from an
existing
interface
Release() Decrements
the
reference
count on
the
specified
interface
ReleaseComObject() Decrements
the
reference

System.T
ype
object
GetTypeInfoName() Retrieves
the name
of the
type
represent
ed by an
ITypeInfo
GetTypeLibGuid() Retrieves
the GUID
of a type
library
GetTypeLibGuidForAssembly() Retrieves
the GUID
that is
assigned
to a type
library
when it
was
exported
from the
specified
assembly
GetTypeLibLcid() Retrieves
the LCID


Nhờ tải bản gốc
Music ♫

Copyright: Tài liệu đại học © DMCA.com Protection Status