1

Тема: JScript: ссылка на SafeArray

Привет!

Есть задача: передать методу COM-объекта ссылку на SafeArray в виде параметра. Этот метод запишет в искомый SafeArray некоторые данные.

HRESULT STDMETHODCALLTYPE SomeMethod(LPSAFEARRAY* Buffer);

Вопрос: как это сделать?

Пробовал так:

var dictionary = new ActiveXObject("Scripting.Dictionary");
for (var i = 0; i < 10; i++) {
  dictionary.add(i, 0);
};
 
var ar1 = dictionary.items();
var ar2 = VBArray(ar1);
var ar3 = VBArray(ar1).toArray();
var ar4;
 
SomeObj.SomeMethod(ar1);
SomeObj.SomeMethod(ar2);
SomeObj.SomeMethod(ar3);
SomeObj.SomeMethod(ar4);

Все 4 варианта возвращают исключение: Type mismatch.

WScript.echo(typeof ar1); // unknown
WScript.echo(typeof ar2); // unknown
WScript.echo(typeof ar3); // object
WScript.echo(typeof ar4); // undefined

Если кто-нибудь сталкивался с подобной проблемой, прошу подсказать путь к решению.

Пока нашел кривое решение:
написал промежуточную ActiveX dll, которая работает непосредственно с искомым COM сервером, получив SAFEARRAY она возвращает JScript-у массив в виде объекта CkData (http://www.chilkatsoft.com/refdoc/xCkDataRef.html).

Но это лишь временное решение... Существует необходимость работать с данным COM без посредников.

2

Re: JScript: ссылка на SafeArray

WScript.Echo(new VBArray(dictionary.Items()).toArray());

3

Re: JScript: ссылка на SafeArray

Спасибо за ответ, но мне нужно не вывести на экран содержимое массива, а передать готовый массив методу объекта в виде параметра. Этот объект запишет в массив данные.

Это я делаю здесь:

SomeObj.SomeMethod(ar3);

А WScript.echo-ом а просто демонстрирую каким типом JScript видит каждую переменную.

Напоминаю как как выглядит структура SafeArray::

typedef struct tagSAFEARRAY 
{
  USHORT cDims; // количество измерений массива
  USHORT fFeatures; // набор флагов, определяющих атрибуты массива
  USHORT cbElements; // размер элементов массива
  USHORT cLocks; // счетчик ссылок, указывающий количество блокировок, наложенных на массив
  USHORT handle; // не используется
  PVOID pvData; // указатель на данные массива
  SAFEARRAYBOUND rgsabound[1];
}  SAFEARRAY;

typedef struct tagSAFEARRAYBOUND
{
  ULONG cElements; // число элементов массива
  LONG lLbound;  // нижняя граница
} SAFEARRAYBOUND;

Так вот мне надо передать ссылку на экземпляр данной структуры.

4 (изменено: Xameleon, 2011-03-21 17:00:50)

Re: JScript: ссылка на SafeArray

SomeObj.SomeMethod(new VBArray(dictionary.Items()).toArray())

Не рублю JScript практически, поэтому извиняйте если скажу глупость, но разве не это нужно ?

На сколько я понял код

new VBArray(dictionary.Items()).toArray()

тут dictionary.Items преобразовывается в SafeArray. Его и надо скормить методу.

Хотя - наверное ошибаюсь, но вроде бы dictionary.Items уже возвращает SafeArray. Который можно напрямую отдавать COM объекту. Но видимо каких то тонкостей JS не знаю.

--------------
Добавлено чуть позже.

А можно поглядеть на реальный код передачи массива ?

Т.е в какой именно метод этой библиотеки вы обращаетесь ?

Меня терзают подозрения, что тут Вам и не особо то нужен "переходник"

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

5

Re: JScript: ссылка на SafeArray

ar1 в примере имеет тип VT_ARRAY | VT_VARIANT. Существует ещё тип VT_SAFEARRAY — возможно, его-то и ждёт метод объекта. Отсюда и type mismatch.

А в VBScript Вы проверяли, там работает? Вроде в справке пишут, что в VBScript safe array используется.

6

Re: JScript: ссылка на SafeArray

Большое спасибо за ответы.
Попробовал в VBScript, результат тот же: Type mismatch
Передавал как экземпляр Dictionary.Items, так и родной массив VBScript-а...

А можно поглядеть на реальный код передачи массива ?
Т.е в какой именно метод этой библиотеки вы обращаетесь ?

Вот что пишет производитель COM о данном методе:

Syntax

object.Read(Offset, Buffer())

Offset - Required. A Double expression that represents the offset in the file where the reading operation will begin.

Buffer - Required. A Byte array expression representing the buffer into which the data will be read. The Buffer must be allocated before calling the method according to the required length to read. The buffer cannot be bigger than the actual bytes left from the pointer to the end of the file.

Такой код в интерфейсе данного объекта сгенерил midl:

virtual HRESULT STDMETHODCALLTYPE Read( 
    double Offset,
    SAFEARRAY * *Buffer) = 0;

Такой сгенерил Delphi:

procedure Read(Offset: Double; var Buffer: PSafeArray); safecall;

Метод записывает данные в массив, указатель на SafeArray которого надо передать.

В C++ и Delphi я создаю массив байт, заполняю структуру SafeArray (либо руками либо через API), и передаю указатель на неё данному методу и всё прекрасно работает... есть ли способ проделать аналогичные действия в JScript?

7 (изменено: Xameleon, 2011-03-22 11:22:51)

Re: JScript: ссылка на SafeArray

Ааа. Ну тогда не удивительно ) Метод требует Byte() массив. а вы передаете масив типа Variant

