I had the need to impose per-file access control for several files in our repository. Long story short was that some files were subject to designer modification and subsequently commiting those files would break the build. I initially experimented with 'needs-lock' property, but with auto-get-lock functionality, it ended up causing more problems (and confusion) than it solved.
I looked around for examples of per-file access control and found little. Most posts said it can't be done, one said it could be done through modification of the authz config file and provided an example which resulted in my entire repository being locked. Unfortunately it didn't work and locked my entire repository. Other solutions tended toward a hook, but I wasn't interested in that route as it appears too involved and requires access to the server, something which I don't necessarily have (depending on the project team and IT/admin).
After some trial and error I found something that does seem to work. It is simple and only involves modifying the authz config file. Remember, order is important, so place restrictions AFTER permissions. The format is as follows:
[repo:/path/to/file.txt]
* = access-mode
where
- path/to/file.txt is the full path to the repository, optionally prefixed with the repository name if needed
- access-mode is the access desired, such as blank - no access; r - read-only; w - read-write
- * applies to everyone, you can substitute or augment with user names or groups
Example:
# disallow write access to license.lic in the /trunk/source folder
[/trunk/source/license.lic]
* = r
Note: I found that it may take a few minutes after you have saved your authz config file for the changes to actually propagate to the svn server/service (probably dependent on operating system, etc.). If you find that the change doesn't appear to work, wait a bit and/or restart the svn server.
Thursday, July 18, 2013
Thursday, April 26, 2012
Microsoft Visual C++ Redistributable Runtime Hell
Microsoft has a nice knack of making problems where there are none. C++ redistributables/runtimes is one such case. Ever tried debugging a missing/incorrect redistributable (runtime) version issue? Don't.
I've been unable to locate ANY meaningful description of what is a runtime compared to a redistributable and the million or so versions that show up in Add/Remove programs. Don't take what I describe below as definitive, merely it is my best guess as to what is what, where, and why. Redistributables/runtimes are nothing new in Visual Studio 2010, but I'm limiting my comments to that version simply because it is all that I have the patience to dwadle with at the moment. I'm also assuming x86, although I presume the same versioning applies to x64.
Runtime or Redistributable?
If you have the development environment installed, it is likely that you will see BOTH installed. It appears that runtimes are installed as part of IDE service packs, redistributables are installed independently and separately from the IDE. On a non-development machine, you will probably only see redistributables installed (which includes the runtime).
Version Please?
Would it kill Microsoft to actually include a meaningful and corresponding SP VERSION in the name? There are SPECIFIC VERSIONS of the redistributable that correspond with SPECIFIC VERSIONS of the compiler that was used to build the actual executable. None of this is apparent in the name, description, or listed version from Add/Remove programs.
Built with: Visual Studio 2010
Redistributable name: Microsoft Visual C++ 2010 x86 Redistributable
Redistributable version: 10.0.30319
Location: http://www.microsoft.com/en-us/download/details.aspx?id=5555
Built with: Visual Studio 2010 SP1
Redistributable name: Microsoft Visual C++ 2010 x86 Redistributable
Redistributable version: 10.0.40219
Location: http://www.microsoft.com/en-us/download/details.aspx?id=8328
What I have found is that if your executable was built with VS 2010 SP1 you MUST HAVE the SP1 redistributable installed (40219); the non-SP1 version of the redistributable installed (30319) is insufficient.
What a nighmare.
I've been unable to locate ANY meaningful description of what is a runtime compared to a redistributable and the million or so versions that show up in Add/Remove programs. Don't take what I describe below as definitive, merely it is my best guess as to what is what, where, and why. Redistributables/runtimes are nothing new in Visual Studio 2010, but I'm limiting my comments to that version simply because it is all that I have the patience to dwadle with at the moment. I'm also assuming x86, although I presume the same versioning applies to x64.
Runtime or Redistributable?
If you have the development environment installed, it is likely that you will see BOTH installed. It appears that runtimes are installed as part of IDE service packs, redistributables are installed independently and separately from the IDE. On a non-development machine, you will probably only see redistributables installed (which includes the runtime).
Version Please?
Would it kill Microsoft to actually include a meaningful and corresponding SP VERSION in the name? There are SPECIFIC VERSIONS of the redistributable that correspond with SPECIFIC VERSIONS of the compiler that was used to build the actual executable. None of this is apparent in the name, description, or listed version from Add/Remove programs.
Built with: Visual Studio 2010
Redistributable name: Microsoft Visual C++ 2010 x86 Redistributable
Redistributable version: 10.0.30319
Location: http://www.microsoft.com/en-us/download/details.aspx?id=5555
Built with: Visual Studio 2010 SP1
Redistributable name: Microsoft Visual C++ 2010 x86 Redistributable
Redistributable version: 10.0.40219
Location: http://www.microsoft.com/en-us/download/details.aspx?id=8328
What I have found is that if your executable was built with VS 2010 SP1 you MUST HAVE the SP1 redistributable installed (40219); the non-SP1 version of the redistributable installed (30319) is insufficient.
What a nighmare.
Friday, February 11, 2011
InstallShield Build Error -6199

