Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
Конференция «Технологии управления данными 2018»
СУБД, платформы, инструменты, реальные проекты.
29 ноября 2018 г.

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 на этапе выполнения

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

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

 

Новости мира IT:

Архив новостей

Последние комментарии:

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...