White Papers                  Home |  White Papers  |  Message Board |  Search |  Products |  Purchase | News |  Web Log |
 
            

Creating Multi-threaded .NET components
for COM Interop with Visual FoxPro

 

By Rick Strahl
www.west-wind.com
rstrahl@west-wind.com

 

Last Update:

September 14, 2012

 

Other Links:

   Download Examples for this article
   Leave a Comment or Question

 

Other articles in the series:

   1. Passing objects between FoxPro and .NET COM Components
   2. Handling .NET Events in FoxPro via COM Interop
   3. Creating multi-threaded components .NET for COM Interop with Visual FoxPro


This is Article 3 of a 3 part series on COM Interop between .NET and Visual FoxPro.

 

Multithreading is a powerful feature that allows you to asynchronously execute code while continuing to work or provide a responsive user interface to your application. .NET makes multithreading very easy and in this installment youíll see how to create and execute multithreaded components and communicate with them via events.

 

In the last installment of this series I introduced the concept of multithreaded .NET components exposed to COM. In that article I used a very simple multithreading example, that made a simple email component asynchronous, running on a separate thread and using events to communicate the progress back to our Visual FoxPro client application.

 

The point of the simple example was to demonstrate that multithreading and events go together like a wave and a surfboard. In order for multithreaded code to communicate with your client component some mechanism needs to be applied to let the client application know that a status change has occurred. In some rare situations you may not need to be notified; a pure call and forget scenario is actually a common pattern in distributed applications. But in most situations, especially in user interface scenarios, the calling application needs to know whatís going on in the background.

 

So letís start with typical user interface scenarios. Letís create a new component that downloads a file for us in the background and notifies our application of progress. This is similar to the email example I showed last time. Itís a good review to make this article self contained, but Iíll mix things up a little by using an Asynchronous Delegate for the multithreaded code invocation.

An Asynchronous Http Component

In this articleís accompanying code thereís a class called wwHttp which is a wrapper around the .NET HttpWebRequest class that is used to access HTTP content. The class provides a single class interface to Http interface instead of the multi-object requirements that HttpWebRequest requires. There are a number of methods that allow you to download (and upload) content to a Web server.

 

The method in the class that weíre interested in is GetUrlBytes(). This method downloads an HTTP response from a URL into an array of bytes. Why bytes and not a string Ė in .NET strings cannot contain binary data as they do in FoxPro so strings are really Unicode strings and Binary data is raw data that can contain anything. The method takes two parameters Ė a Url and a buffer size. It returns a Byte array or a binary response of the downloaded data. Note that if we want to retrieve binary content we have to return the data as byte[]. String values cannot contain binary data like Nulls and upper some ASCII characters so if a string were returned the Http result would be corrupted. Listing 1 shows what the C# method code does in abbreviated form. The full source code is available in the code download.

 

Listing 1: Downloading data from a Web site with the GetUrlBytes Method

[ComSourceInterfaces( typeof(IwwHttpEvents) )]

[ClassInterface(ClassInterfaceType.AutoDual)]

[ProgId("DotNetCom.wwHttp")] 

public class wwHttp

