Jump to content
PierreC

Marshalling COM Objects

Recommended Posts

Hello,

First of all, I want to congratulate the creator of P4D for the excellent work done.
I am currently trying to use P4D to add scripting functions to an existing application. This one already has interfaces and co-class to manipulate it. I can't manage to make these COM objects available to the python code, would you have a clue ? I don't know if it is possible to do late-binding in python (calls by the Invoke method of the IUnknown interface) or if it is mandatory to use ComTypes or Win32Types to generate the corresponding python classes. But in this case, how to transtype the objects from Delphi ?
If it is necessary, I can build an example project.

 

Pierre

Share this post


Link to post

You can use TPyDelphiWrapper.WrapInterface to wrap interfaces.  Have a look at Tests/WrapDelphiTest.pas to see how this works.

Alternatively the PyWin32 python module has its own way of wrapping Com objects.

Share this post


Link to post

Hy again,

I was able to reproduce the example given in WrapDelphiTest.pas without difficulty but I think that it does not suit my situation.
If I'm not mistaken, it seems to me that this method can only work if you have the Delphi class that implements the interface because it is RTTI that enumerates the members of the class and makes them available to the wrapper. In my case, I don't have the Delphi classes at my disposal because they are defined in other dlls.
Does my reasoning seem correct to you?
Here is an example of what I would like to do, I made it as short as possible and it uses an interface and a co-class normally available on any Windows machine.

uses
  Rtti, VarPyth, ComObj;

{$R *.dfm}

const
  IID_IRegExp: TGUID = '{3F4DACA0-160D-11D2-A8E9-00104B365C9F}';
  CLASS_RegExp: TGUID = '{3F4DACA4-160D-11D2-A8E9-00104B365C9F}';
type
  IRegExp = interface(IDispatch)
    ['{3F4DACA0-160D-11D2-A8E9-00104B365C9F}']
    function Get_Pattern: WideString; safecall;
    procedure Set_Pattern(const pPattern: WideString); safecall;
    function Get_IgnoreCase: WordBool; safecall;
    procedure Set_IgnoreCase(pIgnoreCase: WordBool); safecall;
    function Get_Global: WordBool; safecall;
    procedure Set_Global(pGlobal: WordBool); safecall;
    function Execute(const sourceString: WideString): IDispatch; safecall;
    function Test(const sourceString: WideString): WordBool; safecall;
    function Replace(const sourceString: WideString; const replaceString: WideString): WideString; safecall;
    property Pattern: WideString read Get_Pattern write Set_Pattern;
    property IgnoreCase: WordBool read Get_IgnoreCase write Set_IgnoreCase;
    property Global: WordBool read Get_Global write Set_Global;
  end;

  CoVBScriptRegExp = class
    class function Create: IRegExp;
  end;

class function CoVBScriptRegExp.Create: IRegExp;
begin
  Result := CreateComObject(CLASS_RegExp) as IRegExp;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  pythonEngine: TPythonEngine;
  delphiModule: TPythonModule;
  pyDelphiWrapper: TPyDelphiWrapper;
  Py : PPyObject;
  regExp: IRegExp;
begin
  pythonEngine:= TPyThonEngine.Create(nil);

  delphiModule := TPythonModule.Create(nil);
  delphiModule.Name := 'DelphiModule';
  delphiModule.Engine := pythonEngine;
  delphiModule.ModuleName := 'delphi';

  pyDelphiWrapper := TPyDelphiWrapper.Create(nil);
  PyDelphiWrapper.Name := 'PyDelphiWrapper';
  PyDelphiWrapper.Engine := pythonEngine;
  PyDelphiWrapper.Module := delphiModule;

  pythonEngine.LoadDll;

  regExp:= CoVBScriptRegExp.Create;
  regExp.Pattern:= 'i';
  ShowMessage(regExp.Replace('This is a beautiful day', '##'));

  Py := pyDelphiWrapper.WrapInterface(TValue.From(regExp));
  delphiModule.SetVar('regexp', Py);
  pythonEngine.Py_DecRef(Py);

  try
    pythonEngine.ExecString(
      'from delphi import regexp' + sLineBreak +
      'print(regexp.Replace("This is a beautiful day", "##"))');
    { This raises the following error:
      AttributeError: Error in getting property "Replace".
      Error: Unknown attribute. }
  finally
    pyDelphiWrapper.Free;
    delphiModule.Free;
    pythonEngine.Free;
  end;
