ПЕРЕЛІК ДИСЦИПЛІН:
  • Адміністративне право
  • Арбітражний процес
  • Архітектура
  • Астрологія
  • Астрономія
  • Банківська справа
  • Безпека життєдіяльності
  • Біографії
  • Біологія
  • Біологія і хімія
  • Ботаніка та сільське гос-во
  • Бухгалтерський облік і аудит
  • Валютні відносини
  • Ветеринарія
  • Військова кафедра
  • Географія
  • Геодезія
  • Геологія
  • Етика
  • Держава і право
  • Цивільне право і процес
  • Діловодство
  • Гроші та кредит
  • Природничі науки
  • Журналістика
  • Екологія
  • Видавнича справа та поліграфія
  • Інвестиції
  • Іноземна мова
  • Інформатика
  • Інформатика, програмування
  • Юрист по наследству
  • Історичні особистості
  • Історія
  • Історія техніки
  • Кибернетика
  • Комунікації і зв'язок
  • Комп'ютерні науки
  • Косметологія
  • Короткий зміст творів
  • Криміналістика
  • Кримінологія
  • Криптология
  • Кулінарія
  • Культура і мистецтво
  • Культурологія
  • Російська література
  • Література і російська мова
  • Логіка
  • Логістика
  • Маркетинг
  • Математика
  • Медицина, здоров'я
  • Медичні науки
  • Міжнародне публічне право
  • Міжнародне приватне право
  • Міжнародні відносини
  • Менеджмент
  • Металургія
  • Москвоведение
  • Мовознавство
  • Музика
  • Муніципальне право
  • Податки, оподаткування
  •  
    Бесплатные рефераты
     

     

     

     

     

     

         
     
    Зворотні виклики в MIDAS через TSocketConnection
         

     

    Інформатика, програмування

    Зворотні виклики в MIDAS через TSocketConnection

    Передача повідомлень між клієнтськими додатками

    Роман Ігнатьєв (Romkin)

    Введення

    Зворотні виклики в технології СОМ - досить звичайне справа. Клієнт підключається до сервера, і сервер в деяких випадках сповіщає клієнта про події, що відбуваються в системі, просто викликаючи методи інтерфейсу зворотного дзвінка. Однак реалізація механізму для TRemoteDataModule, який звичайно застосовується на сервері додатків, досить загадкова. У цій статті як раз і описується спосіб реалізації викликів клієнтської частини з боку сервера додатків.

    Все почалося з того, що я поновив Delphi з 4 на 5 версію, і при цьому виявив, що у TSocketConnection з'явилося властивість SupportCallbacks. У довідковій системі написано, що при встановленні цього властивості в True сервер додатків може робити зворотні виклики методів клієнта, і більше практично ніяких подробиць. При цьому можливість додати підтримку зворотних викликів при створенні Remote data module відсутній, і не зовсім ясно, як же реалізовувати зворотні виклики клієнта в цьому випадку. З одного боку, здатність сервера додатків повідомляти своїх клієнтів про які-небудь події дуже приваблива, з іншого боку - без цього як-то до сих пір обходилися.

    Нарешті, дивлячись в черговий раз на цю властивість, я вирішив провести деякі дослідження, результат яких викладено нижче. Хочу відразу сказати, що всі нижчевикладене носить характер простого дослідження можливостей, і практично поки не застосовується, так що рекомендую застосовувати цей спосіб з обережністю. Справа в тому, що мені хотілося реалізувати всі як можна більш простим і зрозумілим способом, не відволікаючись на тонкощі реалізації викликів. Загалом, здається, все працює як треба, але поки цей механізм не випробуваний на деле, я не можу поручитися за правильність цього підходу.

    Отже, що ж мені хотілося зробити. Мені хотілося зробити механізм, що дозволяє серверу додатків надсилати повідомлення всім підключеним до нього клієнтам, а заразом дати можливість однієї клієнтської частини викликати методи інших клієнтських частин, наприклад, для організації простого обміну повідомленнями. Як видно, друге завдання включає в себе перший, адже якщо сервер додатків знає, як надсилати повідомлення всім клієнтам, достатньо просто виділити цю процедуру в окремий метод інтерфейсу, і будь-яке клієнтське додаток зможе робити те ж саме. Оскільки зазвичай я працюю з серверами додатків, віддалені модулі даних у яких працюють за моделлю Apartment (в фабриці класу коштує параметр tmApartment), мені хотілося зробити метод, що працює саме в цій моделі. Як буде видно нижче, це пов'язано з деякими складнощами.

    Після декількох спроб реалізувати зворотні виклики, написавши при цьому якомога менше коду, і при цьому ще зрозуміти, що ж саме робиться, з'ясувалося наступне:

    Писати все довелося вручну, стандартні механізми зворотних викликів змусити працювати мені не вдалося. Як відомо, при реалізації зворотного виклику клієнтська частина просто неявно створює кокласс для реалізації інтерфейсу зворотного виклику, і передає посилання на його інтерфейс COM-сервера, який у міру потреби викликає його методи. Цього ж результату можна домогтися, написавши об'єкт автоматизації на клієнті і передавши його інтерфейс сервера. Нижче так і зроблено.

    На жаль, при моделі Apartment кожен віддалений модуль даних працює у своєму потоці, а просто так викликати інтерфейс з іншого потоку неможливо, і необхідно виробляти ручної маршалинга або користуватися GIT. Такий механізм в COM є, зі способом виклику можна ознайомитися, наприклад, на http://www. techvanguards.com/com/tutorials/tips.asp # Marshal% 20interface% 20pointers% 20across% 20apartments (на нашому сайті ви можете знайти розбір тих самих питань українською мовою). Мені так робити не захотілося, по-перше, це досить складно і я залишив це "на солодке", по-друге, я спробував маршалинга через механізм повідомлень, що дозволяє реалізувати як синхронні виклики, так і асинхронні. Зухвалий модуль в цьому випадку не очікує обробки викликів клієнтами, що, як мені здається, є додатковою перевагою. Втім, при стандартному маршалинга реалізується практично такий же механізм.

    Ось що в мене вийшло в результаті.

    Сервер додатків

    Складається з одного віддаленого модуля даних, в якому немає доступу до бази даних, тільки реалізація зворотних викликів (фактично, ніяких компонентів на формі немає). Відповідно, у бібліотеці типів для нього потрібно описати два методи: отримання інтерфейсу зворотних викликів від клієнтської частини і метод для передачі повідомлення від однієї клієнтської частини всім іншим (широкомовної розсилки повідомлень). Я зупинився на варіанті, коли в зворотному виклику передається рядок, але ніщо не заважає реалізувати будь-який розділ параметрів.

    У бібліотеці типів треба оголосити власне інтерфейс зворотного виклику, який стане відомий клієнтської частини при імпорті бібліотеки типів сервера.

    У результаті бібліотека типів прийняла вигляд, наведений на малюнку 1.

    Малюнок 1.

    Проект називається BkServer. Модуль даних називається rdmMain, і в його інтерфейсі оголошені методи, опис яких наведено нижче.        

    procedure   RegisterCallBack (const BackCallIntf: IDispatch); safecall;     

    В даний метод має передаватися інтерфейс зворотного виклику IBackCall, метод OnCall якого і служить для забезпечення зворотного дзвінка. Однак параметр оголошений як IDispatch, з іншими типами підключення по сокета просто не працює.        

    procedure   Broadcast (const MsgStr: WideString); safecall;     

    Цей метод служить для широкомовної розсилки повідомлень.

    В інтерфейсі зворотного виклику (IBackCall) є тільки один метод:        

    procedure   OnCall (const MsgStr: WideString); safecall;     

    Цей метод отримує повідомлення.

    Отримані клієнтські інтерфейси треба десь зберігати, причому бажано забезпечити до них доступ з глобального списку, тоді повідомлення можна передати всім клієнтських частинах, просто пройшовши за цим списком. Мені здалося зручним зробити клас-оболонку, і вставляти в список посилання на клас. Як списку використовується простий TThreadList, описаний як глобальна мінлива в секції implementation:        

    var CallbackList: TThreadList;     

    і, відповідно, примірник списку створюється в секції initialization модуля і звільняється під час завершення роботи програми в секції finalization. Обрано саме TThreadList (потокобезопасний список), оскільки, як уже згадувалося, використовується модель apartment, і звернення до списку будуть йти з різних потоків.

    У секції initialization записано наступне оголошення фабрики класу:        

    TComponentFactory.Create (ComServer,   TrdmMain, Class_rdmMain, ciMultiInstance, tmApartment);     

    На сервері додатків створюється один модуль даних на кожне з'єднання, і кожен модуль даних працює у своєму потоці.

    У CallbackList зберігаються посилання на клас TCallBackStub, в якому і зберігається посилання на інтерфейс клієнта:        

    TCallBackStub =   class (TObject)   

    private   

    // Callback-інтерфейси повинні бути   disp-інтерфейсами.   

    // Виклики повинні йти через Invoke   

    FClientIntf: IBackCallDisp;   

    FOwner: TrdmMain;   

    FCallBackWnd: HWND;   

    public   

    constructor Create (AOwner:   TrdmMain);   

    destructor Destroy; override;   

    procedure   CallOtherClients (const MsgStr: WideString);   

    function OnCall (const MsgStr:   WideString): BOOL;   

    property ClientIntf:   IBackCallDisp read FClientIntf write FClientIntf;   

    property Owner: TrdmMain read   FOwner write FOwner;   

    end;     

    Примірник цього класу створюється і знищується rdmMain (в обробника OnCreate і OnDestroy). Посилання на нього зберігається в змінної TrdmMain.FCallBackStub, при цьому клас відразу вставляється в список:        

    procedure TrdmMain.RemoteDataModuleCreate (Sender: TObject);   

    begin   

    // Відразу робимо оболонку для   callback-інтерфейсу   

    FCallbackStub: = TCallBackStub.Create (Self);   

    // І відразу реєструємо в загальному списку   

    CallbackList.Add (FCallBackStub);   

    end;      

    procedure TrdmMain.UnregisterStub;   

    begin   

    if Assigned (FCallbackStub) then   

    begin   

      CallbackList.Remove (FCallbackStub);   

    FCallBackStub.ClientIntf: =   nil;   

    FCallBackStub.Free;   

    FCallBackStub: = nil;   

    end;   

    end;      

    procedure TrdmMain.RemoteDataModuleDestroy (Sender: TObject);   

    begin   

    UnregisterStub;   

    end;     

    Призначення полів досить зрозуміло: у FClientIntf зберігається власне інтерфейс зворотного дзвінка, в FOwner - посилання на TRdmMain ... А от третє полі (FCallBackWnd) служить для маршалинга викликів між потоками, про це буде сказано трохи нижче. У виклику методу RegisterCallBack інтерфейс просто передається цього класу, де і виробляється безпосередній виклик callback-інтерфейсу (через Invoke):        

    procedure TrdmMain.RegisterCallBack (const BackCallIntf: IDispatch);   

    begin   

    lock;   

    try   

    FCallBackStub.ClientIntf: =   IBackCallDisp (BackCallIntf);   

    finally   

    unlock;   

    end;   

    end;     

    Всього цього цілком достатньо для дзвінків клієнтської частини з віддаленого модуля даних, до якого вона приєднана. Однак завдання полягає саме в тому, щоб викликати інтерфейси клієнтських частин, що працюють з іншими модулями. Це забезпечується двома методами класу TCallBackStub: CallOtherClients і OnCall.

    Перший метод досить простий, і викликається з процедури Broadcast:        

    procedure TrdmMain.Broadcast (const MsgStr: WideString);   

    begin   

    lock;   

    try   

    if Assigned (FCallbackStub)   then// переводимо стрілки:)   

      FCallbackStub.CallOtherClients (MsgStr);   

    finally   

    unlock;   

    end;   

    end;   

    procedure TCallBackStub.CallOtherClients (const MsgStr: WideString);   

    var   

    i: Integer;   

    LastError: DWORD;   

    ErrList: string;   

    begin   

    ErrList: ='';   

    with Callbacklist.LockList do   

    try   

    for i: = 0 to Count - 1 do   

    if Items [i] <> Self   then// для всіх, крім себе   

    if not   TCallbackStub (Items [i]). OnCall (MsgStr) then   

    begin   

    LastError: = GetLastError;   

    if LastError <>   ERROR_SUCCESS then   

    ErrList: = ErrList +   SysErrorMessage (LastError) + # 13 # 10   

    else   

    ErrList: = ErrList + 'Щось незрозуміле' + # 13 # 10;   

    end;   

    if ErrList <>''then   

    raise Exception.Create ( 'Виникли помилки:' # 13 # 10 +   ErrList);   

    finally   

    Callbacklist.UnlockList;   

    end;   

    end;     

    Організовується прохід за списком Callbacklist, і для всіх TCallbackStub у списку викликається метод OnCall. Якщо виклик не вийшов, збираємо помилки і видаємо повідомлення. Помилка може бути системною, як видно нижче. Я не став створювати свій клас виняткову ситуацію, на клієнті вона все одно буде виглядати як EOLEException.

    Якщо б модель потоків була tmSingle, у методі OnCall достатньо було б просто викликати відповідний метод інтерфейсу IBackCallDisp, але при створенні віддаленого модуля даних була обрана модель tmApartment, і прямий виклик IBackcallDisp.OnCall негайно призводить до помилки, потоки-то різні. Тому доводиться робити виклики інтерфейсу з його власного потоку. Для цього використовується вікно, що створюється кожним екземпляром класу TCallBackStub, handle якого і зберігається у змінній FCallBackWnd. Основна ідея така: замість прямого виклику інтерфейсу послати повідомлення у вікно, і викликати метод інтерфейсу в процедурі обробки повідомлень цього вікна, яка опрацює повідомлення в контексті потоку, який створив вікно:        

    function TCallBackStub.OnCall (const MsgStr: WideString): BOOL;   

    var   

    MsgClass: TMsgClass;   

    begin   

    Result: = True;   

    if Assigned (FClientIntf) and   (FCallbackWnd <> 0) then   

    begin   

    // MsgClass - це просто оболонка   повідомлення, тут же можна передавати   

    // додаткову службову інформацію.   

    MsgClass: = TMsgClass.Create;   

    // А ось звільнений об'єкт буде в   обробнику повідомлення.   

    MsgClass.MsgStr: = MsgStr;   

    // Синхронізація - послав і забув :-))   Виходимо відразу.   

    // При SendMessage викликав клієнт буде   чекати, поки всі інші клієнти   

    // опрацюють повідомлення, а це небажано   

    Result: = PostMessage (FCallBackWnd,   CM_CallbackMessage,   

    Longint (MsgClass), Longint (Self ));   

    if not Result then// то й не треба:)   

    MsgClass.Free;   

    end;   

    end;     

    Що виходить: повідомлення посилається в чергу кожного потоку, і там повідомлення накопичуються. Коли модуль даних звільняється від поточної обробки даних, а вона може бути досить довгою, всі повідомлення в черги обробляються і передаються на клієнтську частину в порядку надходження. Побічним ефектом є те, що клієнт, який викликав Broadcast, не очікує закінчення обробки повідомлень усіма іншими клієнтськими частинами, так як PostMessage повертає керування негайно. У підсумку виходить досить симпатична система, коли один клієнт надсилає повідомлення всім іншим і тут ж продовжує роботу, не чекаючи закінчення передачі. Інші ж клієнти отримують це повідомлення в момент, коли ніякої обробки даних не відбувається, можливо - набагато пізніше. Клас TMsgClass оголошений в секції implementation наступним чином:        

    type   

    TMsgClass = class (TObject)   

    public   

    MsgStr: WideString;   

    end;     

    і служить просто конвертом для рядки повідомлення, в принципі, в нього можна додати будь-які інші дані. Посилання на екземпляр цього класу зберігається тільки в параметрі wParam повідомлення, і теоретично можлива ситуація, коли повідомлення буде надіслано модулю, який вже знищується (клієнт від'єднався). І, природно, повідомлення оброблено не буде, і не буде знищений екземпляр класу TMsgClass, що призведе до витоку пам'яті. Виходячи з цього, при знищенні клас TCallBackStub вибирає за допомогою PeekMessage всі повідомлення, що залишилися, і знищує MsgClass до знищення вікна. FCallbackWnd створюється в конструкторі TCallBackStub і знищується в деструктор:        

    constructor TCallBackStub.Create (AOwner: TrdmMain);   

    var   

    WindowName: string;   

    begin   

    inherited Create;   

    Owner: = AOwner;   

    // створюємо вікно синхронізації   

    WindowName: = 'CallbackWnd' +   

      IntToStr (InterlockedExchangeAdd (@ WindowCounter, 1 ));   

    FCallbackWnd: =   

      CreateWindow (CallbackWindowClass.lpszClassName, PChar (WindowName), 0,   

    0, 0, 0, 0, 0, 0, HInstance,   nil);   

    end;      

    destructor TCallBackStub.Destroy;   

    var   

    Msg: TMSG;   

    begin   

    // Можуть залишитися повідомлення - видаляємо   

    while PeekMessage (Msg, FCallbackWnd, CM_CallbackMessage,   

    CM_CallbackMessage,   PM_REMOVE) do   

    if Msg.wParam <> 0 then   

    TMsgClass (Msg.wParam). Free;   

    DestroyWindow (FCallbackWnd);   

    inherited;   

    end;     

    Зрозуміло, перед створенням вікна потрібно оголосити і зареєструвати його клас, що і зроблено в секції implementation модуля. Процедура обробки повідомлень вікна викликає метод OnCall інтерфейсу при отриманні повідомлення CM_CallbackMessage:        

    var   

    CM_CallbackMessage: Cardinal;      

    function CallbackWndProc (Window: HWND; Message: Cardinal;   

    wParam, lParam: Longint):   Longint; stdcall;   

    begin   

    if Message = CM_CallbackMessage   then   

    with TCallbackStub (lParam) do   

    begin   

    Result: = 0;   

    try   

    if wParam <> 0 then   

    with TMsgClass (wParam) do   

    begin   

    Owner.lock;   

    try   

    // Безпосередній виклик інтерфейсу клієнта   

    if   Assigned (ClientIntf) then   

      ClientIntf.OnCall (MsgStr);   

    finally   

    Owner.unlock;   

    end;   

    end;   

    except   

    end;   

    if wParam <> 0 then// повідомлення відпрацьовано - знищуємо   

    TMsgClass (wParam). Free;   

    end   

    else   

    Result: =   DefWindowProc (Window, Message, wParam, lParam);   

    end;     

    Номер повідомленням CM_CallbackMessage присвоюється викликом        

    RegisterWindowMessage ( 'bkServer   Callback SyncMessage');     

    також у секції ініціалізації.

    Ось, власне, і все - зворотний виклик здійснюється з потрібного потоку. Тепер можна приступати до реалізації клієнтської частини.

    Клієнтська частина

    Складається з однієї форми, просто щоб спробувати механізм передачі повідомлень. На етапі розробки форма виглядає таким чином (Малюнок 2):

    Малюнок 2

    Тут присутній TSocketConnection (scMain), яка з'єднується з сервером BkServer. Кнопка "Помилка з'єднання" (btnConnect) призначена для установки з'єднання, кнопка "Відправити" (btnSend) -- для відправлення повідомлення, записаного у вікні редагування (eMessage) іншим клієнтським частинах.

    Код клієнтської частини досить короткий:        

    procedure TfrmClient.btnConnectClick (Sender: TObject);   

    begin   

    with scMain do   

    Connected: = not Connected;   

    end;      

    procedure TfrmClient.btnSendClick (Sender: TObject);   

    var   

    AServer: IrdmMainDisp;   

    begin   

    if not scMain.Connected then   

    raise Exception.Create ( 'Немає з'єднання');   

    AServer: =   IrdmMainDisp (scMain.GetServer);   

    AServer.Broadcast (eMessage.Text);   

    end;      

    procedure TfrmClient.scMainAfterConnect (Sender: TObject);   

    var   

    AServer: IrdmMainDisp;   

    begin   

    FCallBack: = TBackCall.Create;   

    AServer: =   IrdmMainDisp (scMain.GetServer);   

    AServer.RegisterCallBack (FCallBack);   

    lConnect.Caption: = 'З'єднання встановлено';   

    btnConnect.Caption: = 'Від'єднатись';   

    end;      

    procedure TfrmClient.scMainAfterDisconnect (Sender: TObject);   

    begin   

    FCallBack: = nil;   

    lConnect.Caption: = 'Немає з'єднання';   

    btnConnect.Caption: = 'З'єднатися';   

    end;     

    Фактично все управляється scMain, обробник OnAfterConnect (реєструючим callback-інтерфейс) і OnAfterDisconnect (виробляє зворотну дію). Зрозуміло, бібліотека типів сервера підключена до проекту, але не через Import Type Library. Справа в тому, що в проекті присутній?? т ActiveX Object TBackCall, який реалізує інтерфейс IBackCall, описаний в бібліотеці типів сервера. Зробити такий об'єкт дуже просто: треба просто вибрати New -> Automation Object і в діалозі ввести ім'я BackCall (можна й інше, це не принципово), вибрати ckSingle, і натиснути ОК. У вийшла бібліотеці типів відразу видалити інтерфейс IBackCall, і на вкладці uses бібліотеки типів підключити бібліотеку типів сервера (є локальне меню). Після цього на вкладці Implements кокласса вибрати зі списку інтерфейс IBackCall. Після оновлення в модулі буде створений заглушка для методу OnCall, а в каталозі проекту клієнта організується файл імпорту бібліотеки типів сервера BkServer_TLB.pas, що залишається тільки підключити до проекту і прописати в секціях uses модулів головної форми і СОМ-об'єкту. Метод OnCall я реалізував найпростішим чином:        

    procedure TBackCall.OnCall (const MsgStr: WideString);   

    begin   

    ShowMessage (MsgStr);   

    end;     

    Після компіляції додаток можна запустити в двох-трьох примірниках і перевірити його працездатність. Необхідно враховувати, що повідомлення отримують всі клієнти, крім того, хто вислав його.

    Таким чином, вийшло хоч і мінімальний, але працездатний додаток із зворотними викликами та передачею повідомлень між клієнтськими частинами. Хоча практично все реалізовано вручну, без використання готових методик COM, мені цей спосіб видається найбільш кращим, я просто реалізував зворотні виклики і маршалинга так, як мені хотілося. У результаті вся реалізація досить зрозуміла і дозволяє програмувати виклики так, як хочеться.

    Хоча мої друзі обізвали цей спосіб маршалинга викликів "хакерів", мені все одно хотілося б висловити їм глибоку вдячність за поради і терпіння, з яким вони відповідали на мої запитання ;-)).        

    ПРИМІТКА   

    Виконувані модулі були створені в   Delphi5 SP1. Для роботи програми, природно, необхідно запустити Borland   Socket Server, що входить в постачання Delphi.     

    Список літератури

    Для підготовки даної роботи були використані матеріали з сайту http://www.rsdn.ru/

         
     
         
    Реферат Банк
     
    Рефераты
     
    Бесплатные рефераты
     

     

     

     

     

     

     

     
     
     
      Все права защищены. Reff.net.ua - українські реферати ! DMCA.com Protection Status