Профиль должен быть заполнен на русском языке кириллицей. Заполнение профиля заведомо ложными или некорректными данными - причина возможного отказа в регистрации на форуме.

WinCC, C-Script, db.h - запрос к БД из С-скрипта

Аватара пользователя

Автор темы
Exactamente
частый гость
частый гость
Сообщения: 409
Зарегистрирован: 20 ноя 2012, 12:45
Ф.И.О.: :.О.N.Ф
Благодарил (а): 3 раза
Поблагодарили: 3 раза

WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение Exactamente » 20 янв 2015, 13:39

Как работать с функцией DBGetRecordSQL? Что за параметр LPVOID lpUser? В каком виде возвращается результат запроса?
«Сразу видно внимание к каждой мелочи, неиспорченным не осталось ничто».


LexSL
здесь недавно
здесь недавно
Сообщения: 42
Зарегистрирован: 16 дек 2011, 14:13
Ф.И.О.: Михайлов Алексей
Поблагодарили: 1 раз

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение LexSL » 20 янв 2015, 16:03

В ODK хелпе написано, что функция перечисляет записи, содержащиеся в таблице...
Посмотрел пример DB01.cpp в сэмплах ODK - там расписана работа с таблицей MCPTVARIABLEDESC в базе данных WinCC. По опыту знаю, что там находятся данные о тегах в проекте WinCC...сам недавно с ней работал, но только не через ODK, а напрямую SQL запросами.
Вот накидал класс работы с ней (я сам пишу все на C#, поэтому вызовы из библиотек приходится маршалить), по комментариям в коде можно разобраться
что делать нужно, если будут вопросы - обращайтесь, поясню.
файл db.cs - класс, отвечающий за вызов из библиотеки функции и объявление делегата коллбека

db.cs

Код: Выделить всё

using System;
using System.Runtime.InteropServices;

namespace DBWinCC
{
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
    public class CMN_ERROR
    {
        [MarshalAs(UnmanagedType.U4)]
        public UInt32 dwError1;
        public UInt32 dwError2;
        public UInt32 dwError3;
        public UInt32 dwError4;
        public UInt32 dwError5;

        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 512)]
        public String szErrorText;
    }

    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate bool DB_ENUM_RECORDS_CALLBACK(uint hDB, IntPtr hRecord, IntPtr lpUser);

    public class DatabaseWinCC
    {
        [DllImport("db.dll", CharSet = CharSet.Ansi, EntryPoint = "DBGetRecordSQL", SetLastError = true, CallingConvention = CallingConvention.Winapi)]
        internal static extern bool DBGetRecordSQL(ref UInt32 hDB,
           [In] DB_ENUM_RECORDS_CALLBACK lpfnCallback, [In] [MarshalAs(UnmanagedType.LPTStr)] string lpSQLStatement,
            [In] IntPtr lpvUser, [In, Out] [MarshalAs(UnmanagedType.LPStruct)] CMN_ERROR lpdmError);
    }
}


Далее, например создаем WindowsFormsApplication приложение, добавляем файл db.cs

Form1.cs

Код: Выделить всё

   public partial class Form1 : Form
    {
        List<string> userData = new List<string>();
        private DB_ENUM_RECORDS_CALLBACK callback;
      // делегат - ссылка на метод callback - обратного вызова, данную переменную нужно объявлять глобальной! а не локальной
      
        uint hDB = 0; // идентификатор полученный в результате DBOpen

        public Form1()
        {
            InitializeComponent();
           
         //тут DBConnect(.., DSN, ...)
         //потом DBOpen(.., HDB, ...)
            callback = CallbackProc;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            GCHandle handle1 = GCHandle.Alloc(userData);
            IntPtr userDataPtr =  GCHandle.ToIntPtr(handle1);
           
            CMN_ERROR error = new CMN_ERROR();
            bool result = DatabaseWinCC.DBGetRecordSQL(ref hDB, callback, null, userDataPtr, error);
            handle1.Free();
        }

        private bool CallbackProc(uint hDB, IntPtr hRecord, IntPtr lpUser)
        {
            //здесь по идее должны нам отдаваться данные в результате вызова менеджером WinCC нашего callback
            // это по видимому hRecord , вот только какого это типа данные - надо разобраться
            //int FieldNum = 5; //столбец 5 к примеру
            //string Data = "";
            //CMN_ERROR error = new CMN_ERROR();
            //DBGetFieldData(hrecord, FieldNum, Data, error);
         //Data - данные из таблицы

            //здесь наш лист получаем и работаем с ним - напрямую обращаться к нему нельзя, иначе
            //вывалится ошибка доступа из другого потока , не принадлежащего нам (поток WinCC)
            GCHandle handle2 = GCHandle.FromIntPtr(lpUser);
            List<string> userData = handle2.Target as List<string>;
            userData.Add(string.Format("{0} - WinCC прислал данные", DateTime.Now.ToShortTimeString()));
            handle2.Free();
            return true;
        }
    }


