Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Программирование, связанное с MS Office > VBA и DLL


Автор: Dementor 11.2.2016, 12:26
И снова всем привет!

Возникла проблема связывания макроса на VBA с DLL, которая пишется на C++.

Сразу уточню - DLL пытаюсь писать так же я, поэтому нет никаких ограничений в решении проблемы - лишь бы решить.

Итак в чем суть: есть макрос на VBA, который пишется под Microstation, и DLL, которая фактически реализует возможности http://www.cs.man.ac.uk/~toby/gpc/.

В DLL пока есть одна функция, доступная для VBA, которая на входе получает два SAFEARRAY и один gpc_polygon, который в итоге, по моему мнению, она и должна заполнить.

Как выглядит сама функция в DLL (ее внутренности не привожу, т.к. особого смысла в этом нет):
Код

int __stdcall GPC::ClipPolygons(SAFEARRAY **Master, SAFEARRAY **Slave, gpc_polygon *Result)


Из VBA в качестве Master и Slave подаются массивы Point3d (это тип данных, состоящий из 3-х double), которые сама DLL переводит в gpc_polygon, для того чтобы уже их отдать General Polygon Clipper для обработки. В итоге работы General Polygon Clipper мы получаем еще один gpc_polygon, который и должны как-то вернуть в макрос VBA.

Вопрос именно в том как этот gpc_polygon вернуть. Я предполагал, что за это будет отвечать тот самый *Result, но к сожалению это не работает. Как я понимаю не работает по причине того, что сам gpc_polygon внутри себя содержит динамические массивы и соответственно VBA у меня на этом и падает, выдавая ошибку. При этом в том же gpc_polygon есть и обычная переменная num_contours, которая вполне нормально может быть возвращена через gpc_polygon  *Result.

Сам тип gpc_polygon я реализовал и на VBA конечно же, хотя если честно я не совсем уверен, что подобные вещи вообще нужно делать и что подобные типы данных могут передаваться.

Очень прошу помощи! Как подобное можно реализовать? Может быть есть какой-то вариант возвращения не самого gpc_polygon, а просто нескольких массивов?

Автор: Akina 11.2.2016, 13:15
Я мало что смыслю в С++, но назвал бы это процедурой, а не функцией.

Автор: Dementor 17.2.2016, 11:48
Akina, я тоже не большой специалист, но все же это функция - она же возвращает значение, а процедура была бы void и ничего бы не возвращала.

Но суть не в том как называть конечно.

В общем решил я проблему использованием в качестве Result того же SAFEARRAY, который содержал Point3d. Но так как вернуться может несколько контуров, то пришлось пойти дальше и опять нарваться на непонимание.

Я в DLL объявил вот такую структуру:
Код

typedef struct
{
    SAFEARRAY *Vertexes;
} Contour;

Итак на входе у функции имеются три SAFEARRAY, каждый из которых содержит внутри себя массив структур Contour. В общем если говорить по-простому, то на входе у нас есть куча контуров, каждый из которых состоит из кучи вершин.

И в общем-то если говорить о Master и Slave, то все отлично - никаких проблем: передали, получили, обработали.

А вот с заполнением Result есть проблема. Если, например, в первом варианте функции SAFEARRAY **Result у меня содержал просто массив Point3d, то все было отлично - функция его замечательно заполняла и далее VBA-макрос его замечательно обрабатывал. А вот теперь получается проблема: массив Result проходит изменение размера до нужного, далее в цикле по контурам, которые мне надо добавить в массив, я создаю еще один SAFEARRAY *NewContour, в который записываю вершины, и после того как вершины записаны я с помощью SafeArrayPutElement добавляю NewContour в Result. На мой взгляд все правильно и логично, но вот на деле оказывается иначе: после того как все отработает я получаю Result нужного размера, но содержащий в качестве всех элементов только последний добавленный NewContour.

В общем видимо надо писать в раздел по C++, ибо я уже совсем не понимаю что не так.

Автор: Romikgy 17.2.2016, 12:08
насколько мне известно понятие ссылка у с++ и у вба отличаются... поэтому могут вести по разному

Автор: Dementor 18.2.2016, 16:00
В общем что бы проблема получила решение я опишу мое решение и может быть это кому-нибудь да пригодится (решение не является 100% правильным!).

Итак, основная проблема у меня состояла в том, что я вынужден работать с SAFEARRAY, который содержит в качестве элементов другие SAFEARRAY, которые свою очередь содержат структуры Point3d (повторюсь, что Point3d - это просто три double).

Самое правильное и легальное решение подобной проблемы (как я это понимаю) - объявить структуру в IDL и далее уже беспрепятственно создавать массивы таких структур (под массивами надо понимать именно SAFEARRAY, а не обычный массив C/C++). Но к сожалению я не владею полными данными о том, как это делать, поэтому был вынужден придумывать обход.

 Суть обхода заключается в том, что я должен был как-то "обмануть" систему. И вот как я это сделал:
1. На входе в DLL-функцию подается 3 массива SAFEARRAY, два из которых заполнены данными, а третий нужен для возврата результата в VBA;
2. Используя SafeArrayCopy мы копируем один из заполненных массивов в результирующий массив. Вот тут и была моя первая ошибка - я не копировал массив, а делал просто *Result = *Master;
3. Теперь результирующий массив у нас имеет как минимум один элемент (а это по условиям моей задачи тоже SAFEARRAY), который отвечает всем правилам (т.е. как-будто мы его создали SAFEARRAY используя структуру IDL). Почему как минимум один элемент я думаю понятно;
4. Изменяем размер результирующего массива с помощью SafeArrayRedim до нужного (при этом понимаем, что если у нас после копирования в результирующем массиве был только 1 элемент, а теперь стало 5, то первый элемент никак не изменился!);
5. А теперь копируем первый элемент результирующего массива во все остальные используя все тот же SafeArrayCopy. Таким образом мы получаем результирующий массив, состоящий из нужного количества элементов SAFEARRAY, которые гарантированно способны хранить в себе структуры Point3d. Думаю понятно, что тут была моя вторая ошибка ибо я делал вот такую ересь: NewContour = MasterArray[0].Vertexes;.

Понятно, что такой подход нельзя назвать грамотным и правильным, но если нет желания копать про IDL, то вариант вполне рабочий.

Автор: kapbepucm 29.2.2016, 16:31
насколько я правильно понял от пункта 2 до пункта 5 - это выполнение вне VBA, со есть в C++ DLL; а result получаете в VBA? корректный?
VBA не даёт такого гибкого доступа к памяти, как это можно делать в C++, однако можно через winapi попробовать

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)