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 на этапе выполнения
Напомним, что перед запуском приложения следует убедиться, что брокер и сервер
запущены.
Назад | Содержание
| Вперед