The Inquisitive Coder - Davy Brion’s Blog

Trying to walk that thin line between intelligence and ignorance

Archive for February, 2008

Creating remote TCP/IP printer ports from code

Posted by Davy Brion on 27th February 2008

At work, i had to find a way to create new TCP/IP printer ports on a remote print server from the .NET code of my application. I couldn’t use WMI, so i had to find something else. Luckily, the XcvData Windows function does just that. Unfortunately, it is a royal pain in the ass to use and there’s not a lot of documentation available on how to use it. And i certainly didn’t find anything on how to call it from .NET code. So after wasting about 2 days on trying to get this to work, i figured i might as well put the solution online in case anyone needs to do this from .NET code again.

This is the signature of the function:

BOOL WINAPI XcvData(HANDLE  hXcv, LPCWSTR  pszDataName, PBYTE  pInputData, DWORD  cbInputData,
    PBYTE  pOutputData, DWORD  cbOutputData, PDWORD  pcbOutputNeeded, PDWORD  pdwStatus);

Just looking at that makes me feel bad for everyone who’s ever had to code against Windows API’s. Anyway, according to the documentation, the first parameter (hXcv) should be a handle to the print server (which you can retrieve with a call to OpenPrinter), the second parameter (pszDataName) has to be “AddPort” if you want the function to create a new port. And then comes the fun part… the third parameter (pInputData) should be a pointer to a PORT_DATA_1 structure and the fourth parameter has to contain the size in bytes of the PORT_DATA_1 structure you passed as the third argument. The other parameters can be ignored (nice API design btw) except for the last one, which is an out parameter that will return a numeric code which will indicate either success or the cause of the failure.

I had a lot of problems trying to pass a pointer to a valid PORT_DATA_1 structure. The structure looks like this:

typedef struct _PORT_DATA_1 {
    WCHAR  sztPortName[MAX_PORTNAME_LEN];
    DWORD  dwVersion;
    DWORD  dwProtocol;
    DWORD  cbSize;
    DWORD  dwReserved;
    WCHAR  sztHostAddress[MAX_NETWORKNAME_LEN];
    WCHAR  sztSNMPCommunity[MAX_SNMP_COMMUNITY_STR_LEN];
    DWORD  dwDoubleSpool;
    WCHAR  sztQueue[MAX_QUEUENAME_LEN];
    WCHAR  sztIPAddress[MAX_IPADDR_STR_LEN];
    BYTE   Reserved[540];
    DWORD  dwPortNumber;
    DWORD  dwSNMPEnabled;
    DWORD  dwSNMPDevIndex;
} PORT_DATA_1, *PPORT_DATA_1;

As you can see, the struct contains a couple of Unicode character arrays and even a byte array. Defining a struct in C# that could be marshalled to this turned out to be the tricky part in getting this stuff to work.

But first of all, we needed to be able to call the OpenPrinter function to retrieve a handle to the print server where we need to create the new printer port:

        public enum PrinterAccess

        {

            ServerAdmin = 0×01,

            ServerEnum = 0×02,

            PrinterAdmin = 0×04,

            PrinterUse = 0×08,

            JobAdmin = 0×10,

            JobRead = 0×20,

            StandardRightsRequired = 0×000f0000,

            PrinterAllAccess = (StandardRightsRequired | PrinterAdmin | PrinterUse)

        }

 

        [StructLayout(LayoutKind.Sequential)]

        public struct PrinterDefaults

        {

            public IntPtr pDataType;

            public IntPtr pDevMode;

            public PrinterAccess DesiredAccess;

        }

 

        [DllImport("winspool.drv", SetLastError = true)]

        public static extern int OpenPrinter(string printerName, out IntPtr phPrinter,

            ref PrinterDefaults printerDefaults);

 

        [DllImport("winspool.drv", SetLastError = true)]

        public static extern int ClosePrinter(IntPtr phPrinter);

Allright, now can retrieve the handle with a call to OpenPrinter and we can cleanup afterwards by passing the handle to the ClosePrinter function.

Now we need a C# definition of a struct that can be marshalled to a PORT_DATA_1 struct:

        public const int MAX_PORTNAME_LEN = 64;

        public const int MAX_NETWORKNAME_LEN = 49;

        public const int MAX_SNMP_COMMUNITY_STR_LEN = 33;

        public const int MAX_QUEUENAME_LEN = 33;

        public const int MAX_IPADDR_STR_LEN = 16;

        public const int RESERVED_BYTE_ARRAY_SIZE = 540;

 

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]

        public struct PortData

        {

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PORTNAME_LEN)]

            public string sztPortName;

            public UInt32 dwVersion;

            public UInt32 dwProtocol;

            public UInt32 cbSize;

            public UInt32 dwReserved;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_NETWORKNAME_LEN)]

            public string sztHostAddress;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_SNMP_COMMUNITY_STR_LEN)]

            public string sztSNMPCommunity;

            public UInt32 dwDoubleSpool;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_QUEUENAME_LEN)]

            public string sztQueue;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_IPADDR_STR_LEN)]

            public string sztIPAddress;

            [MarshalAs(UnmanagedType.ByValArray, SizeConst = RESERVED_BYTE_ARRAY_SIZE)]

            public byte[] Reserved;

            public UInt32 dwPortNumber;

            public UInt32 dwSNMPEnabled;

            public UInt32 dwSNMPDevIndex;

        }

First of all, the struct has to have Sequential as its LayoutKind, and each string must be marshalled as a unicode string (.NET strings are unicode by default, but when marshalled to native code they are converted to ANSI strings, so the CharSet setting is definitely required). Then, for each array in the original struct, you need to make sure our string is converted to an array of the expected size. Marshalling those strings as ByValTStr and setting the SizeConst parameter did the trick there. Then there’s the byte array in the original struct. The function expects there to be a byte array of 540 elements. Marshalling it as ByValArray and setting the SizeConst makes that work as well.

