Gregory Koehn 0 Posted November 9, 2020 (edited) 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 November 9, 2020 by Gregory Koehn Changed title to reflect how I am using this delphi code Share this post Link to post
Gregory Koehn 0 Posted November 9, 2020 (edited) 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 November 9, 2020 by Gregory Koehn Added Delphi Code to read the SafeArray and illustrate the issue with using SafeArrays Share this post Link to post
Anders Melander 1782 Posted November 9, 2020 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
Gregory Koehn 0 Posted November 9, 2020 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? Share this post Link to post
Anders Melander 1782 Posted November 9, 2020 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
Gregory Koehn 0 Posted November 9, 2020 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... ComServerTest.zip Share this post Link to post
Gregory Koehn 0 Posted November 10, 2020 (edited) 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 November 10, 2020 by Gregory Koehn Forgot the Code Tag for my code Share this post Link to post
Gregory Koehn 0 Posted November 10, 2020 (edited) 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! Edited November 10, 2020 by Gregory Koehn Added more code to post Share this post Link to post
A.M. Hoornweg 144 Posted November 11, 2020 (edited) 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 November 11, 2020 by A.M. Hoornweg Share this post Link to post
Anders Melander 1782 Posted November 11, 2020 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
A.M. Hoornweg 144 Posted November 12, 2020 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
A.M. Hoornweg 144 Posted November 12, 2020 Does anybody know in which Delphi version the TLB editor ceased to support Delphi syntax? Share this post Link to post
Anders Melander 1782 Posted November 12, 2020 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
A.M. Hoornweg 144 Posted November 12, 2020 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! Share this post Link to post
Gregory Koehn 0 Posted November 12, 2020 (edited) 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 November 12, 2020 by Gregory Koehn Edited wording Share this post Link to post
Anders Melander 1782 Posted November 12, 2020 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
Gregory Koehn 0 Posted November 13, 2020 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