МТ5 и trans2quik.dll - страница 5

 

Совсем забросил идею связать МТ5 и Квик, остановился только на Квик (DEE server + trans2quik.dll)

Обдумываю реализацию этой программы.


Selector1 постоянно получает данные от DDE сервера и "складирует" их в Storage а так же вызывает функцию OnTick в соответствующем Child.

При вызове GetStorageData должен приостанавливаться DDE сервер и запись в Storage

А по приходу Коллбэка от Selector2 должен приостанавливаться и DDE сервер и запись в Storage и блокиковаться вызов GetStorageData 

Т.е. Selector2 имеет высокий приоритет, GetStorageData нормальный, а Selector1 низкий. 

Вопросы:

Как грамоотно синхронизировать работу Selector1, Selector2 и GetStorageData?

Может быть есть конкретные примеры токой синхронизации (я никогда не реализовывал подобного)?

 
prostotrader:

Забросил идею связать МТ5 и Квик, остановился только на Квик (DEE server + trans2quik.dll)

Обдумываю реализацию этой программы.

1. Оч правильное решение оставить только Квик.

2. Связь через ДДЕ - оч спорное решение. Многие говорят, что ДДЕ нестабильно, но, не знаю.

На мой взгляд, лучшее и более универсальное решение, это Луа-ДЛЛ-приложение. Я использую этот вариант. Разумеется, дело хозяйское.

 
Yuriy Asaulenko:

1. Оч правильное решение оставить только Квик.

2. Связь через ДДЕ - оч спорное решение. Многие говорят, что ДДЕ нестабильно, но, не знаю.

На мой взгляд, лучшее и более универсальное решение, это Луа-ДЛЛ-приложение. Я использую этот вариант. Разумеется, дело хозяйское.

Я давно написал DDE сервер для Квик - работает без сбоев и достаточно быстро (не медленнее, чем Луа - ДЛЛ),

при этом совсем не обязательно писать дополнительный код на Луа и DDL приёмник данных. 

Добалено

Собственно говоря, я уже написал программу изображенную на диаграмее (и она работает), но столкнулся с проблемой синхронизации.

 
prostotrader:

Я давно написал DDE сервер для Квик - работает без сбоев и достаточно быстро (не медленнее, чем Луа - ДЛЛ),

при этом совсем не обязательно писать доаолнительный код на Луа. 

Т.к. ДДЕ не занимался, вопрос - как сделан ДДЕ? Вроде там нужно сделать таблицу с данными, а потом ее пустить через ДДЕ.

Тут непонятка с событиями. Что-то изменилось, а через ДДЕ вроде отправляется вся таблица. Или ошибаюсь?

Допустим, ошибаюсь. Тогда как идентифицировать событие на приемной стороне?

prostotrader:

Собственно говоря, я уже написал программу изображенную на диаграмее (и она работает), но столкнулся с проблемой синхронизации.

Чего с чем?

С Луа этот вопрос решается коллбэками из ДЛЛ к произвольным данным.

 
Yuriy Asaulenko:

Т.к. ДДЕ не занимался, вопрос - как сделан ДДЕ? Вроде там нужно сделать таблицу с данными, а потом ее пустить через ДДЕ.

Тут непонятка с событиями. Что-то изменилось, а через ДДЕ вроде отправляется вся таблица. Или ошибаюсь?

Допустим, ошибаюсь. Тогда как идентифицировать событие на приемной стороне?

В Квик формируется нужная таблица для вывода.

Запускается свое приложение вулючающее в себе DDE сервер, выводим эту таблицу по DDE

При первом выводе из Квик по DEE передается вся таблица, а затем только строка из таблицы,

в которой произошли изменения.

В самой сторке (она передаётся полностью) есть (у меня например) название инструмента - это и есть идентификатор


 

Сам DDE сервер несколько строк (у меня на Паскале, но в Инете множество примеров есть на других яззыках)

unit DdeUnit;

interface

uses
  Winapi.Windows, System.Classes, System.Types, Vcl.Forms, Winapi.DdeMl,
  System.SysUtils, System.StrUtils, Vcl.Controls, Winapi.Messages, QTypes;

const
  WM_DDE_ADDQUE = WM_USER + 2;

var
  ServiceName: string = 'quikdde';             // DDE server name

//  procedure ClearTable(Table: TDataTable); overload;

type
  TPokeAction = (paAccept, paPass, paReject);
  TDdePokeEvent = procedure(const Topic: string; var Action: TPokeAction) of object;
  TDdeDataEvent = procedure(const Topic: string; Cells: TRect; Data: TDataTable) of object;

  PDdeQueItem = ^TDdeQueItem;
  TDdeQueItem = packed record
    data: Pointer;
    size: integer;
    sTopic: String;
    sCells: String;
  end;

  TDdeServer = class(TWinControl)
  private
    Inst: Integer;
    ServiceHSz: HSz;
    TopicHSz: HSz;
    fOnPoke: TDdePokeEvent;
    fOnData: TDdeDataEvent;
    fDdeQue: TThreadList;
  protected
    function XLTDecodeV(data: pointer; datasize: integer): TDataTable;
    function DecodeCellAddr(CellAddr: string): TRect;
    procedure AddQue(var Message: TWMSysCommand); message WM_DDE_ADDQUE;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property OnPoke: TDdePokeEvent read fOnPoke write fOnPoke;
    property OnData: TDdeDataEvent read fOnData write fOnData;
  end;