Вам поможет ADODB.Stream либо SAPI.spFileStream или SAPI.spMemoryStream

И никаких обёрток на COM не нужно

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

8

Re: JScript: ссылка на SafeArray

Большое спасибо! :-)
Попробую...

9 (изменено: Xameleon, 2011-03-22 11:58:16)

Re: JScript: ссылка на SafeArray

2 dolu: Видимо я слепну. ) Пересмотрел всю страницу http://www.chilkatsoft.com/refdoc/xCkDataRef.html и не нашёл там такого метода там не нашёл. Рад бы помочь Вам уже готовым примером, но для этого мне надо понять:
1) Что за байтовый массив надо подсунуть. Картинка / произвольный файл / произвольный массив ?
2) И где же всё таки этот волшебный метод у объекта описан на сайте ? Может я не там смотрю ?
3) Есть подозрение, что и COM этот для Вашей задачи не особо нужен. Хотелось бы больше подробностей - Какая конечная задача стоит ?

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

10

Re: JScript: ссылка на SafeArray

Существует сервер с защищенным хранилищем данных, у этого хранилища есть свои API оформленные в виде ActiveX COM. Существует необходимость работать с данными, находящимися в этом хранилище, из скрипта под Windows.

По поводу ссылки на chilkat, я просто пользовался объектом CkData для передачи массива данных из моего COM скрипту, он не относится к выше упомянутым API.

Подсунуть надо пустой (например, заполненный нулями) массив, в него метод запишет данные из заданного файла защищенного хранилища.

Да в принципе, не важно что за API и что за данные... Просто если есть способ передать в некий метод объекта какого-то COM указатель на SafeArray, хотелось бы его найти :-)

Ещё раз спасибо.

11

Re: JScript: ссылка на SafeArray

Понятно, что ничего непонятно ). Ну попробуем тогда интуитивно "нащупать землю"

Dim spMemoryStream, i, Data
Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
For i=0 to 1000
    spMemoryStream.Write CByte(0)
Next

Data = spMemoryStream.GetData
MsgBox TypeName(Data)
MsgBox Ubound(Data)
Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

12 (изменено: dolu, 2011-03-22 12:41:38)

