PSafeArray

Version: Delphi 3.02
Trying to understand how to pass an array to an Activex executable for which I have a type library. It is the PSafeArray type that I have not seen before and don't understand. There are several functions in this Activex executable that I ultimately need to be able to use correctly but here is an example of one as defined in the type library:

 _cTC2000 = interface(IDispatch)
    ['{41B5E9B0-F986-11D2-85E2-0A0C90C555D}']
    procedure GetCompanyNames(var  CompanyNames: PSafeArray); safecall;
end;

From the documentation (for VB of course) I should simply be passing an array of strings and GetCompanyNames would fill my array accordingly. When I created the type library in Delphi 3 I ended up with the PSafeArray stuff.

What I need to know is what the heck is a PSafeArray and given what I have described above, how would I create the correct array of strings as a PSafeArray to pass to this function and, when returned, how would I read this array. Here is a simple example of what I am trying to get at:

procedure TForm1.BitBtn1Click(Sender: TObject);
var
   MyTC:       _cTC2000;
   NamesArray: PSafeArray;
   bnds:       Array[0..0] of TVarArrayBound;
begin

   bnds[0].LowBound:=0;
   bnds[0].ElementCount:=250;
   NamesArray:=SafeArrayCreate   (varOLEStr, 1, bnds);

   MyTC:=cocTC2000.Create;
   IsAvail:=MyTC.TCEnabled;

   MyTC.GetCompanyNames(NamesArray);

   {not sure if what I have done above re the SafeArray stuff
   is even correct but even less sure as to what to do
   next to actually retrieve the PSafeArray elements.}

end;


Thanks very much.

Chris
cdk033198Asked:
Who is Participating?

[Webinar] Streamline your web hosting managementRegister Today

x
 
EpsylonConnect With a Mentor Commented:
Remove the stringgrid and take a listbox:

   SafeArrayGetLBound(NamesArray, 1, LB);
   SafeArrayGetUBound(NamesArray, 1, UB);
   for index := LB to UB do
   begin
     SafeArrayGetElement(NamesArray, index, data);
     ListBox1.Items.Add(data);
   end;
0
 
EpsylonCommented:
PSafeArray is a pointer to a record and is defined in ActiveX.pas:

{ from OAIDL.H }
  PSafeArrayBound = ^TSafeArrayBound;
  {$EXTERNALSYM tagSAFEARRAYBOUND}
  tagSAFEARRAYBOUND = record
    cElements: Longint;
    lLbound: Longint;
  end;
  TSafeArrayBound = tagSAFEARRAYBOUND;
  {$EXTERNALSYM SAFEARRAYBOUND}
  SAFEARRAYBOUND = TSafeArrayBound;

  PSafeArray = ^TSafeArray;
  {$EXTERNALSYM tagSAFEARRAY}
  tagSAFEARRAY = record
    cDims: Word;
    fFeatures: Word;
    cbElements: Longint;
    cLocks: Longint;
    pvData: Pointer;
    rgsabound: array[0..0] of TSafeArrayBound;
  end;
  TSafeArray = tagSAFEARRAY;
  {$EXTERNALSYM SAFEARRAY}
  SAFEARRAY = TSafeArray;
0
 
cdk033198Author Commented:
Epsylon:

Thanks but I already made it that far. What I am not sure of is:

a) is my usage of the PSafeArray stuff correct; and

b) how to I read the 'array' after it is returned. It is certainly not a standard array that can be indexed.

Activex.pas has basic definition but I need a little guidance on how to use the stuff. Thanks.

Chris
0
Free Tool: Subnet Calculator

The subnet calculator helps you design networks by taking an IP address and network mask and returning information such as network, broadcast address, and host range.

One of a set of tools we're offering as a way of saying thank you for being a part of the community.

 
cdk033198Author Commented:
Epsylon:

Thanks but I already made it that far. What I am not sure of is:

a) is my usage of the PSafeArray stuff correct; and

b) how to I read the 'array' after it is returned. It is certainly not a standard array that can be indexed.

Activex.pas has basic definition but I need a little guidance on how to use the stuff. Thanks.

Chris
0
 
westy100697Commented:
cdk,
    Try something like this

