본문 바로가기
Application/Delphi Lecture

COM+ in Delphi

by 현이빈이 2008. 8. 13.
반응형

원문 위치

http://www.agnisoft.com/white_papers/complus/complus.asp



COM+ in Delphi


Author: Deepak Shenoy

Download the code for this white paper (ComPlusCode.zip, 255 KB)

Abstract

Most of you are now using Windows 2000 or XP. As a programmer you now have a new model to work with - COM+. But are you using it at all? In this session you'll see what COM+ provides you more than COM, what you CAN use in your applications now.

Here's a list of what I'll talk about:

1. Overview of COM+
2. Object Pooling
3. COM+ Events
4. Queued Components
5. Role Based Security
6. Component Administration
7. Finally I'll address the topic "Is COM Dead?" which seems to be the question on everyone's mind in view of the .NET initiative by Microsoft.

What is COM+?

COM+ is the new "version" if you may, of COM. I'm assuming you already know what COM is so I'm not going to delve into Interfaces, GUIDs etc. You can also look at COM+ as a "merger" of sorts, between COM and MTS. MTS, or Microsoft Transaction Server, was available on NT for transaction and lifetime management of COM servers. Now, it's all a part of the "Component Services" in COM+.

What's new? Here's a not-so-comprehensive list:

  • Threading
  • Security
  • Asynchronous COM
  • COmponent Services (A new way to work with Components in Windows 2000)

I'll talk about each of these in detail. But first, let me brief you on how I will structure this presentation. What I'll do is to talk about each new aspect of COM+, with details using code examples. I have built a no. of applications that will demonstrate the various parts of COM+.

Component Services

This is by far the most important part of COM+. COM+ Applications are registered in the Component Services, and they contain components. (In MTS, COM+ applications were referred to as "packages") Components in turn implement certain interfaces, which consist of methods. THis is represented like so:

Why do we need an "application" layer when we are already doing so well with COM COmponents? Well, here are some reasons:

1) Each application can be configured as a whole - aspects like security and queuing are applied to applications , which therefore helps define security boundaries (there's no security checks when talking between components in the same application, but if you want to talk across applications you need a security check) and deployment boundaries.
2) You get a context for transactions and synchronization across a group of logically common components.
3) Loading, unloading, debugging are all application specific. I'll talk about this later.

There are multiple types of applications:

1) Server applications: A server application would run in it's own process. Pre-MTS, this was specifically any COM application that was an EXE file. Now you can also use DLL files run in their own process, using the provided DLLHost.exe.
2) Library applications: A library application will run in the process of the client that created it. This therefore also restricts all Library applications to reside in DLLs, and also they have to be on the same machine as the client application. COM+ also denies access to queued components from Library applications.
3) Application proxies: Assuming you have a server application configured in a remote machine, you can configure an application proxy on your machine that allows client applications to access this application remotely. Usually the remote application will have to be a server application (unless it's anothe application proxy) and you don't need to transfer the entire application to your client machine - just the client configuration. No more messing with DComCnfg.exe!
4) PreInstalled applications: These are placed into a different category simply because they cannot be tampered with. You'll find COM+ Utilities, COM+ Administration and IIS System applications as "preinstalled applications.

To exercise this concept further take a look at the Component Services Snap-in, usually available from your Admistrative Tools in the Control Panel. Check out the properties of the various COM+ applications already in there.

We've seen what COM+ applications are, now let's see some individual features COM+ provides to components in such applications.

Object pooling :

Object pooling is service provided by COM+ that allows you to have component instances kept active in a pool, ready to serve a component request. In comparison, what vanilla COM allowed you to do was only to create component instances, but releasing an object instance unloads this object from memory. Now if you're getting a continuous series of requests for such component instances, you will notice severe performance delay due to the fact that objects are being created and destroyed. Object pooling allows you to keep allocated instances in a pool and only activate or deactivate them when a client requests or releases the component.

Technically we will now evaluate exactly what is required of a COM+ pooled component.

When the COM+ application starts, the pool will be populated up to the minimum level of instances that you have specified in the COM+ Component Services MMC Plugin. As client requests for the component come in, they are satisfied on a first-come-first-served basis from the pool. If no pooled objects are available, and the pool is not yet at its specified maximum level, a new object is created and activated for the client. So we have a minimum and maximum level and the object instance count will always be between these limits.