Re: JScript: ссылка на SafeArray

Да, так я уже попробовал и получил Type mismatch при передаче Data методу :-(

В Delphi, например, работает так:

arr: array of Byte; 
SA: TSafeArray;
pSA: PSafeArray;

...

arr := nil;
SetLength(arr, 1000);

SA.cDims := 1;
SA.fFeatures := FADF_AUTO;
SA.cbElements := SizeOf(Byte);
SA.pvData := arr;
SA.rgsabound[0].cElements := 1000;
pSA := @SA;

Obj.Read(0, pSA);

13

Re: JScript: ссылка на SafeArray

OFF: Дабы не засорять форум дальнейшим "нащупыванием" правды - предлагаю связаться по ICQ. А потом уже опубликуем решение в теме, если найдём.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

14

Re: JScript: ссылка на SafeArray

dolu пишет:

у этого хранилища есть свои API оформленные в виде ActiveX COM.

Можешь куда-нибудь выложить?

15

Re: JScript: ссылка на SafeArray

2 Xameleon OFF: Боюсь что у меня нет возможность сейчас, пользоваться ICQ и пр.  подобными средствами передачи быстрых сообщений ((( но всё равно спасибо за предложенную помощь :-)

2 chessman

Можешь куда-нибудь выложить?

К сожалению нет :-( ибо не хочу потом огребать проблем от тех, кто эти API покупал... но даже если бы выложил... для того чтобы протестить работу нужен сервер... в этом нет смысла...
Можно просто написать ActiveX компонент с одним объектом и методом, который требует параметр SAFEARRAY * * и попытаться ему его передать из скрипта. :-)

16

Re: JScript: ссылка на SafeArray

dolu пишет:

Можно просто написать ActiveX компонент с одним объектом и методом, который требует параметр SAFEARRAY * * и попытаться ему его передать из скрипта. :-)

Вы уверены, что пробывали именно так ?

Dim spMemoryStream, i, Data
Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
For i=0 to 1000
    spMemoryStream.Write CByte(0)
Next

Data = spMemoryStream.GetData

Obj.Read 0, Data

На выходе из GetData Вы получаете тот самый SafeArray.

P.S Если интерфейс хранилища оформлен в виде ActiveX COM, то Вам в 99% случаев не нужен никакой промежуточный объект.

----------------------------

Подумал.... Всё таки если так не сработает (и предположительно метод у COM объекта описан не совсем так как Вы написали), то возможно в той обёртке, которую Вы используете нужно получить указатель на первый элемент массива. К сожалению не владею знаниями о Delphi, поэтому предположу как это должно быть на Visual Basic 6.0

Вам нужно получить указатель на первый элемент байтового массива VarPtr(arr(0))

Значит метод у COM должен быть наподобии

Public Sub SendByteArray(data)
      Dim b() as Byte
      b = data
      ApiObject.SomeMethod(0,VarPtr(b(0)))
End Sub

Это в самом грубом варианте. Без проверок на типы данных и границы массива. В этом случае в SomeMethod уйдёт указатель типа Long. Сишные функции в массе своей именно так принимают массивы. Но ещё раз повторюсь - это предположение на случай если первый вариант не сработает.

И всё таки очень хотелось бы наладить связь через альтернативные средства обмена сообщениями. Так проще экспериментировать.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

17 (изменено: dolu, 2011-03-22 14:28:32)

Re: JScript: ссылка на SafeArray

Вы уверены, что пробовали именно так ?

Да, только в VBScript так:

Obj.Read 0, Data

без скобок (ещё раз проверил... Type mismatch)

Если интерфейс хранилища оформлен в виде ActiveX COM, то Вам в 99% случаев не нужен никакой промежуточный объект.

Золотые слова :-) я уже так намучился с этим промежуточным объектом...

18

Re: JScript: ссылка на SafeArray

Посмотрите мой пост выше. Возможно вариант на VB 6 Вам подойдёт. Если нужно - могу скомпилить. Но тогда опять же нужны подробности. Хранилище даром не нужно, а вот на ActiveX я всё таки хотел бы "посмотреть".

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

19

Re: JScript: ссылка на SafeArray

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

Read(Offset As Double, Buffer() As Byte)

В результате такой код:

Dim spMemoryStream, i, Data
Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
For i=0 to 1000
    spMemoryStream.Write CByte(0)
Next

Data = spMemoryStream.GetData

Obj.Read 0, Data

вызывает Type Mismatch. Из-за того, что байтовый массив укладывается в Varinat "оболочку" переменной Data.

Dim spMemoryStream, i, Data
Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
For i=0 to 1000
    spMemoryStream.Write CByte(0)
Next

Obj.Read 0, spMemoryStream.GetData

Предполагаю, что передача напрямую пройдёт нормально, но при этом смысл такой передачи теряется. Т.к массив уйдёт напрямую в функцию и обратно мы его уже не получим.

Пока решено поэкспериментировать с промежуточным COM объектом.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

20

Re: JScript: ссылка на SafeArray

Xameleon пишет:

Предполагаю, что передача напрямую пройдёт нормально, но при этом смысл такой передачи теряется. Т.к массив уйдёт напрямую в функцию и обратно мы его уже не получим.

Что за передача напрямую? Скрипт с методами напрямую не работает. Он с ними работает через метод Invoke интерфейса IDispatch. В Invoke передаётся dispid нужного метода и его параметры в виде массива Variant'ов. То, что Вы в скрипте не пишете значение в переменную, ничего не изменит.

Видимо, просто нужен Variant типа VT_SAFEARRAY, он и будет после передачи в Invoke корректно преобразован в массив байтов, который и будет передан методу. В JScript такого типа данных нет, и те объекты, что здесь испробованы, не его возвращают, а VT_ARRAY.

21

Re: JScript: ссылка на SafeArray

Может быть имеет смысл на VBScript перевести код?

22

Re: JScript: ссылка на SafeArray

2 YMP:

YMP пишет:
Xameleon пишет:

Предполагаю, что передача напрямую пройдёт нормально, но при этом смысл такой передачи теряется. Т.к массив уйдёт напрямую в функцию и обратно мы его уже не получим.

Что за передача напрямую? Скрипт с методами напрямую не работает. Он с ними работает через метод Invoke интерфейса IDispatch. В Invoke передаётся dispid нужного метода и его параметры в виде массива Variant'ов. То, что Вы в скрипте не пишете значение в переменную, ничего не изменит.

Видимо, просто нужен Variant типа VT_SAFEARRAY, он и будет после передачи в Invoke корректно преобразован в массив байтов, который и будет передан методу. В JScript такого типа данных нет, и те объекты, что здесь испробованы, не его возвращают, а VT_ARRAY.

Вот я как раз и ждал Вашего мнения коллега. ) Т.к всех тонкостей этого вопроса я не знаю. Изучал как говорится методом проб и ошибок. )

