Open pinke opened 7 years ago
use oleutil.ConnectObject
.
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.
This is example that received event of winsock data arrival.
Thank you. I'll try.
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:
At this point, I can't go on, so I would like to ask you what to do next.
@GhostCakeMaker i have the same problem. Did you resolve this?
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
}
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
}
So, does your problem solve the problem?
@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.
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)
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)))
}
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
?
Event how to ,tks. in c# bind ocx event like this. netobj1.OnConnected += new System.EventHandler(this.netobj1_OnConnected);