go-ole / go-ole

win32 ole implementation for golang
MIT License
1.16k stars 193 forks source link

Event how to? #142

Open pinke opened 7 years ago

pinke commented 7 years ago

Event how to ,tks. in c# bind ocx event like this. netobj1.OnConnected += new System.EventHandler(this.netobj1_OnConnected);

mattn commented 7 years ago

use oleutil.ConnectObject.

GhostCakeMaker commented 4 years ago

Could you tell me how to use ConnectObject? I can't find any examples on the Internet. I want to subscribe to events in COM components, such as DataChange events in opcdaauto.dll.

mattn commented 4 years ago

This is example that received event of winsock data arrival.

https://github.com/go-ole/go-ole/blob/83594137c64630475a86ffd4645f8c85c514d156/_example/winsock/winsock.go#L129

GhostCakeMaker commented 4 years ago

Thank you. I'll try.

GhostCakeMaker commented 4 years ago

I tried, and now there are two questions, both of which are in this function: runtime\syscall_windows.go\compileCallback. line67 and line72 1、Errors are reported in these two operations:compileCallback: expected function with one uintptr-sized result dest.lpVtbl.pAddRef = syscall.NewCallback(AddRef) dest.lpVtbl.pRelease = syscall.NewCallback(Release) I just changed the return values of these two functions to uintptr. 2、The following errors are reported in this sentence:compileCallback: argument size is larger than uintptr dest.lpVtbl.pGetIDsOfNames = syscall.NewCallback(GetIDsOfNames) I tracked it down and found it: image

At this point, I can't go on, so I would like to ask you what to do next.

savely-krasovsky commented 4 years ago

@GhostCakeMaker i have the same problem. Did you resolve this?

GhostCakeMaker commented 4 years ago

I have solved the bug in the ConnectObject method, I overwrote the ConnectObject method, removing 33 to 53 lines of code, because in syscall.newcallback, all the return value of the function must be uintptr and all the parameters must be less than 64 bits (8 bytes). In my scenario, such functions as getIdsOfNames, getTypeInfoCount, getTypeInfo, invoke are not used. If you use it, you can modify the parameter types that exceed the size limit. Mine only used the OnDataChange event, so only one was left in Vtbl.

type DataChangeEventReceiver struct {
    lpVtbl *DataChangeEventReceiverVtbl
    ref    int32
    host   *ole.IDispatch
}

type DataChangeEventReceiverVtbl struct {
    //这里按照顺序列出该虚表的方法
    //pQueryInterface uintptr
    //pAddRef         uintptr
    //pRelease        uintptr
    ole.IUnknownVtbl
    OnDataChange uintptr
    //后面的好像都没有用到
    //GetTypeInfoCount uintptr
    //GetTypeInfo      uintptr
    //GetIDsOfNames    uintptr
    //Invoke           uintptr
}
func (this *OPCClient) InitReqIOInterface() {

    this.receiver = &DataChangeEventReceiver{}
    this.receiver.lpVtbl = &DataChangeEventReceiverVtbl{}
    this.receiver.lpVtbl.QueryInterface = syscall.NewCallback(queryInterface)
    this.receiver.lpVtbl.AddRef = syscall.NewCallback(addRef)
    this.receiver.lpVtbl.Release = syscall.NewCallback(release)
    this.receiver.lpVtbl.OnDataChange = syscall.NewCallback(this.OnDataChange)
    this.receiver.host = this.OpcGroup.ToIDispatch()

    err := this.OpcGroup.ConnectObject(opcutil.IID_IOPCDataCallback, (*ole.IUnknown)(unsafe.Pointer(this.receiver)))
    if err != nil {
    }
}
//连接COM的连接点(实现事件的监听)
func (this *OPCGroup) ConnectObject(iid *ole.GUID, idisp interface{}) (err error) {
    unknown, err := this.ToIDispatch().QueryInterface(ole.IID_IConnectionPointContainer)
    if err != nil {
        return
    }

    container := (*ole.IConnectionPointContainer)(unsafe.Pointer(unknown))
    var point *ole.IConnectionPoint
    err = container.FindConnectionPoint(iid, &point)
    if err != nil {
        return
    }
    if edisp, ok := idisp.(*ole.IUnknown); ok {
        this.Cookie, err = point.Advise(edisp)
        //container.Release()
        if err != nil {
            return ole.NewError(ole.E_INVALIDARG)
        }
    }
    this.ConnectionPointContainer = container
    this.ConnectionPoint = point
    //container.Release()
    return

}
savely-krasovsky commented 4 years ago