{

Ö properties and events

 

 

/// <summary>

/// Fires progress events when receiving data from the server

/// </summary>

public event ReceiveDataDelegate ReceiveData;

public delegate void ReceiveDataDelegate(

        object sender, ReceiveDataEventArgs e);

 

/// <summary>

/// Fires progress events when using GetUrlEvents() to retrieve a URL.

/// </summary>

public event ReceiveDataDelegate SendData;

 

public byte[] GetUrlBytes(string Url,

                      long BufferSize)

{

HttpWebResponse Response =

      this.GetUrlResponse(Url);

BinaryReader HttpResponse =

new BinaryReader(

      Response.GetResponseStream());

 

if (HttpResponse == null)

      return null;

 

if (BufferSize < 1)

      BufferSize = 8192;

 

long lnSize = BufferSize;

if (Response.ContentLength > 0)

      lnSize = this.WebResponse.ContentLength;

else

      lnSize = 0;

 

byte[] Result = new byte[lnSize];

byte[] lcTemp = new byte[BufferSize];

 

ReceiveDataEventArgs EventArguments =

      new ReceiveDataEventArgs();

EventArguments.TotalBytes = (int)lnSize;

 

lnSize = 1;

int Count = 0;

long TotalBytes = 0;

 

while (lnSize > 0)

{

      if (TotalBytes + BufferSize > 

            this.WebResponse.ContentLength)

            BufferSize =

            this.WebResponse.ContentLength -

            TotalBytes;

 

      lnSize = HttpResponse.Read(Result,

                        (int) TotalBytes,

                        (int) BufferSize);

      if (lnSize > 0)

      {

            Count++;

            TotalBytes += lnSize;

 

            // *** Raise an event if hooked up

            if (this.ReceiveData != null)

            {

                  /// *** Update the event handler

            EventArguments.CurrentByteCount =

                                    (int) TotalBytes;

            EventArguments.NumberOfReads = Count;

            EventArguments.CurrentChunk = Result; 

            this.ReceiveData(this,EventArguments);

 

                  // *** Check for cancelled flag

                  if (EventArguments.Cancel)

                  {

                        this.bCancelled = true;

                        break;

                  }

            }

            Thread.Sleep(10); // give up timeslice

      }

} // while

 

 

HttpResponse.Close();

 

// *** Send Done notification

if (this.ReceiveData != null &&

      !EventArguments.Cancel)

{

      // *** Update the event handler

      EventArguments.Done = true;

     

      // *** If events are hooked up assume we

      // *** might want to read the last response

      // *** NOT THREAD SAFE! assume 1 instance

      this.AsyncHttpResponseBytes = Result;

 

      this.ReceiveData(this,EventArguments);

}

 

return Result;

}

 

// *** result storage when firing events

public byte[] AsyncHttpResponseBytes = null;

 

}

 

/// *** Event Interface for wwHttp class

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

public interface IwwHttpEvents

{

   [DispId(0)]void ReceiveData(object sender,
                               ReceiveDataEventArgs e);

   [DispId(1)]void SendData(object sender,

                            ReceiveDataEventArgs e);

}

 

The behavior code is not all that important, but pay attention to the code around the ReceiveData and SendData blocks. If you read the last article this should look familiar. ReceiveData and SendData are event delegates and if set, the code in the GetUrlBytes method fires these events as data is retrieved. Note the COM Event export attribute on the wwHTTP class:

 

[ComSourceInterfaces( typeof(IwwHttpEvents) )]

 

which points the event interface at the IwwHttpEvents interface at the bottom of Listing 1. With this code in place you can now call the GetUrlBytes() method of the .NET COM object and attach to two published events with the code shown in Listing 2.

 

Listing 2 Ė Synchronous Http retrieval from Visual FoxPro firing events

SET PROCEDURE TO wwUtils ADDIT

lcUrl = "http://www.west-wind.com/files/wwIpstuff.zip"

lcFile = "wwiptstuff.zip"

 

loHttp = CREATEOBJECT("DotNetCom.wwHttp")

loHttpEvents = CREATEOBJECT("IwwHttpEvents")

 

EVENTHANDLER(loHttp,loHttpEvents)

 

lcBinary = loHttp.GetUrlBytes(lcUrl,4096)

 

STRTOFILE(lcBinary,lcFile)

GoUrl(lcFile)

 

 

 

DEFINE CLASS IwwHttpEvents AS session OLEPUBLIC

  IMPLEMENTS IwwHttpEvents IN "DotNetCom.wwHttp"

 

PROCEDURE IwwHttpEvents_ReceiveData(

         sender AS VARIANT, e AS Variant) AS VOID

 

*** e is autogenerated as a parm but you canít use e Ė itís an Alias!!!
*** Watch out for this. Eeasiest: Just rename e parm to EventArg

EventArg = e

 

WAIT WINDOW NOWAIT "Downloading: " + TRANSFORM( ;

  EventArg.CurrentByteCount,"999,999,999") +;

  " of " +;

  TRANSFORM(EventArg.TotalBytes,"999,999,999")

                 

ENDPROC

 

