Автозапуск экспорта всех таблиц по DDE в Quik на C#

     Достаточно часто при написании привода или полностью автоматизированной системы работающей в тандеме с Quik необходимо не только уметь отправлять сделки в торговую систему, но и получать из нее различную торговую информацию. К сожалению trans2quik.dll не предоставляет возможности получения какой либо информации о котировках. Поэтому большинство пользователей делают это через квиковские DDE или ODBC. Первый мне кажется более простым в и понятным в реализации. Необходимо создать свой собственный DDE сервер для приема информации(об этом будет рассказано в следующих постах), после чего в Quik настраиваются таблицы которые будут выводиться. Но вот незадача, получается что при каждом запуска нашей программы придется заходить в Quik и запускать экспорт таблиц. Мне кажется это не очень удобно, но никакой возможности это сделать через trans2quik.dll нет.
     Ниже представлен небольшой код, который находит все запущенные терминалы Quik и запускает экспорт всех таблиц путем нажатия пункта меню 'Экспорт Данных -> Начать экспорт таблиц по DDE'.
     Код не требует никаких дополнительных библиотек, вам просто достаточно добавить этот код в свой проект.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;

namespace QuikIntegration.dde
{
    public class QuikUtil
    {
        private static string _exportMenuKey = "Экспорт данных";
        private static string _stopExportMenuItem = "Остановить экспорт таблиц по &DDE";
        private static string _startExportByDDEMenuItem = "Начать экспорт таблиц по &DDE";

        // ReSharper disable InconsistentNaming
        [DllImport("user32.dll")]
        static extern IntPtr GetMenu(IntPtr hWnd);

        [DllImport("user32.dll")]
        static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);

        [DllImport("user32.dll")]
        static extern int GetMenuItemCount(IntPtr hMenu);

        [DllImport("user32.dll")]
        static extern int GetMenuString(IntPtr hMenu, uint uIDItem, StringBuilder lpString, int nMaxCount, uint uFlag);

        [DllImport("user32.dll")]
        static extern uint GetMenuItemID(IntPtr hMenu, int nPos);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll", SetLastError = true)]
        static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);

        private const UInt32 MF_BYPOSITION = 0x00000400;

        public static bool StartDDE()
        {
            IntPtr[] quikWindows = FindQuikWindow();
            if (quikWindows.Length == 0)
            {
                return false;
            }
            foreach (var quikWindow in quikWindows)
            {
                IntPtr mainMenu = GetMenu(quikWindow);
                int exportMenuIndex = (int)FindMenuItemByPart(mainMenu, _exportMenuKey);
                IntPtr exportMenu = GetSubMenu(mainMenu, exportMenuIndex);

                uint exportIndex = 6; // FindMenuItemByPart(exportMenu, _startExportByDDEMenuItem);
                uint menuItem = GetMenuItemID(exportMenu, (int)exportIndex);
                PostMessage(quikWindow, 0x111, (IntPtr)menuItem, (IntPtr)0);
            }

            return true;
        }

        public static bool StopDDE()
        {
            IntPtr[] quikWindows = FindQuikWindow();
            if (quikWindows.Length == 0)
            {
                return false;
            }

            foreach (var quikWindow in quikWindows)
            {
                IntPtr mainMenu = GetMenu(quikWindow);
                int exportMenuIndex = (int)FindMenuItemByPart(mainMenu, _exportMenuKey);
                IntPtr exportMenu = GetSubMenu(mainMenu, exportMenuIndex);

                uint exportIndex = 7; // FindMenuItemByPart(exportMenu, _stopExportMenuItem);
                uint menuItem = GetMenuItemID(exportMenu, (int)exportIndex);
                PostMessage(quikWindow, 0x111, (IntPtr)menuItem, (IntPtr)0);
            }
            return true;
        }

        private static IntPtr[] FindQuikWindow()
        {
            Process[] processes = Process.GetProcessesByName("info");
            IntPtr[] result = new IntPtr[processes.Length];
            for (int i = 0; i < processes.Length; i++)
            {
                result[i] = processes[i].MainWindowHandle;
            }
            return result;
        }

        private static uint FindMenuItemByPart(IntPtr menu, string name)
        {
            int menuItemsCount = GetMenuItemCount(menu);
            for (uint menuIndex = 0; menuIndex < menuItemsCount; menuIndex++)
            {
                StringBuilder result = new StringBuilder();
                GetMenuString(menu, menuIndex, result, 1024, MF_BYPOSITION);
                string buffer = result.ToString();
                if (buffer.Contains(name))
                {
                    return menuIndex;
                }
            }
            return 0;
        }
    }
    // ReSharper restore InconsistentNaming
}


Для того, чтобы запустить экспорт всех таблиц нужно выполнить комманду:
QuikUtil.StartDDE();
остановить
QuikUtil.StopDDE();