Oh, I also thought this! And tried it literally 30 min before your answer. But in my case DLL is 32-bit, so I have to use Golang i386. So I guess in my case it's not 8 bytes, but even 4.

Currently I could get Interface not supported and compileCallback: argument size is larger than uintptr errors depends on what args I use.

From DLL headers:

struct __declspec(uuid("ba559e00-85e1-4687-876e-5288eee5eb28"))
__IMoagent : IDispatch
{
    //
    // Wrapper methods for error-handling
    //

    // Methods:
    HRESULT MosaixEvent (
        VARIANT_BOOL errFlag,
        _bstr_t notifyType,
        _bstr_t MosaixDataPacket,
        _bstr_t errCode,
        _bstr_t errText );
};

Go event handler func:

func onMoAgentData(errFlag *ole.VARIANT, notify, moagentData, errCode, errText *string) uintptr {
    if notify != nil {
        fmt.Println(*notify)
    }
    if moagentData != nil {
        fmt.Println(*moagentData)
    }
    if errCode != nil {
        fmt.Println(*errCode)
    }
    if errText != nil {
        fmt.Println(*errText)
    }

    return ole.S_OK
}
GhostCakeMaker commented 4 years ago

So, does your problem solve the problem?

savely-krasovsky commented 4 years ago

@GhostCakeMaker sadly, but currently no, I didn't fix it. I am thinking to try other language. Go OLE libraries seem to be not well tested and stable, at least in case of x86-32.

yechin commented 2 years ago

I have a amazing problem with my code:

package main

import (
    "fmt"
    "syscall"
    "unsafe"

    "github.com/go-ole/go-ole"
)

const IID_IMsTscAxEvents = "{336D5562-EFA8-482E-8CB3-C5C0FC7A7DB6}"

type EventReceiver struct {
    lpVtbl *EventReceiverVtbl
    ref    int32
    host   *ole.IDispatch
}

type EventReceiverVtbl struct {
    ole.IUnknownVtbl
    GetTypeInfoCount uintptr
    GetTypeInfo      uintptr
    GetIDsOfNames    uintptr
    Invoke           uintptr
}

func queryInterface(this *ole.IUnknown, iid *ole.GUID, punk **ole.IUnknown) uintptr {
    s, _ := ole.StringFromCLSID(iid)

    *punk = nil
    if ole.IsEqualGUID(iid, ole.IID_IUnknown) ||
        ole.IsEqualGUID(iid, ole.IID_IDispatch) {
        addRef(this)
        *punk = this
        return ole.S_OK
    }
    if s == IID_IMsTscAxEvents {
        addRef(this)
        *punk = this
        return ole.S_OK
    }
    return ole.E_NOINTERFACE
}

func addRef(this *ole.IUnknown) uintptr {
    pthis := (*EventReceiver)(unsafe.Pointer(this))
    pthis.ref++
    return uintptr(pthis.ref)
}

func release(this *ole.IUnknown) uintptr {
    pthis := (*EventReceiver)(unsafe.Pointer(this))
    pthis.ref--
    return uintptr(pthis.ref)
}

func getTypeInfoCount(this *ole.IUnknown, pcount *int) uintptr {
    if pcount != nil {
        *pcount = 0
    }
    return ole.S_OK
}

func getTypeInfo(this *ole.IUnknown, index, lcid int, ptypeif *uintptr) uintptr {
    *ptypeif = uintptr(0)
    return ole.E_NOTIMPL
}

func getIDsOfNames(this *ole.IUnknown, iid *ole.GUID, wnames **uint16, namelen int, lcid int, pdisp *int32) uintptr {
    return ole.E_NOTIMPL
}

func invoke(this *ole.IDispatch, dispid int, riid *ole.GUID, lcid int, flags int16, dispparams *ole.DISPPARAMS, result *ole.VARIANT, pexcepinfo *ole.EXCEPINFO, nerr *uint) uintptr {
    //fmt.Printf("[callback] invoke called. dispid: %d, dispparams: %#v \r\n", dispid, dispparams)
    switch dispid {
    case 1:
        fmt.Println("onConnecting.")
    case 2:
        fmt.Println("onConnected")
    case 3:
        fmt.Println("onLoginComplete.")
    case 4:
        fmt.Println("onDisconnected")
    }
    return ole.S_OK
}