When the pool reaches its maximum level, client requests are queued, and will receive the first available object from the pool. You can specify a "timeout" of a client request after which if no object is available, the request is returned rejected. Here's how we will create a pooled Object in Delphi. First, create an ActiveX Library by choosing File|New|ActiveX library.

Then create a New Transactional object, call it DelphiPooled. Remember to mark the threading Both.

Why? COM+ Pooled objects may not have any thread affinity, which therefore eliminates Single Threaded and Apartment Threaded objects. And this is one of the reasons Visual Basic cannot create COM+ Pooled Objects (VB only creates Apartment Threaded objects) So we have to either mark the object Free or Neutral threaded. Note: To change it to neutral threading you must change the following line in the initialization section of the implementation unit:

TAutoObjectFactory.Create(ComServer, TDelphiPooled, Class_DelphiPooled, ciMultiInstance, tmBoth); // change tmBoth to tmNeutral for Neutral threading

Now, simply enable object pooling in your component by Setting Pooled to True in the Initialize procedure. Note: You should override the Initialize function to do any instance initialization, rather than override the constructor.

procedure TDelphiPooled.Initialize; begin inherited; Pooled := True; end;

Finally we will compile the component by using the Install COM+ Objects option in the Run Menu:

You should see this:

Click the DelphiPooled option and you will see this:

I've chosen to install in an already existing object, you can always create a new one. Now run Component Services, which is in your Administrative Tools folder, usually in Control Panel.

Now if you click on Properties of the Pooled.DelphiPooled object, you can see the Activation Page:

Here are some points:

  • You have to click Enable Object Pooling for pooling to happen.
  • The property Minimum pool size indicates the least no. of object instances to be created on the first call. This is the initial pool size.
  • Maximum pool size: Indicates the limit above which no object instances will be created, and a further request will wait until an instance is available.
  • Creation timeout: Milliseconds that a request will wait until an object is freed AFTER the pool is full, for an instance to get freed.
  • If the Minimum pool size is "n" and the Maximum is "m", if there are n+1 requests active then the pool size will increase to n+1 (assuming n+1 is still less than m) Now if an instance is released, the pool size does not decrease! It stays at n+1, which means now there are n+1 objects active. So you must choose your minimum and maximium sizes carefully.

To figure out how this works, right click the component in the Component Services Snap In, and click View | Status View. You can then see certain new columns in the report view on the right. Check the source code for some client code that creates and frees objects - here's a screen shot:

You'll need to do a lot of checking for object pooling to work properly and make sure:

  • That you do not have instances which retain state. If any object instance has state information it should set Pooled to False.
  • There is no thread affinity. You cannot even maintain local variables across sessions because each variable is initialized in the Thread local variable. You might be able to do this with a "Neutral" model though.
  • Implement IObjectControl (which is done by IMtxAutoObject for you) and override teh Activate and Deactivate to do request specific initialization (as opposed to "Instance" specific initialization)

Quite related to Object Pooling is Just-In-Time (JIT) Activation. JIT activation ensures that an client retains a reference to an object, but the object can "deactivate" itself, until the next call from the client. This can save server resources and also increase the efficiency of the servers in cases where calls are not frequent, but are frequent enough to not have to release and recreate the objects. You don't have to do anything but set the option in COM+ Component Services, and if you want to deactivate the object simply call SetComplete. (Or, you can use IContextState.SetDeactivateOnReturn)

COM+ Events

In COM we had the ability to call events on a client object, usually by sinking interfaces. In COM+ this model has been extended to provide support late-bound calls, in which the client does not repeatedly poll the server; the server notifies all interested clients.

This mechanism is called the publisher-subscriber mechanism. A com server "publishes" an event and other COM objects "subscribe" to this event. The event, when triggered by the publishers, is transmitted to all subscribers by the COM+ system. The concept is fairly simple to think of. We have exactly three entities:

  1. The Events: The actual Event Interface which contains all the methods that will get triggered at various points in time. Each Event Interface can have multiple methods, each of which can be triggered by a publisher and handled by (many) subscribers.
  2. The Publisher: This is the COM server that triggers the methods on the Event Interface.
  3. The Subscriber: The Com clients that require to be notified when any of the event methods are triggered.

Let's now see what we'll do. I'll take the standard COM+ Event demo in your Demos\ActiveX folder under "COM+ Events". There are three folders within, let's look at each of them:

The Event In the project named "Events.dpr" you find there's a simple example like so:

TClockEvent = class(TAutoObject, IClockEvent) protected procedure HourChange(Hour: Integer); virtual; safecall; abstract; procedure MinuteChange(Minute: Integer); virtual; safecall; abstract; procedure SecondChange(Second: Integer); virtual; safecall; abstract; { Protected declarations } end;

Notice the fact that all the methods are declared Abstract. This means there is no implementation for these events at all, and this is just a declaration for an event, with three different methods. Let's now compile this project, and install it as a COM+ Event. Go to Component Services and select the DelphiComPlus Application.

Then right click and select New Component.

Click on Install new Event Class(es) and select the Event.dll we just compiled.

Click on Next and Finish.

The Publisher
Open the publisher project named Publisher.dpr. Notice that this project has imported the Type library used in the Event project, and this is the code used:

procedure TMainForm.Timer1Timer(Sender: TObject); var Event: IClockEvent; Hour, Minute, Second: Integer; begin Event := CoClockEvent.Create; Hour := StrToInt(FormatDateTime('h', Now())); Minute := StrToInt(FormatDateTime('n', Now())); Second := StrToInt(FormatDateTime('s', Now())); TimeLabel.Caption := IntToStr(Hour) + ':' + IntToStr(Minute) + ':' + IntToStr(Second); Event.HourChange(Hour); Event.MinuteChange(Minute); Event.SecondChange(Second); end;

So we see that the Event object has been created using CoClockEvent.Create and then the methods directly called. But how does this work if we have no implementation of the event? They were declared as "abstract" remember? Here's where subscribers come in :

Subscribers
Check out the subscriber.dpr project. You can see that this is again a COM server so it has a .TLB file. This tlb file "imports" the Event TLB as shown below:

We have just one class here, which implements the IClockEvent interface.

type TClockSubscriber = class(TAutoObject, IClockEvent) protected procedure HourChange(Hour: Integer); safecall; procedure MinuteChange(Minute: Integer); safecall; procedure SecondChange(Second: Integer); safecall; { Protected declarations } end; implementation uses ComServ, SysUtils, Dialogs; var FHour, FMinute, FSecond: Integer; procedure UpdateTime; begin ShowMessage(IntToStr(FHour) + ':' + IntToStr(FMinute) + ':' + IntToStr(FSecond)); end; procedure TClockSubscriber.HourChange(Hour: Integer); begin FHour := StrToInt(FormatDateTime('h', Now())); UpdateTime; end; procedure TClockSubscriber.MinuteChange(Minute: Integer); begin FMinute := StrToInt(FormatDateTime('n', Now())); UpdateTime; end; procedure TClockSubscriber.SecondChange(Second: Integer); begin FSecond := StrToInt(FormatDateTime('s', Now())); UpdateTime; end;

Very straightforward - you can see that you'll get a message for each event method when fired. Let's now install this as a subscription. First, Compile this project and Use Run | Install COM+ Objects to install this new Subscriber.DLL into the DelphiComPlus Application. Now go to Component services.

Right click on Subscriptions of the Subscriber.ClockSubscriber object (not the subscriber object) and click New | Subscription. Here's the wizard:

Now we only need to select IClockEvent (since we've implemented ALL the event methods, but if we hadn't we could choose to implement only some) and click Next.

Select the Event.ClockEvent class and click Next.

Give a name for your subscription and then check Enable this subscription Immediately and finish the wizard.

That's it. Now run the Publisher application, and you'll see this screen:

Role Based Security

You can restrict access to your component by role by using the COM+ Component Services, and in the properties of your COM+ Application. There is a security tab in which you must set the "Enforce Security Checks for this application" to checked, and then Add "Roles" to your application. Each role can be restricted to certain users. A component can check the role the user is using this component in by calling ObjectContext.IsCallerInRole('Manager').

Queued Components

Queued Components provide a way to invoke and execute components asynchronously. Which means one component can call a method on another without the other being activated or ready. When it does get ready it will then execute the method. This works great for high-load servers where instant status reporting is unnecessary and therefore a method can be treated like a "submission". It's like saying "take a look at this when you're free and then get back to me".

The COM+ Queued Components service consists of the following parts:

  • Recorder (for the client or send side).
  • Listener (for the server or receive side).
  • Player (for the server or receive side).
  • Message mover utility for moving messages from one queue to another.

