2

I am trying to make a proof of concept where in a cpp program I fetch the windows username and then call this program from a java code, using Java native interface(JNI). Now what i have so far is, a sample JNI hello world program which is able to compile and print Hello world or what what ever parameter I set up. Now in a separate cpp snippet i am able to fetch the current username and it works as well; looks as follows:

#include <iostream>
#include <windows.h>
using namespace std;
int main()
{
    char acUserName[100];
    DWORD nUserName = sizeof(acUserName);
    if (GetUserName(acUserName, &nUserName)) {
        cout << "User name is " << acUserName << "." << endl;
        cin.get();
    }
    return 0;
}

When compiled and executed using my G++ in command prompt it prints the current username correctly.

Now i just want to combine the two programs. When I do that as follows

public class Sample1
 {
    public native String fetchCurrentUserName();
 
    public static void main(String[] args)
    {
     System.loadLibrary("Sample1");
     Sample1 sample = new Sample1();     
     String  userName   = sample.fetchCurrentUserName();       
     System.out.println("userName: " + userName);
     
    }
 }

This is the java part and the cpp part is as follows:

 #include "Sample1.h"
 #include <string.h>
 #include <iostream>
 #include <windows.h>
 using namespace std;


  JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName
   (JNIEnv *env, jobject obj) {

    char acUserName[100];
    DWORD nUserName = sizeof(acUserName);
    if (GetUserName(acUserName, &nUserName)) {
        cout << "User name is " << acUserName << "." << endl;
        cin.get();
    }

    const char* userName = acUserName;
    //const char* userName = "acUserName";
    return env->NewStringUTF(userName);
 }
 
 void main(){}

This does not compile anymore, it says

Sample1.obj : error LNK2019: unresolved external symbol __imp_GetUserNameA referenced in function Java_Sample1_fetchCurrentUserName
Sample1.dll : fatal error LNK1120: 1 unresolved externals

I used the following command to compile the JNI code.

cl -I"C:\Program Files\Java\jdk1.8.0_131\include" -I"C:\Program Files\Java\jdk1.8.0_131\include\win32" -LD Sample1.cpp -FeSample1.dll

as per tutorial from https://www.ibm.com/developerworks/java/tutorials/j-jni/j-jni.html. Now from few other questions in stack overflow it seems somehow my program is unable to link the windows library or leader somehow. But I have no clue as to how this can be solved. Any body have any bright ideas? Please make my day! Thank you for your time :D I looked into Linking of dll file in JNI which is very relevant for my question. But not sure if the solutions are applicable to me.

Edit

error: LNK1120: 5 unresolved externals This question had similar problem and it seems to have same solution which is as suggested by Remy to link with Advapi32.lib. At least now i learned few new things like #pragma comments and the role of Advapi32 library. Many thanks!

Community
  • 1
  • 1
