Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / Delphi Новый топик    Ответить
 2 варианта создания PSafeArray для маршаллинга - в чём разница?  [new]
WinterGraveyard
Member

Откуда:
Сообщений: 69
Есть библиотека типов вот с таким IDL (фрагмент):

[
odl,
uuid(1EF13171-6593-42BF-9BCF-CE2E2A036624),
version(1.0),
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "TestLib.ITest")
]
interface ITest : IUnknown {
HRESULT _stdcall GetData([out, retval] SAFEARRAY(VARIANT)* pRetVal);
};

[
odl,
uuid(98CB1A74-6895-444B-8CC8-394C23543B2D),
version(1.0),
dual,
oleautomation,
custom(0F21F359-AB84-41E8-9A78-36D110E6D2F9, "TestLib.ITestUser")
]
interface ITestUser : IDispatch {
[id(0x60020000)]
HRESULT Do([in] ITest* Test);
};

В дельфи эта библиотека экспортируется вот так:
ITest = interface(IUnknown)
  ['{1EF13171-6593-42BF-9BCF-CE2E2A036624}']
  function GetData(out pRetVal: PSafeArray): HResult; stdcall;
end;

ITestUser = interface(IDispatch)
  ['{98CB1A74-6895-444B-8CC8-394C23543B2D}']
  procedure Do_(const Test: ITest); safecall;
end;

Реализация интерфейса ITestUser - внешняя (дотнетовская сборка, если это имеет значение), интерфейс ITest реализуется в дельфи.
Если мы собираем PSafeArray как-то так:
function TTest.Variant1: PSafeArray;
var
  Bounds: array [0..0] of TSafeArrayBound;
  i: Integer;
  b: TBytes;
  procedure PutElement(Value: Variant);
  begin
    OleCheck(SafeArrayPutElement(Result, i, Value));
    Inc(i);
  end;
begin
  Bounds[0].lLbound:=0;
  Bounds[0].cElements:=4;
  Result:=SafeArrayCreate(varVariant, 1, Bounds);
  OleCheck(SafeArrayLock(Result));
  PutElement(123);
  PutElement(WideString('Мама мыла раму'));
  PutElement(Now);
  SetLength(b, 2);
  b[0]:=32;
  b[1]:=255;
  PutElement(b);
  OleCheck(SafeArrayUnlock(Result));
end;

то всё отрабатывает нормально - внешний компонент через интерфейс получет данные, и нормально их разбирает.
Но если мы собираем PSafeArray вот так (этот способ фигурирует во многих примерах и пособиях в интернете):
function TTest.Variant2: PSafeArray;
var
  i: Integer;
  b: TBytes;
  Arr: Variant;
  procedure PutElement(Value: Variant);
  begin
    Arr[i]:=Value;
    Inc(i);
  end;
begin
  Arr:=VarArrayCreate([0, 4], varVariant);
  VarArrayLock(Arr);
  PutElement(123);
  PutElement(WideString('Мама мыла раму'));
  PutElement(Now);
  SetLength(b, 2);
  b[0]:=32;
  b[1]:=255;
  PutElement(b);
  VarArrayUnlock(Arr);
  Result:=PSafeArray(TVarData(Arr).VArray);
end;

то внешний компонент на вызове ITest.GetData выбрасывает ошибку "SafeArray ранга 65262 передан в метод, которому требуется массив ранга 1". Пробовал пробежаться по результату второго варианта (в дельфи) - вроде всё в порядке, количество измерений какое нужно, размер требуемый, все данные на месте - но вот видимо всё же есть какая-то разница между первым способом и вторым, и она важна при маршаллинге внешнему компоненту. А вот в чём она, эта разница?
Delphi 2010.

Сообщение было отредактировано: 30 июн 20, 10:11
30 июн 20, 10:12    [22159475]     Ответить | Цитировать Сообщить модератору
 Re: 2 варианта создания PSafeArray для маршаллинга - в чём разница?  [new]
GunSmoker
Member

Откуда:
Сообщений: 3140
Серьёзно? Во втором варианте вы передаёте из функции указатель на память и тут же эту память освобождаете (Arr будет удалён до выхода из функции)!

В каких-таких местах такой способ показан?
30 июн 20, 11:36    [22159530]     Ответить | Цитировать Сообщить модератору
 Re: 2 варианта создания PSafeArray для маршаллинга - в чём разница?  [new]
WinterGraveyard
Member

Откуда:
Сообщений: 69
GunSmoker
Серьёзно? Во втором варианте вы передаёте из функции указатель на память и тут же эту память освобождаете (Arr будет удалён до выхода из функции)!

В каких-таких местах такой способ показан?

Вот здесь, например. Плюс очень часто встречал такой способ, изучая исходник JclDotNet. Но в первом случае нюанс передачи данных куда-то вообще не играет роли, а во втором все внешние методы, в которые передаются данные, возвращают управление до выхода из метода, который формирует данные. А у меня фактически callback, и ситуацию с автоматической очисткой данных после выхода из метода я как-то упустил. Для второго варианта вынес Arr в поле класса - заработал и он. Теперь всё понятно, спасибо.

Сообщение было отредактировано: 30 июн 20, 12:16
30 июн 20, 12:18    [22159577]     Ответить | Цитировать Сообщить модератору
 Re: 2 варианта создания PSafeArray для маршаллинга - в чём разница?  [new]
GunSmoker
Member

Откуда:
Сообщений: 3140
Что-то вы не то делаете.

Если вы используете первый вариант, то вызывающий поюзает массив и спокойно его удалит вызовом SafeArrayDestroy. По сути, здесь используется shared менеджер памяти, скрытый за фасадом SafeArrayXYZ функций.

Если же вы используете второй вариант, то вызывающий никак не может удалить массив, ведь он хранится у вас в поле класса, доступа к которому у вызывающего нет.

В указанных вами ссылках есть существенное отличие: в них права на владение массивом не отдаются во внешний код.
30 июн 20, 13:58    [22159664]     Ответить | Цитировать Сообщить модератору
 Re: 2 варианта создания PSafeArray для маршаллинга - в чём разница?  [new]
WinterGraveyard
Member

Откуда:
Сообщений: 69
GunSmoker,

Для второго способа я вынес вариантную переменную в поле только для проверки того, что маршаллинг пройдёт нормально, и сделал это только в тестовом наколеночном примере, чтобы окончательно разобраться. В рабочий код пошёл первый способ, с SafeArrayCreate.
Про shared менеджер памяти я по пути примерно догадался сам, потому что тоже пытался указатель на PSafeArray сохранять в поле класса, и при повторном обращении к методу, если указатель инициализирован, вызывать SafeArrayDestroy - нарвался на EOlySysError: Memory is locked, посмотрел на счетчик блокировок экземпляра SafeArray, и увидев странное значение типа -123789, предположил, что его до меня уже зачистили. Убрал хранение указателя в поле и SafeArrayDestroy, и никаких заметных утечек памяти не обнаружил (поскольку это всё в рабочем коде используется для передачи достаточно больших объемов, утечки были бы очень заметны).
Жаль, что литературу по этой теме сейчас трудно найти - хрестоматийную Inside COM Роджерсона я прочитал, но там такие нюансы не разъясняются. Всё остальное - оставшиеся обзорные статьи и MSDN, которые надо долго перелопачивать, чтобы узнать про вот этот же shared-менеджер.
30 июн 20, 14:15    [22159684]     Ответить | Цитировать Сообщить модератору
Все форумы / Delphi Ответить