1

I successfully created a directory or folder and I also managed to create a file in that directory but how can create a directory and a file in the user's appdata local if I don't know what is their username or their user folder name? thanks!

I used codeblocks

#include <sys/stat.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void main()
{
    int check;
    char* dirname = "C:/Pyromagne";

    check = mkdir(dirname);

    FILE* fp;

    fp = fopen("C:/Pyromagne/test.pyr", "w+");
    fputs("Pyromagne\n", fp);
    fputs("qwertyuiop", fp);
    fclose(fp);

    FILE* ffpp;
    char user[25];
    char pass[25];
    char uuser[25];

    ffpp = fopen("C:/Pyromagne/test.pyr", "r");

    strcpy(uuser, fgets(user, 255, (FILE*)ffpp));
    printf("%s\n", uuser);

    strcpy(uuser, fgets(user, 255, (FILE*)ffpp));
    printf("%s\n", uuser);

    fclose(ffpp);
}

Pyromagne
  • 45
  • 8
  • 4
    You can try `getenv("APPDATA")`, this will give you "users/name/appdata/roaming". You should really use `SHGetKnownFolderPath` with Unicode support, specially for non-English systems. But I don't know how to do it in gcc with C. – Barmak Shemirani Dec 23 '21 at 02:37
  • 1
    @BarmakShemirani not that hard: `#include PWSTR path; HRESULT hRes = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &path); if (SUCCEEDED(hRes)) { /* use path as needed... */ CoTaskMemFree(path); }` and be sure to link to `Shell32.lib`, too. – Remy Lebeau Dec 23 '21 at 03:57
  • 1
    @RemyLebeau It's easy in Visual Studio, but there is something odd about MinGW. It won't even compile in c++, it compiles in c, I link to `libshell32.a` (MinGW's version) but get *"undefined reference"* error. I can run `ShellExecute` which is also shell32.lib function, which doesn't even need explicit link command. – Barmak Shemirani Dec 23 '21 at 05:19
  • 2
    @BarmakShemirani - Remy's code should compile fine with GCC in C (works for me at least, compiling in gcc under Windows), but you need to add libuuid.a for folderids. – Simon Mourier Dec 23 '21 at 08:13
  • @BarmakShemirani what is difference of those two, the `getenv("APPDATA")` and `SHGetKnownFolderPath`? What libraries do I need in order to use the `getenv("APPDATA")` function? – Pyromagne Dec 23 '21 at 13:09
  • @BarmakShemirani I don't use Visual Studio for C/C++ development, I use C++Builder. My guess is MinGW's `shell32` import lib is just outdated, since `SHGetKnownFolderPath()` is fairly new compared to the old `ShellExecute()`. As for `getenv()`, it is part of the C runtime, so there is no external lib you need to link to to use it. But on Windows, it is likely just a wrapper for `GetEnvironmentVariable()` in `kernel32`. The difference being, an environment block is part of the calling process and can be tweaked per process, whereas KnownFolders are part of Windows' base configuration. – Remy Lebeau Dec 23 '21 at 16:52
  • @SimonMourier I wouldn't have though of libuuid.a, it sorts out `KNOWNFOLDERID`, I still couldn't link `SHGetKnownFolderPath`. There are different versions of MinGW, I must be missing something. – Barmak Shemirani Dec 23 '21 at 17:36
  • @RemyLebeau, yes it appears that the library is old. – Barmak Shemirani Dec 23 '21 at 17:39
  • SHGetKnownFolderPath might be hidden behind a WINVER define. For older folders like appdata you might as well use SHGetFolderPath instead. – Anders Dec 23 '21 at 18:30
  • @Anders it compiles so it's not WINVER issue, it won't link. – Barmak Shemirani Dec 24 '21 at 00:53
  • @BarmakShemirani *it compiles so it's not WINVER issue, it won't link* Not necessarily. If some `WINVER` value is needed to expose a macro redefining `SHGetKnownFolderPath` to either `SHGetKnownFolderPathA` or `SHGetKnownFolderPathW` (something the Windows headers often do for functions...), then `SHGetKnownFolderPath` will be left in your source code as a function call with no prototype, possibly generating a warning but perhaps successfully compiling, but then the symbol `SHGetKnownFolderPath` won't be found at link time. – Andrew Henle Dec 24 '21 at 01:04
  • @AndrewHenle `SHGetKnownFolderPath` is Unicode only, it doesn't have W/A version. The link error shows the same function name. I don't know gcc well enough, I tried `nm libshell32.a` the function doesn't seem to be listed. – Barmak Shemirani Dec 24 '21 at 01:39

2 Answers2

2

SHGetKnownFolderPath obtains the Unicode path to AppData, Documents, etc.

KNOWNFOLDERID for "C:\Users\MyName\AppData\Local" is FOLDERID_LocalAppData

This function needs additional libraries for CoTaskMemFree, KNOWNFOLDERID, and SHGetKnownFolderPath

gcc file.c libole32.a libuuid.a libshell32.a

Using MinGW, 64-bit, gcc version 4.8.3, SHGetKnownFolderPath does not appear to be in libshell32.a. The command line nm libshell32.a does not list this function either. So in MinGW, we have to load this function manually as follows:

#define _WIN32_WINNT 0x0600
#include <stdio.h>
#include <Windows.h>
#include <shlobj.h>

//add libraries for libole32.a and libuuid.a

HRESULT MySHGetKnownFolderPath
(const KNOWNFOLDERID* const id, DWORD flags, HANDLE token, PWSTR* str)
{
    typedef HRESULT(WINAPI* lpf)(const KNOWNFOLDERID* const, DWORD, HANDLE, PWSTR*);
    HMODULE lib = LoadLibraryW(L"shell32.dll");
    if (!lib) return E_FAIL; 
    lpf fnc = (lpf)GetProcAddress(lib, "SHGetKnownFolderPath");
    HRESULT result = fnc ? fnc(id, flags, token, str) : E_FAIL;
    FreeLibrary(lib);
    return result;
}

int main(void)
{
    wchar_t* temp;
    if SUCCEEDED(MySHGetKnownFolderPath(&FOLDERID_LocalAppData, 0, NULL, &temp))
    {
        wchar_t path[1024];
        swprintf(path, 1024, L"%s\\_add_new_dir", temp);
        CoTaskMemFree(temp); //free this memory as soon as possible
        wprintf(L"path: %s\n", path); //CreateDirectoryW(path, NULL);
    }

    return 0;
}

Additionally, you can use getenv or _wgetenv (Unicode version)

#include <stdio.h>
#include <Windows.h>

int main(void)
{
    wprintf(L"%s\n", _wgetenv(L"LOCALAPPDATA"));
    wprintf(L"%s\n", _wgetenv(L"APPDATA"));
    wprintf(L"%s\n", _wgetenv(L"USERPROFILE"));

    wchar_t buf[1024];
    swprintf(buf, 1024, L"%s\\_add_new_dir", _wgetenv(L"LOCALAPPDATA"));
    wprintf(L"buf: %s\n", buf); //CreateDirectoryW(buf, NULL);
    return 0;
}

To add the libraries in Code::Blocks, click Menu -> Settings -> Compiler, it should bring up this window:

enter image description here

Then click the "Add" button, find MinGW installation folder, the libraries should be at

C:\My_MinGW_folder\mingw\lib\libole32.a
or
C:\My_MinGW_folder\mingw\lib32\libole32.a (for 32-bit program)

You can figure out which libraries you need by looking at documentation for the function. For example SHGetKnownFolderPath says it needs "shell32.lib" (for Visual Studio) MinGW uses "libshell32.a" instead.

Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77
  • Where can I get those Libraries? – Pyromagne Dec 24 '21 at 07:14
  • 1
    @Pyromagne see updated answer. Note that the `_wgetenv` method doesn't need additional libraries – Barmak Shemirani Dec 24 '21 at 08:32
  • Hi Barmak about the mingw, do I need to install it before I can use that libraries or I can just download the libraries? – Pyromagne Dec 26 '21 at 08:05
  • what should I install? MinGW or MinGW-w64? – Pyromagne Dec 26 '21 at 08:12
  • Can you run this program `MessageBoxW(0, L"test", 0, 0);`? or the code I posted for `_wgetenv`? If so, you have already installed GCC compiler and MinGW library, it will be in a different folder, maybe in c:\ or somewhere under c:\users\myname. For GCC, run this program: `void *p; printf("sizeof pointer: %d\n", sizeof(p));` if it prints `8`, then you are using 64-bit version. If it prints 4, you are using 32-bit version. For Windows development I would recommend Visual Studio. – Barmak Shemirani Dec 26 '21 at 12:48
2

Under MSYS2/UCRT, I recently (like yesterday...) used SHGetFolderPathA() to obtain a user's profile directory in C code.

It's worked so far in limited unit testing on a Windows Server 2016 AWS installation after being compiled with whatever GCC version comes with the latest MSYS2/UCRT installation. (The system is currently shut down and I'm on vacation so i can't check details)

This might work in your environment:

char buffer[ MAX_PATH ] = { 0 };

HRESULT result = SHGetFolderPathA( NULL, CSIDL_APPDATA, NULL, 0, buffer );
if ( result != S_OK )
{
    //handle error
}

Somewhat off-topic, I have grown to prefer MSYS2/UCRT over the alternatives.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56