PROCEDURE IwwHttpEvents_SendData(

  sender AS VARIANT, e AS VARIANT) AS VOID

ENDPROC

 

ENDDEFINE

 

Iím implementing the Event interface as a separate class and hooking up code to the ReceiveData event so I can show progress of the download. An instance of the event object is then bound to the wwHttp object with the VPF EVENTHANDLER() function. When you run this code now you should see a wait window while the file is downloaded and saved to disk when the method call completes. This is still synchronous code.

 

If you run this synchronous code the file will download just fine and you will see the Wait Window updating, but the user interface is blocked Ė youíre waiting for the download to complete. So letís make the GetUrlBytes() operation asynchronous. The Wait Window is populated from the second paramameter e which contains current and total bytes as well as flags for Cancel and Done and a CurrentChunk of the data retrieved.

Using an Asynchronous Delegate

In the last article I created a brand new thread to run the async operation. However, .NET supports a number of ways to run code asynchronously and in this case an easier way is to use an Asynchronous Delegate. Delegates in .NET are function pointers that can be used to execute code as we saw in the last article. When you create a new thread you also use a delegate to tell the Thread object which method to invoke. To review hereís the code we used to create a new thread and have it call the parameterless SendMailWithEvents() method:

 

ThreadStart delSendMail =  new ThreadStart(this.SendMailWithEvents);

Thread myThread = new Thread(delSendMail);

myThread.Start();

 

ThreadStart is a delegate and Thread.Start internally calls the delegateís Invoke method once the new thread has been created to start up the method you specified.

 

Delegates by themselves allow directly for asynchronous operation via the BeginInvoke and EndInvoke methods. When using these methods, .NET runs the method call asynchronously on a .NET Thread Pool thread. .NET provides a fixed number of Thread Pool threads (25 by default) which are recycled after they have completed. Thread Pool threads tend to be much more efficient in startup and tear down as .NET doesnít have to spin up a new thread from scratch. Instead threads are returned to the pool and reused whenever a request completes.

 

Delegates also provide more flexibility than raw threads when calling methods. Raw threads can only call methods that take no parameters and can return no result value. Delegates on the other hand can work with any method signature and provide an option for a Callback method that is called when the thread completes processing.

 

A delegate makes sense in this scenario because we need to call GetUrlBytes() which takes two parameters. Because of the parameters, we need to define a custom delegate that matches this signature:

 

private delegate byte[]

    delGetUrlBytes(string Url,int BufferSize);

 

 

Next we need a method that we can call from the COM object to start the Async operation:

 

public void GetUrlBytesAsync(string Url,

                             int BufferSize)

{

   delGetUrlBytes AsyncDelegate = new delGetUrlBytes( this.GetUrlBytes );

   IAsyncResult ar = AsyncDelegate.BeginInvoke(Url,BufferSize,null,null);

}

 
The code creates a new instance of the delegate and points it at the GetUrlBytes() method of the class. We then call BeginInvoke() on this method to start processing. BeginInvoke() takes the same two parameters that GetUrlBytes() takes, plus two additional parameters: One for a callback method which we donít need here and an optional state object. Note that I must capture the result in an IAsyncResult reference Ė the return value is significant even if it isnít used here as weíre doing a Call and Forget Async call.

 

I set both of the final parameters to null. We donít need a callback in this scenario because we donít need to pick up the result value from GetUrlBytes(). If you go back and look at Listing 1 you can see that the GetUrlBytes method does this for us when events are firing by setting the AsyncHttpResponseBytes property with the resulting byte[] array.

 

Delegates can also pick up return values, but this process is a little more involved and beyond the scope of this article. In many cases itís actually a better choice to provide result values via specialized events that return the value from the actual async method code or by storing results as properties on the originating objects.

 

To avoid requiring a Callback method I opted to write the Http Response in the GetUrlBytes method to the AsyncHttpResponseBytes property just before the final event is fired:

 

// *** Send Done notification

if (this.ReceiveData != null &&
    !EventArguments.Cancel)

{

   // *** Update the event handler

   EventArguments.Done = true;

 

   // *** If events are hooked up assume we

   // *** might want to read the last response

   this.AsyncHttpResponseBytes = Result;

   this.ReceiveData(this,EventArguments);

}

 

