# Implements _both_ a connectable client, and a connectable server.
#
# Note that we cheat just a little - the Server in this demo is not created
# via Normal COM - this means we can avoid registering the server.
# However, the server _is_ accessed as a COM object - just the creation
# is cheated on - so this is still working as a fully-fledged server.

import pythoncom
import win32com.server.util
import win32com.server.connect
from win32com.server.exception import Exception
from pywin32_testutil import str2bytes

# This is the IID of the Events interface both Client and Server support.
IID_IConnectDemoEvents = pythoncom.MakeIID("{A4988850-49C3-11d0-AE5D-52342E000000}")

# The server which implements
# Create a connectable class, that has a single public method
# 'DoIt', which echos to a single sink 'DoneIt'

class ConnectableServer(win32com.server.connect.ConnectableServer):
	_public_methods_ = ["DoIt"] + win32com.server.connect.ConnectableServer._public_methods_
	_connect_interfaces_ = [IID_IConnectDemoEvents]
	# The single public method that the client can call on us
	# (ie, as a normal COM server, this exposes just this single method.
	def DoIt(self,arg):
		# Simply broadcast a notification.
		self._BroadcastNotify(self.NotifyDoneIt, (arg,))

	def NotifyDoneIt(self, interface, arg):
		interface.Invoke(1000, 0, pythoncom.DISPATCH_METHOD, 1, arg)

# Here is the client side of the connection world.
# Define a COM object which implements the methods defined by the
# IConnectDemoEvents interface.								
class ConnectableClient:
	# This is another cheat - I _know_ the server defines the "DoneIt" event
	# as DISPID==1000 - I also know from the implementation details of COM
	# that the first method in _public_methods_ gets 1000.
	# Normally some explicit DISPID->Method mapping is required.
	_public_methods_ = ["OnDoneIt"]
	def __init__(self):
		self.last_event_arg = None
	# A client must implement QI, and respond to a query for the Event interface.
	# In addition, it must provide a COM object (which server.util.wrap) does.
	def _query_interface_(self, iid):
		import win32com.server.util
		# Note that this seems like a necessary hack.  I am responding to IID_IConnectDemoEvents
		# but only creating an IDispatch gateway object.
		if iid==IID_IConnectDemoEvents: return win32com.server.util.wrap(self)
	# And here is our event method which gets called.
	def OnDoneIt(self, arg):
		self.last_event_arg = arg

def CheckEvent(server, client, val, verbose):
	client.last_event_arg = None
	server.DoIt(val)
	if client.last_event_arg != val:
		raise RuntimeError("Sent %r, but got back %r" % (val, client.last_event_arg))
	if verbose:
		print("Sent and received %r" % val)

# A simple test script for all this.
# In the real world, it is likely that the code controlling the server
# will be in the same class as that getting the notifications.
def test(verbose=0):
	import win32com.client.dynamic, win32com.client.connect
	import win32com.server.policy
	server = win32com.client.dynamic.Dispatch(win32com.server.util.wrap(ConnectableServer()))
	connection = win32com.client.connect.SimpleConnection()
	client = ConnectableClient()
	connection.Connect(server, client, IID_IConnectDemoEvents)
	CheckEvent(server, client, "Hello", verbose)
	CheckEvent(server, client, str2bytes("Here is a null>\x00<"), verbose)
	CheckEvent(server, client, "Here is a null>\x00<", verbose)
	val = "test-\xe0\xf2" # 2 extended characters.
	CheckEvent(server, client, val, verbose)
	if verbose:
		print("Everything seemed to work!")
	# Aggressive memory leak checking (ie, do nothing!) :-)  All should cleanup OK???

if __name__=='__main__':
	test(1)
