summaryrefslogtreecommitdiff
path: root/Master/tlpkg/tlperl/lib/Win32/OLE/TPJ.pod
blob: e45770baa4211d162cd76b7c12f0bccfeb9e2e15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
=pod

=head1 NAME

The Perl Journal #10 - Win32::OLE by Jan Dubois

=head1 INTRODUCTION

Suppose you're composing a document with Microsoft Word. You want to
include an Excel spreadsheet. You could save the spreadsheet in some
image format that Word can understand, and import it into your
document. But if the spreadsheet changes, your document will be out of
date.

Microsoft's OLE (Object Linking and Embedding, pronounced "olay") lets
one program use objects from another. In the above scenario, the
spreadsheet is the object. As long as Excel makes that spreadsheet
available as an OLE object, and Word knows to treat it like one, your
document will always be current.

You can control OLE objects from Perl with the Win32::OLE module, and
that's what this article is about. First, I'll show you how to "think
OLE," which mostly involves a lot of jargon. Next, I'll show you the
mechanics involved in using Win32::OLE. Then we'll go through a Perl
program that uses OLE to manipulate Microsoft Excel, Microsoft Access,
and Lotus Notes. Finally, I'll talk about Variants, an internal OLE
data type.


=head1 THE OLE MINDSET

When an application makes an OLE object available for other
applications to use, that's called OLE I<automation>. The program
using the object is called the I<controller>, and the application
providing the object is called the I<server>. OLE automation is guided
by the OLE Component Object Model (COM) which specifies how those
objects must behave if they are to be used by other processes and
machines.

There are two different types of OLE automation servers. I<In-process>
servers are implemented as dynamic link libraries (DLLs) and run in
the same process space as the controller. I<Out-of-process> servers
are more interesting; they are standalone executables that exist as
separate processes - possibly on a different computer.

The Win32::OLE module lets your Perl program act as an OLE
controller. It allows Perl to be used in place of other languages like
Visual Basic or Java to control OLE objects. This makes all OLE
automation servers immediately available as Perl modules.

Don't confuse ActiveState OLE with Win32::OLE. ActiveState OLE is
completely different, although future builds of ActiveState Perl (500
and up) will work with Win32::OLE.

Objects can expose OLE methods, properties, and events to the outside
world. Methods are functions that the controller can call to make the
object do something; properties describe the state of the object; and
events let the controller know about external events affecting the
object, such as the user clicking on a button. Since events involve
asynchronous communication with their objects, they require either
threads or an event loop. They are not yet supported by the Win32::OLE
module, and for the same reason ActiveX controls (OCXs) are currently
unsupported as well.

=head1 WORKING WITH WIN32::OLE

The Win32::OLE module doesn't let your Perl program create OLE
objects. What it does do is let your Perl program act like a remote
control for other applications-it lets your program be an OLE
controller. You can take an OLE object from another application
(Access, Notes, Excel, or anything else that speaks OLE) and invoke
its methods or manipulate its properties.

=head2 THE FIRST STEP: CREATING AN OLE SERVER OBJECT

First, we need to create a Perl object to represent the OLE
server. This is a weird idea; what it amounts to is that if we want to
control OLE objects produced by, say, Excel, we have to create a Perl
object that represents Excel. So even though our program is an OLE
controller, it'll contain objects that represent OLE servers.

You can create a new OLE I<server object> with C<< Win32::OLE->new >>.
This takes a program ID (a human readable string like
C<'Speech.VoiceText'>) and returns a server object:

  my $server = Win32::OLE->new('Excel.Application', 'Quit');

Some server objects (particularly those for Microsoft Office
applications) don't automatically terminate when your program no
longer needs them. They need some kind of Quit method, and that's just
what our second argument is. It can be either a code reference or a
method name to be invoked when the object is destroyed. This lets you
ensure that objects will be properly cleaned up even when the Perl
program dies abnormally.

