[Erlang Systems]

3 Examples

Detailed examples on how to use Comet

3.1 Comet examples

This chapter describes in detail som examples on Comet usage; the simpler ones first and the most advanced last.

Four examples are given:

Source code for these are included in the distribution, in the directory comet/examples .

The abbreviations VB and VBA are used for Visual Basic and Visual Basic for Applications.

3.2 Requirements

The first example requires that Internet Explorer 4.0 or later is installed.

Example two requires Excel from Office 97 or Office 2000.

The last example can be run as it is, but to modify the COM-library, Visual C++ 5.0 or later is required.

3.3 Example one, opening a browser to a specific URL

This example shows how to open a browser (Internet Explorer), and navigate through it to a specific address.

To get the COM interface for the browser, we use a tool such as OLE/COM Object Viewer, which is included in Microsoft's Windows Platform SDK, Visual C and Visual Basic.

Checking the interface for Internet Explorer, we find a couple of things that we need. First, we need the class ID. Then we need the name and parameter list of the funcions and properties required to create and use a browser.

Since starting a browser is not a performance-critical task, we can use the slowest and safest way to do it from Erlang. This means starting the erl_com as a port process, and using the IDispatch interface to access Internet Explorer.

Although Internet Explorer provides a dual interface, (that is an interface with both a method table and an IDispatch-interface), the IDispatch interface is safer and slower. Giving it a bad parameter list, returns an error code, rather than a core dump.

To use a COM object, we have to start the server (which starts the port) and start a thread. Then we can create the object, and do what we want with it.

To be able to use constants, we put the source in a module, rather than call it interactively in the Erlang shell.

-module(win_browse).

-include("erl_com.hrl").

-export([open/1, example/0]).
open(Url) ->
    {ok, Pid}= erl_com:start_process(),
    T= erl_com:new_thread(Pid),
    Obj= erl_com:create_dispatch(T, "InternetExplorer.Application", 
                                 ?CLSCTX_LOCAL_SERVER),
    erl_com:invoke(Obj, "Navigate", [Url]),
    erl_com:property_put(Obj, "Visible", true),
    Obj.

example() ->
    open("www.erlang.org").

The internet explorer application has a dispatch interface, that implements the IWebBrowser interface. There are a lot of methods. We use the Navigate method to open a specific URL, and the Visible property to show the browser. (By default, the browser is created invisible, like other Microsoft programs used from COM.)

3.4 Example two, making a graph in Excel

In this example, we also start an instance of the Excel application. We use the program name "Excel.Application", which can be used instead of a class ID. This selects the Excel that is installed; Excel from Office 97 or Office 2000.

The easiest way to do anything with Excel is to first record a VBA macro. The resulting VBA macro is shown in figure 1. This macro is manually rewritten a bit to make it simpler. We try it out, and the result is shown in figure 2.

Now, to perform this into Erlang, we have two choices: either we can call the VB code as a subroutine using COM from Erlang, or we can reimplement the VB macro in Erlang. Since this is a user's guide, we of course choose the latter.

To get to the interfaces, we use OLE/COM Object Viewer, and get the IDL for Excel. There is an Excel type library available. We do not want all of it because it is huge. We just pick the needed interfaces, which are _Application, _Graph and _Range. We also extract some enums, which are constants used for parameters in the COM calls.

There are some tricky issues when calling COM from Erlang

First, VB handles releasing of COM interfaces implicitly. Erlang and COM does not do this, so we have to make calls to erl_com:release/1 for every interface we get. For instance, every _Range we get from the property _Application.Range, has to be released. We do this in the helper function data_to_column/3.

Secondly, when an interface is returned, it is returned as an integer. This integer is actually an index into an interface array contained in the erl_com_drv port program. When calling functions in erl_com, we have to provide both the pid and the thread number, so there is a helper function erl_com::package_interface/2, that repackages the interface integer with given thread or other interface. When giving the interface as a parameter to a COM function (through erl_com:call or erl_com:invoke), however, the interface should be converted to a pointer, which is done with the tuple notation for COM types: {vt_unknown, Interface}.

