Jump to content

Vtable & DllCallbackRegister


jugador
 Share

Recommended Posts

search Autoit Forum:-
 solvedcallback-function-and-objet by @trancexx
Or ObjectFromTag by @trancexx in Ribbon thread

search github for RTD(RealTimeData server) Client:-
https://github.com/crosbymichael/thinkorswim/tree/master/ThinkOrSwim
https://github.com/neberej/tos-client/tree/master/Adapter
https://github.com/mdvx/kafka-rtd/blob/master/kafka-rtd/ExcelComInterfaces.cs
https://github.com/osullivj/kkaddin/blob/master/RTDServer.cs

https://gist.github.com/nochristrequired/c7ee0fe6780fac19faa9344f50f2a4f7#file-gistfile1-txt  RTD Client in C#

Google search
https://docs.microsoft.com/en-us/office/vba/api/excel.irtdserver
https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.irtdserver?view=excel-pia

https://www.codeproject.com/articles/245265/guide-to-writing-custom-functions-in-excel-part

Excel RTD Servers: Minimal C++ Implementation

Excel RTD Servers: Multiple Topics in C#

C# without the Excel Assembly Reference

but still no clue
All this DllCallbackRegister & DllStructCreate bit complicated for me.

 

#include <WinAPI.au3>
#include <array.au3>

Const $sCLSID_App = "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
Const $sIID_IScripRTD  = "{EC0E6191-DB51-11D3-8F3E-00C04F3651B8}"
Const $sIID_IRTDEvent  = "{A43788C1-D91B-11D3-8F39-00C04F3651B8}"   

;;--------
Const $tagIScripRTD_1 = _
        "ServerStart hresult(object*;long*);" & _
        "ConnectData hresult(long;variant*;bool*;variant*);" & _
        "RefreshData hresult(long;variant*);" & _
        "DisconnectData hresult(long);" & _
        "Heartbeat hresult(long*);" & _
        "ServerTerminate hresult();"
;;--------
Const $dtag_IRTDEvent = _
        "UpdateNotify hresult();" & _
        "get_HeartbeatInterval hresult(long);" & _
        "put_HeartbeatInterval hresult(long*);" & _
        "Disconnect hresult();"
;;--------

;;=============
Global $oErrorHandler
$oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")
;;=============

Global $oApp_Obj
;------------------
;~ when using interface_description as Default 
;~ Heartbeat atleast returning -1
;~ indicating server not started
$oApp_Obj = ObjCreateInterface($sCLSID_App, $sIID_IScripRTD, Default)
    If Not @error Then MsgBox(0, "", "ObjCreate() successful")  ;;[ok]

Local $oHrt_beat
$oHrt_beat = $oApp_Obj.Heartbeat()
    If Not @error Then MsgBox(0, "", $oHrt_beat)                    ;;[ok]  [returning -1]
;------------------
$oApp_Obj = 0
MsgBox(0, "", "--The End--")
Exit