Вкратце, LPVOID lpUser - "пользовательский" объект, который передаешь в функцию вызова (DBGetRecordSQL) и получаешь обратно в callback
Для чего это нужно - для того чтобы не возникло ошибки доступа к объекту не в том потоке, в котором он был создан.
В коде это прокомментировано.

Аватара пользователя

Автор темы
Exactamente
частый гость
частый гость
Сообщения: 409
Зарегистрирован: 20 ноя 2012, 12:45
Ф.И.О.: :.О.N.Ф
Благодарил (а): 3 раза
Поблагодарили: 3 раза

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение Exactamente » 23 янв 2015, 17:14

Не, использовать эту беду нужно в скрипте самой WinCC. С вопросом LPVOID lpUser прояснилось, и, кстати, это не обязательно целое число (у вас IntPtr), туда очень удобно структуру положить.

Тут ещё нюанс выяснился, что эта db.dll внезапно не все запросы хавает, на особо извращённые отвечает "enum first failed". Плохая длл-ка. Вы не знаете, как ещё можно читать из базы C-Sript'ами?
«Сразу видно внимание к каждой мелочи, неиспорченным не осталось ничто».


LexSL
здесь недавно
здесь недавно
Сообщения: 42
Зарегистрирован: 16 дек 2011, 14:13
Ф.И.О.: Михайлов Алексей
Поблагодарили: 1 раз

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение LexSL » 27 янв 2015, 09:16

Здравствуйте!

По поводу LPVOID lpUser: Вы правы, туда можно любой свой объект положить, а IntPtr это не целое число, а "указатель" в C#, в своем примере я положил туда объект List...ну да ладно...
Приведу пример еще один с коллбеком, принцип похожий, может что и получится:
метод SaveLimits - сохраняет в файл значения переменных, список переменных задается фильтром

Код: Выделить всё

#include "apdefap.h"
#include "dmclient.h"

//тут объявлен внешний метод
extern BOOL SaveLimitsEnumVariables(LPDM_VARKEY key, LPVOID lpvUser);

void SaveLimits()
{
#pragma code ("User32.dll")
int WINAPI MessageBoxA(DWORD handle, char* text, char* title, DWORD flags);
#pragma code ()

  char projFile[_MAX_PATH];
  char limitFile[_MAX_PATH];
  char drive[_MAX_DRIVE];
  char dir[_MAX_DIR];
  char fname[_MAX_FNAME];
  char ext[_MAX_EXT];

  DM_VARFILTER flt;
  CMN_ERROR err;
  FILE* file;
  DWORD dwFilterTypes[3];


  memset(projFile, 0, _MAX_PATH);
  memset(limitFile, 0, _MAX_PATH);
  memset(&err,0,sizeof(err));
  memset(&flt, 0, sizeof(DM_VARFILTER));
  //фильтр по имени и типу переменной
flt.dwFlags = DM_VARFILTER_NAME | DM_VARFILTER_TYPE;
  dwFilterTypes[0] = DM_VARTYPE_FLOAT;
    flt.pdwTypes = dwFilterTypes;
  flt.dwNumTypes = 1;
  //фильтр по имени тега, который начинается со слова Limit
  flt.lpszName = "Limit*";

  if (!DMGetRuntimeProject(projFile, sizeof(projFile), &err)) {
    MessageBoxA(0, err.szErrorText, "DMGetRuntimeProject", MB_OK);
    return;
  }

  _splitpath(projFile, drive, dir, fname, ext);
  _makepath(limitFile, drive, dir, "Limits", "txt");
  file = fopen(limitFile,"w");
  if (!file) {
    //MessageBoxA(0, "Error Opening 'Limits.txt' for writing", "", MB_OK);
    return;
  }
     // вызов перечисления переменных с коллбеком
  if (!DMEnumVariables(projFile, &flt, SaveLimitsEnumVariables, file, &err)) {
    //MessageBoxA(0, err.szErrorText, "DMEnumVariables", MB_OK);
  }
  fclose(file);
}


