ДДЕ из Квика (C# Sample incl)

  • Автор темы AleksHyp
  • Дата начала

AleksHyp

New member
Частенько меня спрашивают на эту тему. User32.dll и чуть-чуть соображалки для представления логики работы.
Как оказалось, в моём датасервере много "лишнего" для разъяснения вопрошающим принципа работы, пришлось изрядно покромсать и переписать. На реальном ДДЕ-обмене не проверял, но если что - спрашивайте.
Давненько не заглядывал в код, некоторые вещи сейчас сделал бы по другому :)

Код:
public partial class Form1 : Form
    {
        public struct Cell
        {
            public short t;
            public string s;
            public double d;
        }
        [DllImport("user32.dll", EntryPoint = "DdeInitialize", CharSet = CharSet.Ansi)]
        static extern int DdeInitialize(ref int pidInst, DDECallBackDelegateQK pfnCallback, int afCmd, int ulRes);
        [DllImport("user32.dll", EntryPoint = "DdeCreateStringHandle", CharSet = CharSet.Ansi)]
        static extern IntPtr DdeCreateStringHandle(int idInst, string psz, int iCodePage);
        [DllImport("user32.dll", EntryPoint = "DdeNameService", CharSet = CharSet.Ansi)]
        static extern IntPtr DdeNameService(int idInst, IntPtr hsz1, IntPtr hsz2, int afCmd);
        [DllImport("user32.dll", EntryPoint = "DdeUninitialize", CharSet = CharSet.Ansi)]
        static extern bool DdeUninitialize(int idInst);
        [DllImport("user32.dll", EntryPoint = "DdeQueryString", CharSet = CharSet.Ansi)]
        static extern int DdeQueryString(int idInst, IntPtr hsz, byte[] psz, int cchMax, int iCodePage);
        [DllImport("user32.dll", EntryPoint = "DdeGetData", CharSet = CharSet.Ansi)]
        static extern int DdeGetData(IntPtr hData, [Out] byte[] pDst, int cbMax, int cbOff);
        [DllImport("user32.dll", EntryPoint = "DdeConnect", CharSet = CharSet.Ansi)]
        static extern IntPtr DdeConnect(int idInst, IntPtr hszService, IntPtr hszTopic, IntPtr pCC);
        [DllImport("user32.dll", EntryPoint = "DdeFreeStringHandle", CharSet = CharSet.Ansi)]
        static extern bool DdeFreeStringHandle(int idInst, IntPtr hsz);
        [DllImport("user32.dll", EntryPoint = "DdeGetLastError", CharSet = CharSet.Ansi)]
        static extern int DdeGetLastError(int idInst);

        internal delegate IntPtr DDECallBackDelegateQK(int uType, int uFmt, IntPtr hConv, IntPtr hsz1, IntPtr hsz2, IntPtr hData, IntPtr dwData1, IntPtr dwData2);

        public const int DDE_FACK = unchecked((int)0x8000);
        public const int CP_WINANSI = 1004;
        public const int DNS_REGISTER = unchecked((int)0x0001);
        public const int DNS_UNREGISTER = unchecked((int)0x0002);
        public const int XCLASS_BOOL = unchecked((int)0x1000);
        public const int XCLASS_DATA = unchecked((int)0x2000);
        public const int XCLASS_FLAGS = unchecked((int)0x4000);
        public const int XTYPF_NOBLOCK = unchecked((int)0x0002);
        public const int XTYP_CONNECT = unchecked((int)(0x0060 | XCLASS_BOOL | XTYPF_NOBLOCK));
        public const int XTYP_POKE = unchecked((int)(0x0090 | XCLASS_FLAGS));
        public const int XTYP_DISCONNECT = unchecked((int)(0x00C0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK));
        public const int XCLASS_NOTIFICATION = unchecked((int)0x8000);
        public const int XTYP_CONNECT_CONFIRM = unchecked((int)(0x0070 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK));
        public const int XTYP_REGISTER = unchecked((int)(0x00A0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK));
        public const int XTYP_UNREGISTER = unchecked((int)(0x00D0 | XCLASS_NOTIFICATION | XTYPF_NOBLOCK));

        IntPtr hzService, hszTopicTTP;
        public IntPtr hConvTTP;
        DDECallBackDelegateQK _DDECallbackQK;
        int idInstr;

        public Form1()
        {
            InitializeComponent();
        }
        private void Form1_Load(object sender, EventArgs e)
        {
            _DDECallbackQK = new DDECallBackDelegateQK(DDECallBackQK);
            int res = DdeInitialize(ref idInstr, _DDECallbackQK, 0, 0); //MF_POSTMSGS | MF_SENDMSGS, 0); 
            hzService = DdeCreateStringHandle(idInstr, "MyDDEServer", CP_WINANSI); // В Квике: DDE сервер - "MyDDEServer"
            hszTopicTTP = DdeCreateStringHandle(idInstr, ("[ExChange]TTP"), CP_WINANSI); // Рабочая книга - "ExChange", лист - "TTP"
            /// hszTopicTicks = DdeCreateStringHandle(idInstr, ("[ExChange]Ticks"), CP_WINANSI); // 
            if (DdeNameService(idInstr, hzService, IntPtr.Zero, DNS_REGISTER).ToInt32() != 1) throw new Exception("DDENameService fail");
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            DdeFreeStringHandle(idInstr, hszTopicTTP);
            IntPtr DDENS = DdeNameService(idInstr, hzService, IntPtr.Zero, DNS_UNREGISTER);
            DdeUninitialize(idInstr);
        }
        IntPtr DDECallBackQK(int uType, int uFmt, IntPtr hConv, IntPtr hsz1, IntPtr hsz2, IntPtr hData, IntPtr dwData1, IntPtr dwData2)
        {
            if (uType == XTYP_REGISTER)
            {
                return (IntPtr)DDE_FACK;
            }
            if (uType == XTYP_UNREGISTER)
            {
                return IntPtr.Zero;
            }
            if (uType == XTYP_CONNECT)
            {
                return (IntPtr)DDE_FACK;
            }
            if (uType == XTYP_CONNECT_CONFIRM)
            {
                if (hsz1 == hszTopicTTP)
                {
                    hConvTTP = hConv; 
                    return (IntPtr)DDE_FACK;
                }
                throw new Exception("UnkTable");
                //return IntPtr.Zero;
            }
            if (uType == XTYP_DISCONNECT)
            {
                if (hConv == hConvTTP)
                {
                    hConvTTP = IntPtr.Zero; 
                    return (IntPtr)DDE_FACK;
                }
                throw new Exception("UnkTable");
                //return IntPtr.Zero;
            }
            if (uType == XTYP_POKE)
            {
                if (hsz1 == hszTopicTTP) 
                {
                    // Это пример работы с ДДЕ. На самом деле добавляем в очередь, а отдельный поток изымает из очереди и обрабатывает, как следствие - масимально быстро освобождаем коллбэк
                    if (hConvTTP != hConv) throw new Exception("hConvTTP != hConv"); // на всякий случай
                    int iCount = DdeQueryString(idInstr, hsz2, null, 0, CP_WINANSI) + 1;
                    byte[] bf = new byte[iCount];
                    DdeQueryString(idInstr, hsz2, bf, iCount, CP_WINANSI);
                    //string s = ""; for (int i = 0; i < iCount - 1; i++) s = s + (char)bf[i];
                    string s = System.Text.Encoding.Default.GetString(bf, 0, iCount - 1);
                    int firstRow = int.Parse(s.Substring(1, s.IndexOf('C') - 1)) - 1;
                    iCount = DdeGetData(hData, null, 0, 0);
                    bf = new byte[iCount];
                    DdeGetData(hData, bf, iCount, 0);
                    short Cols, Rows;
                    Cell[,] Tbl = ParseXTbl(bf, out Cols, out Rows); // не самое красивое решение, предложения принимаются, приведение типов и парсить прямо тут - не предлагать

                    // Ну вот, имеем таблицу
                    for (int r = 0; r < Rows; r++)
                    {
                        // имеем как хотим :)
                        string Name = Tbl[r, 0].s;
                        double Cs = Tbl[r, 0].d;
                    }
                    return (IntPtr)DDE_FACK;
                }
                throw new Exception("ХЗ что за Tbl"); //return IntPtr.Zero;
                //return IntPtr.Zero;
            }
            //DF.TTP.DDELog.WriteLine(DF.NTP.Now.ToString() + " " + uType.ToString());
            return IntPtr.Zero;
        }
        public Cell[,] ParseXTbl(byte[] bf, out short Cols, out short Rows)
        {
            //byte cb = (byte)(bf[a] + bf[a + 1] * 256); a += 2;
            short cb = BitConverter.ToInt16(bf, 0);
            if (cb != 16) throw new Exception("<>16");
            int a = 2;
            //cb = (byte)(bf[a] + bf[a + 1] * 256); a += 2;
            //Rows = (Int16)(bf[a] + bf[a + 1] * 256); a += 2; 
            //Cols = (Int16)(bf[a] + bf[a + 1] * 256); a += 2;
            cb = BitConverter.ToInt16(bf, a); a += 2;
            Rows = BitConverter.ToInt16(bf, a); a += 2;
            Cols = BitConverter.ToInt16(bf, a); a += 2;
            Cell[,] Tbl = new Cell[Rows, Cols];
            //fixed (byte* ptr = bf)
            for (int R = 0; R < Rows; R++) for (int C = 0; C < Cols; )
                {
                    //byte Tp = (byte)(bf[a] + bf[a + 1] * 256); a += 2; 
                    //cb = (byte)(bf[a] + bf[a + 1] * 256); a += 2;
                    short Tp = BitConverter.ToInt16(bf, a); a += 2;
                    cb = BitConverter.ToInt16(bf, a); a += 2;
                    if (Tp == 1)
                    {
                        for (; cb > 0; cb -= 8, a += 8, C++)
                        {
                            Tbl[R, C].d = BitConverter.ToDouble(bf, a);
                            Tbl[R, C].t = Tp;
                            //Tbl[R, C].d = *((double*)(ptr + a)); Tbl[R, C].t = Tp;
                        }
                    }
                    else if (Tp == 2)
                    {
                        for (; cb > 0; cb--, C++)
                        {
                            byte cb1 = bf[a]; a++;
                            cb -= cb1;
                            Tbl[R, C].s = System.Text.Encoding.Default.GetString(bf, a, cb1); a += cb1;
                            //Tbl[R, C].s = "";
                            //for (; 0 < cb1; cb1--, a++) Tbl[R, C].s = Tbl[R, C].s + (char)bf[a];
                            Tbl[R, C].t = Tp;
                        }
                    }
                    else throw new Exception("UnkType");
                }
            
            return Tbl;
        }
    }
 
Последнее редактирование:

Fylhtq

New member
А где вы обрабатываете tdtBlank?
Насколько я понял квик такой тип не поддерживает, а передает пустые строки,
я их никак не могу отловить, просто приходит одна строка и все, а куда ее вставлять, в моей таблице,
ума не приложу?!?!
 
Your email address will not be publicly visible. We will only use it to contact you to confirm your post.
Сверху