To access a server object on a different computer, replace the first
argument with a reference to a list of the server name and program ID:

  my $server = Win32::OLE->new(['foo.bar.com',
                                'Excel.Application']);

(To get the requisite permissions, you'll need to configure your
security settings with F<DCOMCNFG.EXE>.)

You can also directly attach your program to an already running OLE
server:

  my $server = Win32::OLE->GetActiveObject('Excel.Application');

This fails (returning C<undef>) if no server exists, or if the server
refuses the connection for some reason. It is also possible to use a
persistent object moniker (usually a filename) to start the associated
server and load the object into memory:

  my $doc = Win32::OLE->GetObject("MyDocument.Doc");

=head2 METHOD CALLS

Once you've created one of these server objects, you need to call its
methods to make the OLE objects sing and dance. OLE methods are
invoked just like normal Perl object methods:

  $server->Foo(@Arguments);

This is a Perl method call - but it also triggers an OLE method call
in the object. After your program executes this statement, the
C<$server> object will execute its Foo() method. The available methods
are typically documented in the application's I<object model>.

B<Parameters.> By default, all parameters are positional
(e.g. C<foo($first, $second, $third)>) rather than named (e.g.
C<< foo(-name => "Yogi", -title => "Coach") >>). The required parameters
come first, followed by the optional parameters; if you need to
provide a dummy value for an optional parameter, use undef.

Positional parameters get cumbersome if a method takes a lot of
them. You can use named arguments instead if you go to a little extra
trouble - when the last argument is a reference to a hash, the
key/value pairs of the hash are treated as named parameters:

  $server->Foo($Pos1, $Pos2, {Name1 => $Value1,
                              Name2 => $Value2});

B<Foreign Languages and Default Methods.> Sometimes OLE servers use
method and property names that are specific to a non-English
locale. That means they might have non-ASCII characters, which aren't
allowed in Perl variable names. In German, you might see C<Öffnen> used
instead of C<Open>. In these cases, you can use the Invoke() method:

  $server->Invoke('Öffnen', @Arguments);

This is necessary because C<< $Server->Öffnen(@Arguments) >> is a syntax
error in current versions of Perl.

=head2 PROPERTIES

As I said earlier, objects can expose three things to the outside
world: methods, properties, and events. We've covered methods, and
Win32::OLE can't handle events. That leaves properties. But as it
turns out, properties and events are largely interchangeable. Most
methods have corresponding properties, and vice versa.

An object's properties can be accessed with a hash reference:

  $server->{Bar} = $value;
  $value = $server->{Bar};

This example sets and queries the value of the property named
C<Bar>. You could also have called the object's Bar() method to
achieve the same effect:

  $value = $server->Bar;

However, you can't write the first line as C<< $server->Bar = $value >>,
because you can't assign to the return value of a method call. In
Visual Basic, OLE automation distinguishes between assigning the name
of an object and assigning its value:

  Set Object = OtherObject

  Let Value = Object