The Recorder is what is activated when a client calls a queued component. It packages the call and stores it into a queue on the message Queue. A lot of other information, like the callers security context, roles etc. are also packaged.

The Listener simply gets the messages from the queue ans passes it on to the Player.

The Player unpackages the message, figures out callers security context, impersonates (or otherwise simulates) the caller and then invokes the server component and makes method calls. This happens only after the client callers transaction is committed.

The message mover is what is used to move failed messages to another queue for retrial.

You can configure authentication, Security and transaction support for Queues using the Com+ Component Services Administrator. First, let's create a server. This is akin to a COM server, but remember that your interface methods are restricted to use only in parameters, and no return values. (Why? Because a caller's lifetime can be very different from the server, so the server could run when the client is no longer active, so where do we return things to?)

Create a New ActiveX Library and then a new Automation Object. Let's name it "MyQueuedComponent", that has a single function :

procedure Add( A, B: Integer ); safecall;

Here's a screen shot of the type library:

Now we add the two parameters and show the result in a message (so we know when the function is executed). Here's the function code:

procedure TMyQueuedComponent.Add(A, B: Integer); begin ShowMessage( '[in] A = ' + IntToStr(A) + #13#10 + '[in] B = ' + IntToStr(B) + #13#10 + 'Result: ' + IntToStr(A+B)); end;
Install this as a COM+ Application (Create a New application) and if you click Properties on your Application, you must check the two check boxes in the "Queuing" tab:

Also, go to the properties of the IMyQueuedComponent interface and mark it "Queued".

For the client, create a New Application and write code that calls the server, except we won't use the normal method of using CreateOleObject, we'll use a CoGetObject call (using a new imported definition as shown below) and a special "Queue:" parameter.

Function NewCoGetObject(pazName: PWideChar; pBindOptions: PBindOpts; const iid: TIID; out ppv): HResult; stdcall; external 'ole32.dll' name 'CoGetObject'; procedure TForm1.Button1Click(Sender: TObject); var Moniker : PWideChar; MyQueue : IMyQueuedComponent; begin Moniker := 'Queue:/new:{05EB6936-F693-40DF-BE94-FE406FF225F9}'; OleCheck(NewCoGetObject(Moniker, nil, IMyQueuedComponent, MyQueue)); MyQueue.Add(10,20); end;
Run the client application, click the button and shut it down. You'll notice the server hasn't responded with a message. But if you go to Component Services, and to your Application and hit the "Start" button - the message will come up.

This is a very basic component, of course, but it should get you started on the concept.

COM+ Administration

The Entire COM+ Component Services Administration is automatable. What does this mean? You can actually write scripts or Delphi applications that can :

  • Load and Unload COM+ Applications
  • Set properties of applications, components, events and methods.
  • Figure out what the status of an application or component is, for eg. you can find out the current no. of instances of a Pooled object that are created and not in use.
  • Configure security, authentication, roles and impersonation.

These are not comprehensive and I'm sure you will find a lot more use for the COM+ Admin utilities. I have provided with this paper, in the "Admin" folder of the code, a project that demonstrates how to install the Queued Component we had created earlier, complete with setting the Queuing properties etc.

Is COM Dead?

In the light of the relatively new .NET framework from Microsoft, a number of people have said that the new SDK makes COM obsolete and therefore any venture into COM or COM+ is a total waste of time. This is perhaps true if you consider a five to ten year time frame. But in that time frame do you see any value in ANYTHING you are doing today? Paradigms will change but that does not mean you give up something useful today.

.NET is about a completely new paradigm. You will no longer write "native" applications, or call the Windows SDK - but all this depends on the availability of a .NET Runtime on all machines. For at least a few years from now, you will not find this runtime universally available (or easily downloadable given it's around 22MB for the entire SDK and 10 MB for th runtime). Given this, you can easily assume that COM will live effortlessly through the teething years of .NET, a time I would expect to be around 3 to 4 years. Also, .NET doesn't overthrow COM! It interoperates with it - as in, you can expose a .NET class as a COM server, and you can expose a COM+ component to a .NET application with ease. Even Microsoft expects the initial acceptance of .NET to be dependent on the various interoperability features it provides that helps .NET talk to COM.

So in summary, I believe COM+ is not dead, or dying. It will continue to be used in organizations big and small, and will also be expanded to help .NET gain more acceptance. 

반응형