; #FUNCTION# =============================================================================
; Name...........: _ErrFunc()
; ========================================================================================
; User's COM error function. Will be called if COM error occurs
#forceref $oErrorHandler
Func _ErrFunc($oError)
    ; Do anything here.
    MsgBox(0, "", @ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
            @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
            @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
            @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
            @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
            @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
            @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
            @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
            @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
            @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc   ;==>_ErrFunc

but when using vtable no result due to wrong structure

;~ but when using Vtable
;~ Heartbeat dosent return anything no error nothing
;~ may be due to wrong vtable structure
$oApp_Obj = ObjCreateInterface($sCLSID_App, $sIID_IScripRTD, $tagIScripRTD_1)
    If Not @error Then MsgBox(0, "", "ObjCreate() successful")  ;;[returning successful]

;~ 
Local $oHrt_beat
$oApp_Obj.Heartbeat($oHrt_beat)
    If Not @error Then MsgBox(0, "", $oHrt_beat)        ;;[no error nothing]

so what's the correct vtable for IScripRTD & IRTDUpdateEvent

& how to implement (IRTDUpdateEvent* callback) in Autoit to start the server.

TypeLib file.txt

Link to comment
Share on other sites

I'm not exactly sure as to why you want the vtable if it was fine the way it was.  I don't have the patience to look through the methods to see if there's an issue.  The only difference I see is the first working attempt you get the HResult return of the function with no parameters passed.  The second time tho you attempt to use an autoit variable passed as a long ptr parameter... id guess that is at least part of the issue.   If you want that value then the first thing I'd do is create a dllstruct and pass dllstructgetptr($struct) as the parameter and pull the data out via structgetdata().  What's the worst that could happen? Break it twice as hard.

Edited by markyrocks
Link to comment
Share on other sites

@junkew Sorry forgot to mention that I did try your code but it not work.

input:-

ServerStart([in] IRTDUpdateEvent* callback, 
            [out, retval] long* result);

ConnectData([in] long topicId, 
            [in] SAFEARRAY(VARIANT)* strings, 
            [in, out] VARIANT_BOOL* newValues, 
            [out, retval] VARIANT* values);

RefreshData([in, out] long* topicCount, 
            [out, retval] SAFEARRAY(VARIANT)* data);

DisconnectData([in] long topicId);

Heartbeat([out, retval] long* result);

ServerTerminate();

output:-

"ServerStart hresult(ptr;long);" & _
"ConnectData hresult(long;intptr;ptr;variant);" & _
"RefreshData hresult(long;intptr);" & _
"DisconnectData hresult(long);" & _
"Heartbeat hresult(long*);" & _
"ServerTerminate hresult();"

Main problem is to implement (IRTDUpdateEvent* callback)
can you show me how to do it.

Edited by jugador
Link to comment
Share on other sites

  • 2 weeks later...

I search through @trancexx, @Danyfirex, @Bilgus and few other’s posts but still no luck on IRTDUpdateEvent interface callback
I know you people are busy but still it’s been two week without any help from the forum members.
At-least say or give some hint :mad2: , what I am missing in my post to get solution on this.
Or should I assume that ServerStart( IRTDUpdateEvent* callback ) can’t be done in Autoit.
 
again this also failed.......

#include <AutoItConstants.au3>
#include <WinAPI.au3>
#include <array.au3>

;;--------
Const $sCLSID_App = "{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}"
Const $sIID_IScripRTD  = "{EC0E6191-DB51-11D3-8F3E-00C04F3651B8}"
Const $sIID_IRTDEvent  = "{A43788C1-D91B-11D3-8F39-00C04F3651B8}"
;;--------
Const $dtag_IRTDUpdateEvent = _
                            "UpdateNotify hresult();" & _
                            "GetHeartbeatInterval hresult(long*);" & _
                            "PutHeartbeatInterval hresult(long);" & _
                            "Disconnect hresult();"
;;--------
Const $sIID_IUnknown = "{00000000-0000-0000-C000-000000000046}"

;;=============
Global $oErrorHandler
$oErrorHandler = ObjEvent("AutoIt.Error", "_ErrFunc")
;;=============

;;--------
Local $oApp_Obj

$oApp_Obj = ObjCreateInterface($sCLSID_App, $sIID_IScripRTD, Default)
    If Not @error Then MsgBox(0, "", "ObjCreate() successful")  ;;[ok]

Local $pIRTDUpdateEvents
Local $ooIRTDUpdateEvents = ObjectFromTag("IRTDUpdateEvent_", $dtag_IRTDUpdateEvent, $pIRTDUpdateEvents)
    If Not @error Then MsgBox(0, "", "ObjCreate() successful")  ;;[ok]

Local $prt_IUnknown = $ooIRTDUpdateEvents()

;;ServerStart should return a 1 on success, and a negative value or 0 on failure.
Local $oServer_Start
$oServer_Start = $oApp_Obj.ServerStart($prt_IUnknown)
    If Not @error Then MsgBox(0, "", $oServer_Start)  ;;[error ==> 0x80020005 (Type mismatch) ]
    #cs
    err.number is:      0x80020005
    err.windescription: Type mismatch.

    err.description is:
    err.source is:
    err.helpfile is:
    err.helpcontext is:
    err.lastdllerror is:    0
    err.scriptline is:  38
    err.retcode is:     0x00000000
    #ce

$oApp_Obj = 0
MsgBox(0, "", "--The End--")
Exit
;;--------

;;=============
Func IRTDUpdateEvent_UpdateNotify($pSelf)
    Return $S_OK
EndFunc   ;==>IRTDUpdateEvent_UpdateNotify

Func IRTDUpdateEvent_GetHeartbeatInterval($pSelf, $pvalue)
    Local $S_result = 0
    ConsoleWrite('> $S_result: ' & $S_result & @CRLF)
    Return $S_result
EndFunc   ;==>IRTDUpdateEvent_GetHeartbeatInterval

Func IRTDUpdateEvent_PutHeartbeatInterval($pSelf, $pvalue)
    ConsoleWrite('> $pvalue: ' & $pvalue & @CRLF)
    Return $S_OK
EndFunc   ;==>IRTDUpdateEvent_PutHeartbeatInterval

Func IRTDUpdateEvent_Disconnect($pSelf)
    Return $S_OK
EndFunc   ;==>IRTDUpdateEvent_Disconnect

Func IRTDUpdateEvent_QueryInterface($pSelf, $pRIID, $pObj)
    Local $sIID = StringFromGUID($pRIID)
    If $sIID = $sIID_IUnknown Then
        DllStructSetData(DllStructCreate("ptr", $pObj), 1, $pSelf)
        Return $S_OK
    ElseIf $sIID = $sIID_IRTDEvent Then
        DllStructSetData(DllStructCreate("ptr", $pObj), 1, $pSelf)
        Return $S_OK
    Else
        ConsoleWrite('QueryInterface ' & $sIID & @CRLF)
        Return $E_NOINTERFACE
    EndIf
EndFunc   ;==>IRTDUpdateEvent_QueryInterface

Func IRTDUpdateEvent_AddRef($pSelf)
    Return 1
EndFunc   ;==>IRTDUpdateEvent_AddRef

Func IRTDUpdateEvent_Release($pSelf)
    Return 1
EndFunc   ;==>IRTDUpdateEvent_Release
;;=============

;;--------
Func StringFromGUID($pGUID)
    Local $aResult = DllCall("ole32.dll", "int", "StringFromGUID2", "struct*", $pGUID, "wstr", "", "int", 40)
    If @error Then Return SetError(@error, @extended, "")
    Return SetExtended($aResult[0], $aResult[2])
EndFunc   ;==>StringFromGUID
;;--------
;;--------
Func ObjectFromTag($sFunctionPrefix, $tagInterface, ByRef $tInterface, $fPrint = False, $bIsUnknown = Default, $sIID = "{00000000-0000-0000-C000-000000000046}") ; last param is IID_IUnknown by default
    If $bIsUnknown = Default Then $bIsUnknown = True
    Local $sInterface = $tagInterface ; copy interface description
    Local $tagIUnknown = "QueryInterface hresult(ptr;ptr*);" & _
            "AddRef dword();" & _
            "Release dword();"
    ; Adding IUnknown methods
    If $bIsUnknown Then $tagInterface = $tagIUnknown & $tagInterface
    ; Below line is really simple even though it looks super complex. It's just written weird to fit in one line, not to steal your attention
    Local $aMethods = StringSplit(StringReplace(StringReplace(StringReplace(StringReplace(StringTrimRight(StringReplace(StringRegExpReplace(StringRegExpReplace($tagInterface, "\w+\*", "ptr"), "\h*(\w+)\h*(\w+\*?)\h*(\((.*?)\))\h*(;|;*\z)", "$1\|$2;$4" & @LF), ";" & @LF, @LF), 1), "object", "idispatch"), "hresult", "long"), "bstr", "ptr"), "variant", "ptr"), @LF, 3)
    Local $iUbound = UBound($aMethods)
    Local $sMethod, $aSplit, $sNamePart, $aTagPart, $sTagPart, $sRet, $sParams, $hCallback
    ; Allocation
    $tInterface = DllStructCreate("int RefCount;int Size;ptr Object;ptr Methods[" & $iUbound & "];int_ptr Callbacks[" & $iUbound & "];ulong_ptr Slots[16]") ; 16 pointer sized elements more to create space for possible private props
    If @error Then Return SetError(1, 0, 0)
    For $i = 0 To $iUbound - 1
        $aSplit = StringSplit($aMethods[$i], "|", 2)
        If UBound($aSplit) <> 2 Then ReDim $aSplit[2]
        $sNamePart = $aSplit[0]
        $sTagPart = $aSplit[1]
        $sMethod = $sFunctionPrefix & $sNamePart
        If $fPrint Then
            Local $iPar = StringInStr($sTagPart, ";", 2), $t
            If $iPar Then
                $t = "Ret: " & StringLeft($sTagPart, $iPar - 1) & "  " & _
                        "Par: " & StringRight($sTagPart, StringLen($sTagPart) - $iPar)
            Else
                $t = "Ret: " & $sTagPart
            EndIf
            Local $s = "Func " & $sMethod & _
                    "( $pSelf ) ; " & $t & @CRLF & _
                    "EndFunc" & @CRLF
            ConsoleWrite($s)
        EndIf
        $aTagPart = StringSplit($sTagPart, ";", 2)
        $sRet = $aTagPart[0]
        $sParams = StringReplace($sTagPart, $sRet, "", 1)
        $sParams = "ptr" & $sParams
        $hCallback = DllCallbackRegister($sMethod, $sRet, $sParams)
        If @error Then
            ConsoleWrite('! ' & @error & ' ' & $sMethod & @CRLF & @CRLF)
        EndIf

        DllStructSetData($tInterface, "Methods", DllCallbackGetPtr($hCallback), $i + 1) ; save callback pointer
        DllStructSetData($tInterface, "Callbacks", $hCallback, $i + 1) ; save callback handle
    Next
    DllStructSetData($tInterface, "RefCount", 1) ; initial ref count is 1
    DllStructSetData($tInterface, "Size", $iUbound) ; number of interface methods
    DllStructSetData($tInterface, "Object", DllStructGetPtr($tInterface, "Methods")) ; Interface method pointers
    Return ObjCreateInterface(DllStructGetPtr($tInterface, "Object"), $sIID, $sInterface, $bIsUnknown) ; pointer that's wrapped into object
EndFunc   ;==>ObjectFromTag
;;--------


; #FUNCTION# =============================================================================
; Name...........: _ErrFunc()
; ========================================================================================
; User's COM error function. Will be called if COM error occurs
#forceref $oErrorHandler
Func _ErrFunc($oError)
    ; Do anything here.
    ConsoleWrite(@ScriptName & " (" & $oError.scriptline & ") : ==> COM Error intercepted !" & @CRLF & _
            @TAB & "err.number is: " & @TAB & @TAB & "0x" & Hex($oError.number) & @CRLF & _
            @TAB & "err.windescription:" & @TAB & $oError.windescription & @CRLF & _
            @TAB & "err.description is: " & @TAB & $oError.description & @CRLF & _
            @TAB & "err.source is: " & @TAB & @TAB & $oError.source & @CRLF & _
            @TAB & "err.helpfile is: " & @TAB & $oError.helpfile & @CRLF & _
            @TAB & "err.helpcontext is: " & @TAB & $oError.helpcontext & @CRLF & _
            @TAB & "err.lastdllerror is: " & @TAB & $oError.lastdllerror & @CRLF & _
            @TAB & "err.scriptline is: " & @TAB & $oError.scriptline & @CRLF & _
            @TAB & "err.retcode is: " & @TAB & "0x" & Hex($oError.retcode) & @CRLF & @CRLF)
EndFunc   ;==>_ErrFunc

 

Link to comment
Share on other sites

uh maybe a  dumb question..

did you set interval and trying UpdateNotify() manually I didnt see that in your examples above

https://docs.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.irtdupdateevent.heartbeatinterval?view=excel-pia#Microsoft_Office_Interop_Excel_IRTDUpdateEvent_HeartbeatInterval

Quote

Setting the HeartbeatInterval property to -1 will result in the Heartbeat() method not being called.

your vtable looks right to me..

https://github.com/objectcomputing/OpenDDS/blob/master/tools/excelRTD/IRTDServer.idl

have you tried changing the pointers form long* to ptr? sometimes it atleast crashes to show it got called

Link to comment
Share on other sites

9 hours ago, Bilgus said:

did you set interval and trying UpdateNotify() manually I didnt see that in your examples above

Thank's @Bilgus

No. I haven’t set anything

The thing is I am using software that has a feature to get Real-time Data (RTD) to excel.
As RTD function is based on COM technology, so instead of excel I want to use Autoit to retrieve this data.
Basically trying to make Rtd Client in Autoit.

Now when I do....

;~ [IRtdServer.Heartbeat method Returns a Long value. a positive number indicates that the server is active]
;~ [work perfectly]  
;~ [return -1] [indicate server not started]

Local $oHrt_beat
$oHrt_beat = $oApp_Obj.Heartbeat()
    If Not @error Then MsgBox(0, "", $oHrt_beat)

So to make it work need to implement IRtdServer.ServerStart method first.

0000.PNG.f9bb8ad76f2e313a25ad9064ff9464e0.PNG

This is Rtd Client in C#  https://github.com/crosbymichael/thinkorswim/tree/master/ThinkOrSwim

but my bad as no knowledge in C#

Edited by jugador
Link to comment
Share on other sites

Its a little fuzzy but I remember having to go through hoops to make a callback function that was compatible with autoit and my COM object

I still think you should try setting the interval and try calling the update function directly to see if it works

Butttttttttttttt as referenced here:

https://weblogs.asp.net/kennykerr/Rtd8

Quote

There are two very important requirements that you must keep in mind. This applies equally to developers using C# or C++ for their implementations.

The first is that the IRTDUpdateEvent interface pointer that the RTD server receives must only be called from the apartment in which it was received. Since this interface pointer is received in a call to the RTD server’s ServerStart method it follows that this interface must only be called from the apartment that the RTD server lives in. This is where a strong foundation in COM is helpful. If you’re a .NET developer you’re probably scratching your head wondering what on earth an apartment is.

so it might be that you have to jump through hoops like creating a shared piece of memory in the excel process and then monkey patch in some asm to act as an intermediary

maybe I'm just making it complicated..

 

Link to comment
Share on other sites

Start with this thread from @LarsJ  as I feel your server looks logically similar to whats done with iuiautomation events.

 

Clients in python seem to exist so no reason to believe it cannot be done with Autoit

I think there are only a dozen of people max in the forum that can help in detail.

My advice make a c# server with visual studio that works with excel. Share that so we have a reproducer for working with an rtd server.

From there we could look to make an RTD client similar as excel would do.

Probably easier is just to use a different api to the server thats less complicated.

 

 

Link to comment
Share on other sites

  • 2 weeks later...

@Bilgus

After lot of search found this github C++ project which is working.....
https://github.com/seenuchennai/TradingTools/tree/master/AmibrokerFeeder/RTDMan/source

He created callback interface to implement (IRTDUpdateEvent* callback)
and use OpenEvent & SetEvent to call IRTDUpdateEvent. UpdateNotify() which is _WinAPI_CreateEvent & _WinAPI_SetEvent in Autoit.

https://github.com/seenuchennai/TradingTools/blob/master/AmibrokerFeeder/RTDMan/source/rtd_callback.cpp

//https://github.com/seenuchennai/TradingTools/blob/master/AmibrokerFeeder/RTDMan/source/rtd_callback.cpp
#include "rtd_callback.h"
 
CallbackImpl::CallbackImpl(){
    
    Event_RTD_Update = OpenEvent( EVENT_MODIFY_STATE , false, _T("RTD_UPDATE") );
                
    if( Event_RTD_Update == NULL ){
        throw "RTD_UPDATE Open error - " + GetLastError() ;
    }    
}

HRESULT STDMETHODCALLTYPE CallbackImpl::UpdateNotify(){     

    if( Event_RTD_Update )        
        SetEvent ( Event_RTD_Update );          // Signal Update
    
    return S_OK;
}

HRESULT STDMETHODCALLTYPE  CallbackImpl::Disconnect(){
    return S_OK;
}
HRESULT STDMETHODCALLTYPE  CallbackImpl::get_HeartbeatInterval( long *value){
    *value=-1;
    return S_OK;
}
HRESULT STDMETHODCALLTYPE  CallbackImpl::put_HeartbeatInterval( long value ){
    return S_OK;
}

https://github.com/seenuchennai/TradingTools/blob/master/AmibrokerFeeder/RTDMan/source/rtd_client.cpp

//https://github.com/seenuchennai/TradingTools/blob/master/AmibrokerFeeder/RTDMan/source/rtd_client.cpp
void RTDClient::startServer(){

    long    server_status = -1;

    CComObject<CallbackImpl>::CreateInstance(&callback);    // Create Callback Object   
    callback->AddRef();

    HRESULT hr  = comObjectScripRTD->ServerStart( callback , &server_status );

    while(  server_status <= 0  ){      // Returns 0 if callback null, +ve if SUCCESS

        std::cout << "Unable to start RTD Server. Waiting \r";
        std::flush(std::cout);
        
        Sleep(1000);
        hr  = comObjectScripRTD->ServerStart( callback , &server_status );  // Try to connect to RTD server again
    }
    std::cout << "RTD Server Started \t\t\t\t\t\t\t" << std::endl;
}

@LarsJ has also started a thread on ObjectFromTag() to implement COM callback.

So guys please help me to convert this c++ code to autoit.
not the whole code just callback interface [ CallbackImpl() ] & RTDClient::startServer () portion.

Edited by jugador
Link to comment
Share on other sites

Do you have a server that we can use to test the code? I would rather not have to start implementing a server myself.

Link to comment
Share on other sites

On 2/17/2021 at 7:33 PM, LarsJ said:

Do you have a server that we can use to test the code? I would rather not have to start implementing a server myself.

@LarsJ sorry no such server as its for a trading software.

but on google search “Rtd Server Github” got this link......

https://www.codeproject.com/articles/245265/guide-to-writing-custom-functions-in-excel-part

https://github.com/mdvx/kafka-rtd

https://github.com/mdvx/redis-rtd

https://github.com/mdvx/crypto-rtd

https://github.com/mdvx/rabbit-rtd

Edited by jugador
Link to comment
Share on other sites

Hi @junkew.

I'm not 100% sure.

Out of the box, autoitobject_internal won't work as the interface needed is IRtdServer. my code only support IDispatch and IUnknown.

You can easily override the IUnknown::QueryInterface when calling the IDispatch function from my code.

That would allow you to return the IRtdServer interface pointer, created and managed in AutoIt code.

 

I'm a bit unsure how the callback works. If Excel provides the IRTDUpdateEvent object to our IRtdServer method, then there is no problem, and it should be possible.

If Excel does not provide the IRTDUpdateEvent object i am not sure where it is supposed to come from.

 

TLDR;

Maybe, but it would require building the IRtdServer part of the code yourself :)