func setEventCallback(mstscax *ole.IDispatch) (uint32, error) {
    iid, err := ole.CLSIDFromString(IID_IMsTscAxEvents)
    if err != nil {
        return 0, err
    }

    // 获取 connection point container 接口
    unknown, err := mstscax.QueryInterface(ole.IID_IConnectionPointContainer)
    if err != nil {
        return 0, err
    }

    // 强制转换
    container := (*ole.IConnectionPointContainer)(unsafe.Pointer(unknown))

    // 查询连接点
    var point *ole.IConnectionPoint
    err = container.FindConnectionPoint(iid, &point)
    if err != nil {
        return 0, err
    }

    // 创建本地的IUnknown接口
    receiver := &EventReceiver{
        lpVtbl: &EventReceiverVtbl{
            IUnknownVtbl: ole.IUnknownVtbl{
                QueryInterface: syscall.NewCallback(queryInterface),
                AddRef:         syscall.NewCallback(addRef),
                Release:        syscall.NewCallback(release),
            },
            GetTypeInfoCount: syscall.NewCallback(getTypeInfoCount),
            GetTypeInfo:      syscall.NewCallback(getTypeInfo),
            GetIDsOfNames:    syscall.NewCallback(getIDsOfNames),
            Invoke:           syscall.NewCallback(invoke),
        },
        host: mstscax,
    }

    fmt.Printf("receiver: %#v \r\n", receiver)  //   <------  notice this line

    // 连接
    return point.Advise((*ole.IUnknown)(unsafe.Pointer(receiver)))
}

The above code works fine, but .... if I comment out the second to last line of code that in the last function setEventCallback:

  fmt.Printf("receiver: %#v \r\n", receiver)

then, this program crash when msg loop :

Exception 0xc0000005 0x0 0x8 0x7feede0329e PC=0x7feede0329e

syscall.Syscall(0x7761991c, 0x1, 0xc000051b70, 0x0, 0x0, 0x0, 0x0, 0x0) /usr/local/go/src/runtime/syscall_windows.go:188 +0xe9 github.com/lxn/win.DispatchMessage(0xc000051b70, 0x0) // <------ dispatch msg /Users/yechin/go/pkg/mod/github.com/lxn/win@v0.0.0-2021021816391 1e959e/user32.go:2324 +0x88 main.createRDPSession(0xc00008e080)

yechin commented 2 years ago

sorry, i'v made a mistake, the receiver should not be a local var, because GC will release it.

I'v fixed this:

var receiver *EventReceiver = nil  // define a global var

func setEventCallback(mstscax *ole.IDispatch) (uint32, error) {
    iid, err := ole.CLSIDFromString(IID_IMsTscAxEvents)
    if err != nil {
        return 0, err
    }

    // 获取 connection point container 接口
    unknown, err := mstscax.QueryInterface(ole.IID_IConnectionPointContainer)
    if err != nil {
        return 0, err
    }
    // 强制转换
    container := (*ole.IConnectionPointContainer)(unsafe.Pointer(unknown))

    // 查询连接点
    var point *ole.IConnectionPoint
    err = container.FindConnectionPoint(iid, &point)
    if err != nil {
        return 0, err
    }

    // 创建本地的IUnknown接口
    receiver = &EventReceiver{
        lpVtbl: &EventReceiverVtbl{
            IUnknownVtbl: ole.IUnknownVtbl{
                QueryInterface: syscall.NewCallback(queryInterface),
                AddRef:         syscall.NewCallback(addRef),
                Release:        syscall.NewCallback(release),
            },
            GetTypeInfoCount: syscall.NewCallback(getTypeInfoCount),
            GetTypeInfo:      syscall.NewCallback(getTypeInfo),
            GetIDsOfNames:    syscall.NewCallback(getIDsOfNames),
            Invoke:           syscall.NewCallback(invoke),
        },
        host: mstscax,
    }

    // 连接
    return point.Advise((*ole.IUnknown)(unsafe.Pointer(receiver)))
}
edwinhuish commented 2 months ago

I was tried many times, but it's no luck with the custom com event.

This is a COM example wrote by VB6:

' COM object: vbcom.demo

Public Event MyEvent(ByVal msg As String)

Public Sub TriggerEvent()
    RaiseEvent MyEvent(Now() & ": this is a test")
End Sub

How can I bind the MyEvent by go-ole