Оригинальный DVD-ROM: eXeL@B DVD !
eXeL@B ВИДЕОКУРС !

ВИДЕОКУРС ВЗЛОМ
выпущен 8 мая!


УЗНАТЬ БОЛЬШЕ >>
Домой | Статьи | RAR-cтатьи | FAQ | Форум | Скачать | Видеокурс
Новичку | Ссылки | Программирование | Интервью | Архив | Связь

ПРОГРАММИРОВАНИЕ НА C и С++



Программисты долго мучаются с кодом прогаммы, изучают С++, WinAPI функции, MSDN. Потом пишут банальную систему защиты или навешивают банальный протектор, а крэкеры и реверсеры справляются с такой защитой за 5 минут. В итоге, продажи программы почти нулевые. Чтобы такого не допустить, тут самому надо немного поднабрать опыта отладки, реверсинга, тот же отладчик Ollydbg изучить или дизассемблер IDA Pro. Но где искать по крохам эти знания? Нет, конечно можно годами "методом тыка" разбираться, но куда быстрее видеокурс специальный посмотреть. Вот тут он есть: ссылка. Автор курса с большим опытом и объясняет понятно, я из этого курса много узнал про то как работает компьютер, процессор, про инструменты специальные и как с ними работать. Мои коллеги программисты на работе ничего такого и не знают, теперь я им нос утру.

Работа с 1C Предприятием из Visual C++



Вообще то это не FAQ - овский вопрос - его задают нечасто, но пока у нас не открылся раздел со статьями - он будет лежать тут.

Предисловие

В данной статье показано, как можно работать с 1С Предприятием из С++ с помощью OLE DB.
Так же она будет интересна тем, кто не пользуется C++, но хочет узнать подробности "а как оно устроено внутри 1С".

В данной статье речь пойдет об 1С Предприятии версии 7.7. Полагаю, что в версии 8 мало что изменилось.

Предполагается, что читатель хотя бы чуть-чуть знаком с 1С Предприятием. Так же предполагается, что вы изучали официальное руководство 1С по вопросам OLE DB (часть вторая описания языка), но осталься недоволен - т.к. исходники там приведены для Visual Basic, а вам ну очень нужен именно C++ (кстати - не обязательно Visual - главное что бы в вашем компиляторе была возможность работать с OLE).




1С Предприятие предоставляем пользователям механизм OLE DB. Если Вам вдруг захотелось использовать какие либо данные из 1С Предприятия в вашей программе – вы можете воспользоваться этим механизмом. Совсем просто это делается в таких языках, как Visual Basic или Delphi. В них вся работа с OLE-интерфейсами замаскирована от программиста насколько возможно. Это, с одной стороны, очень удобно, но с другой стороны – у нас всегда есть возможность повысить производительность путём использования С++. Он от своих адептов ничего не маскирует, это позволяет кое где сэкономить лишний байт или миллисекунду, но превращает работу с OLE DB в ад. Дело в том, что 1С не предоставляет для своих пользователей библиотеки импорта (*.tlb), поэтому единственный способ работы (если не использовать какие либо обёртки) – это позднее связывание. В Сети полно информации, как работать с 1С Предприятием из Delphi или Visual Basic, но практически совсем нет примеров с Visual C++.


Эта статья призвана заполнить этот пробел. Здесь Вы найдете кусочки рабочего кода, который Вы можете дописывать и модифицировать по своему усмотрению. По себе знаю, что порою достаточно подсмотреть одним глазком, что бы стало ясно, что к чему. Кроме того, я расскажу про некоторые «тонкие» моменты работы с 1С, которые мягко обойдены в официальных изданиях, имеющих красивый красно-желтый цвет.


Наиболее разумным и удобным будет следующее решение: мы организуем всю необходимую работу с 1С в виде отдельной экспортируемой функции глобального модуля (я надеюсь, Вы уже научились открывать в 1С Глобальный модуль и добавлять в него экспортируемые функции). Мы будем вызывать нужную функцию глобального модуля, которая будет возвращать нужное значение. Для многих практических приложений этого вполне достаточно.


#include <objbase.h>
#include <comdef.h>

//для начала инициализируем COM
HRESULT hr = CoInitialize(NULL);
if(FAILED(hr))
{
  AfxMessageBox("Невозможно инициализировать COM!");
  return FALSE;
}

/*
Прежде всего, нам необходимо получить
ID сервера OLE Automation 1С Предприятия.
*/
CLSID   cls77;

/*
Используем универсальный ключ 1С Предприятия 

Подробнее см. КЖК – если у Вас установлена единственная 
версия 1С – то этого достаточно, если несколько разных, 
то нужно загрузить нужный. Вот краткий список возможных значений:

V1CEnterprise.Application - версия независимый ключ;
V77.Application - версия зависимый ключ;
V77S.Application - версия зависимый ключ, SQL-версия;
V77L.Application - версия зависимый ключ, локальная-версия;
V77M.Application - версия зависимый ключ, сетевая-версия.
*/

