SendMessage/OnSendMessage implementation

Add-in Express™ Support Service
That's what is more important than anything else

SendMessage/OnSendMessage implementation
 
Ming Chao




Posts: 30
Joined: 2019-01-23
Hi,

i have read the article "wait a little" to use SendMessage/OnSendMessage to handle task on the main thread. this seems to be a sensitive area that i would like understand the concept correctly.

basically my apps makes API calls based on ui selections then the API will return a large set of table data that will parsed into a 2D array. finally the app will output the data to excel.

in order to output data to excel, i have a service class that utilize the SendMessage(int message, IntPtr wParam, IntPtr lParam) as below to send the message with parameters

//MyServiceClass
public void SendMessageToExcelMainThread(MyCustomObjectWrapper dataWrapper, Delegate callback)
{
    GCHandle dataWrapperHandle = GCHandle.Alloc(dataWrapper);
    IntPtr dataWrapperIntPtr = GCHandle.ToIntPtr(dataWrapperHandle);

    GCHandle callbackHandle = GCHandle.Alloc(callback);
    IntPtr callbackPtr = GCHandle.ToIntPtr(callbackHandle);

    AddinModule.SendMessage(MessageCode, dataWrapper, callbackPtr);

private void OutputDataToExcel(_Application excelApp, MyCustomObjectWrapper dataWrapper, Delegate callback)
{
   //my codes to handle output to excel
}
}


in a subclass extends ADXAddinModule class, OnSendMessage handler is set to the following
private void adx_OnSendMessage(object sender, ADXSendMessageEventArgs e)
{
    if (e.Message == MyMessageCode)
    {
        GCHandle dataWrapperHandle = GCHandle.FromIntPtr(e.WParam);
        GCHandle callbackHandle = GCHandle.FromIntPtr(e.LParam);
        MyCustomObjectWrapper dataWrapper = dataWrapperHandle.Target as MyCustomObjectWrapper;
        MyServiceClass.Callback callback = callbackHandle.Target as MyServiceClass.Callback;

        MyServiceClassInstance.OutputDataToExcel(ExcelApp, dataWrapper, callback); //calling MyServiceClass 

        if (callbackHandle.IsAllocated)
            callbackHandle.Free();
        if (dataWrapperHandle.IsAllocated)
            dataWrapperHandle.Free();
    }
}


my concerns are
1. is this implementation correct? does OnSendMessage() run on Excel main thread per implementation?
2. is parameter must be passed as IntPtr? well, there's way to pass without it but is this the recommended way?
3. is GCHandle being freed in a correct manner per implementation?
Posted 22 Oct, 2019 11:44:44 Top
Andrei Smolin


Add-in Express team


Posts: 18823
Joined: 2006-05-11
Hello Ming,

I think I may misunderstand this. It looks like you pass pointers to methods to be invoked when the message is received. I would suggest a simpler implementation. Below is a raw sketch:


    interface ITask
    {
        void DoTask();
    }
    class MyTask :ITask 
    {
        public object someData { get; private set; }
        public MyTask(object someData)
        {
            this.someData = someData;
        }
        void ITask.DoTask()
        {
            // use someData to complete the work
        }
    }
// in the add-in module or in another class:

        private const int WM_USER = 0x0400;
        private const int DONEXTTASK_MESSAGE = WM_USER + 1000;
        Queue<ITask> tasks = new Queue<ITask>();
        private void AddinModule_OnSendMessage(object sender, ADXSendMessageEventArgs e)
        {
            if (e.Message == DONEXTTASK_MESSAGE)
            {
                ITask task = tasks.Dequeue();
                task.DoTask();
            }
        }



Andrei Smolin
Add-in Express Team Leader
Posted 23 Oct, 2019 04:23:12 Top
Ming Chao




Posts: 30
Joined: 2019-01-23
thank you Andrei. Using queue makes the code much cleaner than passing pointer and not need to worry about freeing the GCHandles.
Posted 25 Oct, 2019 15:43:43 Top
Andrei Smolin


Add-in Express team


Posts: 18823
Joined: 2006-05-11
Hello Ming,

You are welcome.


Andrei Smolin
Add-in Express Team Leader
Posted 28 Oct, 2019 04:06:36 Top