Jump to content
Gregory Koehn

Creating Simple Com Server to return array of strings to Python 3.8

Recommended Posts

Help me out! I cannot get this figured out.

function TGridInfo.Method4: OleVariant;
var
  I: integer;
  ListItemCount: integer;
  gklist: tstringlist;
  V: OleVariant;
 begin
  gklist := tstringlist.Create;
  try
    gklist.Add('hello');
    gklist.Add('world');
    ListItemCount := gklist.Count;
    //V := VarArrayCreate([0,ListItemCount-1], VT_BSTR);  //<-- This does not work gives error on result := V;
    //V := VarArrayCreate([0,ListItemCount-1], VT_LPWSTR); //<-- errors here.
    V := VarArrayCreate([0,ListItemCount-1], varVariant); //<-- This does not work either gives error on result := V;
    for I := 0 to ListItemCount-1 do
      V[I]:=gklist[I];
    result := V;
  finally
    gklist.Free;
  end;
end;

I am using Delphi 10.3.

Notice my comments in source code as to the problems I have been having.

 

I am using Python 3.8 to test this system

from win32com import client
com_object = client.Dispatch("Project1.GridInfo")
res = com_object.Method4()
print(res)
com_object = None

 

Edited by Gregory Koehn
Changed title to reflect how I am using this delphi code

Share this post


Link to post

I just want to be able to assemble an array of string values and return them using COM automation to Python.

I have been successful in returning an array of integers by using this code...

function TGridInfo.GetIntArray: PSafeArray;
var
  ArrayBounds : TSafeArrayBound;
  i : integer;
  ArrayData : pointer;
  lElemCount: integer;
  type
    IntegerArray = Array of integer;
begin
  lElemCount := 11;
  ArrayBounds.lLbound := 0;
  ArrayBounds.cElements := lElemCount;
  result := SafeArrayCreate( varInteger, 1, @ArrayBounds );
  if SafeArrayAccessData( result, ArrayData ) = S_OK then
    begin
      for i:= 0 to lElemCount-1 do
        begin
          IntegerArray(ArrayData)[i]:= i;
        end;
      SafeArrayUnAccessData(result);
    end;
end;

I then try to convert this to String Array...

function TGridInfo.GetStringArray: PSafeArray;
var
  ArrayBounds : TSafeArrayBound;
  i : integer;
  ArrayData : pointer;
  lElemCount: integer;
  type
    StringArray = Array of widestring;
begin
  lElemCount := 11;
  ArrayBounds.lLbound := 0;
  ArrayBounds.cElements := lElemCount;
  result := SafeArrayCreate( VT_BSTR, 1, @ArrayBounds );
  if SafeArrayAccessData( result, ArrayData ) = S_OK then
    begin
      for i:= 0 to lElemCount-1 do
        begin
          StringArray(ArrayData)[i]:= 'element ' + inttostr(i);
        end;
      SafeArrayUnAccessData(result);
    end;
end;

This returns some strange numbers to Python instead of strings.

This also returns strange numbers to Delphi instead of strings.

Here is my Delphi reading code...

implementation

{$R *.dfm}

uses
  ComObj
  ;

procedure TForm1.Button1Click(Sender: TObject);
var
  ComServer: OleVariant;
  vUDTArray: OleVariant;
  i : Integer;
  value : String;
begin
  ComServer := CreateOleObject('Project1.GridInfo');
  vUDTArray := ComServer.GetStringArray;
  if VarIsArray(vUDTArray) then
    begin
      for i := VarArrayLowBound(vUDTArray, 1) to VarArrayHighBound(vUDTArray, 1) do
        begin
          Value := vUDTArray[i];
          ShowMessage(Value);
        end;
    end;
  ComServer := null;
end;

end.

 

Edited by Gregory Koehn
Added Delphi Code to read the SafeArray and illustrate the issue with using SafeArrays

Share this post


Link to post

Have you tried VarArrayCreate(..., varOleStr)?

 

1 hour ago, Gregory Koehn said:

This does not work either gives error on result := V

I can't see why you would get an error assigning one OleVariant to another but it would help if you specified what error you're getting.

Share this post


Link to post

Anders, Good to hear from you!

Here is what Delphi gives me...

See attached png file.

I just can't get peoples examples on the internet to work when actually running it in Delphi IDE!

Has the compiler changed? Does it have a bug?

DelphiErrorOnAssignOfOleVariant.png

Share this post