Arka Mallick
  • 1,206
  • 3
  • 15
  • 28
  • 2
    Your C++ code needs to link to `Advapi32.lib`, either through a linker parameter in the project options, or via a `#pragma comment(lib)` statement in the C++ code itself. Otherwise, you can use the Win32 API `LoadLibrary()` and `GetProcAddress()` functions to get a pointer to `GetUserNameA()` directly from `Advapi32.dll` at runtime instead of linking to `Advapi32.lib` at compile-time – Remy Lebeau Apr 25 '18 at 19:45
  • On the other hand, why use `GetUserName()` in C++ at all? Java has its own access to the username: [Get login username in java](https://stackoverflow.com/questions/797549/) – Remy Lebeau Apr 25 '18 at 19:47
  • I have seen this solution in other question. However i myself have no idea what this Advapi32.lib does. Could you please tell me why is this important in my case and where do i look to get the exact command/code to perform the task. I am super noob in cpp and JNI both sorry. :( – Arka Mallick Apr 25 '18 at 19:48
  • 2
    This has nothing to do with JNI. It is how C++ compilers\linkers work in general. `Advapi32.lib` is the import library for `Advapi32.dll`, it tells the linker which functions are exported from the DLL and how. The linker uses import libs to resolve DLL function calls made by the C++ code. – Remy Lebeau Apr 25 '18 at 19:50
  • This is a proof of concept in order to learn about java native api, later on I need to fetch SID and something to do with native api. Not yet there. So I need to do this in cpp only. But thank you for the link as well :D – Arka Mallick Apr 25 '18 at 19:50
  • Ok thanks for the explanation on Advapi32.lib role. I will try to find a way to link it now. – Arka Mallick Apr 25 '18 at 19:52
  • Ok @RemyLebeau I just added #pragma comment(lib,"advapi32.lib") line in my cpp and it compiled fine, just as you suggested. Your solution works great. May I request you to answer that so that I can officially accept it. Many thanks sir. – Arka Mallick Apr 25 '18 at 20:07
  • 1
    I posted a more detailed answer for you – Remy Lebeau Apr 25 '18 at 20:35
  • You can get a lot of user information via JAAS: see [`com.sun.security.auth.module.NTSystem`](https://docs.oracle.com/javase/8/docs/jre/api/security/jaas/spec/com/sun/security/auth/module/NTSystem.html). – user207421 Apr 26 '18 at 00:06
  • ok @EJP I will look into JAAS. It seems it has lot of user SID etc meaningful information for me, in the first look itself. Thank you. – Arka Mallick Apr 26 '18 at 09:11

1 Answers1

3

Your code compiles fine. You are actually getting a linker error, not a compiler error.

From the error message, you can see that your C++ code is trying to call a GetUserNameA() function. GetUserName() is a preprocessor #define macro in winbase.h (which is included by windows.h) that resolves to GetUserNameA() when UNICODE is not defined:

WINADVAPI
BOOL
WINAPI
GetUserNameA (
    __out_ecount_part_opt(*pcbBuffer, *pcbBuffer) LPSTR lpBuffer,
    __inout LPDWORD pcbBuffer
    );
WINADVAPI
BOOL
WINAPI
GetUserNameW (
    __out_ecount_part_opt(*pcbBuffer, *pcbBuffer) LPWSTR lpBuffer,
    __inout LPDWORD pcbBuffer
    );
#ifdef UNICODE
#define GetUserName  GetUserNameW
#else
#define GetUserName  GetUserNameA // <-- HERE!
#endif // !UNICODE

As there is no GetUserNameA() function implemented in your C++ code, but there is at least a declaration available (from winbase.h), the compiler emits machine code that references GetUserNameA() externally (via a symbol named __imp_GetUserNameA). When the linker is then invoked after compiling is finished, the linker outputs the "unresolved" error because it is not able to find an implementation of any GetUserNameA function to satisfy that reference.

GetUserNameA() is a Win32 API function implemented in Advapi32.dll. You need to link to Advapi32.lib from your compiler's Windows SDK so the linker can find the implementation of GetUserNameA().

One way to do that is to add a #pragma comment(lib) statement to your C++ code, eg:

#include "Sample1.h"
#include <windows.h>
#include <iostream>

using namespace std;

#pragma comment(lib, "Advapi32.lib") // <-- add this!

JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
    char acUserName[100] = {};
    DWORD nUserName = sizeof(acUserName);
    if (GetUserNameA(acUserName, &nUserName)) {
        cout << "User name is " << acUserName << "." << endl;
    }
    else {
        DWORD dwErr = GetLastError();
        cout << "Unable to get User name, error " << dwErr << "." << endl;
    }

    return env->NewStringUTF(acUserName);
}

void main() {}

Alternatively:

#include "Sample1.h"
#include <windows.h>
#include <iostream>
#include <vector>

using namespace std;

#pragma comment(lib, "Advapi32.lib")

JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
    DWORD nUserName = 100, dwErr;
    std::vector<char> acUserName(nUserName);
    do {
        if (GetUserNameA(&acUserName[0], &nUserName)) {
            cout << "User name is " << &acUserName[0] << "." << endl;
            break;
        }
        dwErr = GetLastError();
        if (dwErr != ERROR_INSUFFICIENT_BUFFER) {
            cout << "Unable to get the User name, error " << dwErr << "." << endl;
            break;
        }
        acUserName.resize(nUserName);
    }
    while (true);

    return env->NewStringUTF(&acUserName[0]);
}

void main() {}

That being said, NewStringUTF() requires an input string in modified UTF-8 format. However, GetUserNameA() outputs ANSI format (hence the A in its name), not in UTF-8, let alone modified UTF-8. Windows has no concept of modified UTF-8 at all. Your code will work correctly only if the username does not contain any non-ASCII characters in it.

Your C++ code really should be calling GetUserNameW() instead, which outputs in UTF-16 format. Then you can use NewString() instead of NewStringUTF() (Java strings use UTF-16 anyway), eg:

#include "Sample1.h"
#include <windows.h>
#include <iostream>

using namespace std;

#pragma comment(lib, "Advapi32.lib")

JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
    WCHAR acUserName[100];
    DWORD nUserName = 100;
    if (GetUserNameW(acUserName, &nUserName)) {
        --nUserName; // ignore the null terminator
        wcout << L"User name is " << acUserName << L"." << endl;
    }
    else
    {
        nUserName = 0;
        DWORD dwErr = GetLastError();
        cout << "Unable to get the User name, error " << dwErr << "." << endl;
    }

    return env->NewString(reinterpret_cast<jchar*>(acUserName), nUserName);
}

void main() {}

Alternatively:

#include "Sample1.h"
#include <windows.h>
#include <iostream>
#include <vector>

using namespace std;

#pragma comment(lib, "Advapi32.lib")

JNIEXPORT jstring JNICALL Java_Sample1_fetchCurrentUserName(JNIEnv *env, jobject obj) {
    DWORD nUserName = 100, dwErr;
    std::vector<WCHAR> acUserName(nUserName);
    do {
        if (GetUserNameW(&acUserName[0], &nUserName)) {
            --nUserName; // ignore the null terminator
            wcout << L"User name is " << &acUserName[0] << L"." << endl;
            break;
        }
        dwErr = GetLastError();
        if (dwErr != ERROR_INSUFFICIENT_BUFFER) {
            nUserName = 0;
            cout << "Unable to get the User name, error " << dwErr << "." << endl;
            break;
        }
        acUserName.resize(nUserName);
    }
    while (true);

    return env->NewString(reinterpret_cast<jchar*>(&acUserName[0]), nUserName);
}

void main() {}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770