A VB6 class to allow a TCP/IP server to map ports through a UPnP NAT router without manual intervention and retrieve the router's external public IP address.
Feature List
Queries and returns the external public address of the router's public (Internet facing) interface. Can add and remove port mappings for TCP and UDP ports. Uses the Windows UPnP Control Point interface instead of the sometimes more problematic NATUPnP library.
Packaged with a simple chat server and client to demonstrate basic use. PortMapper.cls contains comments documenting its properties, methods, and events.
Author Name
Bob Riemersma
System Requirements
Server OS:
Windows XP or later. Must have SSDP components ("Network Discovery") enabled and the Windows Firewall or other software firewall must allow UPnP traffic through.
NAT router:
Must support some level of UPnP port mapping ("Virtual Servers" or "Forwarded Ports" defined via UPnP). UPnP must be enabled (may require reboot).
License Info
Unencumbered freeware. No guarantee of support. Use at your own risk. This software is offered AS IS.
Testing
PortMapper has been tested on Windows XP SP3, Vista SP2, and Windows 7 RC using a D-Link EBR-2310 wired router (Version B1 hardware, Version 2.01 firmware).
Working With PortMapper
Much more detail can be found by reading the comments in PortMapper.cls.
To add PortMapper to your VB6 project copy PortMapper.cls and PortMapperCBs.cls from the SimpleServer folder to your project folder, then use Project|Add file... to add each of these two modules to your Project.
To use the demo chat client and server compile each of the two programs, then run the server and click its Listen button, then run the client and fill in the server's external IP address and a "chat handle" name and click its Connect button.
Be sure to scroll down for the latest version!
Last edited by dilettante; Sep 8th, 2010 at 08:03 PM.
Reason: Reposted attachment, added note re. Win7 testing
New version posted that seems to resolve the program crashes on XP SP3!
Anyone who downloaded the previous version of the attachment might want to get the new copy. The new sources are marked as version 1.3 instead of the previous 1.2, if you want to check this.
I'm playing with trying to compile this under Windows XP SP3 and it fails. So far from what I see the upnp.dll in WinXP doesn't provide COM type information for some of the interfaces in this library. The symptom is that the program won't compile, with the compiler complaining about a "missing UDT" (the usual message when something is undefined).
So at this point it looks like you have to compile under Vista or later.
I'm looking into a workaround for this. These old OSs get harder to support every day!
New version 1.4 that by default uses the late-bound "VBScript" approach to get around the limitations of compiling under Windows XP.
Version 1.3 must be compiled under Vista or later, but should run fine on any OS from XP forward.
Version 1.4 by default can be compiled under XP or later, and should run on any OS from XP forward.
Version 1.4 can be modified by changing two lines in PortMapperCBs.cls to compile the same way as Version 1.3 (using early binding). More details can be found in comments within PortMapperCBs.cls.
Oops!
Version 1.4 compiles but doesn't actually work properly on any OS. Right now compiling using 1.3 on Vista is the only absolutely known working configuration.
Last edited by dilettante; Nov 23rd, 2009 at 01:38 PM.
Reason: Removed 1.4
I have created a .TLB that extends the type information in the Win XP version of upnp.dll. It allows Version 1.3 to compile under XP if you add another reference (to the new UPnPExtXP.tlb) and make a one-line change to the Implements statement in PortMapperCBs.cls.
If there is interest I can post it, but Version 1.4 may meet everyone's needs unless they want to make more use of upnp.dll's capabilities from VB6 when compiling under Windows XP.
You are probably using it from the same computer. You can't use external IP from the computer on which the server is running. You have to run the server on one computer, and client on the other.
You are probably using it from the same computer. You can't use external IP from the computer on which the server is running. You have to run the server on one computer, and client on the other.
Actually this works fine for me.
The issues involved are probably:
Whether or not your router will turn connections addressed to its external port back into the private network.
Whether your PC's software firewall is allowing incoming connections on this port.
I've been using this for a while now in a program I made on XP, and it works very well. But I've now moved to windows 7 and when I run it, I get: error: -2147220223 Automation error
When I click debug it takes me to the sub: Private Sub PMCBs_DeviceAdded(ByVal Device As UPNPLib.IUPnPDevice)
and highlights the line: For Each Service In .Services
I've enabled network discovery, and the upnp service is running. So I don't know what this can be.
I just retested after recompiling the posted files and it is running fine on Windows 7 here.
If you used the 1.4 version (the most recently posted above) I can only think of one possibility. Have you tried to deploy the upnp.dll from Windows XP? The version in Windows 7 is different and already part of Windows 7.
However it sounds as if you are seeing this problem while testing within the IDE on Windows 7. Are you running the VB6 IDE elevated? If not this could be the problem. Ideally you are elevating the VB6.EXE IDE by using a manifest (and not simply using Run As Administrator or marking it to Run As Administrator via an application compatibility setting). This avoids a lot of registry virtualization issues. Be sure to test this elevated if you haven't though.
It is possible that the version of upnp.dll in Windows 7 lacks type information the program requires. This would be similar to the problem encountered on Windows XP.
I have not tried compiling this on a Windows 7 machine myself yet. But a version compiled under Vista seems to work on all three versions of Windows when I try it. I would expect the Windows 7 version of upnp.dll to be almost identical to the Vista version in terms of the type information provided.
Until I get VB6 installed on a Windows 7 machine I'm not sure what else I can suggest. MSDN does not describe any changes in upnp.dll for Windows 7, but we're probably looking at a very fine point here.
I was playing around with the portmappercbs module and I think I've fixed it.
I uncommented the implements statement and set XPcompile to true, even though this is windows 7.
I'll let you know if the error comes back.
That sounds odd. Are you by any chance using "XP Mode" to run VB6.EXE? If so, you aren't compiling on Windows 7 at all, so of course you'd have to fall back to the more limited XP approach to using upnp.dll. I really don't recommend using XP Mode for VB6.EXE. It severely handicaps your ability to do development for Windows-as-it-is and limits you to Windows-as-it-was.
If you're running into this with VB6 fully installed in Windows 7 (not the XP VM) I'd like to know about it.
No I'm not using vb6 in xp mode.
But actually, now it has started to give me the same error on my xp machine. If I do the fix that I posted above, then it doesn't give me the error, but it says "No router, upnp disabled, or firewall blocking upnp" when I am definitely connected to the router and no firewall is blocking upnp. Although I can still communicate through two computers on the same network.
EDIT: i have just tried downloading your original version from the first post and that still gives me the error. So it can't be anything that I've changed in the project.
EDIT 2: I have now managed to fix the error. I recently changed the channel of my wireless router to 3 instead of 1 to improve the signal, I didn't think this would make any difference to the program, but obviously it did.
So the program only works if the router is on channel 1. And I don't know why this would affect it at all. Is there any way that the program can be modified to work with the router on any channel?
The program doesn't know anything about wireless channels. Whether the router is wired or wireless makes no difference.
Your error is &H80040501 "Event subscription timeout." It means your router has failed to reply to a UPnP query.
The problem is at your router. Resetting/rebooting the router is the only known fix, assuming the router has UPnP enabled to begin with. Perhaps your efforts to hop channels caused a reset.
Use this interface to enumerate the list of services associated with a device.
Be aware that use of these properties cause the UPnP framework to contact the service across the network. If the service does not exist, the application is blocked until the connection request fails. It is recommended that applications use the Item property in most instances, and reserve the enumeration for instances when an application is starting up.
This means the service requests can still fail, just that we might recode to trap them locally.
By rewriting my code to use For X = 1 To .Services.Count and then .Services.Item(X) instead it would be easier to trap this error and produce some other result. But that "other result" can only be to raise another error or perhaps an error event. It doesn't cure anything.
I suppose it could be friendlier though, and my code could raise Opened(False) which the main program could handle more easily.
Last edited by dilettante; Sep 8th, 2010 at 07:59 PM.
Ok, I hadn't realized that Device.Services was not a full-blown collection in the VB sense. It is more like collections returned by Shell32 methods: you can't use an index to access it, only the key value (string).
The changes don't result in any "cure" for the problem described above. However the Opened event will be raised passing the value False to let the main program know that contact was not made with the UPnP router. That makes this a worthwhile if small improvement.
Hey nice job. Just a heads up. If using a port higher than 32767, the port sent to the router is invalid.
I see you are using the CUint function. I have removed that function, and this resolved the issue. I don't know if this is just an XP issue (i wouldnt believe so).
Yes, not only did CUInt() have a bug, it wasn't necessary because of the way a Variant is structured internally.
Safest bet is to replace CUInt() by CLng() though, so that's what I did here.
Slight overkill since the only places CUInt() was being used was on Long variables, but it helps me remember later what was going on. I just don't want to fall into a trap using:
var = 123
... to replace
var=CUInt(123)
... just to find that the VB compiler decided to put a Single into the Variant or something!
I hope people who get confused using an earlier version drop back to find the fix.
I can not understand one thing. If I import your class modules in my project everything works fine, but if I write the code by myself, or if I copy and paste your code, the method "IDispatchCallback"is never called.
You may have missed this comment inside PortMapperCBs.cls:
Code:
'NOTE: Must be dispID = 0, i.e. the default method of the class.
This means you must set IDispatchCallback as the default member of the Class. This is done via the Tools|Procedure attributes... dialog in the IDE.
In that dialog select IDispatchCallback in the Name dropdown. Then click the Advanced >> button in that dialog, which will let you set Procedure ID to (default). Then click the Apply button.
If this is unfamiliar to you it may be hazardous to edit these Classes and you might want to just use them as supplied. However Setting the Procedure Attributes may help understand this particular issue, and the article might also be available in your own language there as well.
ok, thanks so much, it works !. Thank you for taking the time to detail how do, and also for the link. I know the visual basic, but not quite the IDE, I do a lot of vba excel and there are many features missing.
i'm french, i just begun to learn the english, I hope I am sufficiently comprehensible.