end;

Do you think it is possible to map the pointer on the interface to a corresponding object in python whose class would have been generated by importing a type library with ComTypes or to a Dispatch class of Win32Com?

I'm sorry if I'm not using the right terms or if my question is a bit obscure but I know very little about python and even less about its API.

 

Best regards,

Pierre

 

Share this post


Link to post

I think the above should work.  Have you enabled the generation of RTTI in your project?

Does adding {$M+} help like in:

{$M+}
ITestInterface = interface(IInterface)
['{AD50ADF2-2691-47CA-80AB-07AF1EDA8C89}']
  procedure SetString(const S: string);
  function GetString: string;
end;
{$M-}

 

Share this post


Link to post

You are absolutely right, I had missed this directive. The example is working correctly now.

I will continue to dig into it to see if it can help me achieve what I need.
Thanks again for your help.

Pierre

Share this post


Link to post

By the way, this is a good way to expose Com objects to python without relying on PyWin32.  

Edited by pyscripter

Share this post


Link to post

Hello,

I have been having a lot of trouble getting this mechanism to work in my application. RTTI doesn't seem to be able to enumerate the methods of my instances, this is certainly due to the fact that there is no "correctly" registered type library.
So I looked at another possibility which is to :
On the Delphi side:
1 - define a variable of type integer in a module,
2 - store the IDispatch interface address of the instance in this variable,
On the Python side
3 - retrieve this value,
4 - Using pythoncom.ObjectFromAddress(), retrieve a python object instance from this address.
5 - Wrap the python object in IDispatch class using the win32com.client.Dispatch() method
This may not be perfect (I'm a total beginner in python) but it seems to works well and allows me to use the IDispatch mechanism, which is fine for me because I have objects implementing dynamic methods and properties through GetIDOfNames and Invoke.

 

Here is a simple demo that may help someone else who is looking for this kind of information:

program MiniIDispatchDemo;

{$APPTYPE CONSOLE}

{$R *.res}

uses
  System.SysUtils,
  PythonEngine,
  ComObj,
  ActiveX;

var
  pythonEngine: TPythonEngine;
  delphiModule: TPythonModule;
  regExp: IDispatch;
  addressAsInt: NativeInt;
  pyAddress: PPyObject;
begin
  // We are using COM, so we need to initialize it first
  CoInitialize(nil);
  try
    // Let's create a PythonEngine
    pythonEngine:= TPyThonEngine.Create(nil);

    // And a module to store our variable
    delphiModule := TPythonModule.Create(nil);
    delphiModule.Name := 'DelphiModule';
    delphiModule.Engine := pythonEngine;
    delphiModule.ModuleName := 'delphi';

    // Initialize the Python library
    pythonEngine.LoadDll;

    // Here we instanciate a RegExp object as a simple IDispatch interface
    // This can be replaced by any other IDispatch interface
    regExp:= CreateOleObject('VBScript.RegExp');
    // Then we get the address as an int
    addressAsInt:= NativeInt(regExp);
    // Get an integer Python object with the same value
    pyAddress:= pythonEngine.PyLong_FromLong(addressAsInt);
    // and store the value in a variable of our module
    delphiModule.SetVar('regexpAddress', pyAddress);
    // Don't forget the decrease the ref count
    pythonEngine.Py_DecRef(pyAddress);

    try
      pythonEngine.ExecString(
        // Imports all needed modules incuing our own module named "delphi"
        'from delphi import regexpAddress' + sLineBreak +
        'import pythoncom' + sLineBreak +
        'from win32com.client import Dispatch' + sLineBreak +
        // Get a python object containing from the address of the interface
        're = pythoncom.ObjectFromAddress(regexpAddress, pythoncom.IID_IDispatch)' + sLineBreak +
        // Cast this object to an IDispatch object
        'regexp = Dispatch(re)' + sLineBreak +
        // And use it's properties and methods
        'regexp.Pattern = "delphi"' + sLineBreak +
        'print(regexp.Replace("Using regexp in delphi", "python"))');
        // This prints: Using regexp in python
    finally
      delphiModule.Free;
      pythonEngine.Free;
    end;
  finally
    CoUninitialize();
  end;
end.

Pierre

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

×