return Result;

 

In FoxPro then the Event method needs to change just a touch to pick up the Http response with this code:

                 

IF EventArg.Done

   STRTOFILE(sender.AsyncHttpResponseBytes,

             "test.txt")

   WAIT WINDOW "Done..." nowait

ENDIF

 

Notice that I pick up the final result from the sender object not the EventArg. This is because the AsyncResponseBytes are stored on the wwHTTP object rather than the EventArg.

 

Ready to run the code? One more modification: Change the code you ran before to the async version of GetUrlBytes:

 

lcBinary = loHttp.GetUrlBytesAsync(lcUrl,4096)

 

You should now see your cursor return to you immediately while the download continues in the background and the Wait Window updates the download progress. You can go on with your own processing while .NET chews on the download. Cool.

 

Letís add the ability to cancel the download which is very easy. Take another look at Listing 1 and youíll notice that within that where loop thereís a check for the Cancel flag of the EventArgument thatís passed to VFP via the event parameter. Add these two lines to your startup code:

 

PUBLIC plCancel

plCancel = .f.

ON KEY LABEL ALT+X plCancel = .t.

 

and add this line to your ReceiveData method:

 

EventArg.Cancel = plCancel

 

and youíre done. If you run the code again you should now be able to press Alt-X and immediately stop downloading data. Note that not only does the event firing stop but the download as well is aborted as the Cancel flag is carried back into the .NET code.

Putting it together

Using a Wait Window is probably not a good way to display progress information from an asynchronous operation. Letís take the above example and wrap it up into a FoxPro component: A download form that can be used to download files in the background and display status information and inform you when the download is complete. Figure 1 shows what this dialog looks like when complete.

 

 

Figure 1 Ė  Multithreaded Download Dialog. This dialog drives the download of a file through a multithreaded .NET component and receives events for updating the download status.

 

This dialog uses two classes: A visual class that holds the form and a the IwwHttpEvents Interface implementation that receives the inbound events from the .NET COM object. The form contains an image control, and two labels plus the cancel button. In addition there are a handful of properties, cMessage, cHttpResponse, cOutputFileName and cDownloadUrl that can be set and are used internally to display the dialog display content.

 

The process starts with the StartDownload implementation as shown in Listing 3:

Listing 3 Ė Managing an HTTP Download from a .NET COM component using BindEvent()

* StartDownload

LPARAMETERS lcUrl, lcMessage

 

this.Visible = .t.

 

IF !EMPTY(lcMessage)

   this.lblMessage.Caption = lcMessage

   this.cMessage = lcMessage

ELSE

   this.lblmessage.Caption = this.cMessage

ENDIF

 

this.ldownloadcancelled = .f.

this.cDownloadurl = lcUrl

 

THIS.oHttp = CREATEOBJECT("DotNetCom.wwHttp")

this.oHttpEvents = NewObject("IwwHttpEvents",

                   "wwHttpDownloadDialog.prg")

EVENTHANDLER(this.oHttp,this.oHttpEvents)

 

*** Bind Interface method to method on form!

BINDEVENT(this.oHttpEvents,

           "IwwHttpEvents_ReceiveData",

           this,"OnReceiveData")

 

this.lblDownloadProgress.Caption =

      "Starting download..."

 

*** Start the download

this.oHttp.GetUrlBytesAsync(

  this.cDownloadUrl,this.nDownloadbuffersize)

 

The process here is very similar to the code used in the previous example. The only difference is the extra call to BINDEVENT() to bind the ReceiveData event to this form so that we can handle it inside of this class instead in the Interface declaration which lies outside of this class domain. This provides additional encapsulation and keeps the logic self contained. The other key method in the class is the OnReceiveData method which receives these events forwarded through BINDEVENT().

 

Listing 4 Ė Handling the BINDEVENT() forwarded event code in the control

* OnRececeiveData
LPARAMETERS
sender as Object,
   e as DotNetCom.ReceiveDataEventArgs

 

*** Bug - need to reassign

EventArg = e

 

