2

In my Delphi application I'd like to get informed about network changes using the Microsoft Windows Network List Manager API (NLM): http://msdn.microsoft.com/library/ee264321

I've looked at the linked "How to register for NLM events" example and translated it to Delphi. However, I have no idea how to continue with this.

var
    pNLM: INetworkListManager;
    pCpc: IConnectionPointContainer;
    pConnectionPoint: IConnectionPoint;
    pSink: IUnknown;
    dwCookie: LongInt;
const
    IID_IConnectionPointContainer: TGUID = '{B196B284-BAB4-101A-B69C-00AA00341D07}';
    IID_IUnknown: TGUID = '{00000000-0000-0000-C000-000000000046}';
begin
    if Succeeded(CoCreateInstance(CLASS_NetworkListManager, nil, CLSCTX_ALL, IID_INetworkListManager, pNLM)) then
    begin
        if Succeeded(pNLM.QueryInterface(IID_IConnectionPointContainer, pCpc)) then
        begin
            if Succeeded(pCpc.FindConnectionPoint(IID_INetworkEvents, pConnectionPoint)) then
            begin
                if Succeeded(pCpc.QueryInterface(IID_IUnknown, pSink)) then
                begin
                    pConnectionPoint.Advise(pSink, dwCookie);
                end;
            end;
        end;
    end;
end;

The article sais:

"After you have created the INetworkListManager object above you will receive INetworkEvents notifications from that point forward. pSink implements the INetworkEvent interface including those event processing methods such as NetworkAdded, NetworkDeleted, NetworkConnectivityChanged, and NetworkPropertyChanged."

Unfortunately I have no idea how to do that. There's no further instructions and so I hope someone here could instruct me / provide an example what else to do to call custom procedures for those events. Thanks.

CodeX
  • 717
  • 7
  • 23

1 Answers1

7

You are passing the wrong object to Advise(). You are passing the IConnectionPointContainer object. You need to instead write your own class that implements the INetworkEvents interface, then create an instance of the class and pass that object to Advise(). That is how the NLM (or any other COM object that uses Connection Points) is able to send events to your code.

Update: You need to change your NLMEvents unit to keep the NLM object alive after StartNLMEventListener() exits, then the events will work correctly. The way you have it coded, the NLM object is local to StartNLMEventListener() and thus is released when StartNLMEventListener() exits, which unregisters your event sink.

Try this instead:

unit NLMEvents;

interface

function StartNLMEventListener: HResult;
function StopNLMEventListener: HResult;

implementation

uses
  Windows, ActiveX, NETWORKLIST_TLB, SysUtils, Unit1;

