views:

768

answers:

4

I'm porting an existing (mostly) cross-platform application to WinCE 4.2. The current entry point for the function is

int main(int argc, char *argv[]){}

I would like to keep this part as-is, and have the WinCE entry point simply call it. I believe something like the following should work:

int WINAPI WinMain( HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPTSTR    lpCmdLine,
                    int       nCmdShow) 
{
    int argc = _tcslen(lpCmdLine);
    char *argv = new char[argc];
    wcstombs(argv,lpCmdLine,1024);
    argc = main(argc,&argv);
    delete [] argv;
    return argc;
}

It compiles, and should run once I figure out why the linker is throwing up, but is it right?

+2  A: 

Your general idea is correct however your conversion of lpCmdLine is likely to cause you some issues. Consider the following:

$> myprogam.exe -a shortargument -b -c -d "long argument with spaces"

the arguments passed to your main function would be something like this:

argc = 7;
argv = {
    "myprogram.exe",
    "-a",
    "shortargument",
    "-b",
    "-c",
    "-d",
    "long argument with spaces"
};

WinMain however will receive a big long string like this:

lpCmdLine = "-a shortargument -b -c -d "long argument with spaces"";

So if you have any command line option parsing going on you are probably going to break it. The most versatile approach would be to loop through lpCmdLine setting all white space (outside of matching quotation marks of course) to the the null character (i.e. '\0' or simply 0) and keep track of the pointers to first valid character after a sequence of null characters.

Addition: If I recall correctly from when I was doing wince development I seem to remember something about lpCmdLine just being there for compatibility with win32 so it is always empty. To get the command line I think you have to use GetCommandLine.

Kevin Loney
would you leave it if the app is only going to be called by our code, never deployed to customers, and arguments that don't get more complicated than "-i in -o out Clayers=15 Corder=LRCP"?
drhorrible
it depends on how you parse your command line options if you were using anything along the lines of the unix getopt functions then it would break your code. Without seeing how you are parsing your command line you'll just have to try it and find out.
Kevin Loney
lpCmdLine is not null, but GetCommandLine() is (at least with my version of WinCE, 4.2)
drhorrible
+1  A: 

No that is not going to work.

int WINAPI WinMain( HINSTANCE hInstance,
                HINSTANCE hPrevInstance,
                LPTSTR    lpCmdLine,
                int       nCmdShow) 
{
    // argc: This is the number of arguments NOT the strlen
    int argc = _tcslen(lpCmdLine);

    // argv: Is an array of char* NOT an array of char.
    char *argv = new char[argc];
    wcstombs(argv,lpCmdLine,1024);

    // The argument passed as argc is not valid here.
    argc = main(argc,&argv);
    delete argv;

    // You are returning the result of main that is correct.
    // But re-using argc like this is smelly. Declare a new
    // variable and let the compiler optimise away the extra use
    // the compiler is VERY good at that.
    return argc;
}

OK. Not an expert with WinCE. If I was doing this I would use std::vector you can modify to work

int WINAPI WinMain( HINSTANCE hInstance,
                HINSTANCE hPrevInstance,
                LPTSTR    lpCmdLine,
                int       nCmdShow) 
{
    std::vector<char*>  args;

    // Split lpCmdLine by space.
    // Remember to watch for quotes when splitting (From 'Kevin Loney')
    // For each argument do
    args.push_back(<SOMTHING>)


    // The last argv[argc] should be NULL. 
    args.push_back(NULL);

    int result;
    try
    {
        // argc does not include the last NULL so do a -1
        result = main(args.size()-1,&args[0]);
    }
    catch(...)
    {
        // If <SOMTHING> includes dynamically allocating memory
        // Then you should delete it here.

        throw;  // Re-Throw the exception to get the same behavior.
    }

    return result;
}
Martin York
I'm curious about argv[argc] == NULL, I have never seen this mentioned in anything I've ever read about command line parsing in C. I can see that it certainly wouldn't hurt but is it defined in the C spec somewhere?
Kevin Loney
Martin York
A quick google: http://publications.gbdirect.co.uk/c_book/chapter10/arguments_to_main.html (Though I don't claim this page is cannon).
Martin York
+1  A: 

Thank you both for your helpful answers. I wrote the following, which works as well as we need it for now. Only our code will be calling this executable, and never with quotes, although that wouldn't be too hard to add. Also, it might not do well if there is more than one space between arguments, but again, we don't have to worry about other people using this program, it's just for academic purposes. If you think improvements are necessary, edit this post, and justify it in your comment.

int WINAPI WinMain( HINSTANCE hInstance,
                    HINSTANCE hPrevInstance,
                    LPTSTR    lpCmdLine,
                    int       nCmdShow ) {
  assert(lpCmdLine != NULL);

  int argc = 1, ret = 0;
  std::vector<char*>  args;

  // Copy the entire array to a regular cstr
  int cmdLineLen = _tcslen(lpCmdLine);
  char *argv = new char[cmdLineLen];
  wcstombs(argv,lpCmdLine,cmdLineLen);
  args.push_back(&argv[0]);

  // Replace spaces with nulls to effectively create array of cstr
  for(int i=0; i<cmdLineLen; i++){
    if(argv[i] == ' '){
      argv[i] = '\0';
      args.push_back(&argv[i+1]); // Keep track of the first char in each word
      argc++;
    }
  }

  // argv[argc] should be NULL. 
  args.push_back(NULL);

  try{ // Run the program
    ret = main(argc,&args[0]);
  }
  catch(...){
    // TODO: Report error here. Commented code works OK for WinCE .NET
    // delete argv;
    // throw; 
    ret = -1;
  }
  delete argv;
  return ret;
}

Also, for those interested, running this at the command line

>myprogam.exe -a shortargument -b -c

will put the following in lpCmdLine

"-a shortargument -b -c"

Also, my first guess was that argv needed to be delete[]'d (because I new char[]'d), but when I did this, the program had a fatal error. When I switched to the above, it worked. Doesn't that violate the new->delete/new[]->delete[] rule?

drhorrible
I noticed a C4297 warning when compiling this WinMain function. Apparently no functions are allowed to get out of WinMain. So the `throw;` statement should probably be removed.
Frerich Raabe
Thanks, I've changed the code. I don't have access to the compiler any more, so I can't make sure this edit works.
drhorrible
+1  A: 

The really simple way to do this is to just change the linker entry point for your project and use the main(...) function.

Open up the project Properties dialog then go down to "Linker->Advancedd" and set the "Entry Point" value based on the following:

if your entry point is this:

int main(int argc, char *argv[])

then set the the entry point to mainACRTStartup

But if you're starting with:

int _tmain(int argc, TCHAR *argv[])

then the entry point is mainWCRTStartup

ctacke
That looks really nifty. If I had the compiler still, I'd try it out.
drhorrible