Как в ATL с помощью Implement Connection Point реализовать событие, передающее клиенту массив значений? Клиент на VB 6.0 видит только первое значение и не воспринимает этот параметр как массив.
Здравствуйте, dmkSar, Вы писали:
S>Как в ATL с помощью Implement Connection Point реализовать событие, передающее клиенту массив значений? S>Клиент на VB 6.0 видит только первое значение и не воспринимает этот параметр как массив.
Для "клиента на VB" массив с необходимостью принимает форму SAFEARRAY и передается через него. Никакой другой тип массивов OLE Automation не знает, т.е никаких [size_is] и т.п. параметров.
Здравствуйте, Vi2, Вы писали:
Vi2>Для "клиента на VB" массив с необходимостью принимает форму SAFEARRAY и передается через него. Никакой другой тип массивов OLE Automation не знает, т.е никаких [size_is] и т.п. параметров.
Вообще, VB версии 6.0 предоставляет прямой доступ к custom-интерфейсу, мой сервер не реализует интерфейс автоматизации. Вопрос возник потому, что с передачей массива в принимающем (inbound) интерфейсе проблем нет, проблема с outbound-интерфейсом.
Здравствуйте, dmkSar, Вы писали:
S>Вообще, VB версии 6.0 предоставляет прямой доступ к custom-интерфейсу, мой сервер не реализует интерфейс автоматизации.
Мы в курсе про VB6. Но не имеет значения, что "твой сервер не реализует интерфейс автоматизации", имеет значение, что VB является клиентом автоматизации и поэтому требует от сервера соответствовать.
S>Вопрос возник потому, что с передачей массива в принимающем (inbound) интерфейсе проблем нет, проблема с outbound-интерфейсом.
Лучше разговаривать предметно, на основе кода. VB клиент и при вызове сервера не передает массив через ByRef параметр As простой_тип, а только через ByRef параметр() As простой_тип, а это и есть SAFEARRAY. Это, конечно, можно объехать с помощью своей прокси/стаб, но это все равно из области трюков.
Здравствуйте, dmkSar, Вы писали:
S>Объявляю метод принимающего интерфейса:
S>interface IOPCCustomDA : IUnknown
S>...
S>HRESULT AsyncWrite([in] long size, [in, size_is(size)] long ServerHandles[],
S> [out, size_is(size)] long Errors[]);
S>В VB: Sub AsyncWrite(size As Long, ServerHandles As Long, Errors As Long)
S>Формирую массив SvrHndl(), вызываю из VB: S>OPC.AsyncWrite ItemCount, SvrHndl(0), Errors(0)
Ты не только формируешь через ReDim SvrHndl(больше чем ItemCount-1), но и должен сформировать ReDim Errors(больше чем ItemCount-1). Хотя туда (в параметр Errors) можно, в забывчивости, передать обычную переменную или ошибиться с размером выделения массива Errors с КАТАСТРОФИЧЕСКИМИ последствями.
Что происходит на деле? VB передает адреса первых элементов массивов, твоя прокси/стаб DLL использует ItemCount ячеек (я использую "ячеек", не уточняя тип переменной), начиная с переданного адреса, в качестве элементов массива. Что, фактически, правильно и отражает реальное положение дел в примере.
S>Все OK, обрабатываю массив Errors().
Количество элементов, доступных для обработки, определяется не сервером (прокси/стаб DLL), как должно было быть, а оператором VB. Согласись, что что-то тут избыточно или не нужно. Ну ладно, раз обрабатывается, и тебя устраивает.
S>Объявляю метод outbound интерфейса:
S>interface _IOPCCustomDAEvents : IUnknown
S>...
S>HRESULT DataChange([in] long NumItems, [in, size_is(NumItems)] long ClientHandles[]);
S>В VB: Event DataChange(NumItems As Long, ClientHandles As Long)
Дело сразу ухудшается, раз речь заходит о вызываемом сервером методе. Тут у тебя нет рулей, которые у тебя были в случае вызова тобой сервера. Нет, например, возможности задать массив, который бы принял ClientHandles.
S>В итоге, в процедуре обработки события S>Private Sub OPC_DataChange(ByVal NumItems As Long, ClientHandles As Long) S> ... S>End Sub
S>получаю ClientHandles как long-переменную, а не массив.
И это всё, конечная, приехали. Можно потыркаться, если силен в описаниях Variant-а, упаковать в него, но гарантий нет. Потому что VB волен вызвать или скопировать этот злосчастный Long, который передал сервер, как ему заблагорассудится. И связи с тем линейным участком, который передал сервер вкупе с прокси/стабом, нет и не предвидится. Почему я так могу говорить? Потому что реально сервер вызывает IDispatch::Invoke, которая в конце концов вызовет OPC_DataChange, но что будет с параметрами во время подготовки этого вызова — я не берусь говорить, тем более, что-либо предложить в качестве работоспособного кода. А не за горами VB.NET, который и объехать не даст.
Единственное, что будет точно работать, т.к. согласовано между клиентами, серверами и передающими средствами СОМа, — это SAFEARRAY.
interface IOPCCustomDA : IUnknown
...
HRESULT AsyncWrite([in] SAFEARRAY(long) *ServerHandles, [out, retval] SAFEARRAY(long) *Errors);
В VC: HRESULT AsyncWrite(SAFEARRAY/*long*/ ** ServerHandles, SAFEARRAY/*long*/ ** Errors)
В VB: Sub AsyncWrite(ServerHandles() As Long, Errors() As Long)
interface _IOPCCustomDAEvents : IUnknown
...
HRESULT DataChange([in] SAFEARRAY(long) *ClientHandles);
В VC: HRESULT DataChange(SAFEARRAY/*long*/ ** ClientHandles)
В VB: Event DataChange(ClientHandles() As Long)
Оверхед в С/С++ — вызов SafeArrayAccessData(&ptr_to_first_element) и SafeArrayUnaccessData с получением линейного массива type* ptr_to_first_element. Зато никаких танцев с бубнами в VB и иже с ним. Оверхед в прокси/стаб — в передаче с 20-30 байт управляющей информации самой структуры SAFEARRAY.
Спасибо за информацию!
Этот вопрос не освещен в "Модель COM и применение ATL 3.0" Трельсена, а где подробнее расписан COM я пока не знаю.
Не подскажешь, нет ли других способов преобразования массива в SAFEARRAY кроме как:
// Имеем массив pvValues, тип VARIANT, размера dwCount
SAFEARRAY *pSA;
SAFEARRAYBOUND bounds = {dwCount, 0};
pSA = SafeArrayCreate(VT_VARIANT, 1, &bounds);
VARIANT *theVariants;
SafeArrayAccessData(pSA, (void**)&theVariants);
for (int i = 0; i < dwCount; i++)
theVariants[i] = pvValues[i];
SafeArrayUnaccessData(pSA);