2 JSman: К сожалению не помогло. Видимо ситуация немного сложнее.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

23

Re: JScript: ссылка на SafeArray

2 YMP: Возник вопрос.

Решил протестировать на самодельном COM компоненте.

Создал Project1.Class1 объект в VB6

С методом TestSub

Sub TestSub(data() As Byte)
    MsgBox TypeName(data), vbInformation, "TestSub"
End Sub

Написал скрипт

Test1

Test2

Sub Test1
    Dim spMemoryStream, i, Data
    Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
    For i=0 to 1000
        spMemoryStream.Write CByte(0)
    Next

    Set P = CreateObject("Project1.Class1")
    P.TestSub spMemoryStream.GetData
End Sub

Sub Test2
    Dim spMemoryStream, i, Data
    Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
    For i=0 to 1000
        spMemoryStream.Write CByte(0)
    Next

    Dim tmp
    tmp = spMemoryStream.GetData
    Set P = CreateObject("Project1.Class1")
    P.TestSub tmp
End Sub

И как я и наблюдал ранее - Первая процедура отрабатывает, вторая "падает". Из чего я когда то давно сделал вывод, что передача "напрямую" и через переменную отличается.

Поясните пожалуйста - я ошибаюсь ?
http://file.qip.ru/file/oTxR29FB/Test.html