When Excel is started, we execute a series of Excel commands to enter data and to draw a graph. The commands are translated from a VBA macro that we got using Excel's standard macro recorder.

We use some constants that are needed for the Excel commands. These are taken from Visual Basic's code generation from the Excel interfaces. Although these can be fetched from Excel using COM, erl_com does not yet support this. (Future releases will include code-generation that will greatly simplify using big COM-interfaces.

-module(xc).
-author('jakob@erix.ericsson.se').

-include("erl_com.hrl").

%% enum XlChartFormat
-define(XlPieExploded, 69).
-define(XlPie, 5).

%% enum XlChartLocation
-define(xlLocationAsNewSheet, 1).
-define(xlLocationAsObject, 2).
-define(xlLocationAutomatic, 3.


%% enum XlRowCol
-define(xlColumns, 2).
-define(xlRows, 1).


-export([populate_area/4, f/3, make_graph/6, sample1/0]).

to_cell_col(C) when C > 26 ->
        [C / 26 + 64, C rem 26 + 64];
to_cell_col(C) ->
        [C+64].

populate_area(E, _, _, []) ->
        ok;
populate_area(E, Row, Col, [Data | Resten]) ->
        Cell= to_cell_col(Col)++integer_to_list(Row),
        io:format(" ~s ~n ", [Cell]),
        N= erl_com:property_get(E, "range", [Cell]),
        Range= erl_com:package_interface(E, N),
        erl_com:property_put(Range, "Value", Data),
        erl_com:release(Range),
        populate_area(E, Row+1, Col, Resten).

f(E, _, []) ->
        ok;
f(E, Startcell, [Data | Resten]) ->
        {R, C}= Startcell,
        Cell= "R"++integer_to_list(R)++"C"++integer_to_list(C),
        io:format(" ~p ~n ", [Cell]),
        f(E, {R+1, C}, Resten).

make_graph(E, Row1, Col1, Row2, Col2, Title) ->
        Charts = erl_com:package_interface(E, erl_com:property_get(E, "Charts")),
        erl_com:invoke(Charts, "Add"),
        ActiveChart= erl_com:package_interface(E, erl_com:property_get
                                               (E, "ActiveChart")),
        erl_com:property_put(ActiveChart, "ChartType", {vt_i4, ?XlPieExploded}),
        erl_com:invoke(ActiveChart, "Location", [{vt_i4, ?xlLocationAsObject}, 
                                                 "Sheet1"]),
        Chart= erl_com:package_interface(E, erl_com:property_get(E, "ActiveChart")),
        R= to_cell_col(Col1)++integer_to_list(Row1)++":"
         ++to_cell_col(Col2)++integer_to_list(Row2),
        io:format(" ~s ~n ", [R]),
        Range= erl_com:property_get(E, "Range", [R]),
        erl_com:invoke(Chart, "SetSourceData", [{vt_unknown, Range}, 
                                                {vt_i4, ?xlColumns}]),
        erl_com:property_put(Chart, "HasTitle", true),
        ChartTitle= erl_com:package_interface(E, erl_com:property_get
                                              (Chart, "ChartTitle")),
        erl_com:property_put(ChartTitle, "Caption", Title).
        %erl_com:release(erl_com:package_interface(E, Range)),
        %erl_com:release(ActiveChart),
        %erl_com:release(Charts).

sample1() ->
        {ok, Pid}= erl_com:start_process(),
        T= erl_com:new_thread(Pid),
        E= erl_com:create_dispatch(T, "Excel.Application", ?CLSCTX_LOCAL_SERVER),
        erl_com:property_put(E, "Visible", true),
        Wb= erl_com:package_interface(T, erl_com:property_get(E, "Workbooks")),
        erl_com:invoke(Wb, "Add"),
        populate_area(E, 1, 1, ["Erlang", "Java", "C++"]),
        populate_area(E, 1, 2, ["25", "100", "250"]),
        make_graph(E, 1, 1, 3, 2, "Programming errors, by programming language"),
        {T, E, Wb}.

3.5 Example three, calling a COM object in C++

To be done.


Copyright © 1991-2000 Ericsson Utvecklings AB