type
  TMyNetworkEvents = class(TInterfacedObject, INetworkEvents, INetworkConnectionEvents, INetworkListManagerEvents)
  public
    function NetworkAdded(networkId: TGUID): HResult; stdcall;
    function NetworkConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
    function NetworkDeleted(networkId: TGUID): HResult; stdcall;
    function NetworkPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
    function ConnectivityChanged(NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
    function NetworkConnectionConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
    function NetworkConnectionPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
  end;

var
  pNLM: INetworkListManager = nil;
  dwCookie1: LongInt = -1;
  dwCookie2: LongInt = -1;
  dwCookie3: LongInt = -1;

const
  IID_IConnectionPointContainer: TGUID = '{B196B284-BAB4-101A-B69C-00AA00341D07}';
  //IID_IUnknown: TGUID = '{00000000-0000-0000-C000-000000000046}';
  //CLSID_NetworkListManager: TGUID = '{DCB00C01-570F-4A9B-8D69-199FDBA5723B}';

function TMyNetworkEvents.ConnectivityChanged(NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('ConnectivityChanged');
  Result := S_OK;
end;

function TMyNetworkEvents.NetworkConnectionConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('NetworkConnectionConnectivityChanged');
  Result := S_OK;
end;

function TMyNetworkEvents.NetworkConnectionPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('NetworkConnectionPropertyChange');
  Result := S_OK;
end;

function TMyNetworkEvents.NetworkAdded(networkId: TGUID): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('NetworkAdded');
  Result := S_OK;
end;

function TMyNetworkEvents.NetworkConnectivityChanged(networkId: TGUID; NewConnectivity: NLM_CONNECTIVITY): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('NetworkConnectivityChanged');
  Result := S_OK;
end;

function TMyNetworkEvents.NetworkDeleted(networkId: TGUID): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('NetworkDeleted');
  Result := S_OK;
end;

function TMyNetworkEvents.NetworkPropertyChanged(networkId: TGUID; fFlags: NLM_NETWORK_PROPERTY_CHANGE): HResult; stdcall;
begin
  Form2.Memo1.Lines.Add('NetworkPropertyChanged');
  Result := S_OK;
end;

function StartNLMEventListener: HResult;
var
  pCpc: IConnectionPointContainer;
  pConnectionPoint: IConnectionPoint;
  pSink: INetworkEvents;
begin
  if pNLM = nil then
  begin
    Result := CoCreateInstance(CLASS_NetworkListManager, nil, CLSCTX_ALL, IID_INetworkListManager, pNLM);
    if Failed(Result) then
      Exit;
  end else
  begin
    Result := S_OK;
  end;

  if Succeeded(pNLM.QueryInterface(IID_IConnectionPointContainer, pCpc)) then
  begin
    pSink := TMyNetworkEvents.Create as INetworkEvents;

    if dwCookie1 = -1 then
    begin
      if Succeeded(pCpc.FindConnectionPoint(IID_INetworkEvents, pConnectionPoint)) then
      begin
        pConnectionPoint.Advise(pSink, dwCookie1);
        pConnectionPoint := nil;
      end;
    end;

    if dwCookie2 = -1 then
    begin
      if Succeeded(pCpc.FindConnectionPoint(IID_INetworkConnectionEvents, pConnectionPoint)) then
      begin
        pConnectionPoint.Advise(pSink, dwCookie2);
        pConnectionPoint := nil;
      end;
    end;

    if dwCookie3 = -1 then
    begin
      if Succeeded(pCpc.FindConnectionPoint(IID_INetworkListManagerEvents, pConnectionPoint)) then
      begin
        pConnectionPoint.Advise(pSink, dwCookie3);
        pConnectionPoint := nil;
      end;
    end;
  end;
end;

function StopNLMEventListener: HResult;
var
  pCpc: IConnectionPointContainer;
  pConnectionPoint: IConnectionPoint;
begin
  if pNLM <> nil then
  begin
    if Succeeded(pNLM.QueryInterface(IID_IConnectionPointContainer, pCpc)) then
    begin
      if dwCookie1 <> -1 then
      begin
        if Succeeded(pCpc.FindConnectionPoint(IID_INetworkEvents, pConnectionPoint)) then
        begin
          pConnectionPoint.Unadvise(dwCookie1);
          pConnectionPoint := nil;
        end;
      end;

      if dwCookie2 <> -1 then
      begin
        if Succeeded(pCpc.FindConnectionPoint(IID_INetworkConnectionEvents, pConnectionPoint)) then
        begin
          pConnectionPoint.Unadvise(dwCookie2);
          pConnectionPoint := nil;
        end;
      end;

      if dwCookie3 <> -1 then
      begin
        if Succeeded(pCpc.FindConnectionPoint(IID_INetworkListManagerEvents, pConnectionPoint)) then
        begin
          pConnectionPoint.Unadvise(dwCookie3);
          pConnectionPoint := nil;
        end;
      end;
    end;

    pNLM := nil;
  end;

  dwCookie1 := -1;
  dwCookie2 := -1;
  dwCookie3 := -1;

  Result := S_OK;
end;

end.
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you very, very much for your rapid and detailed reply! I implemented your example as advised, but wasn't successful yet. I'd like to note (just in case it matters) that I had to make some small changes, because the compiler complained: dwCookie:DWORD -> LongInt, NetworkPropertyChange -> NetworkPropertyChanged. Maybe this needs to be changed as well: pSink:INetworkEvents -> pSink:TMyNetworkEvents (not sure, tried both). Everything compiles and Advise is called correctly, but the NLA events aren't triggered (breakpoints and Memo output). What might be the problem? – CodeX Jul 03 '14 at 21:28
  • Also CLSID_NetworkListManager wasn't available in NETWORKLIST_TLB, so I declared it as TGUID = '{DCB00C01-570F-4A9B-8D69-199FDBA5723B}' (same as CLASS_NetworkListManager). Is that correct? Just two more observations: Advise(...) returns 0 as expected. dwCookie becomes 1 after the call. I'm not sure if that's correct, I assumed it'll become some kind of longer id. – CodeX Jul 03 '14 at 21:30
  • The compiler complains because you are likely missing units in your `uses` clause. `Advise()` really does output a `DWORD` for the cookie, which is defined in the `Windows` unit, for instance. And as long as `Advise()` returns 0 then use the cookie value as-is. Whatever `Advise()` sets the cookie to, that value needs to be passed to `Unadvise()` later. Usually it is an index into the connection point's internal table, but that is not a requirement, it can be anything the connection point implementation wants to use. It is an opaque value to you, don't try to interpret it. – Remy Lebeau Jul 03 '14 at 22:21
  • If the TLB defines `CLASS_NetworkListManager` then the TLB is buggy because it is supposed to be named `CLSID_NetworkListManager` per the documentation and standard practice. But if `CLASS_NetworkListManager` is at least the right GUID value then so be it. And no, `pSink` needs to be declared as `INetworkEvents`, not `TMyNetworkEvents`. – Remy Lebeau Jul 03 '14 at 22:25
  • In uses I have "Windows, NETWORKLIST_TLB, ActiveX". Advise() is declared in ActiveX as "IConnectionPoint = interface ['{B196B286-BAB4-101A-B69C-00AA00341D07}'] ... function Advise(const unkSink: IUnknown; out dwCookie: Longint): HResult; stdcall;". If that's not correct which unit contains the correct declaration? – CodeX Jul 03 '14 at 22:44
  • I created NETWORKLIST_TLB myself with Delphi XE and Windows 7 by using Component > Import Component > Import a Type Library > Network List Manager 1.0 Type Library from C:\Windows\system32\netprofm.dll. I don't know any other way to created the required TLB, so I have no idea why there could be any difference between the two names (CLASS_NetworkListManager vs. CLSID_NetworkListManager). – CodeX Jul 03 '14 at 22:46
  • Unfortunately I don't know how to proceed. Either there's something wrong with my constellation here (as shown in my previous two comments) or the suggested solution isn't complete. Please help! – CodeX Jul 06 '14 at 14:10
  • Just use whatever the TLB provides. The names are wrong, but as long as the GUIDs and layouts are correct, it will be fine. – Remy Lebeau Jul 06 '14 at 22:39
  • Well, I've set up everything as you've instructed but the events still aren't triggered. That's why I listed everything that's varying from your instructions hoping that it might help you to see the problem. Maybe there's some detail missing in the code? Everything compiles but the events are simply not triggered. Do you have any suggestions what I can do? – CodeX Jul 06 '14 at 23:34
  • Is `Advise()` succeeding or failing? Have you tried doing anything to make network connections go up and down? Have you tried registering sinks for the `INetworkListManagerEvents` and `INetworkConnectionEvents` interfaces as well as `INetworkEvents`? – Remy Lebeau Jul 07 '14 at 15:52
  • `Advise()` returns 0, so I guess it succeeds. Yes of course I tried many different network changes to trigger the events (enable/disable NICs in Network Connections, plug/unplug ethernet cable, change settings in Network and Sharing Center), but as I said no event was triggered in the application. And I even extended the class by the 2 interfaces (they contain a total of 3 functions) you're mentioning now. But still unlucky. May I upload the project and post the link so you can have a look at it directly? – CodeX Jul 07 '14 at 16:44
  • I've added my complete current source code in my question. Could you please try it our for yourself? – CodeX Jul 07 '14 at 17:56
  • THANK YOU! It's working now and I learned from your explanation for the future. Thank you very much! :) – CodeX Jul 08 '14 at 15:35