this.lblDownloadProgress.Caption = ;

  "Downloading from " + ;

  THIS.cdownloadurl + CHR(13) +  ;

  ALLTRIM(TRANSFORM(EventArg.CurrentByteCount,;

           "999,999,999")) +;

  " of " + LTRIM(TRANSFORM(EventArg.TotalBytes,;

                     "999,999,999")) +;

  " bytes"

 

this.lblDownloadProgress.Refresh()

 

IF THIS.lDownloadcancelled

   EventArg.Cancel = .T.

ENDIF

IF EventArg.Done

   this.chttpresponse = ;

        this.oHttp.AsyncHttpResponseBytes

 

   IF !EMPTY(THIS.cOutputFileName)

      STRTOFILE(this.cHttpResponse,;

                this.cOutputFileName)

   ENDIF

  

   *** Fire notification event

   *** you can BINDEVENT to this

   this.OnDownloadcomplete()

ENDIF

 

This code is responsible for updating the form display. It also checks for the cancel flag which can be set at any point by clicking on the Cancel button. If triggered the cancel setting is returned to the .NET component which stops downloading and aborts. The CancelDownload method also hides the form.

 

When the download is done that thereís a cOutputFilename property which if set is used to save the download to file. In addition an internal method OnDownloadComplete is fired to indicate completion. The method is unimplemented in the form but you can easily bind to it with BINDEVENT.

 

To call this code from an application youíd write code like this:

 

LPARAMETER lcUrl, lcFile
IF EMPTY
(lcUrl)

   lcUrl = "http://www.west-wind.com/files/wwClient.zip"

ENDIF

IF EMPTY(lcFile)

   lcFile = "wwclient.zip"

ENDIF

*** Public so it stays alive after this PRG exits Ė itís async after all

PUBLIC poDownload

 

poDownload = NEWOBJECT("wwHttpDownloadDialog",

                   "wwHttpDownloadDialog.vcx")

poDownload.Width = 340

poDownload.cOutputFileName = "wwClient.zip"

poDownload.nDownloadBufferSize = 8196

 

poDownload.StartDownload(lcUrl,;

 "Downloading file from West Wind Technologies")

 

*** Hook up download complete notification Ďeventí

*** Donít need a class if you have some place to

*** hook this to Ė here we donít and BINDEVENT

*** requires an object to fire to

PUBLIC poNotification

poNotification=CREATEOBJECT("DownloadNotification")

 

BINDEVENT(poDownload,"OnDownloadComplete",;

          poNotification,"OnDownloadComplete")

 

RETURN

 

 

DEFINE CLASS DownloadNotification as Custom

 

FUNCTION OnDownloadComplete

   poDownload.visible = .f.

   RELEASE poDownload

   DOEVENTS

   MESSAGEBOX("WAKE UP: Download is complete.")

ENDFUNC

 

ENDDEFINE

 

You can now easily download files in the background with progress and completion information. You can find this example in wwHttpDownloadDialog.prg.

Same problem different approach: Using a WinForm

The above approach works well, but you can take yet another approach: Create a Windows Forms interface and have it manage the download process completely externally from your FoxPro application. One advantage of this approach is that thereís no FoxPro code firing while the download is occurring. This reduces overhead and minimizes problems if VFP is busy processing and canít capture the events. Iíve included this example for you to play with in DownloadForm Example in the VS.NET project and the wwHttpDownload.scx form which shows how both the FoxPro and .NET Winforms examples work.

Automating a Windows Form

This brings us to the next topic, which is automating a WinForm through COM. WinForms require special attention. Why bring this up in a multithreading article? As youíll see in a minute youíre required to set up Windows forms on separate threads if you plan on having the form run in the background interactively.

 

Fist a few ground rules. .NET cannot create EXE COM servers. It can be done, but only by implementing the proper COM interfaces yourself using the raw COM Windows APIs. If you want the gory details you can find an example on how to do this here: http://blogs.msdn.com/adioltean/archive/2004/06/18/159479.aspx.

 

The lack of integrated DCOM COM support in .NET means that you canít expose WinForms that are contained in an EXE. If you want to publish WinForms via COM you MUST create the forms in a separate Class Library DLL. This is easily done even for a desktop application. Simply create a new Class Library project and move any of the forms you want use via COM into it. Everything continues to work just like it does if the forms were part of the EXE.

 