procedure TForm1.BitBtn1Click(Sender: TObject);
var
   MyTC:       _cTC2000;
   NamesArray: PSafeArray;
   bnds:       Array[0..0] of TVarArrayBound;
   i : integer ;
begin
  new(NamesArray) ;
  Bnds.ElementCount := 250 ;
  Bnds.LowBound := 0  ;
  NamesArray := SafeArrayCreate   (varOLEStr, 1, bnds);
  for i := 0 to 250 do
  begin
    NamesArray.cbElements := i ;
    NamesArray.pvData := Pchar('Hello'+inttostr(i));
    inc(NamesArray);
end;

Also do not forget to Dispose of Pointer when finished.
Same thing applies when reading from the Record but just back the front in for loop. Do you need me to code that as an example also ?

WESTY :)

0
 
EpsylonCommented:
a) looks ok to me
b) Use SafeArrayGetElement, not sure how it works...
0
 
rwilson032697Commented:
Listening
0
 
cdk033198Author Commented:
Westy:

Thanks but still a no go. BTW, what is the inc(NamesArray) for in your example?

Again to be specific, I need a way to read the elements in this array after the activex function has been called.

Perhaps another tack... the type library I am trying to work from was created by Delphi3 when I imported this activex executable from the Activex tab....perhaps it is incorrect. If I have an activex executable and I have it's GUID... is there maybe another way of creating the custom equivalent of a type library where I could define the activex functions as using 'standard' array types or, at least, OLEVariant types??

Chris
0
 
cdk033198Author Commented:
Westy/Epsylon:

If it is any help... if I were calling this activex executable from within VB, the syntax would be as follows:

Dim Names() as String

Set SFServ=CreateObject("TC2000Dev.cTC2000")

SFServ.GetCompanyNames Names()

....
....

Is there a way to make access to these functions as simple and straightforward as this from within Delphi?

Chris
0
 
EpsylonCommented:
The equivalent could be:

A: Variant;

A := VarArrayCreate([0, 250], varString);
0
 
EpsylonCommented:
Just found something on dejanews (seached for PSafeArray). Perhaps this works:

var
  A: Variant;
  NamesArray: PSafeArray;
begin
  ...

  A := VarArrayCreate([0, 250], varString);
  NamesArray := PSafeArray(TVarData(VarArrayRef(A)).VArray);

  MyTC.GetCompanyNames(NamesArray);

  ShowMessage(A[0]);

  ...
0
 
westy100697Commented:
cdk,
 

>Thanks but still a no go. BTW, what is >the inc(NamesArray) for in your >example?

>Again to be specific, I need a way to >read the elements in this array after >the activex function has been called.


Okay well that inc(NamesArray) inrements
to the next record in the PSafeArray.
If you have 250 elements you would do this 250 times. Very similar to the way you increment an Array.

I tried the code I sent you in the comment above and it does read the data in no probs. You are after reading it from the PSafeArray so I would do the following ....

procedure TForm1.BitBtn1Click(Sender: TObject);
var
   MyTC:       _cTC2000;
   TempArray,NamesArray: PSafeArray;
   bnds:       Array[0..0] of TVarArrayBound;
begin
  New(TempArray) ;
  bnds[0].LowBound:=0;
  bnds[0].ElementCount:=250;
  NamesArray:=SafeArrayCreate   (varOLEStr, 1, bnds);

  MyTC:=cocTC2000.Create;
  IsAvail:=MyTC.TCEnabled;

  MyTC.GetCompanyNames(NamesArray);
  TempArray := NamesArray ;
  NoElements := TempArray.ElementCount ;
  LowerBnd := TempArray.LowBound ;
  For i := LowerBnd to NoElements do
  begin
    //Read the data from here eg.
    Stringgrid1.Cells[1,1] :=  TempArray.pvData ;    
    Inc(TempArray) ;
  end;
end;

 
This is just an example of reading from the PSafeArray. You should be able to read the data into anything you want.

This should work but I will also test this now. Reading into the PSafeArray should be straight forward but you just need a function to pass the data back since you have GetCompanyName() then there must be an equivalent SetCompanyName()

WESTY :|


0
 
cdk033198Author Commented:
Epsylon/Westy:

No go on either yet but Westy's last seems very close. The Inc(TempArray) generates an exception on my system so that must not be the correct syntax.