Сам коллбек

Код: Выделить всё

#include "apdefap.h"
#include "dmclient.h"

BOOL SaveLimitsEnumVariables(LPDM_VARKEY key, LPVOID lpvUser)
{
  FILE* f = (FILE*)lpvUser;
  float data = GetTagFloat(key->szName);
  fprintf(f,"%s\t%f\n", key->szName, data);
  return TRUE;
}


Exactamente писал(а):Вы не знаете, как ещё можно читать из базы C-Sript'ами?

Вы имеете в виду из любой базы данных SQL? или конкретно WinCCевой базы?
Опишите задачу, а то я не знаю даже чем помочь

Аватара пользователя

Автор темы
Exactamente
частый гость
частый гость
Сообщения: 409
Зарегистрирован: 20 ноя 2012, 12:45
Ф.И.О.: :.О.N.Ф
Благодарил (а): 3 раза
Поблагодарили: 3 раза

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение Exactamente » 30 янв 2015, 00:05

С дллкой разобрался, дллка хорошая, запрос был плохой. Всё работает. Спасибо за помощь.

А вы с OPC .NET API от OPC Foundation случаем не работали? Решил поковырять, а инфы в гугле днём с огнём.
«Сразу видно внимание к каждой мелочи, неиспорченным не осталось ничто».


LexSL
здесь недавно
здесь недавно
Сообщения: 42
Зарегистрирован: 16 дек 2011, 14:13
Ф.И.О.: Михайлов Алексей
Поблагодарили: 1 раз

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение LexSL » 30 янв 2015, 12:08

C OPC работал, используя OPCEnum и opcproxy.dll, начал после прочтения очень хорошей книжки Федоренко Дениса "Программирование OPC клиентов на C++ и C#. Часть 1. OPC DA". Подробно разжевано..
Еще есть OPC Automation (Wrapper) (opcdaauto.dll) - обертка над "классическим" OPC интерфейсами, предназначался в основном для Visual Basic, на opcfoundation.org написано что заменен как раз на OPC .NET API. На нем очень просто написать клиента.
OPC .NET API - эхххх, доступ только для корпоративных клиентов, видимо тех, кто входит в группу и вносит "членские" взносы )).

graybox opc toolkit - пробовал как то давно, получалось и сервер написать и клиент, только он платный)

Аватара пользователя

Автор темы
Exactamente
частый гость
частый гость
Сообщения: 409
Зарегистрирован: 20 ноя 2012, 12:45
Ф.И.О.: :.О.N.Ф
Благодарил (а): 3 раза
Поблагодарили: 3 раза

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение Exactamente » 30 янв 2015, 12:48

Не-не, готовые тулкиты и SDK не катят, и платно, и вообще неспортивно)

Да не, вроде, OPC .NET API бесплатный и должен ставиться вместе с OPC Redistributables. А вот исходники и примеры - для корпоративных. Но на одном общеизвестном российском торрент-трекере есть раздача со всем этим добром, там и исходники, и примеры для HDA, UA и прочих есть, а для DA почему-то нету.

>Программирование OPC клиентов на C++ и C#. Часть 1. OPC DA
О! Это хороший документ, большое спасибо, изучу.
«Сразу видно внимание к каждой мелочи, неиспорченным не осталось ничто».


LexSL
здесь недавно
здесь недавно
Сообщения: 42
Зарегистрирован: 16 дек 2011, 14:13
Ф.И.О.: Михайлов Алексей
Поблагодарили: 1 раз

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение LexSL » 30 янв 2015, 14:28

Exactamente писал(а): примеры для HDA, UA и прочих есть


давно хочу разобраться с UA, очень заманчиво отвязаться наконец от DCOM и плясок с бубном...но для этого надо хотя бы владеть SOAP и WCF

Аватара пользователя