Once in a DLL you can publish forms via COM like any other objects. Itís actually quite easy and works the same as any other object. Slap on the appropriate COMattributes, add any event interfaces that you want to expose and off you go. The Download dialog example shows a simple mostly non-interactive sample of how this works.

 

However to demonstrate the core features Iím going to use a different example that demonstrates passing data to and from a WinForm. Iíll use a Customer Entry form as an example. Figure 2 shows the .NET input form in the Visual Studio designer.

 

 

Figure 2 Ė A WinForm exposed to COM. This form is exposed via COM and can be passed a Customer Business object that is displayed, can be edited and read from the FoxPro client code.

 

 

The form contains a number of textboxes. To keep things simple, I used a set of custom controls to databind the controls to a business object named Customer. Each textbox is bound to one of the fields of the business object. The Business Object is created in .NET and passed back to FoxPro, where the values are populated and the object is then passed back to .NET for databinding. Hereís what the code looks like in Visual FoxPro:

 

loNet = CREATEOBJECT(;

   'DotNetCom.DotNetComPublisher')

 

*** Create a Customer Object Instance

loCustomer = GetCustomer()

 

loCustomer.Name = "Rick Strahl"

loCustomer.Company = "West Wind Technologies"

loCustomer.creditLimit = 9999.99

 

loCustomer.oAddress.StreetAddress = "32 Kaiea"

loCustomer.oAddress.Phone = "808 579-8342"

loCustomer.oAddress.Email = "rick@hotmail.com"

 

PUBLIC loNetForm

loNetForm = CREATEOBJECT("DotNetCom.WinformCom")

 

*** Send customer object back to WinForm

loNetForm.LoadCustomer(loCustomer)

loNetForm.Show()  && Show and Bind

 

The WinForm itself uses the custom controls to databind and so LoadCustomer is a very simple method:

 

public void LoadCustomer(Customer Customer)

{

   if (Customer == null)

         return;

 

   this.Customer = Customer;

   this.BindData();                                             

}


BindData() is a custom databinding method that binds the Customer object members to the controls. Now if you run this code youíll see the form filled with the data passed from FoxPro. You can also go ahead and edit the data in the form. But thereís a problemÖ go back to the FoxPro Window and try to type into the Command Window. Ooops Ė the cursor is not responding. Weíve basically locked the FoxPro formís event loop while weíre on the WinForm. Until you close the WinForm, FoxProís UI is not accessible.

 

The problem here is that weíre essentially calling the WinForm modally. The form shows just fine, but thereís no real event loop running in it, so itís sharing the Windows message pump with your FoxPro application, which results in some, shall we say, odd behavior.

 

This is where the multithreading aspect comes in. In order to run a WinForm in the background and active while not affecting your Fox application, you have to run the form on a separate thread and give it its own event loop. Sounds complicated? Well, itís not. What we need to do is create the form on a separate thread. Hereís a Factory class for WinFormCOM that does the trick:

 

[ClassInterface(ClassInterfaceType.AutoDual)]

[ProgId("DotNetCom.WinFormComFactory")]     

public class WinFormComFactory

{

WinFormCom Instance = null;

 

public WinFormCom RunForm()

{

   // Create instance on this thread so

   // we can pass it back to caller

   this.Instance = new WinFormCom();

 

   System.Threading.Thread t =
      new System.Threading.Thread(new ThreadStart( this
.RunThread ) );

 

   // Important!!!

   t.ApartmentState = ApartmentState.STA;

   t.Start();

 

   return this.Instance;

}

 

public void RunThread()

{

   // Show the form

   this.Instance.Show();

   System.Windows.Forms.Application.Run(this.Instance);

}

}

 

RunForm() creates a new instance of a form and starts a new thread that executes the RunThread() method. RunForm() returns the instance of the form back to caller over COM. The new thread then starts and Show() is called on the new thread which causes the formís main UI processing to be tied to that particular thread. Application.Run() then starts on that thread to start a separate message pump for this new thread. So now there are two separate UI threads Ė the original thread for your FoxPro app, and the new WinForm UI thread.

 