14 комментариев:

  1. привет...
    мож подскажешь..
    обнаружил что если в квике открыто окошко системных сообщений
    обычный заголовок "QUIK: окно сообщений"

    то, т.к. в этом окне нет никаких меню ДДЕ экспорта...
    то и запустить дде-экспорт не удастся...

    как бы докопаться до оригинального окна info процесса?
    как закрыть это "оконо сообщений"?
    можно ль послать команду на закрытие этого окна ?
    и потом уж рыть в нужном процессе на счет дде-экспорта

    P.S. приделал поиск нужного окна по UID

    ОтветитьУдалить
  2. проще отключить в настройках показ окна сообщений

    ОтветитьУдалить
  3. В этой строке

    GetMenuString(menu, menuIndex, result, 1023, MF_BYPOSITION);

    получаю:

    System.ExecutionEngineException

    В среде выполнения обнаружена критическая ошибка. Ошибка произошла по адресу 0x707dceca в потоке 0xe18. Код ошибки 0xc0000005. Она может быть вызвана ошибкой в CLR или в небезопасных либо не поддающихся проверке фрагментах пользовательского кода. Обычно источниками таких ошибок бывают ошибки упаковки, допускаемые пользователями при COM-взаимодействии, либо PInvoke, повредивший стек.

    ОтветитьУдалить
  4. Давно не заходил сюда, времени совсем нету.
    Ошибка которую вы описываете у меня тоже последнее время начала возникать, причем возникает не постоянно, а периодически, у меня стоит Windows 7.
    Проблему решил криво, но у меня теперь все работает. Сейчас все работает. В теле поста поправленный код.

    ОтветитьУдалить
  5. Спасибо за код клика по пунктам меню очень помог)

    ОтветитьУдалить
  6. Хотелось бы продолжения про DDE сервер

    ОтветитьУдалить
  7. ребят, очень нужен программист, который бы собственно и настроил экспорт из квика на вебсайт. нужна утилита, которая организует синхронизацию между терминалом и сайтом.
    не пойму как тут написать в личку на блоге, отпишитесь мне в скайп, плз, vasart_ceo или на мыло vasart1@mail.ru

    ОтветитьУдалить
  8. И все же представленный код вызывает исключение. Лекарство простое - надо явно задать длину строки StringBuilder. Строка 94:

    StringBuilder result = new StringBuilder(1024);

    Можно сделать дину поменьше (побольше), но тогда в следующей строке указывать точно такую же длину.

    Кстати прототип в строке 25 лучше вот так описать:

    static extern int GetMenuString(IntPtr hMenu, uint uIDItem, [Out, MarshalAs(UnmanagedType.LPStr)] StringBuilder lpString, int nMaxCount, uint uFlag);


    А вообще спасибо автору за этот пост! Очень хороший код, который практически не пришлось править для своих целей )))) Но можно этот класс нагрузить еще немного и расширить его функционал для автоматического запуска QUIK, автоматического соединения с сервером и всяческими событиями по поводу внезапной кончины QUIK. Я в эту сторону обязательно пойду и, если это интересно, могу выложить дальнейшее развитие класса. Пишите на hamper [at] bk.ru

    Спасибо!

    ОтветитьУдалить
  9. Всем привет!
    У меня в 51-й строке
    PostMessage(quikWindow, 0x111, (IntPtr)menuItem, (IntPtr)0);

    Валится на ошибке:
    Переполнение в результате выполнения арифметической операции.

    Подскажите плиз, с чем это может быть связано )))

    ОтветитьУдалить
  10. А кто нить пробовал сделать, чтобы при разрыве связи он сам восстанавливал DDE экспорт ?))

    ОтветитьУдалить
  11. Привет,

    Странная ошибка, у меня такой никогда не возникало. А какая версия терминала установлена?
    А вот обработку разрыва связи нужно делать не в этом маленьком коде. Вам необходимо повесить слушателя на событие разрыва связи в trans2quik.dll и в случае если произошел разрыв нужно перезапустить dde.

    ОтветитьУдалить
    Ответы
    1. Спасибо, уже решилась ..)
      Как выяснилось дело не в самом Postmessage а в параметрах которые туда поступают. Точно уже не помню, какие точно, помню только лишь что я раскомментил строки
      FindMenuItemByPart(exportMenu, _stopExportMenuItem);
      Может действительно в разных версиях квика разные номера меню ))

      А Вы не реализовывали решение, чтобы он сам восстанавливал DDE экспорт при его разрыве ?)

      Удалить
  12. Здравствуйте!
    Подскажите пожалуйста, а как сделать автозапуск экспорта только лишь таблицы текущих параметров?

    ОтветитьУдалить
  13. Добрый день,

    К сожалению данный код настолько туп, что умеет делать только одну операцию - это имитировать нажатие кнопки меню квика "Экспорт всех таблиц". Чтобы сделать экспорт только таблицы текущий параметров предлагаю вам настроить на экспорт только данную таблицу (хотя конечно я абсолютно не знаю как вы используете остальные таблицы и возможно данный вариант вам не подходит).

    ОтветитьУдалить

Помощь в автоматизации Вашей торговли (просмотр условий)