Лоцман и Visual C++

Автор Хомутов, 08.09.08, 14:30:55

« назад - далее »

0 Пользователи и 1 гость просматривают эту тему.

Хомутов

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

AI

Еще раз отмечу, что не стоит из плагина напрямую работать с СП
А в принципе список серверов лежит в реестре: HKEY_CURRENT_USER\Software\Ascon\Loodsman

Хомутов

Спасибо.
На сколько я знаю, с помощью API Лоцмана нельзя выполнять многоуровневые (да и одноуровневые SQL запросы). А поиск с использованием стандартных средств - довольно длятельная процедура. Дабы избавить клиентские машины от непосредственного обращения к базе данных, написано серверное приложение для обработки таких дапросов, и помещено оно на тот же сервер, где Лоцман лежит.
Но это кажется уже совсем другая тема для разговора.

AI

Можно и запросы выполнять. Создаете в БД хранимую процедуру

CREATE PROCEDURE dbo.[rep_Query]
  (
    @objects varchar(8000),
    @params  text = null
  )
AS
  set nocount on
  exec (@params)
GO

grant execute on dbo.[rep_Query] to LoodsmanUsers
GO


Потом эту процедуру можно вызывать через метод GetReport и передавать произвольный запрос в качестве параметра. Вернется, соответственно результат запроса в виде DataSet

Хомутов

Большое спасибо.
Не нужно велосипеда изобретать. Просто нужно повнимательней API Лоцмана читать.

Хомутов

У меня новая проблема. Впринципе по этой же теме.
Проблема с использованием GetAttrImageValueById, GetAttrPlainTextValue и интерфейса IDataSet.
Впринципе эти функции работают нормально, по крайней мере никаких ошибок при отработке не возникает. Возвращают они набор данных (в моем случае в виде IDataSet). Как же все-таки прочитать значения из полей [_TEXT] и [_IMAGE].
Я так понимаю - это массив байтов, доступ к которому осуществляется с помощью CComSafeArray<BYTE>. Но у меня что-то получается массив нулевой длинны (совсем пустой)

AI

там действительно простой массив байт
а просто к char[] преобразовать не получится?

Хомутов

Что я могу сказать...
Простой текст в COM передается обычно не в формате char[], но в фоормате BSTR (собственно для этого и был придуман этот тип данных). В Лоцмане тоже для передачи текста используется BSTR.
В Ваш пример я внес изменения:
1. добавил директиву препроцессора для обработки HRESULT:
#define HR_CHECK(code) \
do \
{ \
HRESULT hr = code; \
if(FAILED(hr)) \
{ \
_com_error error(hr); \
::MessageBox(NULL,error.ErrorMessage(),"",MB_OK); \
return; \
} \
} \
while(0)

