g+
g+ Communities
Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014 
<== Date ==> <== Thread ==>

Subject: Problem with using strings to access enum indices on Windows
From: Mark Rivers <rivers@cars.uchicago.edu>
To: Andrew Johnson <anj@aps.anl.gov>, "tech-talk@aps.anl.gov" <tech-talk@aps.anl.gov>
Cc: "'Christian Schlepuetz'" <cschlep@aps.anl.gov>, Peter Eng <eng@cars.uchicago.edu>, "'Sarvjit Shastri'" <shastri@aps.anl.gov>, "'James Rezende Piton - LNLS'" <james@lnls.br>, "'Gerry Swislow'" <gerry@certif.com>, "'Peter Kenesei'" <kenesei@aps.anl.gov>, "'bcda@aps.anl.gov'" <bcda@aps.anl.gov>
Date: Sun, 23 Sep 2012 20:37:17 +0000
Folks,

There is a problem writing a number as a string (such a "1") to an EPICS enum when the EPICS PV is hosted on a Windows IOC built with the Microsoft compiler (win32-x86, windows-x64, etc). This problem came to my attention because the popular beamline control software program "spec" does all channel access puts using DBF_STRING.

For example if there is bo record with states 0="Off" and 1="On" then this is what we observe in spec:

epics_put(PV, "On") works fine
epics_put(PV, "1") works fine on Linux, vxWorks, etc. but fails on Windows

When one does epics_put(PV, "1") here is what is supposed to happen:

1) The EPICS IOC first sees if the string matches one of the enum string fields. If it does it selects that enum choice.

2) If the string does not match an enum string, it tries to convert the string to a number, and checks to see if the number is one of the allowed numbers for that enum. If it does it selects that enum choice.

In step 2) it does the attempted conversion from a string to a number using this code in dbFastLinkConv.c.

nargs = sscanf(from," %u %n",&ind,&nchars);
if(nargs==1 && nchars==strlen(from) && ind<nchoices) {
  *pfield = ind;
  return(0);
}

So it uses the %n format string to figure out if sscanf has consumed the entire "from" string. This ensures, for example, that the string "1abc" is not interpreted as "1".

The problem is that the " %u %n" format string does not work on Windows.

When I spoke to Andrew Johnson he said he thought this was because Windows does not implement the %n format specifier.

I have done some more investigation, and found that this is not the case. The problem is the leading space before the %n specifier. On Windows when sscanf hits the blank space in the format specifier, but does not find a blank space in the input string, it stops parsing the input string. So it never reaches the %n format specifier. The GNU run-time library behaves differently, it processes %n even if there is no space in the input. I think it is questionable as to which is the "correct" behavior.

To investigate this I wrote the following little test program. It tests all combinations of 4 input strings with 4 format strings. The input strings differ in having no blanks, leading blank, trailing blank, and both leading and trailing blank. The format strings differ in having no blanks, leading blank, blank between %u and %n, and both leading and between. The program prints the results of the %u and %n conversions.

****************************************
#include <stdio.h>
#include <string.h>

#define NUM_STRINGS 4
#define NUM_FORMATS 4

int main(int argc, char *argv[])
{
  static const char *testStrings[NUM_STRINGS] = {"123", " 123", "123 ", " 123 "};
  static const char *testFormats[NUM_FORMATS] = {" %u %n", " %u%n", "%u %n", "%u%n"};
  unsigned ui;
  int i, j, nchars;

  for (i=0; i<NUM_STRINGS; i++) {
    for (j=0; j<NUM_FORMATS; j++) {
      ui = 0;
      nchars = 0;
      sscanf(testStrings[i], testFormats[j], &ui, &nchars);
      printf("String='%s' format='%s': ui=%u, strlen(string)=%d, nchars=%d\n",
        testStrings[i], testFormats[j], ui, strlen(testStrings[i]), nchars);
    }
  }
  return 0;
}
****************************************