Link to post

I think you must have a bug somewhere else that is causing this. I can't spot a problem in the code you've posted.

The following works for me with D10.3.1:

function Test1: OleVariant;
begin
  Result := VarArrayCreate([0, 1], varOleStr);
  Result[0] := 'Hello';
  Result[1] := 'World';
end;

function Test2: OleVariant;
var
  V: OleVariant;
begin
  V := VarArrayCreate([0, 1], varOleStr);
  V[0] := 'A kitten dies...';
  V[1] := '...every time you use variant arrays';
  Result := V;
end;

procedure DoTest;
var
  V: OleVariant;
begin
  V := Test1;
  V := Test2;
end;

 

Try reproducing the problem in a new empty application.

Share this post


Link to post

I just created a fresh ComServer project in Delphi 10.3.

I did New | VCL Application

Then did File | New | Other... | Individual Files | Automation Object

See 1st-step.png

Then Created a Method in the ridl editor like this...

See 2nd-step.png

Then registered it "Current User".

See 3rd-step.png

Complete code is in the attached zip file.

 

When actually calling this method is when the error pops up.

It only happens when calling it from a COM Client.

Compilation works fine.

How can I get this to work? It very well could be just a simple thing I am doing wrong...

 

1st-step.png

2nd-step.png

3rd-step.png

ComServerTest.zip

Share this post


Link to post

And here is my simple Delphi consumer...

uses
  ComObj
  ;

procedure TForm1.Button1Click(Sender: TObject);
var
  ComServer: OleVariant;
  vUDTArray: OleVariant;
  i : Integer;
  value : String;
begin
  ComServer := CreateOleObject('ComServer.GregsComServer');
  vUDTArray := ComServer.Method1;
  if VarIsArray(vUDTArray) then
    begin
      if VarIsByRef(vUDTArray) then
        ShowMessage('varisbyref');
      for i := VarArrayLowBound(vUDTArray, 1) to VarArrayHighBound(vUDTArray, 1) do
        begin
          Value := vUDTArray;
          ShowMessage(Value);
        end;
    end;
  ComServer := null;
end;

end. 

 

Edited by Gregory Koehn
Forgot the Code Tag for my code

Share this post


Link to post

I got it to work!!!

See ParameterDesign.png attached...

You cannot send it as a result but as an "out" parameter!

Here is my Server "Code Behind" of "Method2"...

procedure TGregsComServer.Method2(out MyVariantArray: OleVariant);
begin
  MyVariantArray := VarArrayCreate([0, 1], varOleStr);
  MyVariantArray[0] := 'Hello';
  MyVariantArray[1] := 'World';
end;

Here is my Delphi Consumer code now...

procedure TForm1.Button2Click(Sender: TObject);
var
  ComServer: OleVariant;
  vUDTArray: OleVariant;
  i : Integer;
  value : String;
begin
  ComServer := CreateOleObject('ComServer.GregsComServer');
  ComServer.Method2(vUDTArray);
  if VarIsArray(vUDTArray) then
    begin
      if VarIsByRef(vUDTArray) then
        ShowMessage('varisbyref');
      for i := VarArrayLowBound(vUDTArray, 1) to VarArrayHighBound(vUDTArray, 1) do
        begin
          Value := vUDTArray[i];
          ShowMessage(Value);
        end;
    end;
  ComServer := null;
end;

Thanks for your help Anders! Good to see you are still producing Delphi code!

ParameterDesign.png

Edited by Gregory Koehn
Added more code to post

Share this post


Link to post

You said it is a COM server. In that case your method has to be declared "safecall". 

 

Function tmyclass.method1:Olevariant; Safecall;

 

That basically does the same as returning the Olevariant as an OUT parameter but in the background it additionally returns an integer (hresult) which tells the caller if the call was successful or not.

 

 

[Edit] I just see you use the type library editor.

 

Since a couple of years Delphi's type library editor no longer works with Delphi syntax directly, unfortunately, but only with RIDL syntax which looks a bit like C. Here you can see that the function's result is a HResult and the Olevariant is an OUT parameter.  You will also see that the automatically created Delphi interface has functions using the "safecall" calling convention.

 

 

Older versions of Delphi had a type library editor that could work in Delphi syntax directly, I found that much more straightforward to use.

 

 

 

 

Edited by A.M. Hoornweg

Share this post


Link to post
3 hours ago, A.M. Hoornweg said:

In that case your method has to be declared "safecall".

Make that: In that case your method can be declared "safecall".

Share this post


Link to post
9 hours ago, Anders Melander said:

Make that: In that case your method can be declared "safecall".

Safecall does the same as stdcall returning a hresult and letting no exceptions out.  It saves a lot of boilerplate code.

Share this post


Link to post
36 minutes ago, A.M. Hoornweg said:

Safecall does the same as stdcall returning a hresult and letting no exceptions out.  It saves a lot of boilerplate code.

Yes, I agree. My point was that it's optional.

safecall transforms hresult into exceptions which sometimes is exactly what you want and sometimes not.

Share this post


Link to post

This is what I'm missing in the current version of the type library editor (see attachment): the possibility to use Pascal syntax instead of IDL Syntax.  In Delphi 2007 it was still there!

 

typelib_pascal.png

Share this post


Link to post

It would be great if someone would write a simple tutorial on Windows Com using Delphi 10.Latest!

Maybe do a video and put it on YouTube.

 

I would like to learn how to create a com object method that returns another com object.

For example...

Com Object Name = "Store"

"Store" has a method that returns Com Object "Item".

"Item" has a string property of "Name".

"Item" has a int property of "InStockCount".

 

I can't take the time right now to learn all this by going through pages and pages of internet sites that were written for Delphi 5.

For now I will just put data into a string array and parse it in the consumer.

 

What I am actually doing is wrapping a 32-bit DLL, provided by another company, in a Com Server, so we can access it by using our 64-bit program.

You know how it is in real life... Get it done as soon as possible!!!

 

I don't want you to do my work for me... It would be good info for future seekers to find and use!

 

Edited by Gregory Koehn
Edited wording

Share this post


Link to post
5 hours ago, Gregory Koehn said:

I would like to learn how to create a com object method that returns another com object.

Here you go:

type
  // The interfaces are probably defined in your type library unless they are purely
  // for internal use within the application.
  IFoo = interface
    ['{922E28A3-0127-42DB-A394-2CFED29A5575}']
    procedure DoFoo; safecall;
  end;

  IBar = interface
    ['{BECBDCE4-192C-48DC-9DC4-85DB92E01E9F}']
    function GetFoo: IFoo; safecall;
  end;

type
  // If you're using dual/dispatch/late bound/automation interfaces then use TAutoObject or 
  // TAutoIntfObject as a base class instead. See the help.
  TFoo = class(TInterfacedObject, IFoo)
  private
    // IFoo
    procedure DoFoo; safecall;
  end;

procedure TFoo.DoFoo;
begin
  ShowMessage('Hello world');
end;

type
  TBar = class(TInterfacedObject, IBar)
  private
    // IBar
    function GetFoo: IFoo; safecall;
  end;

function TBar.GetFoo: IFoo;
begin
  Result := TFoo.Create;
end;

...

procedure Test;
var
  Bar: IBar;
  Foo: IFoo;
begin
  Bar := TBar.Create;
  Foo := Bar.GetFoo;
  Foo.DoFoo;
end;

 

5 hours ago, Gregory Koehn said:

I can't take the time right now to learn all this by going through pages and pages of internet sites that were written for Delphi 5.

The principles hasn't changed since Delphi 5 and you're not going to save any time by not learning this stuff. I recommend you read this (old) one: http://www.techvanguards.com/stepbystep/comdelphi/

Share this post


Link to post

Thanks for the info!

 

I guess I was looking for something a bit more RAD and visual.

For any new Delphi developer coming along, They may want to use the "type library editor".

 

Does anyone have up-to-date tutorials regarding creating it in the "type library editor"?

For example the Delphi Com Client code should be able to access it like...

 

This is not actual syntactically correct code! Just here for illustration...

uses
  ComObj
  ;

procedure TForm1.Button1Click(Sender: TObject);
var
  ComServer: OleVariant;
  Item: OleVariant;
  i : Integer;
  countOfItems: Integer;
begin
  ComServer := CreateOleObject('ComServer.GregsComServer');
  countOfItems := ComServer.Store.ItemsCount();
  for i := 0 to countOfItems-1 do
    begin
      Item := ComServer.Store.Items(i);
      Showmessage(Item.Name);
      Showmessage(inttostr(Item.InStockCount));
    end;
  ComServer := null;
end;

No need to waist time on me... Just if someone is bored and wants some ideas to publish an article about the RAD of Delphi...

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×