3. Реализация вызовов удаленных процедур (RPC). Создание серверов функциональности и клиентских приложений

Помимо функциональности, связанной с доступом к базам данных, можно использовать Entera для реализации любой другой функциональности, то есть использовать RPC (Remote Procedure Calls - вызовы удаленных процедур) для удаленного доступа к функциям, осуществляющим любые другие действия (например, расчеты).

Отметим, что RPC являются основой механизма обмена данными между клиентом и сервером в рассмотренных выше примерах. Генерация этих вызовов осуществляется неявно при использовании компонента TEnteraProvider.

Ниже будут рассмотрены примеры явного вызова RPC (так называемые Simple RPC Calls). В этом случае следует создать stub-код для сервера и клиента; напомним, что вызовы удаленных процедур осуществляются за счет обмена пакетами данных (эта процедура иногда называется маршалингом или маршрутизацией) между двумя stub-объектами в адресных пространствах сервера и клиента (подробнее об этом рассказано в первой статье данного цикла).

Рис. 9. Осуществление вызовов удаленных процедур

С целью иллюстрации этой процедуры создадим простейший TCP-сервер с помощью Entera 3.2.

3.1. Создание DEF-файла

Рассмотрим какую-либо функцию, написанную на Pascal, например, для вычисления синуса путем разложения в ряд:

function sin1(x: Double): Double; 
VAR I:INTEGER; R:DOUBLE;SL:DOUBLE;XX:DOUBLE; 
 CONST DELTA=0.0001 ; 
begin 
SL:=X;         I:=1; 
R:=0;       XX:=X*X;
WHILE (ABS(SL)>DELTA)  DO
BEGIN 
R:=R+SL; 
SL:=-(SL*XX/(2*I))/(2*I+1); 
inc(i); 
END; 
result:=R; 
end; 

Реализация ее с помощью C++ выглядит так:

 double sin1(double x) 
{
     int ii; double xx,r,sl,f,delta=0.0001; 
     sl=x; ii=1;   r=0; xx=x*x; 
     f= fabs(sl);
        while (f>delta) 
        {
        r=r+sl; 
            sl=-(sl*xx/(2*ii))/(2*ii+1); 
            f=fabs(sl); 
            ii=ii+1 ; 
         }
       return(r); 
} 

Создадим простейшее приложение для тестирования этой функции:

Рис. 10. Приложение, подлежащее разбиению на клиента и сервер функциональности

unit SINTST;

interface 

uses
     Windows, Messages, SysUtils, Classes,Graphics, Controls, Forms,
Dialogs, 
     StdCtrls, Buttons, TeEngine, Series, ExtCtrls, 
  TeeProcs, Chart,a1; 

type 
    TForm1 = class(TForm)
         Chart1: TChart; 
         Series1: TFastLineSeries; 
         BitBtn1: TBitBtn; 
         procedure BitBtn1Click(Sender: TObject);
  private 
      { Private   declarations }
  public { Public declarations } 
end; 
var
      Form1: TForm1;
implementation 

{$R *.DFM} 

function sin1(x: Double): Double; 
VAR I:INTEGER; R:DOUBLE;SL:DOUBLE;XX:DOUBLE; 
CONST DELTA=0.0001 ; 
begin 
SL:=X;      I:=1; 
R:=0;         XX:=X*X;
WHILE (ABS(SL)>DELTA) DO 
  BEGIN 
  R:=R+SL; 
  SL:=-(SL*XX/(2*I))/(2*I+1); 
  inc(i); 
  END; 
  result:=R; 
  end; {sin1} 


procedure TForm1.BitBtn1Click(Sender: TObject); 
VAR I:INTEGER;X:DOUBLE;Y:DOUBLE; 
begin 
FOR I:=1 TO 270 DO 
BEGIN X:=0.1*I;
 //Y:=SIN1(I); 
Y:=sin1(X); 
CHART1.SERIES[0].ADDXY(X,Y,FLOATTOSTR(X),clwHITE); 
END 
end; 

end.

