简体   繁体   中英

Correctly creating and running a win32 service with file I/O

I've written a very simple service application based on this code example .

The application as part of its normal running assumes there exists a file in the directory it is found, or in its execution path.

When I 'install' the service and then subsequently 'start' the service from the service manager in control panel. The application fails because it can't find the file to open and read from (even though the file is in the same directory as the installed executable).

My question is when a windows service is run, which is the expected running path supposed to be?

When calling 'CreateService' there only seems to be a path parameter for the binary, not for execution. Is there someway to indicate where the binary should be executed from?

I've tried this on windows vista and windows 7. Getting the same issues.

Since Windows services are run from a different context than normal user-mode applications, it's best if you don't make any assumptions about working directories or relative paths. Aside from differences in working directories, a service could run using a completely different set of permissions, etc.

Using an absolute path to the file that your service needs should avoid this problem entirely. Absolute paths will be interpreted the same regardless of the working directory, so this should make the working directory of your service irrelevant. There are several ways to go about this:

  1. Hard-code the absolute path - This is perhaps the easiest way to avoid the problem, however it's also the least flexible. This method is probably fine for basic development and testing work, but you probably want something a bit more sophisticated before other people start using your program.
  2. Store the absolute path in an environment variable - This gives you an extra layer of flexibility since the path can now be set to any arbitrary value and changed as needed. Since a service can run as a different user with a different set of environment variables, there are still some gotchas with this approach.
  3. Store an absolute path in the registry - This is probably the most fool-proof method. Retrieving the path from the registry will give you the same result for all user accounts, plus this is relatively easy to set up at install time.

By default, the current directory for your Windows service is the System32 folder.

A promising solution is creating an environment variable that keeps the full path of your input location and retrieving the path from this variable at runtime.

If you use the same path as binary, you could just read binary path and modify it accordingly. But this is rather quick-fix rather than designed-solution. If I were you, I would either create system-wide environment variable and store value there, or (even better) use windows registry to store service configuration.

Note:

You will need to add Yourself some privileges using AdjustTokenPrivileges function , you can see an example here in ModifyPrivilege function.

Also be sure to use HKEY_LOCAL_MACHINE and not HKEY_CURRENT_USER. Services ar running under different user account so it's HKCU's will be different than what you can see in your registry editor.

Today I solved this problem as it was needed for some software I was developing.

As people above have said; you can hardcode the directory to a specific file - but that would mean whatever config files are needed to load would have to be placed there.

For me, this service was being installed on > 50,000 computers. We designed it to load from directory in which the service executable is running from.

Now, this is easy enough to set up and achieve as a non-system process (I did most of my testing as a non-system process). But the thing is that the system wrapper that you used (and I used as well) uses Unicode formatting (and depends on it) so traditional ways of doing it doesn't work as well.

Commented parts of the code should explain this. There are some redundancies, I know, but I just wanted a working version when I wrote this. Fortunately, you can just use GetModuleFileNameA to process it in ASCII format

The code I used is:

char buffer[MAX_PATH]; // create buffer
DWORD size = GetModuleFileNameA(NULL, buffer, MAX_PATH); // Get file path in ASCII

std::string configLoc; // make string

for (int i = 0; i < strlen(buffer); i++) // iterate through characters of buffer
{
    if (buffer[i] == '\\') // if buffer has a '\' in it, replace with doubles
    {
        configLoc = configLoc + "\\\\"; // doubles needed for parsing. 4 = 2(str)
    }
    else
    {
        configLoc = configLoc + buffer[i]; // else just add char as normal
    }
}

// Complete location
configLoc = configLoc.substr(0, configLoc.length() - 17); //cut the .exe off the end
                                                          //(change this to fit needs)   
configLoc += "\\\\login.cfg"; // add config file to end of string

From here on, you can simple parse configLoc into a new ifsteam - and then process the contents.

Use this function to adjust the working directory of the service to be the same as the working directory of the exe it's running.

void AdjustCurrentWorkingDir() {
    TCHAR szBuff[1024];
    DWORD dwRet = 0;
    dwRet = GetModuleFileName(NULL, szBuff, 1024); //gets path of exe

    if (dwRet != 0 && GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
        *(_tcsrchr(szBuff, '\\') + 1) = 0; //get parent directory of exe

        if (SetCurrentDirectory(szBuff) == 0) {
            //Error
        }
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM