DotRas Wrapper : SynchronizingObject

Feb 23, 2010 at 10:19 AM

Hi all,

i've used the DotRas library to make a GPRS connection. This works very good when I use the library directly in my application.

But now I want to put all my code in a DLL. Most of it is just copy&paste. But now I never get the "DialCompleted" and "StateChanged" event.

In my application i wrote :

 

RasDialer rd = new RasDialer();

public Form1()
        {
                InitializeComponent();

                rd.SynchronizingObject = this;
                rd.DialCompleted += new System.EventHandler<DotRas.DialCompletedEventArgs>(Dialer_DialCompleted);
                rd.StateChanged += new System.EventHandler<DotRas.StateChangedEventArgs>(Dialer_StateChanged);           
        }

 

By passing the "this" pointer, the events were always firered, and I catched them in the form application. But in the dll, there is no "this" pointer.

My dll is as follow :

public GSM(System.Windows.Forms.Form SyncObject)
        {
            rd.SynchronizingObject =SyncObject;
            rd.DialCompleted += new System.EventHandler<DotRas.DialCompletedEventArgs>(Dialer_DialCompleted);
            rd.StateChanged += new System.EventHandler<DotRas.StateChangedEventArgs>(Dialer_StateChanged);

        }

 

I make an object in my app  :

GSM myGSM = new GSM(this);

 

If the applications passes its "this" pointer to the DLL, and the DLL to the RAS library, the events never occur.

So I have to get the "this" pointer from the app, trough the wrapper dll, to the RASlib. Do you know how I can do this ?

 

Coordinator
Feb 23, 2010 at 2:45 PM

All that SynchronizingObject does is take an ISynchronizingObject (implemented within the base Control class in System.Windows.Forms) and use it to handle event synchronization. How you get that information into the object doesn't matter. If it's all in the same app domain there shouldn't be any additional wiring needed to get the events to fire.

On a side note, you're not trying to put all of the DotRas code into your DLL as well are you?

Feb 23, 2010 at 2:55 PM

Hi,

thanks for your reply.

I just putted 2 functions in my dll, to make a connection & disconnect the GPRS.

I do have to set that SynchronizingObject ? What object can I use for that ?

Coordinator
Feb 23, 2010 at 3:04 PM

Any object that implements the ISynchronizeInvoke interface can be used to synchronize the events to prevent cross thread exceptions. Typically with a Windows Forms interface you'd use a control or form from that object to handle it. If you do not have a user interface you do not have to worry about setting the property as long as your application only uses 1 main thread.

Unless you're doing something strange in your application, setting the synchronizing object, wiring the events, and calling DialAsync should work correctly.

Feb 23, 2010 at 3:44 PM

So if it can't be this SynchronizingObject, it has to be something with another change I made. In my function to make the GPRS connection, I use :

// Open phonebook and create temporary DU entry to connect
RasPhoneBook AllUsersPhoneBook = new DotRas.RasPhoneBook(this.components);
AllUsersPhoneBook.Open();

This is the code I use in the application were the RAS lib is included directly.

In my DLL, I can't use "this.components".  Untill now, I don't give any arguments with the function in the DLL :

// Open phonebook and create temporary DU entry to connect
RasPhoneBook AllUsersPhoneBook = new DotRas.RasPhoneBook();
AllUsersPhoneBook.Open();

Is it possible this causes the problem of not getting any events ?  What argument I need to give with the function ?

Coordinator
Feb 23, 2010 at 7:04 PM

The container system is what Windows Forms uses for disposal of the objects contained by the form. You really shouldn't be calling that constructor, it's there for Visual Studio designer support. The Dispose method the form calls dispose on any objects the container is containing. Which that wouldn't hurt anything, other than your phone book might not be getting disposed of properly unless you're disposing of it yourself. Also, the RasPhoneBook class has no interaction with the RasDialer component on your form since the two aren't related, so that's definitely not what's causing your issue.

If you're wrapping the events from one class and passing them to your interface you should have created events to handle that on your wrapping class. This might give you some ideas on what you can do:

using System;
using DotRas;

public class GrpsWrapper : IDisposable
{
    private RasDialer dialer;

    public GrpsWrapper()
    {
        this.dialer = new RasDialer();
        this.dialer.StateChanged = new EventHandler<StateChangedEventArgs>(this.dialer_StateChanged);
        this.dialer.DialCompleted = new EventHandler<DialCompletedEventArgs>(this.dialer_DialCompleted);
    }

    public event EventHandler<StateChangedEventArgs> DialerStateChanged;
    public event EventHandler<DialCompletedEventArgs> DialerDialCompleted;

    public void DoSomething(string entryName, string phoneBookPath, ISynchronizeInvoke synchronizingObject)
    {
        this.dialer.EntryName = entryName;
        this.dialer.PhoneBookPath = phoneBookPath;
        this.dialer.SynchronizingObject = synchronizingObject;

        this.dialer.DialAsync();
    }

    private void dialer_StateChanged(object sender, StateChangedEventArgs e)
    {
        if (this.DialerStateChanged != null)
        {
            this.DialerStateChanged(this, e);
        }
    }

    private void dialer_DialCompleted(object sender, DialCompletedEventArgs e)
    {
        if (this.DialerDialCompleted != null)
        {
            this.DialerDialCompleted(this, e);
        }
    }
}
Feb 24, 2010 at 10:03 AM
Edited Feb 24, 2010 at 10:07 AM

Well that is what I'm trying to do. My Code is almost the same, but still I don't get any events in my DLL.

It's the first time for me I make my own events, so maybe I don't trow them the good way. But still then I should catch the events DialCompleted & StatusChanged from the RASlib.

Can you give my code a look ?

Remark : The Log() function just writes a string to a text file, not important. The function "GSM.DebugEnable()" just enables this Log() function.

 

My DLL code :

 

 

public class GSM : Debug
    {
       
        public delegate void StateChangedHandler(object o, GPRSArgs e);
        public event StateChangedHandler EventStateChanged;

        public delegate void DialCompletedHandler(object o, GPRSArgs e);
        public event DialCompletedHandler EventDialCompleted;
        
        public class GPRSArgs : EventArgs
        {
            public string NewState;

            public GPRSArgs(string s)
            {
                NewState = s;
            }
        }
        
        RasDialer rd;
        private const String tempDUEntry = "__##TempRASDU";
        private const String GPRSDialNbr = "*99#";

        public GSM()    // , StateChangedHandler StateChanged, DialCompletedHandler DialCompleted
        {
            Log("+GSM_Constructor");

            rd = new RasDialer();
            rd.DialCompleted += new EventHandler<DialCompletedEventArgs>(Dialer_DialCompleted);
            rd.StateChanged += new EventHandler<StateChangedEventArgs>(Dialer_StateChanged);
            
            Log("-GSM_Constructor");
        }

        private void Dialer_StateChanged(object sender, StateChangedEventArgs e)
        {
            Log("+Dialer_StateChanged");
            
            // Throw new event with this state
            GPRSArgs e1 = new GPRSArgs("Dialer_StateChanged");
            EventStateChanged(new object(), e1);

            Log("-Dialer_StateChanged");
        }

        private void Dialer_DialCompleted(object sender, DialCompletedEventArgs e)
        {
            Log("+Dialer_DialCompleted");
           
            string sNewState = "";
            
            if (e.Cancelled)
                sNewState = "Cancelled!";
            else if (e.TimedOut)
                sNewState = "Connection attempt timed out!";
            else if (e.Error != null)
                sNewState = e.Error.ToString();
            else if (e.Connected)
                sNewState = "Connection successful!";
            
            // Throw new event with this state
            GPRSArgs e1 = new GPRSArgs(sNewState);
            EventDialCompleted(new object(), e1);
            
            Log("-Dialer_DialCompleted");
        }

// This function makes a new modem connection in Windows
public bool GPRSConnect(System.ComponentModel.ISynchronizeInvoke SyncObject, string UserInit) // example : "internet.proximus.be" { const string sName = "GPRSConnect"; Log("+" + sName); Log("UserInit : " + UserInit); bool bRetVal = false; try { // Write UserInit in registry RegistryKey hklm = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E96D-E325-11CE-BFC1-08002BE10318}\\0000", true); hklm.SetValue("UserInit", "AT+CGDCONT=1,\"IP\",\"" + UserInit + "\""); hklm.Close(); Log("Writing UserInit OK"); rd.SynchronizingObject = SyncObject; // Open phonebook and create temporary DU entry to connect RasPhoneBook AllUsersPhoneBook = new DotRas.RasPhoneBook(); AllUsersPhoneBook.Open(); //__##TempRASDU will most likely not be used by a user to name a connection RasEntry re = RasEntry.CreateDialUpEntry(tempDUEntry, GPRSDialNbr, RasDevice.GetDeviceByName("HSPA", "modem", false)); re.Options = RasEntryOptions.ModemLights | RasEntryOptions.ShowDialingProgress | RasEntryOptions.IPHeaderCompression | RasEntryOptions.RemoteDefaultGateway; re.ExtendedOptions = RasEntryExtendedOptions.Internet; re.NetworkProtocols = RasNetworkProtocols.IP; if (AllUsersPhoneBook.Entries.Contains(tempDUEntry)) { AllUsersPhoneBook.Entries.Remove(tempDUEntry); } // add the entry AllUsersPhoneBook.Entries.Add(re); //use the dialler to dial the newly made DU entry rd = new RasDialer(); rd.EntryName = tempDUEntry; rd.PhoneBookPath = RasPhoneBook.GetPhoneBookPath(RasPhoneBookType.AllUsers); // Dial the DU connection rd.DialAsync(); bRetVal = true; } catch (Exception ex) { LogError(sName, ex.Message); } Log("-" + sName); return bRetVal; } public bool GPRSDisconnect() { const string sName = "GPRSDisconnect"; Log("+" + sName); bool bRetVal = false; try { //use the dialer to get the temp DU entry rd.EntryName = tempDUEntry; rd.PhoneBookPath = RasPhoneBook.GetPhoneBookPath(RasPhoneBookType.AllUsers); // get the active connections for this dialler -> will be only one System.Collections.ObjectModel.ReadOnlyCollection<RasConnection> ac = rd.GetActiveConnections(); System.Collections.IEnumerator ie = ac.GetEnumerator(); if (ie.MoveNext()) // just in case... ((RasConnection)ie.Current).HangUp(); // Open phonebook and remove temporary DU entry RasPhoneBook AllUsersPhoneBook = new DotRas.RasPhoneBook(); AllUsersPhoneBook.Open(); if (AllUsersPhoneBook.Entries.Contains(tempDUEntry)) AllUsersPhoneBook.Entries.Remove(tempDUEntry); bRetVal = true; } catch (Exception ex) { LogError(sName, ex.Message); } Log("-" + sName); return bRetVal; } }

 

 

 

My application code :

 

 

public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

       
        public void DoSomething(object sender, GSM.GPRSArgs e)
        {
            lblStatus.Text = "got it";
        }

        GSM myGSM = new GSM();

        private void button1_Click(object sender, EventArgs e)
        {
            GSM.DebugEnable();
            
            btnGPRSconnect.Enabled = false;
            btnGPRSconnect.Refresh();

            myGSM.EventDialCompleted += new GSM.DialCompletedHandler(DoSomething);
            myGSM.EventStateChanged += new GSM.StateChangedHandler(DoSomething);

            if (btnGPRSconnect.Text.Equals("Connect"))
            {

                if (myGSM.GPRSConnect(this, "internet.proximus.be"))
                    btnGPRSconnect.Text = "Disconnect";
                else
                    MessageBox.Show("ERROR : GPRS Connect failed.");
            }
            else if (btnGPRSconnect.Text.Equals("Disconnect"))
            {
                if (myGSM.GPRSDisconnect())
                    btnGPRSconnect.Text = "Connect";
                else
                    MessageBox.Show("ERROR : GPRS Disconnect failed.");
            }

            btnGPRSconnect.Enabled = true;
        }
    }

 

 

Coordinator
Feb 24, 2010 at 2:59 PM
Edited Feb 24, 2010 at 3:07 PM

Well, the issue is definitely in your code.

In your constructor for the object you're creating the instance of the RasDialer and wiring up the events. If you look inside your connect method, you're creating another new instance of that object and not wiring the events.

There's also a couple other things I've noticed:

1) When using events with .NET 2.0 or later you should be using the generic EventHandler(Of T) delegate to define the event.

public event EventHandler<GPRSArgs> EventStateChanged;
public event EventHandler<GPRSArgs> EventDialCompleted;

This is common practice and actually encouraged by the C# compiler since not using generics will cause a warning. In the end they work the same way as yours, you just don't need to define a ton of delegates all over the place to handle your events. When working with events you should also check to make sure the event isn't null before calling it as shown in my example. When you were calling the event, you also passed a new object into the event when raising it. The sender object is the object which caused the event, which is almost always the current object.