Предположим, нам нужно разделить это приложение на сервер функциональности, вычисляющий синус, и "тонкого" клиента, рисующего график этой функции. В этом случае создание сервера функциональности следует начать с определения его интерфейса, для чего следует создать DEF-файл с описанием интерфейса этой функции на языке IDL (Interface Definition Language); вспомним, что IDL фактически представляет собой стандарт описания интерфейсов, и все его диалекты похожи друг на друга.

 [
 uuid(cdf19a00-22c8-11d2-a36c-008048eb72de), 
version(1.0) 
]
 interface a1{
 double sin1(
[in] double x); 
} 

Этот код присваивает серверу уникальный код UUID (Universal Unique Identifier), который можно сгенерировать с помощью алгоритма, определенного Open Software Foundation и реализованного в ряде утилит и функций (например, есть функция Windows API CoCreateGUID, реализующая этот алгоритм). В случае TCP-сервера в действительности он не используется, так же как и номер версии сервера, но используется в случае DCE-сервера. Затем объявляется интерфейс сервера, в котором можно описать несколько функций (в нашем случае она одна).

При написании DEF-файлов следует учитывать соответствие типов данных в DEF-файлах и используемых для создания серверных и клиентских частей языков программирования:

Объявления в DEF-файле Входные параметры Pascal Выходные параметры Pascal
Short x x: Smallint var x: Smallint
long x x: Longint var x: Longint
int x x: Integer var x: Integer
float x x: Single var x: Single
double x x: Double var x: Double
char x x: Char var x: Char

3.2. Генерация кода для сервера и клиента

Для создания серверной и клиентской части распределенной системы на базе Entera следует сгенерировать stub-код ("заглушку"), превращающую простые вызовы функций в вызовы удаленных процедур. В частности, при использовании удаленной функции следует сообщить компилятору о том, что ее реализация находится не в клиентском приложении, а на удаленном сервере. "Заглушка" нужна для того, чтобы компилятор мог найти нужную функцию, а ее реализация представляет собой код, осуществляющий вызов удаленных процедур.

Генерация stub-кода для сервера и клиента осуществляется автоматически с помощью утилиты rcmake.exe. Ee параметры в случае любой 32-разрядной версии Delphi выглядят так:

rpcmake -d myserv.def -c delphi2.0 -s delphi2.0

В случае С или С++ параметры rpcmake выглядят следующим образом:

rpcmake -d myserv.def -c c -s c

Здесь параметр -d - имя DEF-файла, -c - язык, для которого должен быть сгенерирован клиентский stub-код, -s - язык, для которого должен быть сгенерирован серверный stub-код (eстественно, эти языки могут быть разными).

В каталоге Entera\TCP\BIN имеется также утилита rpcmgui.exe, представляющая собой GUI-оболочку для rpcmake.exe.

Рис. 11. Утилита RPCMGUI.EXE из комплекта поставки Entera 3.2 для Windows NT.

Наиболее близким описанному выше процессу генерации кода из знакомых Windows-программистам процедур является, пожалуй, генерация stub- и proxy- кода Microsoft Visual C++ для динамически загружаемых библиотек, используемых в COM-сервере и COM-клиенте, с помощью компилятора MIDL на основании IDL-описания интерфейсов сервера. Отметим, что автоматическая генерация stub-кода на основании описания интерфейсов является сейчас общепринятым процессом при организации распределенных вычислений; создание такого кода "вручную" сейчас используется довольно редко.

3.3. Создание серверной части (Delphi)

В результате использования утилиты rpcmake.exe с парамерами, соответствующими выбору Delphi в качестве языка для сервера, получим следующие файлы: a1_c.pas (stub-код, который можно встраивать в клиентское приложение), a1_s.dpr (исходный текст консольного приложения для сервера функциональности), a1.pas - файл-заготовка, используемая при создании сервера, в который разработчику следует добавить код реализации функций (примерно так, как пишутся обработчики событий), a1.inc - код, содержащий объявления функций без их реализации (секция интерфейса).

Код для сервера a1_s.dpr, сгенерированный этой утилитой, выглядит следующим образом:

{$APPTYPE CONSOLE}
program a1_s; 