Compile and then change the code use this code in Fox to run the form from:

 

PUBLIC loNetForm

loNetForm = CREATEOBJECT("DotNetCom.WinformCom")

 

to:

 

PUBLIC loNetForm

loForm = CREATEOBJECT("DotNetCom.WinFormCom")

loNetForm = loForm.RunForm()
ForceFoxFocus()

 

Now run the form and you should be able to switch back and forth between Fox and the WinForm freely and get the user interface to behave properly. You now have full access to the form. For example, change the Customer Name on the form, make sure you tab off the field (to force the data to bind back), then go back into FoxPro and do:

 

? loNetForm.Customer.Name

 

and you should now see the changed name as expected.

 

As you see, using WinForms over COM is a little bit more complicated than plain objects, but once you understand the Windows Event loop issue and the workaround itís easy enough to set this up for any form you need to run over COM.

Multithreading VFP COM components

So far weíve looked at multithreading scenarios where weíre using .NET to do the multithreaded processing. What if you want write your code with FoxPro instead? You can do that quite easily actually. Weíve covered everything that you need to make this happen. FoxPro 6 and later supports multithreaded STA COM servers, so if you want to run multiple VFP COM components itís as easy as creating a .NET method that can launch a VFP COM server on a new thread.

 

Listing 5 shows a generic .NET COM Launcher that runs a COM object asynchronously by calling a specific method with a single parameter and no return value.

 

Listing 5 -  A generic Asynchronous COM object launcher. This class makes it possible to run multiple VFP COM objects simultaneously and asynchronously to your mainline application.

[ClassInterface(ClassInterfaceType.AutoDual)]

[ComSourceInterfaces( typeof(IMultiThreadedComLauncherEvents) )]

[ProgId("DotNetCom.MultiThreadedComLauncher")] 

public class MultiThreadedComLauncher

{

      public string ProgId = "";

      public string Method = "";

      public object Parameter = null;

 

      public event ThreadStart ThreadCompleted;

 

      public void RunMultiThreadedCom(string ProgId,
                                      string Method, object Parameter)

      {

            this.ProgId =  ProgId;

            this.Method = Method;

            this.Parameter = Parameter;

 

            Thread Worker = new Thread(

                  new ThreadStart(this.RunMultiThreadedCom_ThreadMethod) );

 

            // Important for VFP components!

            Worker.ApartmentState = ApartmentState.STA;

            Worker.Start();

      }

 

      private void RunMultiThreadedCom_ThreadMethod()

      {

            Type ComType = Type.GetTypeFromProgID(this.ProgId);

            object ComObj =  Activator.CreateInstance(ComType);

            ComUtils.CallMethod(ComObj,this.Method,this.Parameter);

 

            if (this.ThreadCompleted)

                  this.ThreadCompleted();

      }

}

 

 

[InterfaceType(ComInterfaceType.InterfaceIsIDispatch)]

public interface IMultiThreadedComLauncherEvents

{

      [DispId(1)]void ThreadCompleted();

}


The code creates a new thread and then dynamically launches a COM server using late COM binding. The private method uses Reflection and the ComUtils function I introduced in the first article of the series to dynamically call the specified method with a parameter.

 

Letís try this out from VFP by creating a small sample server shown in Listing 4.

 

Listing 4 -  The Test Server. This server lets you test the multithreading behavior

DEFINE CLASS MultiThreadProcessing ;

      as Session OLEPUBLIC

 

FUNCTION StartProcessing(lcID)

LOCAL lnX, ltStarted

 

SET EXCLUSIVE OFF

 

IF !FILE("MultiThread.dbf")

   CREATE TABLE MultiThread( ID C(25),;

                    TimeStamp T,Count I)

   USE

ENDIF

 

USE MultiThread IN 0 SHARED

 

ltStarted  = DATETIME()

 

DECLARE INTEGER Sleep IN WIN32API INTEGER

 

