Parks Computing
Pedagogy for the Autodidactic Programmer
Welcome to the personal web site of Paul M. Parks. This is where I keep my web development skills current while sharing snippets of code, utilities, libraries, and what little development wisdom I've gathered in my career. It's also where I show off my family, talk about my hobbies, or just pontificate from time to time.
If you find the articles interesting, or if you'd like more information on a particular topic, let me know [paul@parkscomputing.com].
Ripsaw Library Implementation
tail utility. This series of articles details my rewrite of the application from the ground up.So far, we have a test script implemented in JavaScript (or JScript, if you want to be really picky) that is intended to exercise our COM interfaces. There is a base interface that represents a generic log that a client might wish to follow, a more specific interface representing a file, and a
dispinterface declaring the events that a Ripsaw library object can fire. In this article we'll start implementing these interfaces, with the goal of providing just enough functionality to allow us to execute the test script.(Click the "Read the full article" link below for more.)
Implementing the COM Interfaces
To briefly recap the design, there will be a Ripsaw COM library that exports objects representing sources of updates, and a Ripsaw client (such as a log viewer or a script) will create objects that fire events whenever the source changes its status. Now that we've specified the interfaces, it's time to implement them just enough to run the test script that we created in the last installment. I like to use ATL to implement the low-level, boilerplate code that's necessary forIUnknown and IDispatch, though once the wizard is done I usually take over and move all the generated code around. I've never liked round-trip development with code generators.There are three interfaces (for now) that need to be implemented:
IRipsawObject, IRipsawFile, and _IRipsawEvents. The first interface is the base interface for not only IRipsawFile but other potential interfaces that might be added later on as the library is developed. That means I want to create the implementation in such a way that I can easily extend it to other sources of data that Ripsaw might use. Unfortunately, if I took the code that the ATL wizard spat out, I'd have a really difficult time reusing it. I need to split the ATL code into a generic interface implementation and a concrete object implementation.In ATL, when implementing an object derived from
IDispatch, you usually get a class definition that looks like this:class ATL_NO_VTABLE RipsawFile :
public CComObjectRootEx<CComMultiThreadModel>,
public CComCoClass<RipsawFile, &CLSID_RipsawFile>,
public ISupportErrorInfo,
public IConnectionPointContainerImpl<RipsawFile>,
public CProxy_IRipsawEvents<RipsawFile>,
public IDispatchImpl<IRipsawFile, &IID_IRipsawFile, &LIBID_RipLib, /*wMajor =*/ 1, /*wMinor =*/ 0>
That works fine, until it doesn't. If you ever want to derive a class from
RipsawFile, you're going to run into problems. What I usually do is replace IDispatchImpl with my own template class that derives from IDispatchImpl and implements the interface that my object is going to expose. In this case, I want my object to expose both IRipsawFile and IRipsawObject, so I'll start by creating a generic template implementation of the base IRipsawObject interface. The template will be defined in a header file named IRipsawObjectImpl.h.#pragma once
#include <comutil.h>
template <class Root, class T, const IID* piid = &__uuidof(T),
const GUID* plibid = &ATL::CAtlModule::m_libid,
WORD wMajor = 1, WORD wMinor = 0,
class tihclass = ATL::CComTypeInfoHolder>
class IRipsawObjectImpl :
public Root,
public ATL::IDispatchImpl<T, piid, plibid, wMajor, wMinor, tihclass>
{
public:
/* Internal public implementation */
IRipsawObjectImpl() : isOpen(FALSE)
{
}
virtual ~IRipsawObjectImpl()
{
}
public:
/* IRipsawObject interface */
HRESULT STDMETHODCALLTYPE get_Name(
__out BSTR* pName)
{
if (!pName)
{
return E_INVALIDARG;
}
{
ObjectLock(this);
*pName = NULL;
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE get_IsOpen(
__out VARIANT_BOOL* pRetVal)
{
if (!pRetVal)
{
return E_INVALIDARG;
}
LONG retVal = 0;
InterlockedExchange(&retVal, isOpen);
*pRetVal = retVal ? VARIANT_TRUE : VARIANT_FALSE;
return S_OK;
}
HRESULT STDMETHODCALLTYPE Close()
{
_bstr_t fileName;
VARIANT_BOOL tmpIsOpen = VARIANT_FALSE;
get_IsOpen(&tmpIsOpen);
if (tmpIsOpen)
{
ObjectLock(this);
put_IsOpen(FALSE);
BSTR name = 0;
get_Name(&name);
Notify_Close(name);
SysFreeString(name);
}
return S_OK;
}
HRESULT STDMETHODCALLTYPE WaitForUpdate(
__in ULONG timeout,
__out BSTR* pNewData)
{
if (!pNewData)
{
return E_INVALIDARG;
}
*pNewData = NULL;
return S_OK;
}
protected:
/* Internal protected implementation */
HRESULT STDMETHODCALLTYPE put_IsOpen(
__in VARIANT_BOOL value)
{
InterlockedExchange(&isOpen, value);
return S_OK;
}
virtual HRESULT Notify_Open(BSTR fileName)
{
return S_OK;
}
virtual HRESULT Notify_Close(BSTR fileName)
{
return S_OK;
}
virtual HRESULT Notify_Update(BSTR updateData)
{
return S_OK;
}
LONG isOpen;
};
There is quite a bit going on in this template, so let's go over its various parts.
The Root Type
If you look back at the example class definition for RipsawFile, you'll see that it derived from CComObjectRootEx<CComMultiThreadModel>. This is because, when the wizard generated the code, the user selected the option to generate an object that was free-threaded. This is an implementation detail of the object, not of the interface, so that's the first thing we need to abstract away from our generic implementation of the IRipsawObject interface. The problem, however, is that CComObjectRootEx provides a lot of useful types that we should take advantage of, such as methods for guarding blocks of code that need to be serialized when accessed from multiple threads. What I decided to do was make Root a template parameter and derive the implementation from that type. The concrete implementation may provide any type for this parameter as long as it implements the definitions expected from that type, such as ObjectLock.The IDispatchImpl Type
This is a class that is provided by ATL to implement the ugly details of IDispatch. While the average COM developer can (usually) implement a workable IUnknown rather easily, implementing IDispatch is a good deal more complicated. We know, based on our IDL, that IRipsawObject is derived from IDispatch, so this is an implementation detail that's appropriate for our generic implementation. Classes that derive from IRipsawObject will also pick up the IDispatch implementation. All of the template parameters to IRipsawObjectImpl after Root are passed along to IDispatchImpl.Notify_Open, Notify_Close, and Notify_Update
All objects that implement the IRipsawObject interface are expected to fire events to notify clients about open, close, and update events. How these events are generated is an implementation detail best left to the concrete object implementation, but I may need to fire events from the generic implementation. I decided to create stub methods that will be overridden in the concrete implementation.The Rest of the Code
The remainder of the header is devoted to minimal implementations of the methods and properties defined in theIRipsawObject interface. Most of these will be overridden in IRipsawFile and other derived interfaces. Note that the implementations use InterlockedExchange and ObjectLock to serialize access to internal data. This is because we want to allow the generic implementation to work for free-threaded object implementations, which may be called by more than one thread at a time. The ObjectLock type is expected to be provided by the Root template parameter.I Could Use Some More Cowbell
This still isn't enough code to create a usableIRipsawFile implementation, but since that's the default interface that we need to expose from our COM object we'll have to derive another generic implementation from IRipsawObject that will let us finally execute our test script. We'll tackle that template class in the next installment in the series. If you want to cheat and peek ahead, look at the file IRipsawFileImpl.h in the source code.Labels: C++, COM, JavaScript, ripsaw, visual studio
Posted November 23, 2009 0 CommentsRipsaw COM Interface, First Pass
(This is a long article, so click the "Read full article" link below for more.)
[
oleautomation,
uuid(2D486A73-E912-4078-9F38-678226E4A0BD),
dual,
pointer_default(unique)
]
interface IRipsawFile : IDispatch
{
}
The interface is also derived from
IDispatch to support late-binding, which means that scripting languages can discover the methods and properties of the interface at runtime.Before we can write the test script we'll need decide what basic methods a Ripsaw object needs to implement. A single Ripsaw object will represent a connection to a file, and the object will fire events whenever the file changes so that listeners can take an action based on the update (change the display, parse the update, etc.). At a minimum, then, we need to be able to open a file and close a file.
interface IRipsawFile : IDispatch
{
[id(1)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);
[id(2)] HRESULT Close();
}
The
Open method will accept a path to a file and return a Boolean indicating the success or failure of the method call. The Close method will close any existing connection, and we won't bother to check for a return code from that method.Now, it would probably be useful to be able to query a property of the object for the name of the file to which it is connected, and perhaps another property to check if the object is currently opened or closed. Let's add those properties:
interface IRipsawFile : IDispatch
{
[id(1), propget] HRESULT Name(
[out,retval] BSTR* pName);
[id(2), propget] HRESULT IsOpen(
[out,retval] VARIANT_BOOL* pRetVal);
[id(3)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);
[id(4)] HRESULT Close();
}
Okay, not bad so far. We can open a file, query for the status, query for the name, and close the file. That's no good if we can't catch events fired by the object. We need to define a
dispinterface that specifies the events that the object can fire at its clients:[ uuid(6671C129-C761-42F3-AB2F-D12C33D95160) ]
dispinterface _IRipsawEvents
{
properties:
methods:
[id(1)] HRESULT Open([in] BSTR fileName);
[id(2)] HRESULT Close([in] BSTR fileName);
[id(3)] HRESULT Update([in] BSTR updateData);
};
The
Open event fires when a file is opened successfully, of course. Close fires when a file is closed, and Update naturally fires when the file changes. The Update notification will also pass along the data that was gathered from the last file update.Okay, based on those interfaces, our script might look like this:
var ripsaw = WScript.CreateObject("PMP.RipsawFile", "ripsaw_");
if (ripsaw)
{
ripsaw.Open("testfile.log");
if (ripsaw.IsOpen)
{
WScript.Echo("Ripsaw file is open: '" + ripsaw.Name + "'");
/* Uh-oh... */
ripsaw.Close();
if (!ripsaw.IsOpen)
{
WScript.Echo("Ripsaw file is closed: '" + ripsaw.Name + "'");
}
else
{
WScript.Echo("ERROR: Ripsaw file reported open");
}
}
else
{
WScript.Echo("ERROR: Ripsaw file reported closed");
}
}
else
{
WScript.Echo("ERROR: Failed to create Ripsaw file");
}
function ripsaw_Open(fileName)
{
WScript.Echo("ripsaw_Open(" + fileName + ")")
}
function ripsaw_Close(fileName)
{
WScript.Echo("ripsaw_Close(" + fileName + ")")
}
function ripsaw_Update(data)
{
WScript.Echo("ripsaw_Update(" + data + ")")
}
First of all, I'll explain what all this means for those of you that are new to JavaScript — err, JScript — under Windows Script Host (WSH). The first line creates a COM object based on its ProgID, or programmatic identifier. This is a string that resolves, through the registry, to a GUID that uniquely identifies a COM library that provides the requested functionality.
The second parameter to
CreateObject tells the scripting engine to wire events provided by the object to functions that start with the specified prefix. In our script, we want to handle the Open, Close, and Update notifications, so we created functions named ripsaw_Open, ripsaw_Close, and ripsaw_Update to catch these events. Since they all start with the "ripsaw_" prefix, the script engine will call them when any of the corresponding events are fired by our object.If you looked carefully at the script, you noticed that I inserted a comment: "Uh-oh." That's because the script has no way of sitting and waiting for an update other than looping in a tight loop, and that is almost never a good idea. The script would waste CPU time doing nothing while we wait for the file to be updated. In other languages we have access to message loops,
WaitForSingleObject, and other means of yielding CPU until an event occurs, but not in WSH. We need something better. What we'll do is add a method to our object which, when called, will do absolutely nothing until an update occurs. We'll call it WaitForUpdate.interface IRipsawFile : IDispatch
{
[id(1), propget] HRESULT Name(
[out,retval] BSTR* pName);
[id(2), propget] HRESULT IsOpen(
[out,retval] VARIANT_BOOL* pRetVal);
[id(3)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);
[id(4)] HRESULT Close();
[id(5)] HRESULT WaitForUpdate(
[in] ULONG timeout,
[out,retval] BSTR* pNewData);
}
The first parameter is a timeout, in milliseconds, so that the function will return if no updates arrive in the specified amount of time. We'll take a page from Win32 and say that a value of 0xFFFFFFFF will tell the method to wait forever. If the timeout expires, the method will return a null
BSTR; otherwise, the return value will be the new data from the last file update. We'll modify the heart of our script to call the new method: WScript.Echo("Ripsaw file is open: '" + ripsaw.Name + "'");
var timeout = 5000;
WScript.Echo("Waiting for data...");
var newData = ripsaw.WaitForUpdate(timeout);
if (newData)
{
WScript.Echo("Waited for data:", newData);
}
else
{
WScript.Echo("Timed out after waiting for data for " + timeout + " milliseconds");
}
ripsaw.Close();
In a real script we'll probably call
WaitForUpdate in a loop, but for now we'll just wait for one update before exiting.Generalizing the Interface
This looks good for files, but perhaps later on we'd like to extend Ripsaw to listen to other sources of events, like the Windows Event Log (the first Ripsaw actually did that for a while, but I backed out the feature to focus on files). It would be a good idea to go ahead and factor out the common methods and properties into a base interface that could be extended by other event sources.[
oleautomation,
uuid(0270FDFF-56AF-42ec-9971-AE8DCB0DAB36),
dual,
pointer_default(unique)
]
interface IRipsawObject : IDispatch
{
[id(1), propget] HRESULT Name(
[out,retval] BSTR* pName);
[id(2), propget] HRESULT IsOpen(
[out,retval] VARIANT_BOOL* pRetVal);
[id(3)] HRESULT Close();
[id(4)] HRESULT WaitForUpdate(
[in] ULONG timeout,
[out,retval] BSTR* pNewData);
};
[
oleautomation,
uuid(2D486A73-E912-4078-9F38-678226E4A0BD),
dual,
pointer_default(unique)
]
interface IRipsawFile : IRipsawObject
{
[id(10)] HRESULT Open(
[in] BSTR fileName,
[out,retval] VARIANT_BOOL* pSuccess);
};
Later on, if we define an interface for listening to event logs, we'll derive that interface from
IRipsawObject and define an Open method specific to event logs. We'll also assign a special ProgID to event log objects so our client applications can create them:var ripsaw = WScript.CreateObject("PMP.RipsawEventLog", "ripsaw_");
Next Steps
Now we have an interface, and we have a script to exercise that interface. What we need next is some real code to implement the interface and a binary that the script can load and run. We'll start on a preliminary implementation in the next article. Until then, you can download the IDL and the script file and look them over. Please leave a comment with any suggestions you may have.Labels: C++, COM, ripsaw, software
Posted November 9, 2009 0 CommentsRefining Ripsaw's Design
To bring you up to speed, Ripsaw is a log-viewing utility for Windows that I initially wrote about six years ago, but never released widely. I've decided to rewrite it and discuss each step of the rewrite here.
Implementing the API
The initial Ripsaw implementation was a monolithic (albeit small) application, but this time I want to separate the log-reading mechanism from the log-viewing UI. There are times when I'd like to use the log-reading capabilities of Ripsaw from JavaScript or PowerShell, and I'd also like to write more than one viewer application around the log reader. There are at least three possible technologies for this API:- C DLL
- COM
- .NET
What About Registration?
I'd still like to be able to run Ripsaw from a USB drive without having to install anything on the machine I'm debugging or examining, which means I'd like to avoid the hassle of having to run regsvr32 on the target machine before I can use Ripsaw. Fortunately, there is registration-free COM, which will let me run a Ripsaw viewer that loads the Ripsaw COM library with the aid of a manifest. This will let me keep a viewer and the Ripsaw library in a directory on my USB drive so I can just plug it in and run the viewer without having to register or install anything.Supporting Scripting in the Viewer
Since Ripsaw's log-reading capability will be implemented in COM, I'll be able to write JavaScript apps that can watch for data from a log file, and these scripts can either parse the data or take actions based on the data. Not only would this be useful apart from a viewer, but it might also be useful inside a viewer. I could define script actions to operate on log lines, highlight them, parse them, etc., while I'm watching a log in the viewer. To take advantage of this I'll add JavaScript support to the Windows Ripsaw viewer, along with the capability to load pre-defined scripts that act on log data.Viewer Implementation
In keeping with my requirement for making Ripsaw light and portable, I don't want to use .NET for the main viewer implementation. I might create a Windows Forms or WPF viewer later on once .NET becomes more widespread on the systems I mainly work on (point-of-sale terminals and servers for grocery stores), or for when I'm debugging in a controlled environment, but for the main console and GUI viewers I'll use C++ as the development language. COM is still a bit of a pain to use from C++, at least compared to JavaScript or .NET, but it won't be too bad.Multiprocessor Support
Fortunately, the newer POS terminals and servers that are being installed today have multi-core processors, and I definitely want to take advantage of that today. I want to architect the core Ripsaw library to take advantage of multiple threads of execution spread across multiple processors or processor cores. I already write multi-threaded, multi-process systems for these machines, and these systems tend to be heavily instrumented, so sometimes I'm watching several different log files at once during a testing or debugging session. I don't want to slow a system down too much when I start up the viewer, and loading down one core of a multi-core processor with a log viewer would certainly be a bad thing.Okay, Can We Start Coding Now?
Actually, I already have been doing some prototyping, which helped me decide on the details I've described above. Now that I've sorted out some of the lower-level requirements, in the next article I'll start defining the Ripsaw log-reader COM interface. Posted November 6, 2009 0 CommentsDesign Goals for Ripsaw
Split the App Into DLLs
The original Ripsaw was a 32-bit Windows application contained in a single executable. This made it easy to carry around on a USB drive and run on machines that I needed to debug. The downsides were that I had to link the C runtime library into the executable, and application was one monolithic entity that couldn't be easily extended.For the new Ripsaw I'm going to build it to use the C runtime in a DLL. For most of the machines I work with this will require carrying around the C runtime distributable, but I'm willing to do that as long as I don't have to install anything on the terminal. I'm not completely up to speed with side-by-side assemblies; I prefer to just place the VS 2010 C runtime DLL in the same directory as the executable and run the app, but I'll have to verify that's still supported. (Edit: It is. Duh!) So far SxS has caused me a lot of grief, but that's probably because I haven't bothered to really understand it.
Create a Ripsaw API
Since I'm splitting out the C runtime, I'm also going to separate most of Ripsaw's non-visual functionality into a separate API DLL. Not only will this make the core functionality more testable and enforce separation of UI, but I'll be able to build more than one user interface around it. I'd like to build a command-line application as well as a graphical application. Sometimes you just can't beat raw text.Embrace 64-bit
Even though nearly all of my professional development is still 32-bit, I want to build Ripsaw to compile for both 32-bit and 64-bit architectures. I'll need to do this eventually anyway, and this is a good time to start.Support Extensibility
My original plans for Ripsaw included the ability to create filters for processing log output as it arrived in the application. Perhaps only lines containing certain values would be displayed, or maybe certain words would be shown in a particular color or font. Rather than trying to build every possible behavior into Ripsaw, I'll publish an extensibility interface so that I can add features later on (or you can add them) without having to change the core API or UI.Create a Windows 7 UI
The main UI that I'll use will be a 32-bit graphical application targeting Windows XP, but I'd also like to take advantage of some of the new Windows 7 user interface controls such as the ribbon. I'm not inclined to try to serve both interface styles from one application, so after I finish the primary UI I'll create a version that is specific to Windows 7. Since I'm putting most of the non-visual behavior into a separate DLL I'll be free to experiment with different interfaces anyway.Coming Up
In the next few articles I'll delve into some more development specifics such as language and library choices. Posted October 27, 2009 0 CommentsA New Article Series: Ripsaw
I've just downloaded Beta 2 of Microsoft Visual Studio 2010, and I've decided to create a new version of Ripsaw from the ground up so that I can become familiar with the new IDE and compiler. Besides being a chance to finally get Ripsaw right, this will also be an opportunity to create a series of articles on how I develop a complete application, from the first ideas through design, implementation, testing, and release. I'll walk you through all of the design decisions and trade-offs, the problems I run into along the way, and the development methodologies I use.
I would really appreciate your feedback, ideas, suggestions, and criticisms. This is going to be fun!
Labels: C++, ripsaw, software, utilities, visual studio
Posted October 26, 2009 0 CommentsAn API is Forever
At my job I have worked on, and currently maintain, a few APIs implemented in Windows DLLs, some of which have been around for a long time. I had to rewrite significant portions of the internal implementation of one particular DLL in order to add some new features, fix several bugs, and improve the overall performance of the DLL. The old API exposed from the DLL wasn't able to take advantage of the new features, and it wasn't designed all that well to begin with. Since nearly all of the use of that API was through a C++ template wrapper implemented a header, I just added a new set of API functions and changed the wrapper to use the new API.
I did some digging around in the code base (it's a huge code base) to see what else was using the class wrapper around the DLL, and I found out that the component was in far more widespread use than I had anticipated. There were components in all corners of the code base using it now.
The component was still desperately in need of a new API, so what I did was write a new one and forward the old API to the new API. I changed the header file that declared the API functions to look like this:
FICTIONAL_API void NaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR name,
LPCVOID bytes,
DWORD byteCount,
LPCSTR desc);
// The rest of the new API was declared here...
// ...
// ...
/************************************************************************
Old, deprecated API. This API is still supported, but calls to all of
these functions are now forwarded to the new API described above.
Please use only the new API in new code.
************************************************************************/
/* DEPRECATED: Use NaDoStuff instead. */
FICTIONAL_API BOOL OaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR desc,
LPCVOID bytes,
DWORD byteCount);
// The rest of the old API was declared here...
// ...
// ...
I hoped that the comments would make clear that the use of the old API is discouraged, and that any clients should move to the new API. Likewise, in the implementation, I forwarded the old API functions to the new API functions:
FICTIONAL_API void NaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR name,
LPCVOID bytes,
DWORD byteCount,
LPCSTR desc)
{
// Go do stuff
}
// Elsewhere in the code..
FICTIONAL_API BOOL OaDoStuff(
HANDLE hFict,
DWORD flags,
LPCSTR desc,
LPCVOID bytes,
DWORD byteCount)
{
NaDoStuff(hFict, flags, "", bytes, byteCount, desc);
return TRUE;
}
In some cases, forwarding the old API to the new API meant that I had to make the new API implement the semantics of the old API whenever it was being called through the old API functions. For example, the old API had a nasty habit of writing sub-keys all over the company's registry key under HKLM\SOFTWARE. I made the new API put these settings into a tidy sub-key that belonged to the API, like HKLM\SOFTWARE\ComanyName\Fictional. It would even hunt down the all of the old keys created by the old API and move them into the new sub-key. In a later story I'll explain why I had to preserve that behavior in the old API, but for now just remember that I did.
Recently, a co-worker changed a customer-specific branch of the archive so that it would point to the newest implementation of this component. He was writing an application using some libraries that called this component's API. He asked me to help with at a problem he was having, and when I went to look in the registry at some of the settings the API had written, I was dismayed to find that the settings were in the old location, all over the root of the company's HKLM\SOFTWARE key. At first, I thought that he was still somehow using the old DLL, but when I checked the version information on the DLL it was, indeed, the new one. How could this be?
As I mentioned above, most clients use this DLL through a C++ wrapper, since most of the archive is now C++. Only a handful of older C modules call this API directly. The wrapper is a C++ template class, which means it is implemented in a header file. This customer archive was still pointing at the old version the header that called the old API. As I mentioned above, the new API preserves the semantics of the old API when it is called through the old API, and that's what we were seeing here.
My first suggestion, without really thinking about it, was to change the archive to point at the newer header. My co-worker pointed out that this would mean rebuilding all of the components in the archive that used the header, not just the one he was working on.
We left it alone, and everything is working as it should be. At some point in the future we'll move all of the other components up to the newer version of the wrapper, and they'll start seeing the behavior associated with the new API. In the meantime, the customer still benefit from a less buggy and more efficient component.
Labels: C, C++, software, Windows
Posted May 8, 2009 2 CommentsPre-emptive Snarkiness
In my first comment to Raymond's article I mentioned that it took me only eleven minutes to write the utility. That was the entire point of the exercise, really. If the utility happens to be useful as well, that's fantastic. In fact, the reason that I bothered to take an extra 30 seconds and link in the C runtime was to make it easier to use. Just copy it to your favorite USB drive, and off you go. If you don't have 84KB free on your drive for the ZIP file, then that's still okay. You can download the source and VS project and build it any way you like. Not a problem.
So, despite the fact that I clearly stated that I was aware that I could make the utility very tiny if I wanted to take the time to do so (when I said, "Of course you could. So could I."), someone still didn't get it. I guess quoting me out of context, or perhaps even putting words in my mouth, is more fun.
If I wanted to make it smaller, perhaps I could dispense with the CRT altogether. Perhaps I could write it in assembly language. Who knows. It doesn't matter. I just wasn't inclined to take any more time gold-plating what I regarded as a very simple exercise. The utility does what it is supposed to do, with a very tiny investment in programming time. That's all I was out to prove. I made the source freely available, with no strings, in case anyone wanted to tweak it to handle some situation that I didn't think about, or perhaps wanted a different implementation (like setting the width and height separately).
As Raymond might say, I can't believe I had to write that. Posted April 15, 2009 0 Comments
Windows Drag Sensitivity Utility
In Raymond's honor, I'll provide my own pre-emptive snarky comment: "That executable is 164KB! I could write that in only 4KB!"
Of course you could. So could I. I linked in the C runtime library so you wouldn't have to install the Visual Studio 2008 distributable package just to run a simple command-line utility. I don't normally do that with typical desktop applications, but for small utilities like this is saves a lot of trouble.
If you'd like modify the utility, or examine its source, you may download the Visual Studio 2008 project. If you just want the utility itself, you may download a ZIP of the executable.
About the Utility
The utility is actually very simple. It accepts a single parameter, which is the number of pixels the mouse must travel with a button depressed before the motion registers as a drag action. Rather than separate out the width and height, I just set both to the same number since this is generally all that's necessary.BOOL success = FALSE;
success = SystemParametersInfo(SPI_SETDRAGWIDTH, numPixels, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
if (!success)
{
DWORD error = GetLastError();
std::wcout << L"Error " << std::hex << error << std::dec << " while setting drag width." << std::endl;
return 1;
}
success = SystemParametersInfo(SPI_SETDRAGHEIGHT, numPixels, NULL, SPIF_UPDATEINIFILE | SPIF_SENDCHANGE);
if (!success)
{
DWORD error = GetLastError();
std::wcout << L"Error " << std::hex << error << std::dec << " while setting drag height." << std::endl;
return 1;
}
It would be fairly simple to modify the application to set the width and height independently, if you so desired.
Update
I'm already at version 1.1. I decided to add a version resource and support for a "/?" parameter.About the Snarky Comments
Please see the entry entitled Pre-emptive Snarkiness for my response to the comments about my utility that were posted on Raymond's blog.Labels: C++, software, utilities
Posted April 10, 2009 3 CommentsA New, New Wiki
Lisp for C++ Templates
Updated MMP Mancala
More on Accelerated C++ Solutions
Labels: Accelerated C++, C++
Posted 0 CommentsAccelerated C++ Solutions
Labels: Accelerated C++, C++
Posted June 18, 2003 0 CommentsSubscribe to Posts [Atom]