//--- Pointer functions ----
function addp(p: Pointer; increment: int64 = 1): Pointer; overload;
function addp(p: Pointer; increment: pointer): Pointer; overload;
function subp(p: Pointer; decrement: int64 = 1): Pointer; overload;
function subp(p: Pointer; decrement: pointer): Pointer; overload;


implementation
uses main;

function CallbackProc(CallType, Fmt: UINT; Conv: HConv; hsz1, hsz2: HSZ;
                      Data: HDDEData; Data1, Data2: DWORD): HDDEData stdcall;
var
  action: TPokeAction;
  sTopic: String;
  P: PDdeQueItem;
  Buff: array[0..255] of WideChar;
begin
  result:= DDE_FNOTPROCESSED;
//---
  case CallType of
    XTYP_CONNECT: result:= 1;
    XTYP_POKE: begin
      DdeQueryString(QTrader.DdeServer.Inst, HSz1, @Buff,
                                        sizeof(Buff), CP_WINUNICODE);
      sTopic:= string(Buff);
      action:= paPass;
      if Assigned(QTrader.DdeServer.fOnPoke) then
        QTrader.DdeServer.fOnPoke(sTopic, action);
//---
      case action of
        paAccept: begin
          DdeQueryString(QTrader.DdeServer.Inst, HSz2,
                         @Buff, sizeof(Buff), CP_WINUNICODE);
          New(P);
          P^.sTopic:= sTopic;
          P^.sCells:= string(Buff);
          P^.size:= DdeGetData(Data, nil, 0, 0);
          GetMem(P^.data, P^.size);
          DdeGetData(Data, P^.data, P^.size, 0);
//---
          with QTrader.DdeServer.fDdeQue.LockList do
          try
            add(P);
          finally
            QTrader.DdeServer.fDdeQue.UnlockList;
          end;
          if (QTrader.DdeServer.Handle <> 0) then
            PostMessage(QTrader.DdeServer.Handle, WM_DDE_ADDQUE, 0, 0);
          result:= DDE_FACK;
        end;
        paPass: result:=  DDE_FACK;
        paReject: result:=  DDE_FNOTPROCESSED;
      end;
    end;
  end;
end;