Right, now we have the structure, so we still need a way to call the XcvData function:

        [DllImport("winspool.drv", SetLastError = true, CharSet = CharSet.Unicode)]

        public static extern int XcvDataW(IntPtr hXcv, string pszDataName,

            IntPtr pInputData, UInt32 cbInputData, out IntPtr pOutputData, UInt32 cbOutputData,

            out UInt32 pcbOutputNeeded, out UInt32 pdwStatus);

Notice how the DllImport attribute has its CharSet parameter set to unicode as well. If you don’t do this, the function call will crash your app (can’t even catch an exception) because it expects pszDataName to be a unicode string and as mentioned earlier, without specifying CharSet.Unicode it would’ve been marshalled to an ANSI string. Happy times.

Anyways, creating a TCP/IP printer port on a remote server is now as simple as this:

            IntPtr printerHandle;

            InteropStuff.PrinterDefaults defaults =

                new InteropStuff.PrinterDefaults { DesiredAccess = InteropStuff.PrinterAccess.ServerAdmin };

 

            InteropStuff.OpenPrinter(@”\myPrintServer,XcvMonitor Standard TCP/IP Port”, out printerHandle, ref defaults);

 

            InteropStuff.PortData portData = new InteropStuff.PortData

            {

                dwVersion = 1, // has to be 1 for some unknown reason

                dwProtocol = 1, // 1 = RAW, 2 = LPR

                dwPortNumber = 9100, // 9100 = default port for RAW, 515 for LPR

                dwReserved = 0, // has to be 0 for some unknown reason

                sztPortName = “DBR_172.30.164.15″,

                sztIPAddress = “172.30.164.15″,

                sztSNMPCommunity = “public”,

                dwSNMPEnabled = 1,

                dwSNMPDevIndex = 1

            };

 

            uint size = (uint)Marshal.SizeOf(portData);

            portData.cbSize = size;

 

            IntPtr pointer = Marshal.AllocHGlobal((int)size);

            Marshal.StructureToPtr(portData, pointer, true);

 

            try

            {

                IntPtr outputData;

                UInt32 outputNeeded;

                UInt32 status;

 

                InteropStuff.XcvDataW(printerHandle, “AddPort”, pointer, size, out outputData, 0,

                    out outputNeeded, out status);

            }

            finally

            {

                InteropStuff.ClosePrinter(printerHandle);

                Marshal.FreeHGlobal(pointer);

            }

I don’t wanna look at any Windows functions for at least a couple of months :)

Share/Save/Bookmark

Tags: ,
Posted in Software Development | 7 Comments »

The joy of using Windows API’s

Posted by Davy Brion on 26th February 2008

just read this in the documentation of a function i need to use:

If the function returns ERROR_SUCCESS, the spooler marks the port as added.

who comes up with this stuff? seriously…

Share/Save/Bookmark

Posted in Off Topic | No Comments »

The Mythical Man-Month

Posted by Davy Brion on 20th February 2008

So i finally got around to reading Frederick P. Brooks’ The Mythical Man-Month. Many people consider this book a timeless classic. I don’t entirely agree with this though. It is a very good book, and a lot of the stuff in it is awfully familiar even though it’s written 30 years ago. But because it’s been written 30 years ago, there really are some parts that have become kinda dated. And I’m not even talking about the references to writing code. Those are actually interesting, from a more historical point of view. But I don’t really agree on his views on the role and power of the architect of the system. In fact, his entire Surgical Team (as he calls it) does not look appealing to me at all… it kinda conflicts with the world of agile development that we know of today. Since this is the 20th anniversary edition, the book also contains a nice chapter where he looks back on the opinions that he asserted in the original version, and corrects a few of them. That chapter was also written 10 years ago though, so I’d actually love to hear how he feels about a couple of his stated opinions these days. Don’t get the wrong idea though, i really enjoyed reading this book and most of the human and social aspects of software development that he talks about are very insightful and definitely still relevant if you’re in the business of software development, regardless of if you’re a manager or a developer or whatever. Highly recommended.

Share/Save/Bookmark

Posted in Books | No Comments »

Resharper 4 EAP is finally here

Posted by Davy Brion on 16th February 2008

The first nightly build of the Resharper 4 EAP (Early Access Program) is finally available… About a month ago my resharper 3.1 broke on my system, to the point where i simply had to uninstall it to even be able to use Visual Studio. Reinstalling resharper didn’t work either. And coding without resharper was so annoying i just gave up and decided to wait until the first preview of 4.0 would be available. After all, it was supposed to come out in January. The preview has slipped a couple of weeks apparently but now it’s finally here. Just installed it and it at least doesn’t crash Visual Studio anymore like the previous version did. Now i can finally start coding again in my spare time :)

Here’s a quick overview of some of the new features and you can download it here.

Share/Save/Bookmark

Tags:
Posted in Off Topic | 1 Comment »

Learning WCF

Posted by Davy Brion on 4th February 2008

I’ve been pretty busy with non-IT related stuff the last few weeks, that’s why it’s been so quiet around here. I did finally finish Michele Bustamante’s Learning WCF book. This one is definitely going on my ‘Highly Recommended’ list. While it certainly won’t make you an expert on WCF, you do get a pretty good grasp on how it works, what’s possible with it, and how you use it. The book mainly covers contracts, bindings, hosting options, instancing and concurrency options, service reliability, security and dealing with exceptions/faults. If you want to ease into the WCF world, this book is perfect for you.

Share/Save/Bookmark

Posted in Books | No Comments »