2. Длинную функцию для определения типа VARIANTа:
BSTR GetTypeString(VARTYPE type)
{
#define CASE_TYPE(T) \
case T: strType = _bstr_t(#T); break

_bstr_t strType;
switch(type)
{
CASE_TYPE(VT_EMPTY);
CASE_TYPE(VT_NULL);
CASE_TYPE(VT_I2);
CASE_TYPE(VT_I4);
CASE_TYPE(VT_R4);
CASE_TYPE(VT_R8);
CASE_TYPE(VT_CY);
CASE_TYPE(VT_DATE);
CASE_TYPE(VT_BSTR);
CASE_TYPE(VT_DISPATCH);
CASE_TYPE(VT_BOOL);
CASE_TYPE(VT_VARIANT);
CASE_TYPE(VT_UNKNOWN);
CASE_TYPE(VT_DECIMAL);
CASE_TYPE(VT_I1);
CASE_TYPE(VT_UI1);
CASE_TYPE(VT_UI2);
CASE_TYPE(VT_UI4);
CASE_TYPE(VT_I8);
CASE_TYPE(VT_UI8);
CASE_TYPE(VT_INT);
CASE_TYPE(VT_UINT);
CASE_TYPE(VT_VOID);
CASE_TYPE(VT_HRESULT);
CASE_TYPE(VT_PTR);
CASE_TYPE(VT_SAFEARRAY);
CASE_TYPE(VT_CARRAY);
CASE_TYPE(VT_USERDEFINED);
CASE_TYPE(VT_LPSTR);
CASE_TYPE(VT_LPWSTR);
CASE_TYPE(VT_FILETIME);
CASE_TYPE(VT_BLOB);
CASE_TYPE(VT_STREAM);
CASE_TYPE(VT_STORAGE);
CASE_TYPE(VT_STREAMED_OBJECT);
CASE_TYPE(VT_STORED_OBJECT);
CASE_TYPE(VT_BLOB_OBJECT);
CASE_TYPE(VT_CF);
CASE_TYPE(VT_CLSID);
CASE_TYPE(VT_VECTOR);
CASE_TYPE(VT_ARRAY);
CASE_TYPE(VT_BYREF);
CASE_TYPE(VT_RESERVED);
}
#undef CASE_TYPE
return strType;
}

3. А еще добавил третий пункт меню и функцию, которая ииследует поля набора данных, описывающего атрибут "Наименование":
__declspec(dllexport) void __stdcall GetAttrPlainTextValue(IPluginCall * IPC)
{
//Вариантный массив параметров
CComSafeArray<VARIANT> methodParams(5);
//...inIdVersion
methodParams[0] = 0;
//...stAttrName
methodParams[1] = L"Наименование";
//...inIdLink
methodParams[2] = 0;

//Получить идентификатор выбранного объекта
IPC->get_IdVersion(&methodParams[0].lVal);

//Преобразуем CComSafeArray в вариантный массив для передачи методу СП
//Получить результат - указатель на интерфейс набора данных
IDataSet * dataSet = 0;
HR_CHECK(IPC->GetDataSet(CComBSTR(L"GetAttrPlainTextValue"), CComVariant(methodParams), &dataSet));

_bstr_t name; //имя поля в наборе данных
CComVariant value; //значение поля в наборе данных
VARTYPE type; //тип значения поля
//попытаемся перебрать все поля
long cnt;
HR_CHECK(dataSet->get_FieldCount(&cnt));
for(int i=0;i<cnt;i++)
{
//получаем имя поля
HR_CHECK(dataSet->get_FieldName(i,name.GetAddress()));
// значение поля
HR_CHECK(dataSet->get_FieldValue(name,&value));
//начинаем формировать текст сообщения
name = _bstr_t("поле ") + name + _bstr_t(" имеет тип ");

//если тип - "сложный" (проверяем значение правого байта)
type = value.vt >> 8;
type <<= 8;
if(type)
name = name + GetTypeString(type) + _bstr_t(" + ");

//теперь левого байта
type = value.vt << 8;
type >>= 8;
name = name + GetTypeString(type);

//сообщаем что за тип
MessageBox(NULL,name,"",MB_OK);
}
}


Что из этого получилось...
Тип поля [_TEXT] - не BSTR, а SAFEARRAY c элементами типа BYTE (VT_UI1). Но почему-то (это легко проверить) длина массива равна нулю.

Но ведь это не так. В чем я заблуждаюсь? Помогите разобраться.

AI

Атрибут "Наименование" имеет тип "Строка", а не "Текст". Поэтому при попытке получить его значение через  GetAttrPlainTextValue возвращается пустой массив.
Значение атрибутов любого типа, кроме "Текст" и "Изображение", можно получить с помощью методов GetInfoAboutVersion и GetLinkAttributes

Хомутов

Спасибо еще раз.  :)
Надеюсь не последний...

YorikER

Попробую продолжить тему... Кто-нибудь пробовал из собственного приложения в VC++ подключиться к серверу приложений и использовать его методы... Если можно выставьте пример простого консольного приложения с подключением к серверу и получения например списка проектов в базе данных... Заранее благодарю...

Chaa

Без работы с датасетом все просто.
#include "stdafx.h"
#import "LoodsmanAppServerMain.tlb"

int _tmain(int argc, _TCHAR* argv[])
{
    ::CoInitialize(NULL);
    ::CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
        RPC_C_IMP_LEVEL_DELEGATE, NULL, 0, NULL);

    COSERVERINFO ServerInfo = {0};
    ServerInfo.pwszName = L"APPSRV";

    MULTI_QI MultiQi = {0};
    MultiQi.pIID = &__uuidof(LoodsmanServerApplication::IMainSystem);

    HRESULT Hr = ::CoCreateInstanceEx(__uuidof(LoodsmanServerApplication::MainSystem), NULL,
        CLSCTX_REMOTE_SERVER, &ServerInfo, 1, &MultiQi);
    if (Hr == S_OK)
    {
        LoodsmanServerApplication::IMainSystemPtr sp;
            sp.Attach((LoodsmanServerApplication::IMainSystem*)MultiQi.pItf);

        _variant_t ecode, emsg;
        _variant_t dbs = sp->GetDBList(&ecode, &emsg);

        if ((int)ecode == 0)
        {
            // Русские буквы в консоли показываться не будут, нужно использовать
            // _wsetlocale(LC_ALL, L"Russian") и AnsiToOem.
            wprintf(L"%s\n", (wchar_t*)(_bstr_t)dbs);
        }
        else
        {
            wprintf(L"Error %d - %s\n", (int)ecode, (wchar_t*)(_bstr_t)emsg);
        }
    }
    ::CoUninitialize();
    return 0;
}