constructor TDdeServer.Create;
begin
  inherited;
  fDdeQue := TThreadList.Create;
  Parent := TWinControl(AOwner);
  CreateHandle;
  Inst := 0;
  if (DdeInitialize(Inst, @CallbackProc, APPCLASS_STANDARD, 0) = DMLERR_NO_ERROR) then
  begin
    ServiceHSz := DdeCreateStringHandle(Inst, PWideChar(ServiceName), CP_WINUNICODE);
    if(DdeNameService(Inst, ServiceHSz, 0, DNS_REGISTER) = 0) then
      raise Exception.Create( 'Не удалось зарегистрировать имя DDE-сервиса ''' +
                                                           ServiceName + '''' );
    TopicHSz := DdeCreateStringHandle(Inst, PWideChar('Topic'), CP_WINUNICODE);
  end else
    raise Exception.Create( 'Не удалось выполнить инициализацию DDE' );
end;

destructor TDdeServer.Destroy;
begin
  DdeNameService(Inst, ServiceHsz, 0, DNS_UNREGISTER);
  fDdeQue.Free;
  DdeFreeStringHandle(Inst, ServiceHsz);
  DdeUninitialize(Inst);
  inherited;
end;

procedure TDdeServer.AddQue(var Message: TWMSysCommand);
var
  vt: TDataTable;
  Cells: TRect;
  p: PDdeQueItem;
  i: integer;
begin
  with fDdeQue.LockList do
  try
    p:= PDdeQueItem(Items[Count - 1]);
    Cells:= DecodeCellAddr(p^.sCells);
    vt:= XLTDecodeV(p^.data, p^.size);
    if (Assigned(QTrader.DdeServer.fOnData)) then
      QTrader.DdeServer.fOnData(p^.sTopic, Cells, vt);
    for i:= (Count - 1) downto 0 do
    begin
      p:= Items[i];
      FreeMem(p^.data, p^.size);
      Delete(i);
      Dispose(p);
    end;
  finally
    fDdeQue.UnlockList;
  end;
end;


function TDdeServer.XLTDecodeV(data: pointer; datasize: integer): TDataTable;
var
  i: integer;
  curr: pointer;
  BlockType: word;
  BlockSize: word;
  StringSize: byte;
  RealData: real;
  StringData: shortstring;
  DataNum: integer;
begin
  curr:= addp(data, 4);
  result.RowCount := Word(curr^);
  curr:= addp(curr, 2);
  result.ColCount := Word(curr^);
  curr:= addp(curr, 2);
//--- set array size ---
  SetLength(result.Cells, result.RowCount);
//---
  for i := 0 to result.RowCount - 1 do
    SetLength(result.Cells[i], result.ColCount);
//--- data block --------
  DataNum := 0;
  while(Integer(subp(curr, data)) < datasize) do
  begin
    BlockType:= Word(curr^);
    curr:= addp(curr, 2);
    BlockSize:= Word(curr^);
    curr:= addp(curr, 2);
    case BlockType of
      1: begin
           while(BlockSize > 0) do
           begin
             RealData:= Real(curr^);
             curr:= addp(curr, 8);
             dec(BlockSize, 8);
             result.Cells[(DataNum div result.ColCount),
                          (DataNum mod result.ColCount)]:= FloatToStr(RealData);
             inc(DataNum);
           end;
         end;
      2: begin
           while(BlockSize > 0) do
           begin
             StringSize:= Byte(curr^);
             curr:= addp( curr );
             StringData[0]:= AnsiChar(Chr(StringSize));
//---
             for i:= 1 to StringSize do
             begin
               StringData[i]:= AnsiChar(Char(curr^));
               curr:= addp(curr);
             end;
             result.Cells[(DataNum div result.ColCount),
                          (Datanum mod result.ColCount)]:= string(StringData);
             inc(DataNum);
             dec(BlockSize, StringSize + 1);
           end;
         end;
    end;
  end;
end;


function TDdeServer.DecodeCellAddr( CellAddr: string ): TRect;
var
  tmp1, tmp2: integer;
begin
  CellAddr:= UpperCase(CellAddr);
  tmp1:= PosEx('R', CellAddr);
  tmp2:= PosEx('C', CellAddr, tmp1);
  try
    result.Top:= StrToInt(copy(CellAddr, tmp1 + 1, tmp2 - tmp1 - 1)) - 1;
  except
    exit;
  end;
  tmp1:= PosEx('R', CellAddr, tmp2);
  try
    Result.Left:= StrToInt(copy(CellAddr, tmp2 + 1, tmp1 - tmp2 - 1 - 1)) - 1;
  except
    exit;
  end;
  tmp2:= PosEx('C', CellAddr, tmp1);
  try
    result.Bottom:= StrToInt(copy(CellAddr, tmp1 + 1, tmp2 - tmp1 - 1)) - 1;
    result.Right:= StrToInt(copy(CellAddr, tmp2 + 1, Length(CellAddr) - tmp2)) - 1;
  except
    exit;
  end;
end;

function addp(P: Pointer; increment: int64 = 1): Pointer; overload;
begin
  result:= Pointer(Int64(p) + increment);
end;

function addp(P: Pointer; increment: Pointer): Pointer; overload;
begin
  result:= Pointer(Int64(p) + Int64(increment));
end;

function subp(P: Pointer; decrement: int64 = 1): Pointer; overload;
begin
  result:= Pointer(Int64(p) - decrement);
end;

function subp(P: Pointer; decrement: Pointer): Pointer; overload;
begin
  result:= Pointer(Int64(p) - Int64(decrement));
end;

end.
 

По названию инструмента создается дочернее окно (как в МТ 5)


 
Yuriy Asaulenko:

Чего с чем?


Я описал проблему в топике с диаграммой

 
prostotrader:

Я описал проблему в топике с диаграммой

Сорри, не сообразил. Если я правильно понял, то:

Имхо, решение - это в качестве Storage применение СУБД, скажем  MS SQL Server. Возможно, что это частичное решение.

Второе, применение промежуточных буферов-коллекций, типа последним вошел - первым вышел. Ну, и разделение потоков.

Тогда ничего останавливать не надо, все просто пишется в буфера. Ну, а у СУБД многопользовательский доступ.

Я это все применяю, но с Паскалем не дружу со времен 6-ки.

PS Говорят, что из Паскаля можно использовать NET-библиотеки. Для использования в качестве Storage м.б. имеет смысл применять  System.DataSystem.Data.DataSet и System.Data.DataTable. Насколько помню, с многопользовательским доступом в  DataTable проблем не возникало.

ЗЫ2 Сейчас в качестве БД пытаюсь применять SQLite, но определенных результатов пока нет. И это конечно не СУБД, но в урезанном виде многопользовательский доступ возможен, и есть возможность создания БД в памяти.

System.Data Namespace
System.Data Namespace
  • douglaslMS
  • docs.microsoft.com
Пространство имен обеспечивает доступ к классам, представляющим архитектуру ADO.NET. The namespace provides access to classes that represent the ADO.NET architecture. ADO.NET позволяет создавать…
 

Да нет, нужно просто синхронизировать работу 3-х потоков (по сути написать Synchronizer), но

не знаю как.


Причина обращения: