VFP Remote Procedure Call

Remote procedure call allows you to run methods on remote PC (or locally). The basic think is simple. All methods you want to run remotelly add into one object (based on BusinessLogic of BusinessLogic.fxp). Then you can use this object to run methods on remote machine. The BusinessLogic object translate all vfp basic types (char,integer,array,bool,...) and cursors for transfer over network. Final result is compressed in memory. For now, no encryption layer is added, but I plan it for the future.

1) Create the class

Let we say, that your class seems like this:


define class myClass as businessLogic of businessLogic.fxp
    function getCurrentDate
        if this.isClient
            return this.doRPC()
        endif
        return date()
    
    
    function getCursorOrders
        if this.isClient
            return this.doRPC()
        endif
    
        select * from orders into cursor test nofilter
        return tmpCursor("test")
        
        * or
        * return this.sql("select * from orders")

    
enddefine

In your main program create the class. Something like _screen.newobject("BL", "myClass")
From this point, you can use _screen.BL to get results:
? _screen.BL.getCurrentDate()
m.oOrders=_screen.BL.getCursorOrders()
m.oOrders.select
browse

2) Run it on remote machine

To run it remotelly, you need second instance on remote machine. For playing, just start your VFP session second time. On the second instance (let we say Server) create the same class the same way:

_screen.newobject("BL", "myClass")

and tell it to listen:
_screen.BL.doStartServer( "192.168.1.1", 26272 ) && Local IP and Port to listen

Now, go back to first instance and tell it to be client:
_screen.newobject("BL", "myClass")
_screen.BL.doStartClient( "192.168.1.1", 26272 ) && Remote IP and Port to connect

When the client connect to server (.onConnect / .onConnectError), you can use the class in the same way:
? _screen.BL.getCurrentDate()
m.oOrders=_screen.BL.getCursorOrders()
m.oOrders.select
browse

It is the same code as if it is runned locally, but the method is executed on remote machine.

3) Asynchronous

The BusinessLogic object can be configured to be fully asynchronous. In this case, all call's return imediatelly ( return null ) and your application must wait for .onReply or .onConnectionLost events. During the .onReply event, return value is stored in .asyncReplyValue. To switch into asynchronous mode, set
.isAsync=.T.

4) Passing parameters

Passing parameters is easy. Just add it into .doRpc() call. Example:

    function cursor_order( orderID, param2 )
        if this.isClient
            return this.doRPC(@m.orderID, @m.param2)
        endif
    
        return this.sql(...)

5) How to update remote database

Let we create class:

define class myClass as businessLogic of businessLogic.fxp

    * In this example I use sys(2017) to check if record was modified.
    * It is not dogma, you can check modified records in other ways,
    * or do not check for it and update all.

    function cursor_order( orderID )
        if this.isClient
            return this.doRPC( @m.orderID )
        endif
        
        local alias
        m.alias="order_"+sys(2015)
    
        select *, cast('' as c(10)) as __crc32__;
            from orders;
            where orderID=m.orderID;
            into cursor (m.alias) readwrite

        select (m.alias)
        replace all __crc32__ with sys(2017, "__crc32__", 0, 1+2)
        
        return tmpCursor(m.alias)
        
    
    
    function save_order( c as tmpCursor )
        if this.isClient
            return this.doRPC( @m.c )
        endif
        
        m.c.select
        * todo:
        * check values, if incorrect, return .f.
        * if new order, assign orderID, ...
        * ... follow your database design
        * if old, delete unchanged records, ...
        delete for __crc32__ == padr(sys(2017, "__crc32__", 0, 1+2), 10)


        * now is time to begin transaction or lock files,
        * or ... ignore it at all and 'set exclusive on' !

        update orders set;
            field1=u.field1, ;
            field2=u.field2, ;
            ... ;
        from;
            (m.c.alias) as u;
        where;
            orders.rowID = u.rowID

        insert into orders (field1, field2, ...) ;
            select field1,field2 from (m.c.alias);
            where is_New_Record

enddefine

6) More complex documents

In the previous example, we have only one table to update. But, what if I need more ? Header, items, .... no problem. Use empty object for it:

    function open_order( orderID )
        if this.isClient
            return this.doRPC( @m.orderID )
        endif
        
        local head, item
        m.head="order_header_"+sys(2015)
        m.item="order_items_"+sys(2015)

        select *;
            from orders;
            where orderID=m.orderID;
            into cursor (m.head) readwrite
        

        select *;
            from orders_items;
            where orderID=m.orderID;
            into cursor (m.item) readwrite
        

        local eo
        m.eo=createobject("empty")
        =addproperty(m.eo, [head], tmpCursor(m.head))
        =addproperty(m.eo, [item], tmpCursor(m.item))
        
        return m.eo

7) Enjoy !

That's all. Hope that it can be usefull. Any comments welcomed. You can ask on the www.foxite.com

MK 2010/11