С датасетом можно работать либо через XML, либо с помощью DLL библиотеки на Delphi 7. А уже об этом в соседней теме написано.
+ Благодарностей: 1

AI

Вот стандартный пример из дистрибутива - должен работать, начиная с 8.5 sp2. В нем показана работа с датасетами.

Chaa

Цитата: AI от 04.12.09, 17:42:41
Вот стандартный пример из дистрибутива - должен работать, начиная с 8.5 sp2. В нем показана работа с датасетами.
Это пример плагина для клиента Лоцмана, а YorikER спрашивал про собственное приложение. Работать с датасетом из своего приложения на VC будет посложнее.

AI

Цитата: Chaa от 07.12.09, 09:21:46
Работать с датасетом из своего приложения на VC будет посложнее.

Не сложнее, датасет это COM-объект, хранящийся в файле Loodsman.exe. Создавать его можно самому в любом приложении. Преобразовать TClientDataset в IDataSet можно через свойство Data в IDataSet'е.

EP

Цитата: Chaa от 03.12.09, 09:01:15Без работы с датасетом все просто.
#include "stdafx.h"
#import "LoodsmanAppServerMain.tlb"

int _tmain(int argc, _TCHAR* argv[])
{
    ::CoInitialize(NULL);
    ::CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_NONE,
        RPC_C_IMP_LEVEL_DELEGATE, NULL, 0, NULL);

    COSERVERINFO ServerInfo = {0};
    ServerInfo.pwszName = L"APPSRV";

    MULTI_QI MultiQi = {0};
    MultiQi.pIID = &__uuidof(LoodsmanServerApplication::IMainSystem);

    HRESULT Hr = ::CoCreateInstanceEx(__uuidof(LoodsmanServerApplication::MainSystem), NULL,
        CLSCTX_REMOTE_SERVER, &ServerInfo, 1, &MultiQi);
    if (Hr == S_OK)
    {
        LoodsmanServerApplication::IMainSystemPtr sp;
            sp.Attach((LoodsmanServerApplication::IMainSystem*)MultiQi.pItf);

        _variant_t ecode, emsg;
        _variant_t dbs = sp->GetDBList(&ecode, &emsg);

        if ((int)ecode == 0)
        {
            // Русские буквы в консоли показываться не будут, нужно использовать
            // _wsetlocale(LC_ALL, L"Russian") и AnsiToOem.
            wprintf(L"%s\n", (wchar_t*)(_bstr_t)dbs);
        }
        else
        {
            wprintf(L"Error %d - %s\n", (int)ecode, (wchar_t*)(_bstr_t)emsg);
        }
    }
    ::CoUninitialize();
    return 0;
}
С датасетом можно работать либо через XML, либо с помощью DLL библиотеки на Delphi 7. А уже об этом в соседней теме написано.

Попробовал реализовать этот пример на удаленной машине, но в результате выполнения функции CoCreateInstanceEx была выдана ошибка Access_Denied. С самого клиента лоцман:2018 с той же машины соединение проходит успешно. В чем может быть проблема?

Chaa

Цитата: EP от 24.09.21, 11:26:20Попробовал реализовать этот пример на удаленной машине, но в результате выполнения функции CoCreateInstanceEx была выдана ошибка Access_Denied. С самого клиента лоцман:2018 с той же машины соединение проходит успешно. В чем может быть проблема?
С версии 2017 сервер приложений стал другим. Теперь основной метод подключения это WCF, а DCOM оставлен для совместимости со старыми приложениями. Варианты такие:
1. Использовать WCF. Например, через библиотеку Ascon.Plm.ServerApi.dll.
2. Настроить сервер, чтобы DCOM работал. Для чего добавить роли Сервер приложений, COM+. Потом в dcomcnfg.exe дать права на активацию LoodsmanServerApplication нужным пользователям.