FOR lnX = 1 TO 100

   INSERT INTO MultiThread ;

          (Id,TimeStamp,Count) values ;

          (lcId,DATETIME(),lnX)  

   DOEVENTS

 

   lnRand = RAND(-1)

   Sleep(100 * lnRand)  

ENDFOR

 

RETURN

 

ENDDEFINE

 

This server writes values into a table in a loop. When running multiple threads we should see values from the different servers which differentiate themselves by the ID passed in. To create a COM server from the code add the PRG to a project and compile with:

 

BUILD MTDLL multithreadprocessing FROM ;

         multithreadprocessing recompile

 

To test the server use the following code:

 

o = CREATEOBJECT("DotNetCom.MultiThreadedComLauncher")

? o.RunMultiThreadedCom(

"multithreadprocessing.multithreadprocessing",

"StartProcessing","test1")

 

o2 = CREATEOBJECT("DotNetCom.MultiThreadedComLauncher")

? o2.RunMultiThreadedCom(

"multithreadprocessing.multithreadprocessing",

"StartProcessing","test1")

 

o3 = CREATEOBJECT("DotNetCom.MultiThreadedComLauncher")

? o3.RunMultiThreadedCom(

"multithreadprocessing.multithreadprocessing",

"StartProcessing","test1")

 

When you run this code the method calls immediately return to you so your Fox code can go on and do any of its own processing, while the three servers in the background are running on separate threads. The result MultiThread.dbf table should show mixed up values from all three servers.

 

This is only a rough example that demonstrates how easy it is created multi-threaded FoxPro code. One issue you have in this scenario is that you donít know when processing is complete. You can either use table based message that let the Fox instance check for certain values, or you can use events on the COM server. I implemented a ThreadComplete event along with the EventHandler class. Iíll leave the exercise of hooking this up for your FoxPro code to you Ė based on this articles series you should be an expert at this now, right? <g>

A few things to watch out for

This is powerful stuff and I hope this article has given you some ideas of how you can take advantage of this functionality. But be careful with multithreading Ė itís an advanced feature and itís easy to abuse this functionality. Use it only if you really have to. Mulithreaded code can easily lead to very hard to debug errors.


One other thing to watch out for is shutting down your application while threads are still active. .NET is much more forgiving than native Windows Threads, but still-running threads can still cause your application to not shut down or in the worst case scenario crash if you shut it down. You should always build any multi-threaded code with some mechanism to shut down the multi-threaded code, ideally via a flag that is frequently checked inside of your multi-threaded code. As an alternative, .NET also includes methods to forcibly shut down a thread with Thread.Abort which should also be used with care.

 

If you are planning on writing multithreaded code be sure to read an article or two on the subject and especially on thread safety. Itís an important topic that Iíve glossed over here for the sake of focusing on VFP specific features and simple examples.

No needle looking for a thread

Multithreading can open up a whole new world of functionality to your FoxPro applications. You can interact with .NET components that are multithreaded and you can communicate with them by attaching to events. You can access WinForms and run them asynchronously so that your FoxPro code and user interface can interact with them. And finally you can even make your FoxPro code multithreaded as long as you can do so in the context of a COM component.

 

This article concludes a long series on COM Interop with .NET. Weíve covered a lot of ground in these articles and I hope youíve found them useful as you work with both with .NET and Visual FoxPro togetherÖ

 

 

Comments or Questions

If you find this article useful, consider making a small donation to show your support  for this Web site and its content.

 

By Rick Strahl
Rick Strahl is president of West Wind Technologies on Maui, Hawaii. The company specializes in Web and distributed application development and tools with focus on Visual FoxPro and .NET. Rick is author of West Wind Web Connection, a powerful and widely used Web application framework for FoxPro, West Wind HTML Help Builder and West Wind Web Store for both FoxPro and .NET. He's also a Microsoft MVP, a frequent speaker at international developer conferences and a frequent contributor to magazines and books. He is co-publisher of Code magazine. For more information please visit: http://www.west-wind.com/ or contact Rick at rstrahl@west-wind.com.

 

 

If you find this article useful, consider making a small donation to show your support  for this Web site and its content.


 



 

 

 
  White Papers                  Home |  White Papers  |  Message Board |  Search |  Products |  Purchase | News |