Link to comment
Share on other sites

Link to comment
Share on other sites

@junkew.

They define a class that implements IRtdServer :)

IRtdServer is indeed a COM Object

ALL COM Objects (to my knowledge) inherits from IUnknown. When a COM object is recived, IUnknown::QueryInterface is called to receive the pointer for the requested Interface, specified by a IID. A COM Object can have many interfaces associated.

So if you use my code as is, Excel would call the IUnknown::QueryInterface with IID for IRtdServer and since the default QueryInterface function in my code does not support that IID, it would return 0x80004002 (E_NOINTERFACE) and Excel would do nothing else with the Object.

Link to comment
Share on other sites

@junkew.

I've just tried to see if this approach would work.

I don't have a registered Excel version to play around with, so i used WPS as a substitute.

It does not seem like Excel looks for the object in the ROT. I suspect it looks in the windows registry for the ActiveX DLL.

If so, a dummy DLL would be needed to deliver the object, at least.

 

Disclaimer: i do not have the full grasp of the RTD method in Excel, i may just not call it right.

Here's my code:

#include "AutoItObject_Internal.au3"
#include "AutoItObject_Internal_ROT.au3"

$sIIDs = FileRead("C:\Users\Frank\Downloads\iids.txt")

$oIDispatch = IDispatch(QueryInterface2)

