Solved

PSafeArray

Posted on 2000-03-22
28
5,891 Views
Last Modified: 2008-03-17
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
0
Comment
Question by:cdk033198
  • 10
  • 9
  • 8
  • +1
28 Comments
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
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
 

Author Comment

by:cdk033198
Comment Utility
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
 

Author Comment

by:cdk033198
Comment Utility
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
 

Expert Comment

by:westy100697
Comment Utility
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
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
a) looks ok to me
b) Use SafeArrayGetElement, not sure how it works...
0
 
LVL 12

Expert Comment

by:rwilson032697
Comment Utility
Listening
0
 

Author Comment

by:cdk033198
Comment Utility
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
 

Author Comment

by:cdk033198
Comment Utility
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
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
The equivalent could be:

A: Variant;

A := VarArrayCreate([0, 250], varString);
0
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
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
 

Expert Comment

by:westy100697
Comment Utility
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
 

Author Comment

by:cdk033198
Comment Utility
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
 

Expert Comment

by:westy100697
Comment Utility
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
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
Why no tried to use this in the loop:

var data: WideString;

SafeArrayGetElement(NamesArray, index, data);
0
IT, Stop Being Called Into Every Meeting

Highfive is so simple that setting up every meeting room takes just minutes and every employee will be able to start or join a call from any room with ease. Never be called into a meeting just to get it started again. This is how video conferencing should work!

 

Expert Comment

by:westy100697
Comment Utility
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
 

Expert Comment

by:westy100697
Comment Utility
cdk,
    Also use SafeArrayPutElement() to put data into the Array


WESTY :)
0
 

Author Comment

by:cdk033198
Comment Utility
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
 
LVL 13

Accepted Solution

by:
Epsylon earned 400 total points
Comment Utility
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
 

Expert Comment

by:westy100697
Comment Utility
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
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
I'm to bed now, cya 2morrow
0
 

Author Comment

by:cdk033198
Comment Utility
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
 

Expert Comment

by:westy100697
Comment Utility

cya.

0
 

Expert Comment

by:westy100697
Comment Utility
cdk,
    Brilliant, that is what we are all after when we ask questions here. A solution. I am pleased that it works.


WESTY :)
0
 

Author Comment

by:cdk033198
Comment Utility
OK, now the tough question... how to I allocate points to both of you???

Chris
0
 

Expert Comment

by:westy100697
Comment Utility
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
 

Author Comment

by:cdk033198
Comment Utility
Westy:

Will do and thanks again.

Chris
0
 

Author Comment

by:cdk033198
Comment Utility
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
 
LVL 13

Expert Comment

by:Epsylon
Comment Utility
Thanks!

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

Featured Post

Maximize Your Threat Intelligence Reporting

Reporting is one of the most important and least talked about aspects of a world-class threat intelligence program. Here’s how to do it right.

Join & Write a Comment

A lot of questions regard threads in Delphi.   One of the more specific questions is how to show progress of the thread.   Updating a progressbar from inside a thread is a mistake. A solution to this would be to send a synchronized message to the…
Objective: - This article will help user in how to convert their numeric value become words. How to use 1. You can copy this code in your Unit as function 2. than you can perform your function by type this code The Code   (CODE) The Im…
Get a first impression of how PRTG looks and learn how it works.   This video is a short introduction to PRTG, as an initial overview or as a quick start for new PRTG users.
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

10 Experts available now in Live!

Get 1:1 Help Now