Автор темы
Exactamente
частый гость
частый гость
Сообщения: 409
Зарегистрирован: 20 ноя 2012, 12:45
Ф.И.О.: :.О.N.Ф
Благодарил (а): 3 раза
Поблагодарили: 3 раза

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение Exactamente » 30 янв 2015, 16:28

Но всё вокруг работает через DA, так что я пока на него ориентируюсь.


>заменен как раз на OPC .NET API. На нем очень просто написать клиента

И вот нифига. Я уже даже сэмплы нашёл, но они на WinForms, там ад и содомия =(
«Сразу видно внимание к каждой мелочи, неиспорченным не осталось ничто».


shiftyhead
здесь недавно
здесь недавно
Сообщения: 3
Зарегистрирован: 25 ноя 2015, 15:36
Ф.И.О.: Спирин Дмитрий

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение shiftyhead » 25 ноя 2015, 15:38

Добрый день.
В описании к функции DBGetRecordSQL написано: "lpfnCallback - Pointer to your callback function which is called for each data record meeting the condition of lpSQLStatement.". Т.е. вызов callback-функции должен происходить для каждой записи, удовлетворяющей условию в поле lpSQLStatement.
У меня такого не получилось, функция вызывается только один раз.
Кто-нибудь сталкивался?


shiftyhead
здесь недавно
здесь недавно
Сообщения: 3
Зарегистрирован: 25 ноя 2015, 15:36
Ф.И.О.: Спирин Дмитрий

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение shiftyhead » 30 ноя 2015, 10:30

вот мой код:

Код: Выделить всё

#include "apdefap.h"
void OnClick(char* lpszPictureName, char* lpszObjectName, char* lpszPropertyName)
{
// WINCC:TAGNAME_SECTION_START
#define DSN "@DatasourceNameRT"
#define Table "AlgActivatedData"
// WINCC:TAGNAME_SECTION_END

CMN_ERROR Error;
HANDLE hDSN = NULL;
HANDLE hDB = NULL;
DWORD Num;
int ID;
char sDSN[MAX_PATH]="";

strcpy(sDSN,GetTagChar(DSN));

if (!DBConnect(&hDSN,sDSN,&Error))
{
   printf("Error in DBConnect: E1= 0x%04x ; E2= 0x%04x ; %s \r\n",Error.dwError1, Error.dwError2, Error.szErrorText);
   DBDisConnect ( hDSN, 0, &Error);
   return;
}

if (!DBOpen (hDSN, &hDB, Table, 0, &Error))
{
   printf("Error in DBOpen: E1= 0x%04x ; E2= 0x%04x ; %s \r\n",Error.dwError1, Error.dwError2, Error.szErrorText);
}

DBGetNumRecords(hDB,&Num,&Error);
printf("records = %d\r\n",Num);

DBGetRecordSQL(hDB, New_Function,"ID>0",&ID,&Error);
printf("IDex = %d \r\n",ID);

if (!DBDisConnect ( hDSN, 0, &Error))
{
   printf("Error in DBConnect: E1= 0x%04x ; E2= 0x%04x ; %s \r\n",Error.dwError1, Error.dwError2, Error.szErrorText);
}


callback-функция:

Код: Выделить всё

#pragma code ("db.dll");
#include "db.h";
#pragma code();
BOOL New_Function(HANDLE hDB,HRECORD hRecord, int *lpUser)
{
CMN_ERROR Error;
int ID;

if (!DBGetIntFieldData (hRecord, 1, &ID, &Error))
{
   printf("Error in DBGetIntFieldData: E1= 0x%04x ; E2= 0x%04x ; %s \r\n",Error.dwError1, Error.dwError2, Error.szErrorText);
}
memcpy(lpUser,&ID,sizeof(int));
printf("IDin = %d \r\n",ID);

return 0;
}


Output.png


DB.png
У вас нет необходимых прав для просмотра вложений в этом сообщении.


shiftyhead
здесь недавно
здесь недавно
Сообщения: 3
Зарегистрирован: 25 ноя 2015, 15:36
Ф.И.О.: Спирин Дмитрий

Re: WinCC, C-Script, db.h - запрос к БД из С-скрипта

Сообщение shiftyhead » 01 дек 2015, 15:58

была ошибка в callback-функции.
нужно "return 0" заменить на "return 1"


Вернуться в «WinCC»



Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 0 гостей