Will keep trying different permutations. Thanks.
0
 
westy100697Commented:
cdk,
    When you assign data to the records or you copy contents of one to another make sure it is like this

TempArray^ := NamesArray^ ;

forget what I put in there about

New(TempArray)

just do the above assignment..

Also when reading data in or out use

NamesArray^.pvData  etc...


Also I get and exception as well when inc(TempArray) in that for loop too. Still trying but I think nearly there..

WESTY :)



0
 
EpsylonCommented:
Why no tried to use this in the loop:

var data: WideString;

SafeArrayGetElement(NamesArray, index, data);
0
 
westy100697Commented:
cdk,
    Epsylon is onto something there as you can also get the LowerBound from

SafeArrayGetLBound(); and

the UpperBound from

SafeArrayGetUBound() ; therefore you have the loop covered and also with what Epsylon gave you can get each element at that index .


WESTY :}
0
 
westy100697Commented:
cdk,
    Also use SafeArrayPutElement() to put data into the Array


WESTY :)
0
 
cdk033198Author Commented:
Epsylon/Westy:

Getting much warmer now. Readable english is being returned from the function... still generating exceptions though but I am probably just moving too fast. Let me keep tweaking and will post conclusion in the AM. Thanks both of you for your persistence!

Chris
0
 
westy100697Commented:
cdk,
    That is great. Epsylon gave some good direction there with those functions. If you do a Ctrl-F1 on the SafeArrayPutElement etc there is some quite good help also which may be worth looking at. It steps through each funtion that you require to manipulate the PSafeArray even if the examples are in C.

WESTY
0
 
EpsylonCommented:
I'm to bed now, cya 2morrow
0
 
cdk033198Author Commented:
Epsylon/Westy:

"Thank You!" to both of you. We are done and it works like a charm.  Below is the final function (still needs a lot of work but at least it pulls the info in!!). Again, thanks.

Chris

procedure TForm1.BitBtn1Click(Sender: TObject);
var
   IsAvail:            Boolean;
   MyTC:               _cTC2000;
   TempArray:          PSafeArray;
   NamesArray:         PSafeArray;
   bnds:               Array[0..0] of TVarArrayBound;
   i:                  Integer;
   NoElements:         Integer;
   LowerBnd:           Integer;
   Drt:                WideString;
begin

  {Initialize Safe Array - zero elements}
  bnds[0].LowBound:=0;
  bnds[0].ElementCount:=0;
  NamesArray:=SafeArrayCreate(varOLEStr, 1, bnds);

  {Fire up the object}
  MyTC:=cocTC2000.Create;
  IsAvail:=MyTC.TCEnabled;

  {Call one of the TC functions}
  MyTC.GetAllChartListSorts(NamesArray);

  {Assign array to temp... don't see why necessary
  but does not work if this is not done. Westy? }

  TempArray := NamesArray ;

  {Get the number of elements}
  SafeArrayGetUBound(TempArray, 1, NoElements);
  SafeArrayGetLBound(TempArray, 1, LowerBnd);

  {Sanity check}
  ShowMessage(IntToStr(noElements));

  {Write the results to the listbox}
  MyList.Clear;
  For i := LowerBnd to NoElements do
  begin
    SafeArrayGetElement(TempArray, i, Drt);
    MyList.Items.Add(Drt);
  end;

  {Must be a SafeArrayFree but will deal with that later}

end;
0
 
westy100697Commented:

cya.

0
 
westy100697Commented:
cdk,
    Brilliant, that is what we are all after when we ask questions here. A solution. I am pleased that it works.


WESTY :)
0
 
cdk033198Author Commented:
OK, now the tough question... how to I allocate points to both of you???

Chris
0
 
westy100697Commented:
Thats okay give them to Epsylon, as I was awarded points from someone else when Epsylon also had input to that question. Fair is fair and he can be awarded the points if that is okay with you.


WESTY :)
0
 
cdk033198Author Commented:
Westy:

Will do and thanks again.

Chris
0
 
cdk033198Author Commented:
Many thanks to both Epsylon and Westy for dedicating a lot of effort in a short period of time to find the answer to my question!
0
 
EpsylonCommented:
Thanks!

It always feel good to solve these kind of problems   :o)
0
All Courses

From novice to tech pro — start learning today.