See the question and my original answer on StackOverflow

This is a correct python code:

import win32com.client
from win32com.client import Dispatch
import pythoncom
import pywintypes

unk = pythoncom.CoCreateInstance(pywintypes.IID('ComplexComObject'), None, pythoncom.CLSCTX_ALL, pythoncom.IID_IUnknown)
dispevents = Dispatch(unk.QueryInterface(pythoncom.IID_IDispatch))

class MyEvents:
    def OnAdditionDone(self):
        print('Addition is done.')

EventListener = win32com.client.WithEvents(dispevents, MyEvents)

print(dispevents.Addition(123, 456))

But it won't work as is.

First thing to do is make sure the type library (.tlb) you prepare is registered. It's not the case in the ComObjectWithEvents project but you can borrow and adapt the code you already have in the ComObjectWithTLBRegistration project.

If you don't do that you'll get ti = disp._oleobj_.GetTypeInfo() pywintypes.com_error: (-2146234011, 'OLE error 0x80131165', None, None) which is TLBX_W_LIBNOTREGISTERED.

After that you will get another error: Fatal error. System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt. error. This is because your dispinterface is incorrectly defined in C#, it is currently:

[ComVisible(true)]
[Guid(AssemblyInfo.ComEventsGuid)]
[InterfaceType(ComInterfaceType.InterfaceIsDual)] // not good
public interface ComEvents
{
    [DispId(1)]
    void OnAdditionDone();
}

while it must be this:

[ComVisible(true)]
[Guid(AssemblyInfo.ComEventsGuid)]
[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]
public interface ComEvents
{
    [DispId(1)]
    void OnAdditionDone();
}