$dwRegister = _AOI_ROT_register($oIDispatch, "MyServer.MyClass", True)

OnAutoItExitRegister("CleanUp")

ConsoleWrite(@CRLF&@CRLF) ;Spacer to see if QueryInterface is called at any time after the registration

While 1
    Sleep(10)
WEnd

Func QueryInterface2($pSelf, $pRIID, $pObj)
    Local $sGUID=DllCall("ole32.dll", "int", "StringFromGUID2", "PTR", $pRIID, "wstr", "", "int", 40)[2]
    ConsoleWrite($sGUID&@CRLF)
    Return QueryInterface($pSelf, $pRIID, $pObj)
EndFunc

Func CleanUp()
    _AOI_ROT_revoke($dwRegister)
EndFunc

Here's the line used in "Excel":

=RTD("MyServer.MyClass","","AAA",10)

So my main issue currently is delivering the object to "Excel"

Edited by genius257
Link to comment
Share on other sites

@LarsJ one more find....
may be this one will solve the purpose of testing ServerStart(IRTDUpdateEvent* callback).

https://gist.github.com/govert/03df749f38b9582b1217

Create a minimal RTD server in C#

=> Start Visual Studio "As Administrator" (to enable the COM registration - see below).
=> Create a new Class Library project.
=> In the project properties, on the Build tab, enable "Register for COM interop". (Building with this setting on requires admin permissions, so Visual Studio should be run "As   Administrator" if the build fails with an "access denied" error.)
=> In the project properties, on the Debug tab, set the Start Action to be "Start external program" with Excel as the program (on my machine, the path to Excel is "C:\Program Files (x86)\Microsoft Office\Office15\EXCEL.EXE").
=> Add a Reference to the Microsoft.Office.Interop.Excel assembly.
=> Add the following code in the .cs file:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Office.Interop.Excel;