hr = CLSIDFromProgID(L"V77.Application", &cls77); 
if(FAILED(hr))
{
  AfxMessageBox("Переустановите 1С Предприятие!");
  CoUninitialize();
  return FALSE;
}

//основной интерфейс, за который мы будем "дёргать"
IDispatch *pv77;

/*
Создаём инстанцию 1С Предприятия. 

CLSCTX_LOCAL_SERVER – это значит, что 1С Предприятие 
будет запущено в виде отдельного процесса – по другому оно не умеет.
*/

hr = CoCreateInstance(cls77, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void**)&pv77);

if(FAILED(hr)  ||  !pv77)
{
  AfxMessageBox("Невозможно инициализировать интерфейс 1С Предприятия"); 
  CoUninitialize();
  return FALSE;
}

/*
пока всё было понятно и очевидно, дальше начинаются сложности… 
1С предоставляет для запуска приложения функцию Initialize. 
Вызов этой функции выглядит в VB элементарно

V7.Initialize(V7.RMTrade,"D:\1C\ТипаБаза /N"+Пользователь ,"NO_SPLASH_SHOW");

- мы практически забываем, что же происходит внутри.
Но занимающиеся С++ люди хладнокровные, трудностей не боятся. 
Во-первых: мы должны помнить, что аргументы необходимо заталкивать задом наперёд…
Во-вторых: мы должны помнить, что RMTrade - это доже IDispatch интерфейс.. и его сперва нужно получить.
*/

VARIANT       vRet;
DISPID dispIDRmTrade, dispIDInitialize;
DISPPARAMS args = {0, 0, 0, 0};
VARIANT vars[3];  // Параметры для вызова Initialize

//Мы получим IDispatch интерфейс от RMTrade сразу в vars[2]

BSTR rmTrade = L"RMTrade";

hr = pv77->GetIDsOfNames(IID_NULL, &rmTrade, 1, 0, &dispIDRmTrade);

if (FAILED(hr))
{
  AfxMessageBox("Невозможно получить ID от RMTrade");
  if (pv77)
    pv77->Release();
  CoUninitialize();
  return FALSE;
}

hr = pv77->Invoke(dispIDRmTrade, IID_NULL, 0, DISPATCH_PROPERTYGET, &args,
      &vars[2], NULL, NULL);

if (FAILED(hr))
{
  AfxMessageBox("Невозможно получить интерфейс от RMTrade");
  if (pv77)
    pv77->Release();
  CoUninitialize();
  return FALSE;
}

//нужно получить ID для Initialize(..);
BSTR init = L"Initialize";
hr = pv77->GetIDsOfNames(IID_NULL, &init, 1, 0, &dispIDInitialize);
if (FAILED(hr))
{
  AfxMessageBox ("Не удалось получить ID от Initialize")
  if (pv77)
    pv77->Release();
  CoUninitialize();
  return FALSE;
}

/*
а теперь – вызвать этот самый Initialize(..), 
но сперва необходимо заполнить массив аргументов функции
*/

args.cArgs = 3;
args.rgvarg = vars;
vars[0] = _variant_t("NO_SPLASH_SHOW");
vars[1] = _variant_t("/D D:\1S /N Denis /P Denis ");

/*
vars[2] – у нас уже есть, мы его получили при запросе 
интерфейса RMTrade в момент предыдущего Invoke
*/

hr = pv77->Invoke(dispIDInitialize, IID_NULL, 0, DISPATCH_PROPERTYGET, &args, 
   &vRet, NULL, NULL);

if(FAILED(hr) ||  (vRet.vt ==  VT_BOOL && vRet.bstrVal == 0x00))
{
  AfxMessageBox("Невозможно запустить 1С Предприятие");
}



надо заметить, что с этим последним Invoke нужно быть вообще осторожнее. У меня было такое, что даже при правильной командной строке вызов не удавался. При этом должно появиться окно, предлагающее задать имя базы, имя пользователя и т.п., возможно, это такая особенность дизайна 1С. В конце концов пришлось удалить старого пользователя, и создать нового. Осторожнее при разработке сервисов! Это окно будет появляеться на виртуальном десктопе, и Вы просто ничего не увидите (если конечно, не поставите галочку «взаимодействовать с рабочим столом»).

Есть несколько причин, по которым 1С может не быть запущен.
1) Вы пользуетесь локальной версией и в памяти уже висит какое то приложение с 1С Предприятием (в документации об этом сказано очень поверхностно – но поверьте на слово или сами поставьте эксперимент);

2) Произошла ошибка при компиляции глобального модуля (которая происходит каждый раз при запуске 1С), т.о. вам сначала желательно в конфигураторе поправить ошибки 1С Предприятия;

3) Произошло нарушение индексации таблиц из за некорректного завершения работы базы. Т.о. Вам сначала нужно запустить 1С Предприятие в монопольном режиме, что бы восстановить индексацию.
Кстати – в локальной версии тоже есть монопольный режим. Только вот зачем он нужен.. для меня загадка. Напомню, что командная строка для запуска в монопольном режиме должна заканчиваться /M.

