Closed dwang-git closed 1 week ago
Yes, it is possible.
Suppose the .NET code is
// issue46.cs
using System;
namespace Issue46
{
public class Example
{
private int _number;
public int Number
{
get
{
return this._number;
}
set
{
this._number = value;
}
}
}
}
Also, suppose that the .NET library is compiled to a file named issue46.dll
, using the command
csc.exe /platform:x86 /target:library issue46.cs
I'll show two ways that you could implement the Python Client
to get/set the .NET property
from msl.loadlib import Client64, Server32
class Server(Server32):
def __init__(self, host, port):
super().__init__("issue46.dll", "net", host, port)
self._example = self.lib.Issue46.Example()
def get_Number(self):
return self._example.Number
def set_Number(self, number):
self._example.Number = number
class ClientGetSet(Client64):
def __init__(self):
super().__init__(__file__)
def get_Number(self):
return self.request32("get_Number")
def set_Number(self, number):
self.request32("set_Number", number)
class ClientProperty(Client64):
def __init__(self):
super().__init__(__file__)
@property
def Number(self):
return self.request32("get_Number")
@Number.setter
def Number(self, value):
self.request32("set_Number", value)
if __name__ == "__main__":
with ClientGetSet() as c:
print("Using explicit get/set methods")
print(f"Number={c.get_Number()}")
c.set_Number(46)
print(f"Number={c.get_Number()}")
with ClientProperty() as c:
print("Using @property decorator")
print(f"Number={c.Number}")
c.Number = 12
print(f"Number={c.Number}")
@jborbely, Thanks for your suggestions. Yeah, I can see it would work. However, as there are many properties in the C# dll need to be accessed from the client, I wonder whether there is any cleaner way to do that to avoid defining a function for each property in the server class (here get_Number/set_Number correspond to one property "Number" in the Example object).
Example .NET library
// issue46.cs
using System;
namespace Issue46 {
public class Example {
private int _integer;
private string _string;
public int Integer {
get { return this._integer; }
set { this._integer = value; }
}
public string String {
get { return this._string; }
set { this._string = value; }
}
}
}
Compile example library
csc.exe /platform:x86 /target:library issue46.cs
Example Python script to get/set any property in the example library
# issue46.py
from msl.loadlib import Client64, Server32
class Server(Server32):
def __init__(self, host, port):
super().__init__("issue46.dll", "net", host, port)
self._example = self.lib.Issue46.Example()
def get_property(self, name):
print(f"Server32.get({name})")
return getattr(self._example, name)
def set_property(self, name, value):
print(f"Server32.set({name}, {value})")
setattr(self._example, name, value)
class Client(Client64):
def __init__(self):
super().__init__(__file__)
def __getattr__(self, name):
return self.request32("get_property", name)
def __setattr__(self, name, value):
if name.startswith("_"):
super().__setattr__(name, value)
else:
self.request32("set_property", name, value)
if __name__ == "__main__":
c = Client()
print(f"{c.Integer=}")
print(f"{c.String=}")
c.Integer = 46
c.String = "issue46"
print(f"{c.Integer=}")
print(f"{c.String=}")
stdout, stderr = c.shutdown_server32()
print(stdout.read().decode())
Python output
(.venv) ..\issue46> py .\issue46.py
c.Integer=0
c.String=None
c.Integer=46
c.String='issue46'
Server32.get(Integer)
Server32.get(String)
Server32.set(Integer, 46)
Server32.set(String, issue46)
Server32.get(Integer)
Server32.get(String)
Adding print
statements on the Server
are just to show that setting a property actually sets it in the .NET library (and not simply creates a new c.Attribute
in your 64-bit Python Client
instance). I would not keep the print statements as the stdout buffer will get filled (at 4096 characters) and that may cause communication to get blocked.
Thank you @jborbely ! Yeah, that works for me.
I met another problem when accessing the property declared as an interface in .Net library. So I'm having two .Net libraries, one depends on the other (say library A depends on B). In my server code, I only wrapped around library A, but one of the property of A is of interface type, which is defined in library B. So every time when I access that property, there would be the error "TypeError: cannot pickle 'xxx' object". Is there any way to fix or work around this? Thanks!
It is not possible to return an object that resides in memory from the server, since a 32-bit process and a 64-bit process cannot share memory space. So there is no way to fix it, only work around it. Essentially, the interface type must stay on the server and the client must interact with the interface via objects that can be pickled.
Look at #39 to see if that is helpful to see how to deal with a similar case where that library returned objects that must stay in 32-bit memory space.
Thanks @jborbely for your quick response, and the fix in #39 works for me. Really appreciate it! I still have another problem: there is one property (name "cx") of type comobject. When I access the property of cx, it will have exception "AttributeError: 'ComObject' object has no attribute 'Type'". I checked in the C# program, it can access that property of cx without problem. I attached the watch info from Visual Studio of "cx". How can I get it in the python server? Thanks!
I don't have experience accessing a System.__ComObject
from .NET, so I don't know how to fix this. What I can do is provide some additional information that may help you find a solution.
The 32-bit server is using pythonnet to actually load the .NET library. If a __ComObject
is accessible from pythonnet
then it is accessible on the server. Apparently, there are three closed issues that relate to ComObject
on the pythonnet
repo.
Search stackoverflow for pythonnet
and System.__ComObject
and/or perhaps use the pythonnet gitter room to seek advice.
The pythonnet and comtypes packages are bundled with 32-bit server (on Windows). This means that you have full access to these packages (e.g., import clr, comtypes
) so you can use these packages to access the CableEye.ocx COM library. You may also access a COM library using msl-loadlib
(which uses comtypes
behind the scenes).
Maybe the Server32.assembly property is useful to access the underlying .NET Runtime Assembly object on the server.
Is it possible to have a simple way to access the properties defined in .Net library? I have the question also posted in stackoverflow: https://stackoverflow.com/questions/78880602/msl-loadlib-access-the-property-defined-in-net-library
Thanks!