namespace MinimalRtd
{
    [
        ComVisible(true),
        Guid("86541CE9-EA39-4175-B37A-FDAE655AD30C"),
        ProgId("Minimal.RtdServer"),
    ]
    public class RtdServer : IRtdServer
    {
        private IRTDUpdateEvent _callback;
        private Timer _timer;
        private List<int> _topicIds;

        public int ServerStart(IRTDUpdateEvent callback)
        {
            Log("ServerStart");
            _callback = callback;

            _topicIds = new List<int>();

            _timer = new Timer();
            _timer.Tick += new EventHandler(TimerEventHandler);
            _timer.Interval = 1000;
            _timer.Start();

            return 1;
        }

        public void ServerTerminate()
        {
            Log("ServerTerminate");
            _timer.Dispose();
            _timer = null;
        }

        public object ConnectData(int topicId, ref Array strings, ref bool newValues)
        {
            Log("ConnectData: {0}, {1}", topicId, strings.GetValue(0));
            _topicIds.Add(topicId);
            return GetTime();
        }

        public void DisconnectData(int topicId)
        {
            Log("DisconnectData: {0}", topicId);
        }

        // Because we're using a System.Windows.Forms.Timer that was created on the main thread, 
        // this call will always be on the main thread.
        private void TimerEventHandler(object sender, EventArgs args)
        {
            _callback.UpdateNotify();
        }

        // All topics are updated, and get the same value
        public Array RefreshData(ref int topicCount)
        {
            string time = GetTime();

            topicCount = _topicIds.Count;
            object[,] data = new object[2, topicCount];
            for (int i = 0; i < topicCount; i++)
            {
                data[0, i] = _topicIds[i];
                data[1, i] = time;
            }
            return data;
        }

        public int Heartbeat()
        {
            return 1;
        }

        private string GetTime()
        {
            return DateTime.Now.ToString("hh:mm:ss:ff");
        }

        private void Log(string format, params object[] args)
        {
            Debug.Print(format, args);
        }
    }
}

Test the RTD call directly in Excel

=> In a new Workbook, enter the formula: =RTD("Minimal.RtdServer", "", "test")
=> The result should be a ticking time string, updated every 2 seconds (the default Application.RTD.ThrottleInterval is 2000).

Link to comment
Share on other sites

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...