Recently we started having a problem building an InstallShield installer on some of our build machines. Yes, some of them -- this is what made it so frustrating.
The error we'd see, using the standalone build is:
ISDEV : fatal error -6199: Internal build error
Not a very informative error, and not much in terms of diagnostic information out there on the web or in Flexera's KB.
I was finally able to track down the cause: character encoding. The .ism is (typically?) supposed to use a UTF-8 encoding, but there are some caveats. If you are editing the .ism directly (e.g. Notepad), your editor /may/ be encoding it in a format that the IS standalone compiler doesn't understand, an you end up w/ a -6199. Adding to the frustration is that it may compile w/ some standalone builds, including the IDE, but will fail on others for reasons unknown.
Regardless, the simplest solution is to open the offending script in the ide and then force a save. Alternatively, you can edit the file with a binary editor and remove the leading "" from the line 1.
Friday, March 5, 2010
Router Throughput Issues
We have a small office network and recently became aware of some substantial Internet<->network slowdowns. I spent some time trying to isolate the issue and found the culprit to be our Cisco VPN Router (RVL200). When the 'firewall' option was enabled, external traffic was slowed by a factor of 10. Granted the RVL200 is a low-end business-class router, but a slowdown of 10x is beyond the pale.
Our Internet connection speeds are rated at 20/1.7 MB/s by our ISP. Here's my findings:
Conclusion: the RVL200 doesn't have a firewall, it has a *brick* wall. We've since moved to a Sonicwall appliance for our VPN, so the RVL200 is looking for a new home...
Our Internet connection speeds are rated at 20/1.7 MB/s by our ISP. Here's my findings:
ISP: Cox, cable broadband rated at 20/1.7 (download/upload).
Broadband Cable Modem: Motorola Surfboard SB5100, firmware version 2.3.6.0
Primary router: Cisco/Linksys RVL200 VPN Router, firmware 1.1.10.1
Router: Linksys Wireless G Broadband Router WRT45GS v7, firmware 7.50.8
Router: Netgear Wireless-G Router WGR614 v10, firmware 1.0.2.4_39.0.39NA
Speed test: http://www.speakeasy.net/speedtest
My location: Las Vegas
Test location: Los Angeles
Methodology: Asus netbook computer, Windows XP SP3, physical wired connection to devices, nothing else connected. Tests run a couple of times to confirm consistency.
Test 1: Surfboard: 14.13/4.85
Test 2: RVL200 (firewalled): 2.46/2.79
Test 3: RVL200 (not firewalled): 20.04/5.32
Test 4: RVL200 (firewalled, other settings disabled): 2.35/2.88
Test 5: WRT54GS (firewalled): 20.39/4.92
Test 6: WGR614 (firewalled): 20.8/5.41
Conclusion: the RVL200 doesn't have a firewall, it has a *brick* wall. We've since moved to a Sonicwall appliance for our VPN, so the RVL200 is looking for a new home...
Monday, January 4, 2010
Unable to Connect to SQL Server While VPN Active
I've had the following problem for quite some time now, and finally resolved. At first blush, it seems like a simple enough issue to resolve, but in my particular case, it wasn't.
Basically what happened is that I have an instance of SQL (Express) installed on my primary development machine that is used as part of design, development, and building of the various applications I'm working on. I also have need to connect to a particular customer network via VPN (Aventail/SonicWALL). Everything was working fine until a few months ago when the customer updated the VPN client (from 9? to 10), at which point I was no longer able to access my SQL instance through trusted connection.
For example, if connected to the VPN and attempting a command line query, I'd see the following:
C:\>osql -S localhost\sqlexpress -q -E
Login failed for user ''. The user is not associated with a trusted SQL Server
connection.
In checking the event log, I'd see the following error:
SSPI handshake failed with error code 0x8009030c while establishing a connection with integrated security; the connection has been closed. [CLIENT: nnn.nnn.nnn.nnn]
If I disconnected the VPN, the above works as expected (no error).
?
My first conclusion was that the new VPN client was tunnelling ALL traffic through the appliance, and local requests back to my machine, and therefore SQL was considering the request as 'remote' and denying it. I tweaked the necessary SQL properties to allow remote connections -- nada.
After spending a lot of time trying to diagnose, I temporarily gave up and just resolved that I'd either be able to do VPN work or SQL work, but not both simultaneously.
Not satisfied with that 'solution', I resolved to spend more time on it. I'll leave out the countless things that I did try and didn't work and mention what I did find that actually solved the problem for me -- hopefully it will help someone else:
I started looking at the SQL configuration a little more closely (Start Menu\Programs\Microsoft SQL Server 2005\Configuration Tools\SQL Server Configuration Manager) and noticed some differences from some of the other typical SQL installations I have floating around. Specifically:
Under SQL Server 2005 Network Configuration: all protocols were enabled.
Under Aliases, there were several aliases using the tcp protocol.
I reset the above to coincide with what appears to be the default configuration:
SQL Server 2005 Network Configuration
Shared Memory - Enabled
Named Pipes - Disabled
TCP/IP - Disabled
VIA - Disabled
Deleted all SQL Native Client Configuration Aliases.
After that, my VPN/SQL issues were resolved! I'm not exactly sure what was the cause (protocol or alias), but a little more post-fix experimentation leads me to believe tha that the aliases were the problem. How that relates to the updated VPN client, or if it was purely coincidental, I don't know, but it is fixed and I'm satisfied!
Basically what happened is that I have an instance of SQL (Express) installed on my primary development machine that is used as part of design, development, and building of the various applications I'm working on. I also have need to connect to a particular customer network via VPN (Aventail/SonicWALL). Everything was working fine until a few months ago when the customer updated the VPN client (from 9? to 10), at which point I was no longer able to access my SQL instance through trusted connection.
For example, if connected to the VPN and attempting a command line query, I'd see the following:
C:\>osql -S localhost\sqlexpress -q -E
Login failed for user ''. The user is not associated with a trusted SQL Server
connection.
In checking the event log, I'd see the following error:
SSPI handshake failed with error code 0x8009030c while establishing a connection with integrated security; the connection has been closed. [CLIENT: nnn.nnn.nnn.nnn]
If I disconnected the VPN, the above works as expected (no error).
?
My first conclusion was that the new VPN client was tunnelling ALL traffic through the appliance, and local requests back to my machine, and therefore SQL was considering the request as 'remote' and denying it. I tweaked the necessary SQL properties to allow remote connections -- nada.
After spending a lot of time trying to diagnose, I temporarily gave up and just resolved that I'd either be able to do VPN work or SQL work, but not both simultaneously.
Not satisfied with that 'solution', I resolved to spend more time on it. I'll leave out the countless things that I did try and didn't work and mention what I did find that actually solved the problem for me -- hopefully it will help someone else:
I started looking at the SQL configuration a little more closely (Start Menu\Programs\Microsoft SQL Server 2005\Configuration Tools\SQL Server Configuration Manager) and noticed some differences from some of the other typical SQL installations I have floating around. Specifically:
Under SQL Server 2005 Network Configuration: all protocols were enabled.
Under Aliases, there were several aliases using the tcp protocol.
I reset the above to coincide with what appears to be the default configuration:
SQL Server 2005 Network Configuration
Shared Memory - Enabled
Named Pipes - Disabled
TCP/IP - Disabled
VIA - Disabled
Deleted all SQL Native Client Configuration Aliases.
After that, my VPN/SQL issues were resolved! I'm not exactly sure what was the cause (protocol or alias), but a little more post-fix experimentation leads me to believe tha that the aliases were the problem. How that relates to the updated VPN client, or if it was purely coincidental, I don't know, but it is fixed and I'm satisfied!
Friday, November 13, 2009
Agile Development & "When will it be done?"
I've been doing XP/Agile development & management for 5 years now, and I've come to the conclusion that as soon as you have to answer "when will it be done?" you are back on the path to Waterfall.
Friday, October 16, 2009
What is the meaning of GC.KeepAlive()?
What is the purpose of GC.KeepAlive(object obj)? From the method name, it sounds like it will keep obj from being garbage collected after the call. In fact, the opposite is true, it will keep obj from being garbage collected before the call. On the face, that sounds like a pretty absurd method, doesn't it.
The problem is that the method is named incorrectly. Better, but more verbose, names would be GC.MakeExplicitlyElligibleForGarbageCollection() or GC.DontCollectObjectBeforeNow().
So, what's really going on here? As you are presumably well aware, memory is reclaimed in the .NET framework through garbage collection, which can happen at any time, for any object that has no outstanding references (but don't confuse with reference counting). For example:
public void SomeMethod()
{
SomeClass o = new SomeClass();
GC.Collect(); // no references to 0, it may be collected
return;
}
Not very exciting, and makes sense, right? o isn't being used after the point of instantiation, so the collector is free to clean it up and reclaim the memory*.
But suppose that for some reason or another you need o to remain 'alive' until after SomeMethod() exits. In order to guarantee that, you would need to add some reference to 0 at the end of the method so that it can't be collected. That is the purpose of GC.KeepAlive().
So, all that GC.KeepAlive() does is create an artificial reference to 0 so that it can't be collected prior to the GC.KeepAlive() call:
public void SomeMethod_V2()
{
SomeClass o = new SomeClass();
GC.Collect(); // reference to 0 below, it will NOT be collected
GC.KeepAlive(o);
GC.Collect(); // no more references to 0, it may now be collected
return;
}
So, hopefully the above makes it clearer what GC.KeepAlive() does, although we do need to figure out a better name...
*Note: In order to facilitate debuggers, when an application is compiled in Debug mode, the JIT will NOT collect locally-allocated instances until after the method exits. So, in this example, if compiled under Debug, o will never be collected until after SomeMethod() returns. In Release mode, locally-allocated instances can be collected after the point of last reference, even before the method has returned.
The problem is that the method is named incorrectly. Better, but more verbose, names would be GC.MakeExplicitlyElligibleForGarbageCollection() or GC.DontCollectObjectBeforeNow().
So, what's really going on here? As you are presumably well aware, memory is reclaimed in the .NET framework through garbage collection, which can happen at any time, for any object that has no outstanding references (but don't confuse with reference counting). For example:
public void SomeMethod()
{
SomeClass o = new SomeClass();
GC.Collect(); // no references to 0, it may be collected
return;
}
Not very exciting, and makes sense, right? o isn't being used after the point of instantiation, so the collector is free to clean it up and reclaim the memory*.
But suppose that for some reason or another you need o to remain 'alive' until after SomeMethod() exits. In order to guarantee that, you would need to add some reference to 0 at the end of the method so that it can't be collected. That is the purpose of GC.KeepAlive().
So, all that GC.KeepAlive() does is create an artificial reference to 0 so that it can't be collected prior to the GC.KeepAlive() call:
public void SomeMethod_V2()
{
SomeClass o = new SomeClass();
GC.Collect(); // reference to 0 below, it will NOT be collected
GC.KeepAlive(o);
GC.Collect(); // no more references to 0, it may now be collected
return;
}
So, hopefully the above makes it clearer what GC.KeepAlive() does, although we do need to figure out a better name...
*Note: In order to facilitate debuggers, when an application is compiled in Debug mode, the JIT will NOT collect locally-allocated instances until after the method exits. So, in this example, if compiled under Debug, o will never be collected until after SomeMethod() returns. In Release mode, locally-allocated instances can be collected after the point of last reference, even before the method has returned.
Monday, December 22, 2008
Resolving Error LNK2001: unresolved external symbol __imp__time
I don't know if this is a generic situation or not, but it has been frustrating to resolve. The scenario is that I'm building some C/C++ FLEXnet components and was having no fun on getting the Release build to compile and link. After much fiddling, I was able to resolve everything down to one link error:
Error LNK2001: unresolved external symbol __imp__time
After comparing the differences between Debug and Release settings, I ended up stumbling upon the "Whole Program Optimization" setting (Project, Properties, Configuration Properties, General).
In my case, the default for the Release build was set to: Use Link Time Code Generation -- seems reasonable. What isn't reasonable is how it leads to the above linker error. I still have no idea why it causes it, but changing it to No Whole Program Optimization solved the issue.
Note: using Visual Studio 2008, SP1
Error LNK2001: unresolved external symbol __imp__time
After comparing the differences between Debug and Release settings, I ended up stumbling upon the "Whole Program Optimization" setting (Project, Properties, Configuration Properties, General).
In my case, the default for the Release build was set to: Use Link Time Code Generation -- seems reasonable. What isn't reasonable is how it leads to the above linker error. I still have no idea why it causes it, but changing it to No Whole Program Optimization solved the issue.
Note: using Visual Studio 2008, SP1
Tuesday, December 16, 2008
Get Method or Property Name as String in .Net
There are a few occasions where it is helpful to have the string name of the executing method or property.
The cleanest way that I've been able to devise is to use the query the current stack frame using System.Diagnostics.StackFrame.
string methodName = new StackFrame().GetMethod().Name;
When called from within a property, the name includes the getter/setter prefix: get_ or set_. Therefore, if you want the actual property name, use the following:
string propertyName = new StackFrame().GetMethod().Name.Split('_')[1];
The cleanest way that I've been able to devise is to use the query the current stack frame using System.Diagnostics.StackFrame.
string methodName = new StackFrame().GetMethod().Name;
When called from within a property, the name includes the getter/setter prefix: get_ or set_. Therefore, if you want the actual property name, use the following:
string propertyName = new StackFrame().GetMethod().Name.Split('_')[1];
Monday, November 24, 2008
Directory.Delete and Read-only Files
The System.IO.Directory.Delete() method will fail (exception) when it encounters a read-only file, regardless of the recursive flag. Unfortunately, the designers of this method did not have the foresight to provide a means around this limitation.
First off, the exception (UnauthorizedAccessException) only contains the file name; no path information is given. So, if you are in the middle of a recursive delete, the file name is largely irrelevant and there isn't much (programatically) that can be done to manage the situation. The appropriate implementation would have been to include the full path & file name information in the exception, and let the implementor decide on how to represent the file to the UI, as needed.
Second, there is no provision to handle read-only or similar situations w/ the method. At a minimum, there should be the capability to specify whether or not a read-only file should be deleted or not.
Because of this, the recursive implementation of Directory.Delete() is largely useless as it doesn't do what it has been designed to do: delete a directory (and containing files). The consequence of this is that we must write our own method to perform what is actually wanted -- hardly efficient. Anyway, to that end, here is a small function that implements more complete and usable delete functionality:
public void DirectoryDelete(string path, bool recursive)
{
if (recursive)
foreach (string directory in Directory.GetDirectories(path))
DirectoryDelete(directory, true);
foreach (string file in Directory.GetFiles(path))
File.SetAttributes(file, FileAttributes.Normal);
Directory.Delete(path, true);
}
First off, the exception (UnauthorizedAccessException) only contains the file name; no path information is given. So, if you are in the middle of a recursive delete, the file name is largely irrelevant and there isn't much (programatically) that can be done to manage the situation. The appropriate implementation would have been to include the full path & file name information in the exception, and let the implementor decide on how to represent the file to the UI, as needed.
Second, there is no provision to handle read-only or similar situations w/ the method. At a minimum, there should be the capability to specify whether or not a read-only file should be deleted or not.
Because of this, the recursive implementation of Directory.Delete() is largely useless as it doesn't do what it has been designed to do: delete a directory (and containing files). The consequence of this is that we must write our own method to perform what is actually wanted -- hardly efficient. Anyway, to that end, here is a small function that implements more complete and usable delete functionality:
public void DirectoryDelete(string path, bool recursive)
{
if (recursive)
foreach (string directory in Directory.GetDirectories(path))
DirectoryDelete(directory, true);
foreach (string file in Directory.GetFiles(path))
File.SetAttributes(file, FileAttributes.Normal);
Directory.Delete(path, true);
}
Thursday, November 20, 2008
Mapping Alpha to Numeric
I've been working w/ pen input computers for close to 10 years. Initially, it became very apparent that standardard (keyboard) input practices don't scale well to expected value ranges. For example, entering in numeric values on a desktop computer with a keyboard is a non-issue (for the most part). If you intend "6", you type "6". However, if you are on a pen input device, if you intend "6", the recognizer may interpret "b".
Most of my requirements for pen input has focused around fields that should be constrained to numeric values. Simply using a 'numeric input only' text box often doesn't work. This is because if the recognizer interprets "b", the numeric text box discards that value. For the end user, this can be quite frustrating and counter-productive.
Presuming numeric-entry text, I've resolved to create an alphanumeric text box and simulate numeric-only by performing the necessary mapping from recognizer value to expected. Empirically, I've found the following table mapping suits most cases of misrecognition:
lowercase letter O (o) -> zero
uppercase letter O (O) -> zero
uppercase letter D (D) -> zero
lowercase letter I (i) -> one
uppercase letter I (I) -> one
lowercase letter L (l) -> one
slash (/) -> one
backslash (\) -> one
vertical bar (|) -> one
exclamation point (!) -> one
lowercase letter Z (z) -> two
uppercase letter Z (Z) -> two
lowercase letter S (s) -> five
uppercase letter S (S) -> five
lowercase letter B (b) -> six uppercase letter G (G) -> six
right angle bracket (>) -> seven
lowercase letter Q (q) -> nine
lowercase letter G (g) -> nine
single quote (') -> decimal point
comma (,) -> decimal point
In practice, I've found that the above mapping/conversion works pretty well and reduces the frustration and increases the validity of captured input.
Thursday, November 13, 2008
Changing e-mail Address Name Resolution Order in Outlook
Not programming related, but worthy of brief mention nonetheless: The e-mail address name resolution order can be changed in Outlook. From Outlook, navigate to Tools, Address Book..., and then from the Address Book, navigate to Tools, Options.
From this dialog you can change the resolution order in the "When sending mail, check names using these address lists in the following order" list.
Wednesday, November 12, 2008
InstallShield Build Error -4327

I recently started getting an error while building an installation package from the command line with InstallShield (2009):
Error -4327: Could not write string ID "0" to the InstallScript strings file.
Searching the InstallShield KB, I found the following:
Q107858: ERRDOC: Build Error 4327
http://kb.acresso.com/selfservice/viewContent.do?externalID=Q107858
"Make sure that enough disk space is available..." and "Make sure the string text file is not marked as read-only..."
Neither of these were the cause of the problem in my case. After fiddling for a bit, deleting output folders, temp, etc., I resorted to the old version of the three-finger-salute (reboot). Ha! For once, it solved the problem.
Friday, November 7, 2008
Retreiving the Default Value Using NAnt readregistry
I recently had the need to retrieve the default value for a registry key in a NAnt script. The documentation for <readregistry> doesn't specifically mention how to do this.
Turns out that it is pretty simple -- just add a trailing backslash to the key parameter for the task:
<readregistry key="SOFTWARE\Acme\" hive="LocalMachine" property="acme">
If you just specify "SOFTWARE\Acme" for the key, you will an NAnt Registry Value Not Found! error when executing the script.
Turns out that it is pretty simple -- just add a trailing backslash to the key parameter for the task:
<readregistry key="SOFTWARE\Acme\" hive="LocalMachine" property="acme">
If you just specify "SOFTWARE\Acme" for the key, you will an NAnt Registry Value Not Found! error when executing the script.
Thursday, November 6, 2008
Console Output from a GUI APP

I recently needed the functionality to enable text output to the console (if run from a command line) for a non-console (standard GUI) application. Sounds easy, right?
Standard text output (e.g. cout, printf) are redirected to the bit-bucket in the sky for a GUI application, so those don't work in this case(1).
According to the API documentation, AttachConsole(ATTACH_PARENT_PROCESS) should do what I needed, but it has its quirks, namely that the output gets dumped to the console at the current caret position which leads to something like (presuming my intended output is "Hello, world!":
C:\Source\TestApp>test.exe -console
C:\Source\TestApp>Hello, World!
_
Not exactly the formatting that is wanted. Even though the text is output after the prompt, it isn't considered part of the input, i.e., hitting return ignores the line and displays the prompt.
Anyway, I want the text to be on its own line, just like you'd expect from a standard console application. After much fiddling, I found the following seems to work most(2) of the time:
AllocConsole(ATTACH_PARENT_PROCESS);
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(hConsole, szOutput, lstrlen(szOutput), NULL, NULL);
// etc.
FlushFileBuffers(hConsole);
FreeConsole();
With the above, I get the expected output:
C:\Source\TestApp>test.exe -console
Hello, World!
C:\Source\TestApp>_
Notes:
1) There are ways to initialize the CRT output handles using the the handle retrieved from GetStdHandle() if you want to use printf, cout, etc. Search the net for various implementations.
2) I've found that this method works most of the time. Occasionally, the text is still output in the old screwy format for unknown reasons. In this case, it was good enough for government work.
Standard text output (e.g. cout, printf) are redirected to the bit-bucket in the sky for a GUI application, so those don't work in this case(1).
According to the API documentation, AttachConsole(ATTACH_PARENT_PROCESS) should do what I needed, but it has its quirks, namely that the output gets dumped to the console at the current caret position which leads to something like (presuming my intended output is "Hello, world!":
C:\Source\TestApp>test.exe -console
C:\Source\TestApp>Hello, World!
_
Not exactly the formatting that is wanted. Even though the text is output after the prompt, it isn't considered part of the input, i.e., hitting return ignores the line and displays the prompt.
Anyway, I want the text to be on its own line, just like you'd expect from a standard console application. After much fiddling, I found the following seems to work most(2) of the time:
AllocConsole(ATTACH_PARENT_PROCESS);
HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
WriteConsole(hConsole, szOutput, lstrlen(szOutput), NULL, NULL);
// etc.
FlushFileBuffers(hConsole);
FreeConsole();
With the above, I get the expected output:
C:\Source\TestApp>test.exe -console
Hello, World!
C:\Source\TestApp>_
Notes:
1) There are ways to initialize the CRT output handles using the the handle retrieved from GetStdHandle() if you want to use printf, cout, etc. Search the net for various implementations.
2) I've found that this method works most of the time. Occasionally, the text is still output in the old screwy format for unknown reasons. In this case, it was good enough for government work.
Friday, September 19, 2008
Build Warning: The file 'filename.ext' could not be added to the project...
I recently encountered a build warning (C# project, Visual Studio 2005) that was less than informative and somewhat difficult to resolve:
Warning: The file 'keyfile.snk' could not be added to the project. A file with the same path already exists in the project. MyProject.UI
(Note: keyfile.snk and MyProject.UI can be any file 0r project.)
Other than the terse warning, there was nothing to go on. I tried deleting the file from the OS, removing/re-adding to the project, turning off signing, etc. Nothing worked, the error persisted.
I finally opened up the offending project file (MyProject.UI.csproj in this case) in Notepad, and searched for this particular file (keyfile.snk). The source of the warning became immediately obvious -- within one of the <ItemGroup>blocks, I noticed the following:
...
<None Include="keyfile.snk" />
<None Include="keyfile.snk" />
...
So, even though the file was only being included/referenced/visible once in the solution explorer, it was actually being included twice in the project file. I have no idea how/why that happened.
Regardless, the solution is simple -- remove one of the lines, save, and reload your project.
Warning: The file 'keyfile.snk' could not be added to the project. A file with the same path already exists in the project. MyProject.UI
(Note: keyfile.snk and MyProject.UI can be any file 0r project.)
Other than the terse warning, there was nothing to go on. I tried deleting the file from the OS, removing/re-adding to the project, turning off signing, etc. Nothing worked, the error persisted.
I finally opened up the offending project file (MyProject.UI.csproj in this case) in Notepad, and searched for this particular file (keyfile.snk). The source of the warning became immediately obvious -- within one of the <ItemGroup>
...
<None Include="keyfile.snk" />
...
So, even though the file was only being included/referenced/visible once in the solution explorer, it was actually being included twice in the project file. I have no idea how/why that happened.
Regardless, the solution is simple -- remove one of the lines, save, and reload your project.
Friday, September 5, 2008
Specifying an Alternate Assembly for StartupURI
By default, when creating a WPF application in Visual Studio 2008, the StartupURI value is appropriately set to the main window of the application (typically Window1.xaml).
It is simple enough to manually edit the StartupURI to point to another component, provided the code resides in the local assembly. Referencing a window in another assembly is not so intuitive. In order to specify an alternate assembly, the pack:// nomenclature must be used:
StartupUri="pack://application:,,,/assembly_name;component/path/file_name.xaml"
Where:
pack://application:,,,/UI;component/CalculatorView.xaml
assembly - UI.dll
path - none (file at project root)
file_name - CalculatorView
pack://application:,,,/MyApp.UI;component/Views/CalculatorView.xaml
assembly - MyApp.UI.dll
path - Views
file_name - CalculatorView
pack://application:,,,/UI;component/Views/External/CalculatorView.xaml assembly - UI.dll
path - Views/External
file_name - CalculatorView
It is simple enough to manually edit the StartupURI to point to another component, provided the code resides in the local assembly. Referencing a window in another assembly is not so intuitive. In order to specify an alternate assembly, the pack:// nomenclature must be used:
StartupUri="pack://application:,,,/assembly_name;component/path/file_name.xaml"
Where:
- assembly_name is the name of the referenced assembly, sans extension
- path is the subfolder in which the component resides; if the component is at the project root, this element is omitted
- file_name is the file name of the component
pack://application:,,,/UI;component/CalculatorView.xaml
assembly - UI.dll
path - none (file at project root)
file_name - CalculatorView
pack://application:,,,/MyApp.UI;component/Views/CalculatorView.xaml
assembly - MyApp.UI.dll
path - Views
file_name - CalculatorView
pack://application:,,,/UI;component/Views/External/CalculatorView.xaml assembly - UI.dll
path - Views/External
file_name - CalculatorView
Monday, August 18, 2008
Dynamically Changing Cultures and String Resources
I'm working on a project that allows the user to dynamically specify the language used in the UI. This can be set independent of the operating system language settings, and is immediately applied.
In our case, most of our strings are retrieved from resources using ResXFileCodeGeneratorEx tool-generated code. When the user switches the language, we simply set the application CurrentCulture and CurrentUICulture, and refresh the display.
However, I currently ran into an issue where a dialog box wasn't displaying text in the alternate languages. The localized text was in the resource, and everything should have been working as expected.
After a little digging, I realized that the dialog was being displayed in the context of another thread. I would have expected that the .Net framework thread initialization code would use the culture of the executing thread, but rather it defaults to the current OS culture. Subsequently, attempts to retrieve a localized string on that new thread would return text in current OS culture (English) -- not what was anticipated.
The solution was to simply set the Thread.CurrentUICulture (and Thread.CurrentCulture) properties to the values from the executing (main) thread:
thread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
thread.CurrentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
In our case, most of our strings are retrieved from resources using ResXFileCodeGeneratorEx tool-generated code. When the user switches the language, we simply set the application CurrentCulture and CurrentUICulture, and refresh the display.
However, I currently ran into an issue where a dialog box wasn't displaying text in the alternate languages. The localized text was in the resource, and everything should have been working as expected.
After a little digging, I realized that the dialog was being displayed in the context of another thread. I would have expected that the .Net framework thread initialization code would use the culture of the executing thread, but rather it defaults to the current OS culture. Subsequently, attempts to retrieve a localized string on that new thread would return text in current OS culture (English) -- not what was anticipated.
The solution was to simply set the Thread.CurrentUICulture (and Thread.CurrentCulture) properties to the values from the executing (main) thread:
thread.CurrentUICulture = System.Threading.Thread.CurrentThread.CurrentUICulture;
thread.CurrentCulture = System.Threading.Thread.CurrentThread.CurrentCulture;
Thursday, August 14, 2008
Failed string.Format w/ Parameters using CJK
I recently ran into an issue in a localized application that was using the C# .Net Framework string.Format("blah-blah {0}", parm1).
In English, everything worked fine and as expected. However when using a localized format string (Japanese), the parameter substitution failed:
string.Format("数字は、{0}", parm1);
(If you don't have the necessary language pack installed, here is a image representation of the text:
)
It turns out that the translated text was using different braces than 0x7B and 0x7D. In the translated text the left brace was 0xFF5B. Looks like a brace, but isn't for the purposes of string.Format. Further, in looking at the MS Mincho char set, I couldn't find that particular brace. Using the Alt+65371 trick for that character in notepad, I'd get a left bracket -- don't know what/where the brace comes from...
The simple fix was to change the 0xFF5B brace to the correct 0x7B, and then string.Format worked as expected.
In English, everything worked fine and as expected. However when using a localized format string (Japanese), the parameter substitution failed:
string.Format("数字は、{0}", parm1);
(If you don't have the necessary language pack installed, here is a image representation of the text:
)It turns out that the translated text was using different braces than 0x7B and 0x7D. In the translated text the left brace was 0xFF5B. Looks like a brace, but isn't for the purposes of string.Format. Further, in looking at the MS Mincho char set, I couldn't find that particular brace. Using the Alt+65371 trick for that character in notepad, I'd get a left bracket -- don't know what/where the brace comes from...
The simple fix was to change the 0xFF5B brace to the correct 0x7B, and then string.Format worked as expected.
Friday, July 18, 2008
Bugs - Should You Estimate the Effort?
Is there a purpose to estimating the effort that a bug (defect) will take to fix? I think not.
Presuming that the bug has been properly prioritized (id est -- will it be fixed in this release or the next), there is little reason to spend the time estimating the effort that bug will take to fix.
For the purposes of this argument, I don't consider the following bugs:
- Text and UI fixes. The UI shouldn't be implemented by a traditional developer; it should be largely defined and implemented by the team UI designer.
- Misunderstandings. Spec said this, developer implemented that. This isn't a defect in the code, it is a misunderstanding or failure in the communication process -- both of which should be examined and fixed.
- Spec changes. Not a bug, but a feature request or revision.
So, considering legitimate bugs -- something implemented to do a certain behavior, but doesn't -- what is the point of estimating? Presumably the original implementation by the developer was thought-out (and developer tested!), but it isn't behaving now as expected. How can someone actually presume to know how long it will take to: first, find the issue; second, fix the issue? Assuredly most any estimate provided will either be too short or too long -- so what is the value?
Bugs should be prioritized, and then fixed. Don't waste effort on estimation.
Presuming that the bug has been properly prioritized (id est -- will it be fixed in this release or the next), there is little reason to spend the time estimating the effort that bug will take to fix.
For the purposes of this argument, I don't consider the following bugs:
- Text and UI fixes. The UI shouldn't be implemented by a traditional developer; it should be largely defined and implemented by the team UI designer.
- Misunderstandings. Spec said this, developer implemented that. This isn't a defect in the code, it is a misunderstanding or failure in the communication process -- both of which should be examined and fixed.
- Spec changes. Not a bug, but a feature request or revision.
So, considering legitimate bugs -- something implemented to do a certain behavior, but doesn't -- what is the point of estimating? Presumably the original implementation by the developer was thought-out (and developer tested!), but it isn't behaving now as expected. How can someone actually presume to know how long it will take to: first, find the issue; second, fix the issue? Assuredly most any estimate provided will either be too short or too long -- so what is the value?
Bugs should be prioritized, and then fixed. Don't waste effort on estimation.
Subscribe to:
Posts (Atom)