2) There are some issues when you're creating the entry.

When you're calling RasEntry.CreateDialUpEntry you're doing a couple things I discourage with that particular method. The "modem" string you have hard-coded there should be replaced with RasDeviceType.Modem which I have pulled from the Windows SDK. If the value ever changes your application will be responsible of knowing the new value for each platform your application supports. Also, after the call you're overwriting all of the default settings that have been set within the method. This is why I have the work item to change those to booleans, no one knows how to use flags in C#. The call needs to be |= unless you intend on overwriting all the default settings. Again, that method supports settings on every supported platform not just the one you're using.

3) You're not disposing of the RasDialer as instructed in the SDK.

This is a big one. After doing a lot of work with the Windows API and finding the nuances of working with RAS, if the RasDialer isn't disposed of properly once it finishes dialing or if your application crashes your application will REQUIRE a reboot on the machine to release the connection before it can be used again. This includes if you call it from Windows or from your application. Since you're doing asynchronous dialing, you should definitely do something about this. Since you've got it defined at class level, your best bet would be to implement IDisposable on the class and make sure you call it from your application before the Form or whatever is using it terminates.

Feb 24, 2010 at 4:27 PM
Edited Feb 24, 2010 at 4:29 PM

Thanks !

It was such a stupid fault of me to overwrite the instance of the RasDialer. I was searching for a much more complex solution :s  Now it works fine.

For the events, I just followed a tutorial on the internet. The syntax you suggest is much easier to understand too.

 

Thanks again to answer my questions this fast & in detail. I've never been helped so good on a forum.

Coordinator
Feb 24, 2010 at 6:13 PM
Edited Feb 24, 2010 at 6:21 PM

Lol isn't it always the simple problems that cause us the most trouble? I'm glad to hear everything is working correctly.

If you have any other questions (related to the project) feel free to ask. :)

Edit: I thought I'd better warn you. The GetActiveConnections method is not specific to a dialer, it retrieves all RAS connections that are open on the machine. If you've got more than one connection open (ie a dial-up and VPN connection) it could disconnect either of them depending on the order in which Windows reports them and I push them into the collection.

Also, your syntax might be a bit easier for working with enumerators if you use the foreach loop instead of playing with the enumerators directly.

foreach (RasConnection connection in rd.GetActiveConnections())
{
    connection.HangUp();
}

The foreach keyword works with any object that implements IEnumerable (which all collections and arrays do) and returns that enumerator that it uses to handle stepping through the items. Now if the collection returned from the method is null it would throw a null reference exception, but that particular method will never return a null instance, I've made sure of that. Just a helpful hint :)

Feb 25, 2010 at 7:56 AM
Edited Feb 25, 2010 at 2:46 PM

Thanks again. This hint with the foreach() loop is easier indeed.

I've got one more question. In my GPRSConnect() function I use the following code :

 

// Write UserInit in registry
          RegistryKey hklm = Registry.LocalMachine.OpenSubKey("SYSTEM\\CurrentControlSet\\Control\\Class\\{4D36E96D-E325-11CE-BFC1-08002BE10318}\\0000", true);
          hklm.SetValue("UserInit", "AT+CGDCONT=1,\"IP\",\"" + UserInit + "\"");
          hklm.Close();

 

For instance, I write      AT+CGDCONT=1,"IP","internet.proximus.be"        in the registry.

This registry key is always correct for the first Modem-connection device Windows has ever found. If you connect the same/other modem to another USB port, Windows will make a new key  in   ...\\0001

Now I could write in every key representing a modem { \\0000, \\0001, \\0002, ... }  the same UserInit value (using a foreach loop), but that wouldn't be a nice way to do I think.

Is there a possibility I can give this UserInit string to the RASlib ?

 

Coordinator
Feb 25, 2010 at 2:28 PM
Edited Feb 25, 2010 at 2:31 PM

Unfortunately no, there isn't. I've been researching how to do more with the RasDevice classes than simply listing them out, but as of right now the SDK doesn't have that functionality (either in 1.1 or in 1.2 under development). There has been a number of user requests and I created a work item to log any work done for this option, but I haven't done anything but research how to do it as of yet.

Edit:

Here's the link to the work item in case you're interested in following its progress: http://dotras.codeplex.com/WorkItem/View.aspx?WorkItemId=10011