теперь у нас есть запущенное приложение 1С Предприятия, можно к нему обращаться с помощью спецфункций: EvalExpr, CreateObject, ExecuteBatch, подробнее об этом – в официальном руководстве.

Экспортируемая функция глобального модуля, которую мы будем вызывать, будет называться ПриветМир, на языке 1С она выглядит следующим образом:

Функция ПриветМир (Слово) Экспорт
  ВозврСтрока = "Привет мир: " + Слово + "  спасибо за весточку в красно-желтую темницу!";   
  Возврат ВозврСтрока;
КонецФункции


У нас есть несколько способов вызвать эту функцию.

Вариант 1. Выполнить её с помощью функции EvalExpr, которая предназначена исполнения выражений на языке 1С (ExecuteBatch – тоже вариант).

Вариант 2. Взять ID непосредственно функции ПриветМир и вызвать сразу её. В 1С Предприятии нет никакой чёрной магии – она насквозь пропитана технологией OLE, поэтому у каждой функции есть ID. Тут я не уверен насчёт SQL версии, но использовать приведённый здесь код можно и с этой версией тоже.

Приступим!

1 Вариант.

VARIANT vRetEvalExpr;
UINT nArgErrRetEvalExpr;
DISPPARAMS argsRetEvalExpr = {0, 0, 0, 0};
VARIANT varsRetEvalExpr [1];  

///.......///

argsRetEvalExpr.cArgs = 1;
argsRetEvalExpr.rgvarg = varsRetEvalExpr;

BSTR                bstrEvalExpr      = L"EvalExpr";
DISPID              dispIDEvalExpr;
hr = pv77->GetIDsOfNames(IID_NULL, &bstrEvalExpr, 1, 0, &dispIDEvalExpr);    

if (FAILED(hr))
{
  AfxMessageBox("Невозможно получить ID от EvalExpr");
  if (pv77)
    pv77->Release();
  CoUninitialize();      
}

varsRetEvalExpr [0] = _variant_t("ПриветМир(\"Весточка 1С Предприятию\")");

/*
После этого Invoke можете проверить переменную vRetEvalExpr, 
в ней должна располагаться строка-ответ от 1С Предприятия.
*/
hr = pv77->Invoke(dispIDEvalExpr, IID_NULL, 0, DISPATCH_PROPERTYGET, 
  &argsRetEvalExpr, &vRetEvalExpr, NULL, NULL);



Способ с EvalExpr плох.. Почему? Потому что если бы в строке «Весточка 1С Предприятию» затесалась хотя бы одна кавычка, вызов произвести не удалось бы. Это накладывает определённые ограничения на наши действия. Поэтому, для серьёзных целей такой метод не подходит, к тому же – он не самый оптимальный по производительности, потому что сначала вызывается функция EvalExpr, которая потом вызывает функцию ПриветМир. «Чем больше посредников, тем больше в вине воды» (Copyright кто то из древних), поэтому мы не будем пользоваться функцией EvalExpr, а будем вызвать функцию ПриветМир непосредственно.

BSTR bstrHelloWorld      = L"ПриветМир";
DISPID dispIDHelloWorld;
hr = pv77->GetIDsOfNames(IID_NULL, &bstrHelloWorld, 1, 0, &dispIDHelloWorld);    

if (FAILED(hr))
{
  AfxMessageBox("Сначала добавьте в глобальный модуль фукнцию ПриветМир");
  if (pv77)
    pv77->Release();
  CoUninitialize();      
}

VARIANT vRetHelloWorld;
DISPPARAMS argsHelloWorld = {0, 0, 0, 0};
VARIANT varsHelloWorld[1];  

argsHelloWorld.cArgs = 1;
argsHelloWorld.rgvarg = varsHelloWorld;

varsK[0] = _variant_t("Привет в жёлто красную темницу");               

hr = pv77->Invoke(dispIDHelloWorld, IID_NULL, 0, DISPATCH_METHOD, &argsHelloWorld,
  &vRetHelloWorld, NULL, NULL);



Обратите внимание что при вызове Invoke используется DISPATCH_METHOD а не DISPATCH_PROPERTYGET.

Вот в общем то почти всё. Хочу добавить, что можно продвинуться и дальше – функция CreateObject() может создавать объекты (например, справочник товаров), при этом вы получаете IDispatch интерфейс, у которого есть все методы, которые Вы использовали в 1С для работы со справочником товаров. Их можно использовать, но думаю, что дойдя до этого места, Вы захотите написать какую ни будь удобную обёртку (может быть даже tlb-шку?) над всем этим, ибо вызывать каждый раз Invoke вручную уже не останется ни сил, ни терпения. Но это – тема другой статьи.


<< ВЕРНУТЬСЯ В ПОДРАЗДЕЛ

<< ВЕРНУТЬСЯ В ОГЛАВЛЕНИЕ




Материалы находятся на сайте https://exelab.ru/pro/



Оригинальный DVD-ROM: eXeL@B DVD !


Вы находитесь на EXELAB.rU
Проект ReactOS