Исходник, скомпилированый вариант и тестовый скрипт, сложил по ссылке.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

24

Re: JScript: ссылка на SafeArray

Да, в VBS разница есть. При "прямой" передаче Variant, переданный в Invoke, имеет тип VT_ARRAY | VT_UI1, т.е. он содержит указатель на первое число массива. При передаче через переменную имеем такую связку: VT_BYREF | VT_VARIANT —> VT_ARRAY | VT_UI1, т.е. в Invoke передаётся Variant, содержащий указатель на другой Variant, который уже содержит указатель на первое число массива. Проще говоря, в первом случае происходит передача по значению (ByVal), а во втором по ссылке (ByRef).

25

Re: JScript: ссылка на SafeArray

2 YMP: Вот я собственно поэтому и предположил, что передача напрямую "пройдёт", но не будет иметь смысла, т.к связи с этим массивом мы уже иметь не будем.

Я правильно понимаю ?

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

26

Re: JScript: ссылка на SafeArray

Xameleon пишет:

2 YMP: Вот я собственно поэтому и предположил, что передача напрямую "пройдёт", но не будет иметь смысла, т.к связи с этим массивом мы уже иметь не будем.

Я правильно понимаю ?

Думаю, будем. По значению-то передаётся (копируется) не сам массив, а указатель на него.

27

Re: JScript: ссылка на SafeArray

Каким образом ?

Если мы отдаём массив через spMemoryStream.GetData то он уходит в функцию безвозвратно. Т.к генерится копия в момент вызова метода и уже она отдаётся "наружу".

Ошибаюсь ?

Dim spMemoryStream, i, Data, retArray
Set spMemoryStream = CreateObject("SAPI.spMemoryStream")
For i=0 to 1000
    spMemoryStream.Write CByte(0)
Next

Obj.Read 0, spMemoryStream.GetData

retArray = spMemoryStream.GetData

Т.е retArray получит исходный массив, который мы заполнили до вызова.

Или есть ещё варианты его заполучить ?

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

28

Re: JScript: ссылка на SafeArray

А, ну если GetData делает копию самих чисел массива, тогда конечно.

29

Re: JScript: ссылка на SafeArray

2 YMP: На сколько я знаю, GetData у spMemoryStream, равно как и функции Read у ADODB.Stream, да и Recordset.GetChunk отдают байтовые массивы копией. На VB 6.0 попробовал сделать у класс модуля публичный байтовый массив типа

Public Data() as Byte

VB 6 такого не позволяет. Ругается на то, что типизированные данные нельзя так высовывать. Т.е байтовый массив можно отдать только через функцию или свойство.

Public Property Get Data() as Byte()
  'Data = ....
End Property

Public Function GetData() as Byte()
  'Data = ....
End Function

Но такой подход подразумевает отдачу данных с копией массива.

Задумался над методом с Variant параметром

Public Sub Test(Data)
   Dim bData() as byte
   bData() = Data
   Data = bData
End Sub

Пробовал вечером, возможно ошибся. Наружу получил "пустоту". Проверял по MsgBox. Возможно не прав. Надо поизучать содержимое. Попробую завтра.

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !

30

Re: JScript: ссылка на SafeArray

Итог - Сегодня сделал пару тестов на VB 6. ) Добиться необходимого результата возможно. Но смысла не имеет. Проще сделать свою обёртку COM на COM объект разработчиков. )

Передумал переделывать мир. Пашет и так, ну и ладно. Сделаю лучше свой !