uses SysUtils, ODET3020, a1;
procedure rpc_sin1(dce_table:   PTable; socket: Integer); 
var  
		rv : Integer;
		x : Double;
begin 
    x := dce_pop_double(dce_table,'x'); 
    dce_push_double(socket,'dce_result',sin1(x)); 
end; 
procedure rpc_handle(func:   PChar; table: PTable; socket: Integer); 
begin
    if (StrComp(func,'sin1')=0) then 
       rpc_sin1(table,socket) 
    else 
        dce_unknown_func(func, table, socket); 
end; 

{Constants   and global variables follow}
const 
    VARLEN = 100;
var
    ode_file : PChar; 
    ode_server : PChar; 
    dce_func : PChar; 
    argarr : array[0..5] of array[0..VARLEN] of Char; 
    argptrs : array[0..5] of PChar; 
    argv : PChar; 
    argc : Integer; 
    dce_table : PTable; 
    called_init_func : Integer; 
    socket, rsocket, i, rv : Integer; 
    msgstr : String; 
begin {main} 
    GetMem(ode_file,VARLEN); 
    GetMem(ode_server,VARLEN); 
    GetMem(dce_func,VARLEN); 
  called_init_func := 0; 

FillChar(argarr,SizeOf(argarr),#0); 
FillChar(argv,SizeOf(argv),#0); 
  
for i := 0 to ParamCount + 1 do begin 
    StrCopy(argarr[i],PChar(ParamStr(i))); 
    argptrs[i] := @argarr[i]; 
end; {for} 

argc := ParamCount + 1; 
argv := @argptrs; 

rv := parse_args(argc, argv, ode_file); 

if (rv=0) then begin 
    Writeln('Env flag   (-e) not set'); 
        if ParamCount > 1 then 
          StrCopy(ode_file,PChar(ParamStr(ParamCount))); 
  end; 

if (dce_setenv(ode_file,NIL,NIL) = 0) then begin 
          msgstr := 'Set env '+ode_file^+' failed'; 
          WriteLn(msgstr); 
          msgstr := 'Reason: '+dce_errstr; 
          Writeln(msgstr); 
          Halt(1); 
end; 

ode_server := dce_servername('a1'); 
dce_checkver(2, 0); 
socket   := dce_init_server(ode_file,ode_server); 
if (socket <= 0) then begin 
          Writeln('setup   server failed'); 
          Writeln('Reason: '+dce_errstr); 
          dce_set_exit; 
end; 

while(True=True)   do begin 
          dce_table := dce_waitfor_call(socket,dce_func); 
          if (Boolean(dce_should_exit)   or Boolean(dce_err_is_fatal)) 
then 
           Exit 
            else begin 
            if (Boolean(dce_server_is_ded))   then begin 
             dce_spawn(socket,argc,argv,ode_file,ode_server); 
                 socket := dce_retsocket;  (* save for future *) 
              end; 
             rsocket := dce_retsocket(); (* (old socket closed) *)
             rpc_handle(dce_func,dce_table,rsocket); 
             dce_send(rsocket,dce_func); 
             dce_recv_conf(rsocket); 
             dce_release; 
             dce_table_destroy(dce_table); 
             if (dce_server_is_ded=0) then 
                dce_close_socket(rsocket); 
    end; {else}
end; {while} 

 FreeMem(ode_file,VARLEN); 
 FreeMem(ode_server,VARLEN); 
 FreeMem(dce_func,VARLEN); 

 dce_close_socket(rsocket); 
 dce_table_destroy(dce_table); 
end. 

Секция интерфейса a1.inc выглядит следующим образом:

function sin1(x: Double): Double;

Код реализации функций a1.pas имеет следующий вид (жирным шрифтом выделены строки, которые следует добавить разработчику):

unit a1; 
interface 
uses SysUtils, ODET3020; 
{$include a1.inc}

 implementation 
  
function sin1(x: Double): Double; 

VAR I:INTEGER; R:DOUBLE;SL:DOUBLE;XX:DOUBLE; 
CONST DELTA=0.0001 ; 
begin 
SL:=X;         I:=1;
R:=0;       XX:=X*X; 
WHILE (ABS(SL)>DELTA) DO 
BEGIN 
R:=R+SL; 
SL:=-(SL*XX/(2*I))/(2*I+1); 
inc(i); 
END; 
result:=R; 
end; {sin1} 
end. {unit}
Cервер можно скомпилировать из среды разработки или из командной строки, вызвав компилятор Pascal: Dcc32 -b a1_s.dpr

Перед компиляцией проекта файл ODET3020.pas (интерфейс к ODET3020.DLL - библиотеке, содержащей Entera API) следует поместить в тот же каталог, что и компилируемый проект. Исполняемый файл также требует наличия этой библиотеки в каком-либо доступном каталоге.

Полученный сервер функциональности, как обычно, представляет собой консольное приложение.

3.4. Создание серверной части (C/C++)

В результате использования утилиты rpcmake.exe (или rpcmgui.exe) с парамерами, соответствующими выбору C или C++ в качестве языка для сервера, получим следующие файлы: a1_c.c (stub-код, который можно встраивать в клиентское приложение), a1_s.c (исходный текст консольного приложения для сервера функциональности), a1_s.h - h-файл для myserv_s.c, a1.h - h-файл, содержащий объявления функций без их реализации (используется в клиентском приложении). Реализацию этих функций (a1.c) следует создать разработчику.

Код для сервера a1.с, сгенерированный этой утилитой, выглядит следующим образом:

 /*######################## 
# Server Proxy Procedure Code 
# generated by rpcmake  version2.0 
# on Thursday, December 31, 1998 at 19:17:39
# 
# interface: a1 
# 

 ######################## 
# server stub routines # 
########################*/ 
 #ifdef __mpexl 
#include "dceinc.h" 
#else 
#include <dceinc.h>
#endif 

#include <stdio.h>
#include <stdlib.h>
#include <string.h> 

#ifdef __cplusplus 
		extern   "C" { 
#endif 

/* RPC stub and stub handle definitions */ 
void rpc_handle (char   *, struct table *, int); 
void rpc_sin1 (struct table *, int); 
#ifdef __cplusplus 
                                } 
#endif 
void rpc_sin1 (struct table *dce_table,int Socket) 
{
  double x; 
  int _i; 
  double sin1(double);
	
  x = dce_pop_double(dce_table,"x"); 
  dce_push_double(Socket,"dce_result", 
               sin1(x)); 
}

int main(int argc,char **argv) 
{
    char *ode_file = NULL,*ode_server  = NULL; 
    char dce_func[VARLEN]; 
    struct table *dce_table; 
    int called_init_func = 0; 
    int socket,rsocket; 

if (!parse_args(&argc, argv,&ode_file)) { 
    printf ("Env   flag (-e) not set\n"); 
    ode_file = argc > 1 ? argv[argc-1] : (char *) NULL; 
} 
 if (dce_setenv(ode_file,NULL,NULL) == 0) { 
    fprintf(stderr,"Set env %s failed\n",   ode_file); 
    fprintf (stderr,"Reason: %s\n", dce_errstr()); 
    exit(1); 
}
 ode_server   = dce_servername("a1"); 

dce_checkver(2, 0); 

if ((socket = dce_init_server( ode_file,ode_server))   <= 0) { 
    fprintf (stderr,"setup server failed\n"); 
    fprintf (stderr,"Reason: %s\n", dce_errstr()); 
    dce_set_exit(); 
}

while(1) { 
    dce_table = dce_waitfor_call(socket,dce_func); 
     if (dce_should_exit() || 
        dce_err_is_fatal() ) 
        {break; 
        }
    else{ 
      if (dce_server_is_ded())   { 
         dce_spawn(socket,argc,argv,ode_file,ode_server); 
         socket = dce_retsocket();   /* save for future */ 
         }
           rsocket = dce_retsocket(); /* (old socket closed) */ 
           rpc_handle(dce_func,dce_table,rsocket); 
          dce_send(rsocket,dce_func); 
          dce_recv_conf(rsocket); 
          dce_release(); 
          dce_table_destroy(dce_table); 
          if (!dce_server_is_ded()) { 
          dce_close_socket(rsocket); 
            }
       }
}
dce_close_socket(rsocket); 
dce_table_destroy(dce_table); 
return(0); 
} 

 void rpc_handle(char *func,struct table *dce_table, 
     int Socket) 
{
      if (strcmp(func,"sin1")==0) 
      (void)rpc_sin1(dce_table,Socket); 
     else (void)dce_unknown_func(func, dce_table,   Socket); 
}

H-файл a1_s.h выглядит следующим образом:

/************************************* 
  * 
* Server Header for a1 
* Generated by rpcmake version 3.0 
* on Thursday, December   31, 1998 at 19:17:39 
* **************************************/
 #ifdef __cplusplus 
   extern "C" { 
#endif 

extern double sin1(double ); 
#ifdef __cplusplus 
   }
 #endif 

Код реализации функции следует создать разработчику. Он должен иметь примерно следующий вид:

USEUNIT("A1_s.c"); 
USELIB("odet30.lib"); 
//----------------------------------------------------------------- 
 double sin1(double x) 
{
  int ii; double xx,r,sl,f,delta=0.0001; 
  sl=x; ii=1; r=0;   xx=x*x; 
  f= fabs(sl); 
  while (f>delta) 
  {
  r=r+sl; 
     sl=-(sl*xx/(2*ii))/(2*ii+1); 
     f=fabs(sl); 
     ii=ii+1 ; 
     }
     return(r); 
}
Отметим, что все функции, связанные с выделением памяти в обычном С-коде, следует заменить на соответствующие функции c префиксом dce_ (например, dce_malloc) из Entera API.

3.5. Тестирование сервера функциональности

Для тестирования сервера следует создать для него env-файл с описанием переменных окружения:

DCE_BROKER=elmanova,16000 
DCE_DEBUGLEVEL=DEBUG,DEBUG 
DCE_LOG=server.log 

Далее следует запустить Entera Broker (если он еще не запущен), создав предварительно конфигурационный файл broker.env:

start broker -e broker.env

Затем следует создать и запустить командный файл для запуска сервера:

set odedir = c:\OpenEnv\Entera\TCP
start "IT IS A SERVER" a1_s -e server.env 
Только после этого можно запускать или отлаживать клиентское приложение.

3.6. Создание клиентского приложения (Delphi)

Код, сгенерированный утилитой rpcmake для клиента Delphi (a1_c.pas), имеет следующий вид:

unit a1_c; 

interface 

uses SysUtils, Classes, ODET3020; 

function sin1(x: Double): Double; 

implementation 

function sin1(x: Double): Double; 
var 
     dce_table  : PTable; 
     socket       : Integer; 
     rv               : Integer; 
begin
     dce_table := nil; 
     dce_checkver(2,0); 
     socket := dce_findserver('a1'); 
     if (socket > -1) then begin 
          dce_push_double(socket,'x',x); 
          dce_table := dce_submit('a1','sin1',socket); 
     end; 
     sin1 := dce_pop_double(dce_table,'dce_result'); 
     dce_table_destroy(dce_table); 
  end; 
  end. 

Этот код заставляет клиентское приложение обращатьcя к удаленной функции как к локальной. В действительности этот код представляет собой серию вызовов удаленных процедур. Все интерфейсы функций, к которым обращается клиент, содержатся в файле odet30.pas (dceinc.h), а их реализация - в файле odet30.dll.

Создадим клиентское приложение для тестирования созданного сервера. Создадим новый проект, добавим в него сгенерированный модуль a1_c.pas (a1_c.c в случае С++Builder) и сошлемся на него и на odet3020.pas в модуле, связанном с главной формой приложения. На форму поместим интерфейсные элементы, необходимые для тестирования сервера (примерно те же, что и в тестовом примере, расмотренном выше; можно сделать копию этого проекта и внести в нее необходимые изменения).

Создадим обработчики событий, связанных с нажатием на кнопки, а также с созданием и уничтожением формы:

unit sin_cln1; 
interface 
uses 
     Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, 
Dialogs, 
     StdCtrls, Buttons, TeEngine, Series, ExtCtrls, TeeProcs, Chart; 
type 
     TForm1 = class(TForm) 
        Chart1: TChart; 
        Series1: TFastLineSeries; 
        BitBtn1: TBitBtn; 
        procedure BitBtn1Click(Sender: TObject); 
        procedure FormCreate(Sender: TObject); 
        procedure FormDestroy(Sender: TObject); 
     private 
       { Private declarations } 
     public { Public declarations } end; 
var 
     Form1: TForm1; 
implementation 
uses   a1_c, odet3020;

 {$R *.DFM} 
procedure TForm1.BitBtn1Click(Sender: TObject); 
VAR   I:INTEGER;X:DOUBLE;Y:DOUBLE; 
begin 
FOR I:=1 TO 270 DO 
BEGIN 
X:=0.1*I; 
Y:=sin1(X); 
CHART1.SERIES[0].ADDXY(X,Y,FLOATTOSTR(X),clwHITE); END;
end; 

procedure TForm1.FormCreate(Sender:  TObject); 
  Var rv : integer; 
    msg : array [0 .. 200] of char; 
begin
     rv := dce_setenv ('client.env', nil, nil); 
     if (rv = 0) then 
    begin 
      dce_error (msg); 
      MessageDlg('TCP Error: ' + msg, mtInformation, [mbOK], 0); 
      PostMessage(Handle, WM_QUIT, 0, 0); 
    end; 
end; 

procedure TForm1.FormDestroy(Sender: TObject); 
begin 
dce_close_env; 
  end; 

end.
Отметим, что при создании главной формы приложения следует вызвать процедуру dce_setenv из библиотеки odet3020.dll, указав имя конфигурационного файла client.env в качестве параметра. К моменту запуска клиента этот файл должен существовать и иметь примерно следующий вид:
DCE_BROKER=elmanova, 16000 
DCE_LOG=CLIENT.LOG 
DCE_DEBUGLEVEL=D,D 

По окончании работы приложения следует вызвать процедуру dce_close_env, уничтожающую запущенные с помощью dce_setenv сервисы Entera.

3.7. Создание клиентского приложения (C/C++)

Код, сгенерированный утилитой rpcmake для клиента C/C++ (a1_c.c), имеет следующий вид:

/*######################## 
# Client Proxy Procedure Code 
# generated by rpcmake version 3.0
# on Thursday, December 31, 1998 at 19:17:39 
# 
# interface: a1 
#   */ 

#include <stdio.h> 
#if  defined __mpexl || defined _MACINTOSH_ 
#include "dceinc.h" 
#else 
#include <dceinc.h> 
#endif 

double sin1(double x) 
{
     double rv = 0;     int Socket;
     struct table *dce_table = NULL; 

     dce_checkver(2, 0);
     if ((Socket = dce_findserver("a1")) >= 0) { 
       dce_push_double(Socket,"x",x); 
       dce_table = dce_submit("a1", "sin1", Socket); 
    }
     rv = dce_pop_double(dce_table,"dce_result"); 
     dce_table_destroy(dce_table); 
     return(rv);
 }

H-файл для него имеет следующий вид:

 **************************************/ 
* 
* Client Header for a1 
* Generated   by rpcmake version 3.0 
* on Thursday, December 31, 1998 at 19:17:39 
* **************************************/ 
 #ifdef __cplusplus 
     extern "C" {
 #endif 

extern double sin1(double ); 
#ifdef __cplusplus 
		} 
#endif

Код клиентского приложения, аналогичный приведенному выше для Delphi, в случае С++Builder выглядит следующим образом:

//----------------------------------------- 
#include "dceinc.h" 
#include "myserv.h" 
USEUNIT("a1_c.c"); 
USELIB("odet30.lib"); 
 //----------------------------------------- 
void __fastcall TForm1::FormCreate(TObject *Sender) 
{
 dce_setenv("client.env",NULL,NULL); }
 //--------------------------------------------------------------------------- 
  void __fastcall TForm1::FormDestroy(TObject *Sender) 
{
  dce_release(); 
} 
//--------------------------------------------------------------------------- 
  void __fastcall TForm1::BitBtn1Click(TObject *Sender) 
{ 
int i; double x1,y; 
for (i=1;i<271;i++) 
{
 x1=0.1*float(i); 
y=sin1(x1); 
Chart1->Series[0]->AddXY(x1,y,FloatToStr(x1),clWhite); 
  } } 
//----------------------------------------- 

3.8. Создание клиентского приложения (Visual Basic)

Отметим, что клиентское приложение может быть создано с помощью любых версий и разновидностей Delphi (начиная с 1.0 и включая Standard-версии), любых версий C++Builder и вообще любых компиляторов С++. Помимо этого, для создания клиентских приложений можно использовать и другие средства разработки. В качестве примера рассмотрим Visual Basic for Applications.

Для начала создадим клиентский stub-код для Visual Basic, создав и выполнив командный файл вида:

set ODEDIR=F:\OPENENV\ENTERA\TCP 
PATH=%ODEDIR%\BIN;%PATH% 
rpcmake.EXE -d myserv.def  -c bas

В результате получим файл a1_c.vb вида:

Function sin1# (x#) 
      dim dce_table as long, Socket as integer 
		
       call dce_checkver(2,0)
       Socket = dce_findserver("a1") 
       If (Socket > -1) Then 
          Call dce_push_double(Socket,"x",x) 
          dce_table = dce_submit("a1","sin1",Socket) 
         End If 
       sin1 = dce_pop_double(dce_table,"dce_result") 
      Call dce_table_destroy(dce_table) 
End function 

Теперь создадим новый документ MS Word 97 (или MS Excel 97), сделаем видимой панель инструментов Visual Basic, войдем в режим конструктора и выведем на экран панель интерфейсных элементов. Далее поместим в документ кнопку:

Рис. 12. Создание клиента Entera c помощью VBA

Затем дважды щелкнем на созданной кнопке и перейдем в редактор Visual Basic. Добавим к документу форму UserForm1, поместим на ней несколько меток.

Рис. 13. Создание формы клиента Entera

Теперь создадим обработчик события, связанный с нажатием на кнопку CommandButton1 в документе (его прототип уже имеется в редакторе кода):

Private Sub CommandButton1_Click() 
x = sin1#(0.25) 
UserForm1.Label1.Caption = x
x = sin1#(0.5) 
UserForm1.Label2.Caption   = x
x = sin1#(0.75) 
UserForm1.Label3.Caption = x
x = sin1#(1#) 
UserForm1.Label4.Caption   = x
x = sin1#(1.25) 
UserForm1.Label5.Caption = x
x = sin1#(1.5) 
UserForm1.Label6.Caption  = x
x = sin1#(1.75) 
UserForm1.Label7.Caption = x
x = sin1#(2#) 
UserForm1.Label8.Caption   = x 
x = sin1#(2.25)
UserForm1.Label9.Caption = x
x = sin1#(2.5) 
UserForm1.Label10.Caption   = x
x = sin1#(2.75) 
UserForm1.Label11.Caption = x 
x = sin1#(3#)
UserForm1.Label12.Caption   = x
UserForm1.Show 
End Sub

После обработчика события добавим stub-код, содержащийся в сгенерированном файле a1_c.vb.

И, наконец, экспортируем в проект c помощью пункта меню Файл/Экспорт файла модуль odet30.bas из комплекта поставки Entera.

Создадим файл client.env в каталоге, содержащем документ. Возможно, потребуется отредактировать присоединенный к проекту модуль, изменив параметры процедуры dce_setenv, указав путь к файлу client.env:

rv = dce_setenv("client.env", "", "")

Отметим, что библиотека odet30.dll должна быть доступна нашему приложению, так как из нее им производятся вызовы функций.

Теперь можно вернуться в документ из среды разработки Visual Basic for Applications, выйти из режима конструктора и нажать кнопку в документе. На экране появится форма с результатами вызова удаленных процедур примерно следующего вида:

Рис. 14. Клиент Entera на этапе выполнения

Напомним, что перед запуском приложения следует убедиться, что брокер и сервер запущены.

Назад | Содержание | Вперед