The C<Set> statement shown here makes C<Object> refer to the same object as
C<OtherObject>. The C<Let> statement copies the value instead. (The value of
an OLE object is what you get when you call the object's default
method.

In Perl, saying C<< $server1 = $server2 >> always creates another reference,
just like the C<Set> in Visual Basic. If you want to assign the value
instead, use the valof() function:

  my $value = valof $server;

This is equivalent to

  my $value = $server->Invoke('');

=head2 SAMPLE APPLICATION

Let's look at how all of this might be used. In Listing: 1 you'll see
F<T-Bond.pl>, a program that uses Win32::OLE for an almost-real world
application.

The developer of this application, Mary Lynch, is a financial futures
broker. Every afternoon, she connects to the Chicago Board of Trade
(CBoT) web site at http://www.cbot.com and collects the time and sales
information for U.S. T-bond futures. She wants her program to create a
chart that depicts the data in 15-minute intervals, and then she wants
to record the data in a database for later analysis. Then she wants
her program to send mail to her clients.

Mary's program will use Microsoft Access as a database, Microsoft
Excel to produce the chart, and Lotus Notes to send the mail. It will
all be controlled from a single Perl program using OLE automation. In
this section, we'll go through T-Bond. pl step by step so you can see
how Win32::OLE lets you control these applications.

=head2 DOWNLOADING A WEB PAGE WITH LWP

However, Mary first needs to amass the raw T-bond data by having her
Perl program automatically download and parse a web page. That's the
perfect job for LWP, the libwww-perl bundle available on the CPAN. LWP
has nothing to do with OLE. But this is a real-world application, and
it's just what Mary needs to download her data from the Chicago Board
of Trade.

  use LWP::Simple;
  my $URL = 'http://www.cbot.com/mplex/quotes/tsfut';
  my $text = get("$URL/tsf$Contract.htm");

She could also have used the Win32::Internet module:

  use Win32::Internet;
  my $URL = 'http://www.cbot.com/mplex/quotes/tsfut';
  my $text = $Win32::Internet->new->FetchURL("$URL/tsf$Contract.htm");

Mary wants to condense the ticker data into 15 minute bars. She's
interested only in lines that look like this:

  03/12/1998 US 98Mar 12116 15:28:34 Open

A regular expression can be used to determine whether a line looks
like this. If it does, the regex can split it up into individual
fields. The price quoted above, C<12116>, really means 121 16/32, and
needs to be converted to 121.5. The data is then condensed into 15
minute intervals and only the first, last, highest, and lowest price
during each interval are kept. The time series is stored in the array
C<@Bars>. Each entry in C<@Bars> is a reference to a list of 5 elements:
Time, Open, High, Low, and Close.

  foreach (split "\n", $text) {
      # 03/12/1998 US 98Mar 12116 15:28:34 Open
      my ($Date,$Price,$Hour,$Min,$Sec,$Ind) =
           m|^\s*(\d+/\d+/\d+) # " 03/12/1998"
              \s+US\s+\S+\s+(\d+) # " US 98Mar 12116"
              \s+(\d+):(\d+):(\d+) # " 12:42:40"
              \s*(.*)$|x; # " Ask"
      next unless defined $Date;
      $Day = $Date;

      # Convert from fractional to decimal format
      $Price = int($Price/100) + ($Price%100)/32;

      # Round up time to next multiple of 15 minutes
      my $NewTime = int(($Sec+$Min*60+$Hour*3600)/900+1)*900;
      unless (defined $Time && $NewTime == $Time) {
          push @Bars, [$hhmm, $Open, $High, $Low, $Close]
                                            if defined $Time;
          $Open = $High = $Low = $Close = undef;
          $Time = $NewTime;
          my $Hour = int($Time/3600);
          $hhmm = sprintf "%02d:%02d", $Hour, $Time/60-$Hour*60;
      }

      # Update 15 minute bar values
      $Close = $Price;
      $Open = $Price unless defined $Open;
      $High = $Price unless defined $High && $High > $Price;
      $Low = $Price unless defined $Low && $Low > $Price;
  }

  die "No data found" unless defined $Time;
  push @Bars, [$hhmm, $Open, $High, $Low, $Close];

=head2 MICROSOFT ACCESS

Now that Mary has her T-bond quotes, she's ready to use Win32::OLE to
store them into a Microsoft Access database. This has the advantage
that she can copy the database to her lap-top and work with it on her
long New York commute. She's able to create an Access database as
follows:

  use Win32::ODBC;
  use Win32::OLE;

  # Include the constants for the Microsoft Access
  # "Data Access Object".

  use Win32::OLE::Const 'Microsoft DAO';

  my $DSN      = 'T-Bonds';
  my $Driver   = 'Microsoft Access Driver (*.mdb)';
  my $Desc     = 'US T-Bond Quotes';
  my $Dir      = 'i:\tmp\tpj';
  my $File     = 'T-Bonds.mdb';
  my $Fullname = "$Dir\\$File";

  # Remove old database and dataset name
  unlink $Fullname if -f $Fullname;
  Win32::ODBC::ConfigDSN(ODBC_REMOVE_DSN, $Driver, "DSN=$DSN")
                         if Win32::ODBC::DataSources($DSN);

  # Create new database
  my $Access = Win32::OLE->new('Access.Application', 'Quit');
  my $Workspace = $Access->DBEngine->CreateWorkspace('', 'Admin', '');
  my $Database = $Workspace->CreateDatabase($Fullname, dbLangGeneral);

  # Add new database name
  Win32::ODBC::ConfigDSN(ODBC_ADD_DSN, $Driver,
          "DSN=$DSN", "Description=$Desc", "DBQ=$Fullname",
          "DEFAULTDIR=$Dir", "UID=", "PWD=");

This uses Win32::ODBC (described in TPJ #9) to remove and create
F<T-Bonds.mdb>. This lets Mary use the same script on her workstation
and on her laptop even when the database is stored in different
locations on each. The program also uses Win32::OLE to make Microsoft
Access create an empty database.

Every OLE server has some constants that your Perl program will need
to use, made accessible by the Win32::OLE::Const module. For instance,
to grab the Excel constants, say C<use Win32::OLE::Const 'Microsoft
Excel'>.

In the above example, we imported the Data Access Object con-stants
just so we could use C<dbLangGeneral>.

=head2 MICROSOFT EXCEL

Now Mary uses Win32::OLE a second time, to have Microsoft Excel create
the chart shown below.

  Figure 1: T-Bond data generated by MicroSoft Excel via Win32::OLE

  # Start Excel and create new workbook with a single sheet
  use Win32::OLE qw(in valof with);
  use Win32::OLE::Const 'Microsoft Excel';
  use Win32::OLE::NLS qw(:DEFAULT :LANG :SUBLANG);

  my $lgid = MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT);
  $Win32::OLE::LCID = MAKELCID($lgid);

  $Win32::OLE::Warn = 3;

Here, Mary sets the locale to American English, which lets her do
things like use American date formats (e.g. C<"12-30-98"> rather than
C<"30-12-98">) in her program. It will continue to work even when she's
visiting one of her international customers and has to run this
program on their computers.

The value of C<$Win32::OLE::Warn> determines what happens when an OLE
error occurs. If it's 0, the error is ignored. If it's 2, or if it's 1
and the script is running under C<-w>, the Win32::OLE module invokes
C<Carp::carp()>. If C<$Win32::OLE::Warn> is set to 3, C<Carp::croak()>
is invoked and the program dies immediately.

Now the data can be put into an Excel spreadsheet to produce the
chart. The following section of the program launches Excel and creates
a new workbook with a single worksheet. It puts the column titles
('Time', 'Open', 'High', 'Low', and 'Close') in a bold font on the
first row of the sheet. The first column displays the timestamp in
I<hh:mm> format; the next four display prices.

  my $Excel = Win32::OLE->new('Excel.Application', 'Quit');
  $Excel->{SheetsInNewWorkbook} = 1;
  my $Book = $Excel->Workbooks->Add;
  my $Sheet = $Book->Worksheets(1);
  $Sheet->{Name} = 'Candle';

  # Insert column titles
  my $Range = $Sheet->Range("A1:E1");
  $Range->{Value} = [qw(Time Open High Low Close)];
  $Range->Font->{Bold} = 1;

  $Sheet->Columns("A:A")->{NumberFormat} = "h:mm";
  # Open/High/Low/Close to be displayed in 32nds
  $Sheet->Columns("B:E")->{NumberFormat} = "# ?/32";

  # Add 15 minute data to spreadsheet
  print "Add data\n";
  $Range = $Sheet->Range(sprintf "A2:E%d", 2+$#Bars);
  $Range->{Value} = \@Bars;

The last statement shows how to pass arrays to OLE objects. The
Win32::OLE module automatically translates each array reference to a
C<SAFEARRAY>, the internal OLE array data type. This translation first
determines the maximum nesting level used by the Perl array, and then
creates a C<SAFEARRAY> of the same dimension. The C<@Bars> array
already contains the data in the correct form for the spreadsheet:

  ([Time1, Open1, High1, Low1, Close1],
  ...
  [TimeN, OpenN, HighN, LowN, CloseN])

Now the table in the spreadsheet can be used to create a candle stick
chart from our bars. Excel automatically chooses the time axis labels
if they are selected before the chart is created:

  # Create candle stick chart as new object on worksheet
  $Sheet->Range("A:E")->Select;

  my $Chart = $Book->Charts->Add;
  $Chart->{ChartType} = xlStockOHLC;
  $Chart->Location(xlLocationAsObject, $Sheet->{Name});
  # Excel bug: the old $Chart is now invalid!
  $Chart = $Excel->ActiveChart;

We can change the type of the chart from a separate sheet to a chart
object on the spreadsheet page with the C<< $Chart->Location >>
method. (This invalidates the chart object handle, which might be
considered a bug in Excel.) Fortunately, this new chart is still the
'active' chart, so an object handle to it can be reclaimed simply by
asking Excel.

At this point, our chart still needs a title, the legend is
meaningless, and the axis has decimals instead of fractions. We can
fix those with the following code:

  # Add title, remove legend
  with($Chart, HasLegend => 0, HasTitle => 1);
  $Chart->ChartTitle->Characters->{Text} = "US T-Bond";

  # Set up daily statistics
  $Open  = $Bars[0][1];
  $High  = $Sheet->Evaluate("MAX(C:C)");
  $Low   = $Sheet->Evaluate("MIN(D:D)");
  $Close = $Bars[$#Bars][4];

The with() function partially mimics the Visual Basic With statement,
but allows only property assignments. It's a convenient shortcut for
this:

  { # open new scope
    my $Axis = $Chart->Axes(xlValue);
    $Axis->{HasMajorGridlines} = 1;
    $Axis->{HasMinorGridlines} = 1;
    # etc ...
  }

The C<$High> and C<$Low> for the day are needed to determine the
minimum and maximum scaling levels. MIN and MAX are spreadsheet
functions, and aren't automatically available as methods. However,
Excel provides an Evaluate() method to calculate arbitrary spreadsheet
functions, so we can use that.

We want the chart to show major gridlines at every fourth tick and
minor gridlines at every second tick. The minimum and maximum are
chosen to be whatever multiples of 1/16 we need to do that.

  # Change tickmark spacing from decimal to fractional
  with($Chart->Axes(xlValue),
      HasMajorGridlines => 1,
      HasMinorGridlines => 1,
      MajorUnit => 1/8,
      MinorUnit => 1/16,
      MinimumScale => int($Low*16)/16,
      MaximumScale => int($High*16+1)/16
  );

  # Fat candles with only 5% gaps
  $Chart->ChartGroups(1)->{GapWidth} = 5;

  sub RGB { $_[0] | ($_[1] >> 8) | ($_[2] >> 16) }

  # White background with a solid border

  $Chart->PlotArea->Border->{LineStyle} = xlContinuous;
  $Chart->PlotArea->Border->{Color} = RGB(0,0,0);
  $Chart->PlotArea->Interior->{Color} = RGB(255,255,255);

  # Add 1 hour moving average of the Close series
  my $MovAvg = $Chart->SeriesCollection(4)->Trendlines
        ->Add({Type => xlMovingAvg, Period => 4});
  $MovAvg->Border->{Color} = RGB(255,0,0);

Now the finished workbook can be saved to disk as
F<i:\tmp\tpj\data.xls>. That file most likely still exists from when the
program ran yesterday, so we'll remove it. (Otherwise, Excel would pop
up a dialog with a warning, because the SaveAs() method doesn't like
to overwrite files.)


  # Save workbook to file my $Filename = 'i:\tmp\tpj\data.xls';
  unlink $Filename if -f $Filename;
  $Book->SaveAs($Filename);
  $Book->Close;

=head2 ACTIVEX DATA OBJECTS

Mary stores the daily prices in her T-bonds database, keeping the data
for the different contracts in separate tables. After creating an ADO
(ActiveX Data Object) connection to the database, she tries to connect
a record set to the table for the current contract. If this fails, she
assumes that the table doesn't exists yet and tries to create it:

  use Win32::OLE::Const 'Microsoft ActiveX Data Objects';

  my $Connection = Win32::OLE->new('ADODB.Connection');
  my $Recordset = Win32::OLE->new('ADODB.Recordset');
  $Connection->Open('T-Bonds');

  # Open a record set for the table of this contract
  {
    local $Win32::OLE::Warn = 0;
    $Recordset->Open($Contract, $Connection, adOpenKeyset,
                         adLockOptimistic, adCmdTable);
  }

  # Create table and index if it doesn't exist yet
  if (Win32::OLE->LastError) {
      $Connection->Execute(<<"SQL");
        CREATE TABLE $Contract
        (
          Day DATETIME,
          Open DOUBLE, High DOUBLE, Low DOUBLE, Close DOUBLE
        )
  SQL
      $Connection->Execute(<<"SQL");
        CREATE INDEX $Contract
        ON $Contract (Day) WITH PRIMARY
  SQL
      $Recordset->Open($Contract, $Connection, adOpenKeyset,
                                adLockOptimistic, adCmdTable);
  }

C<$Win32::OLE::Warn> is temporarily set to zero, so that if
C<$Recordset->Open> fails, the failure will be recorded silently without
terminating the program. C<Win32::OLE->LastError> shows whether the Open
failed or not. C<LastError> returns the OLE error code in a numeric
context and the OLE error message in a string context, just like
Perl's C<$!> variable.

Now Mary can add today's data:

  # Add new record to table
  use Win32::OLE::Variant;
  $Win32::OLE::Variant::LCID = $Win32::OLE::LCID;

  my $Fields = [qw(Day Open High Low Close)];
  my $Values = [Variant(VT_DATE, $Day),
                $Open, $High, $Low, $Close];

Mary uses the Win32::OLE::Variant module to store C<$Day> as a date
instead of a mere string. She wants to make sure that it's stored as
an American-style date, so in the third line shown here she sets the
locale ID of the Win32::OLE::Variant module to match the Win32::OLE
module. (C<$Win32::OLE::LCID> had been set earlier to English, since
that's what the Chicago Board of Trade uses.)

  {
      local $Win32::OLE::Warn = 0;
      $Recordset->AddNew($Fields, $Values);
  }

  # Replace existing record
  if (Win32::OLE->LastError) {
      $Recordset->CancelUpdate;
      $Recordset->Close;
      $Recordset->Open(<<"SQL", $Connection, adOpenDynamic);
          SELECT * FROM $Contract
          WHERE Day = #$Day#
  SQL
      $Recordset->Update($Fields, $Values);
  }

  $Recordset->Close;
  $Connection->Close;

The program expects to be able to add a new record to the table. It
fails if a record for this date already exists, because the Day field
is the primary index and therefore must be unique. If an error occurs,
the update operation started by AddNew() must first be cancelled with
C<< $Recordset->CancelUpdate >>; otherwise the record set won't close.

=head2 LOTUS NOTES

Now Mary can use Lotus Notes to mail updates to all her customers
interested in the T-bond data. (Lotus Notes doesn't provide its
constants in the OLE type library, so Mary had to determine them by
playing around with LotusScript.) The actual task is quite simple: A
Notes session must be started, the mail database must be opened and
the mail message must be created. The body of the message is created
as a rich text field, which lets her mix formatted text with object
attachments.

In her program, Mary extracts the email addresses from her customer
database and sends separate message to each. Here, we've simplified it
somewhat.

  sub EMBED_ATTACHMENT {1454;}     # from LotusScript

  my $Notes = Win32::OLE->new('Notes.NotesSession');
  my $Database = $Notes->GetDatabase('', '');
  $Database->OpenMail;
  my $Document = $Database->CreateDocument;

  $Document->{Form} = 'Memo';
  $Document->{SendTo} = ['Jon Orwant >orwant@tpj.com>',
                         'Jan Dubois >jan.dubois@ibm.net>'];
  $Document->{Subject} = "US T-Bonds Chart for $Day";

  my $Body = $Document->CreateRichtextItem('Body');
  $Body->AppendText(<<"EOT");
  I\'ve attached the latest US T-Bond data and chart for $Day.
  The daily statistics were:

  \tOpen\t$Open
  \tHigh\t$High
  \tLow\t$Low
  \tClose\t$Close

  Kind regards,

  Mary
  EOT

  $Body->EmbedObject(EMBED_ATTACHMENT, '', $Filename);

  $Document->Send(0);

=head1 VARIANTS

In this final section, I'll talk about Variants, which are the data
types that you use to talk to OLE objects. We talked about this line
earlier:

  my $Values = [Variant(VT_DATE, $Day),
                $Open, $High, $Low, $Close];

Here, the Variant() function creates a Variant object, of type C<VT_DATE>
and with the value C<$Day>. Variants are similar in many ways to Perl
scalars. Arguments to OLE methods are transparently converted from
their internal Perl representation to Variants and back again by the
Win32::OLE module.

OLE automation uses a generic C<VARIANT> data type to pass
parameters. This data type contains type information in addition to
the actual data value. Only the following data types are valid for OLE
automation:

  B<Data Type     Meaning>
  VT_EMPTY      Not specified
  VT_NULL       Null
  VT_I2         2 byte signed integer
  VT_I4         4 byte signed integer
  VT_R4         4 byte real
  VT_R8         8 byte real
  VT_CY         Currency
  VT_DATE       Date
  VT_BSTR       Unicode string
  VT_DISPATCH   OLE automation interface
  VT_ERROR      Error
  VT_BOOL       Boolean
  VT_VARIANT    (only valid with VT_BYREF)
  VT_UNKNOWN    Generic COM interface
  VT_UI1        Unsigned character

The following two flags can also be used:

  VT_ARRAY      Array of values
  VT_BYREF      Pass by reference (instead of by value)

B<The Perl to Variant transformation.> The following conversions are
performed automatically whenever a Perl value must be translated into
a Variant:

  Perl value                  Variant
  Integer values              VT_I4
  Real values                 VT_R8
  Strings                     VT_BSTR
  undef                       VT_ERROR (DISP_E_PARAMNOTFOUND)
  Array reference             VT_VARIANT | VT_ARRAY
  Win32::OLE object           VT_DISPATCH
  Win32::OLE::Variant object  Type of the Variant object

What if your Perl value is a list of lists? Those can be irregularly
shaped in Perl; that is, the subsidiary lists needn't have the same
number of elements. In this case, the structure will be converted to a
"rectangular" C<SAFEARRAY> of Variants, with unused slots set to
C<VT_EMPTY>. Consider this Perl 2-D array:

  [ ["Perl" ],            # one element
    [1, 3.1215, undef]    # three elements
  ]

This will be translated to a 2 by 3 C<SAFEARRAY> that looks like this:

  VT_BSTR("Perl") VT_EMPTY      VT_EMPTY
  VT_I4(1) VT_R8(3.1415)        VT_ERROR(DISP_E_PARAMNOTFOUND)

B<The Variant To Perl Transformation.> Automatic conversion from Variants
to Perl values happens as follows:

  Variant                Perl value
  VT_BOOL, VT_ERROR      Integer
  VT_UI1, VT_I2, VT_I4   Integer
  VT_R4, VT_R8           Float value
  VT_BSTR                String
  VT_DISPATCH            Win32::OLE object

B<The Win32::OLE::Variant module.> This module provides access to the
Variant data type, which gives you more control over how these
arguments to OLE methods are encoded. (This is rarely necessary if you
have a good grasp of the default conversion rules.) A Variant object
can be created with the C<< Win32::OLE::Variant->new >> method or the
equivalent Variant() function:

  use Win32::OLE::Variant;
  my $var1 = Win32::OLE::Variant->new(VT_DATE, 'Jan 1,1970');
  my $var2 = Variant(VT_BSTR, 'This is an Unicode string');

Several methods let you inspect and manipulate Variant objects: The
Type() and Value() methods return the variant type and value; the As()
method returns the value after converting it to a different variant
type; ChangeType() coerces the Variant into a different type; and
Unicode() returns the value of a Variant object as an object of the
Unicode::String class.

These conversions are more interesting if they can be applied directly
to the return value of an OLE method call without first mutilating the
value with default conversions. This is possible with the following
trick:

  my $RetVal = Variant(VT_EMPTY, undef);
  $Object->Dispatch($Method, $RetVal, @Arguments);

Normally, you wouldn't call Dispatch() directly; it's executed
implicitly by either AUTOLOAD() or Invoke(). If Dispatch() realizes
that the return value is already a Win32::OLE::Variant object, the
return value is not translated into a Perl representation but rather
copied verbatim into the Variant object.

Whenever a Win32::OLE::Variant object is used in a numeric or string
context it is automatically converted into the corresponding format.

  printf "Number: %f and String: %s\n",
         $Var, $Var;

This is equivalent to:

  printf "Number: %f and String: %s\n",
         $Var->As(VT_R8), $Var->As(VT_BSTR);

For methods that modify their arguments, you need to use the C<VT_BYREF>
flag. This lets you create number and string Variants that can be
modified by OLE methods. Here, Corel's GetSize() method takes two
integers and stores the C<x> and C<y> dimensions in them:

  my $x = Variant( VT_I4 | VT_BYREF, 0);
  my $y = Variant( VT_I4 | VT_BYREF, 0);
  $Corel->GetSize($x, $y);

C<VT_BYREF> support for other Variant types might appear in future
releases of Win32::OLE.

=head1 FURTHER INFORMATION

=head2 DOCUMENTATION AND EXAMPLE CODE

More information about the OLE modules can be found in the
documentation bundled with Win32::OLE. The distribution also contains
other code samples.

The object model for Microsoft Office applications can be found in the
Visual Basic Reference for Microsoft Access, Excel, Word, or
PowerPoint. These help files are not installed by default, but they
can be added later by rerunning F<setup.exe> and choosing I<custom
setup>. The object model for Microsoft Outlook can be found on the
Microsoft Office Developer Forum at:
http://www.microsoft.com/OutlookDev/.

Information about the LotusScript object model can be found at:
http://www.lotus.com/products/lotusscript.nsf.

=head2 OLE AUTOMATION ON OTHER PLATFORMS

Microsoft also makes OLE technology available for the Mac. DCOM is
already included in Windows NT 4.0 and can be downloaded for Windows
95. MVS and some Unix systems can use EntireX to get OLE
functionality; see
http://www.softwareag.com/corporat/solutions/entirex/entirex.htm.

=head1 COPYRIGHT

Copyright 1998 I<The Perl Journal>. http://www.tpj.com

This article originally appeared in I<The Perl Journal> #10.  It
appears courtesy of Jon Orwant and I<The Perl Journal>.  This document
may be distributed under the same terms as Perl itself.