In my last post I showed how you can distinguish between to different device interfaces being opened on the same device object. While device interfaces are the Microsoft party line for how to expose a PnP device object which can be opened, I realize that many of you want to use the old legacy symbolic link where the name is fixed and can be more "easily" found (I disagreee on that point, but I am not writing your code, am I? ). So how do you distinguish between 2 different legacy symbolic links being opened against your device? There are 2 ways to do this, which method you choose is mostly determined by how much control you have over the application which is opening the link.

The first method is to append a reference string to the symbolic link name itself. Just like device interfaces, the reference string will be passed to your create dispatch routine and you can use the reference string to differentiate which name is being opened. For instance, if you created a PDEVICE_OBJECT with the name \Device\Foo0, you could create 2 symbolic links (whose user mode names would be \\.\Foo\Ref1 and \\.\Foo\Ref2) like this:

    UNICODE_STRING strDevice, strLink1, strLink2;

    RtlInitUnicodeString(&strDevice, L"\\Device\\Foo0");
    RtlInitUnicodeString(&strLink1, L"\\??\\Foo\\Ref1");
    RtlInitUnicodeString(&strLink2, L"\\??\\Foo\\Ref2");

    IoCreateSymbolicLink(&strLink1, &strDevice);
    IoCreateSymbolicLink(&strLink2, &strDevice);
and then you would check the file name in the create dispatch handler for L"\\Ref1" or L"\\Ref2" (remember there is a \ at the front of the string!) and act appropriately. This method works if you can alter the application which is opening your device, but what if you can't change the application? For instance, let's say you are exposing 2 COM ports and you want to open them from HyperTerminal?

In the case where you can't change the application, you reverse where you specify the reference strings. This is not the most intuitive way of doing this (I didn't come up with it, Peter Wieland suggested it to me), but it works very well. Instead of putting the reference string on the link name, you append the string to the device name it links to. Let's say you are creating 2 COM port names (COM5 and COM6) for your device which is named \Device\MySerialPort0, you would create the symbolic links like this:

    UNICODE_STRING strDevice, strCOM5, strCOM6;

    RtlInitUnicodeString(&strCOM5, L"\\??\\COM5");
    RtlInitUnicodeString(&strDevice, L"\\Device\\MySerialPort0\\COM5");
    IoCreateSymbolicLink(&strCOM5, &strDevice);

    RtlInitUnicodeString(&strCOM6, L"\\??\\COM6");
    RtlInitUnicodeString(&strDevice, L"\\Device\\MySerialPort0\\COM6");
    IoCreateSymbolicLink(&strCOM6, &strDevice);
When the application opens COM5, your create dispatch routine can check for the filename L"\\COM5" (again, remember there is a \ at the front of the string) and if COM6 is being opened, the filename will be L"\\COM6". So why can't you use this trick for a device interface and specify the a reference string on the device name? It doesn't work because device interfaces will use the PDO's name as the target device and the device interface APIs do not allow you to specify the reference string on the symbolic link's target device name, only a reference string on the symbolic link name itself. In the end, it doesn't matter where the reference string is, it is passed to your create dispatch routine either way.