Here are the results on Linux:
corvette:dxp/dxpApp/src>../../bin/linux-x86/test_sscanf
String='123' format=' %u %n': ui=123, strlen(string)=3, nchars=3
String='123' format=' %u%n': ui=123, strlen(string)=3, nchars=3
String='123' format='%u %n': ui=123, strlen(string)=3, nchars=3
String='123' format='%u%n': ui=123, strlen(string)=3, nchars=3
String=' 123' format=' %u %n': ui=123, strlen(string)=4, nchars=4
String=' 123' format=' %u%n': ui=123, strlen(string)=4, nchars=4
String=' 123' format='%u %n': ui=123, strlen(string)=4, nchars=4
String=' 123' format='%u%n': ui=123, strlen(string)=4, nchars=4
String='123 ' format=' %u %n': ui=123, strlen(string)=4, nchars=4
String='123 ' format=' %u%n': ui=123, strlen(string)=4, nchars=3
String='123 ' format='%u %n': ui=123, strlen(string)=4, nchars=4
String='123 ' format='%u%n': ui=123, strlen(string)=4, nchars=3
String=' 123 ' format=' %u %n': ui=123, strlen(string)=5, nchars=5
String=' 123 ' format=' %u%n': ui=123, strlen(string)=5, nchars=4
String=' 123 ' format='%u %n': ui=123, strlen(string)=5, nchars=5
String=' 123 ' format='%u%n': ui=123, strlen(string)=5, nchars=4

Here are the results on Windows:
J:\epics\devel\dxp\dxpApp\src>..\..\bin\win32-x86\test_sscanf.exe
String='123' format=' %u %n': ui=123, strlen(string)=3, nchars=0
String='123' format=' %u%n': ui=123, strlen(string)=3, nchars=3
String='123' format='%u %n': ui=123, strlen(string)=3, nchars=0
String='123' format='%u%n': ui=123, strlen(string)=3, nchars=3
String=' 123' format=' %u %n': ui=123, strlen(string)=4, nchars=0
String=' 123' format=' %u%n': ui=123, strlen(string)=4, nchars=4
String=' 123' format='%u %n': ui=123, strlen(string)=4, nchars=0
String=' 123' format='%u%n': ui=123, strlen(string)=4, nchars=4
String='123 ' format=' %u %n': ui=123, strlen(string)=4, nchars=4
String='123 ' format=' %u%n': ui=123, strlen(string)=4, nchars=3
String='123 ' format='%u %n': ui=123, strlen(string)=4, nchars=4
String='123 ' format='%u%n': ui=123, strlen(string)=4, nchars=3
String=' 123 ' format=' %u %n': ui=123, strlen(string)=5, nchars=5
String=' 123 ' format=' %u%n': ui=123, strlen(string)=5, nchars=4
String=' 123 ' format='%u %n': ui=123, strlen(string)=5, nchars=5
String=' 123 ' format='%u%n': ui=123, strlen(string)=5, nchars=4

So Windows does not set nchars with the %n specifier if there is a blank before %n in the format string but no blank after the number in the input string.

My question: Is it necessary to allow the string to contain a trailing blank?  That is the only case where " %u %n" produces a different result from "%u%n".  If we changed these format strings to %u%n then they would also work on Windows.

Mark



Replies:
Re: Problem with using strings to access enum indices on Windows Andrew Johnson

Navigate by Date:
Prev: FW: BPM Screens Southern, Tim
Next: Re: CAC problem between RTEMS and vxWorks Benjamin Franksen
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014 
Navigate by Thread:
Prev: Bug on autosave for the ULONG type array Kim, Kukhee
Next: RE: Problem with using strings to access enum indices on Windows Mark Rivers
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  <20122013  2014 
ANJ, 18 Nov 2013 Valid HTML 4.01! · Home · News · About · Base · Modules · Extensions · Distributions · Download ·
· EPICSv4 · IRMIS · Talk · Bugs · Documents · Links · Licensing ·