Reputation: 1316
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.
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!
Upvotes: 2
Views: 451
Reputation: 595377
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() {}
Upvotes: 3