Issue
When using JNA, I am receiving an Invalid Memory Access error when invoking the QLConnect
method.
Here is my interface mapping the DLL:
import com.sun.jna.Native;
import com.sun.jna.win32.StdCallLibrary;
public interface QuikLimit extends StdCallLibrary {
QuikLimit INSTANCE = (QuikLimit) Native.load(new File("").getAbsolutePath() + "\\QuikLimit.dll", QuikLimit.class);
int QLConnect(String userName, String userPassword, byte[] desc);
//more methods from dll
}
I have another class with constants:
public class Const {
public static final int DESC_SIZE = 1024;
public enum Status {
//enum values
int code;
private Status(int code) {
this.code = code;
}
public int getCode() {
return code;
}
};
}
And here is the place of occurrence in the code:
@Component
public class ConnectionManager {
@Value("${quik.login}")
private String login;
@Value("${quik.password}")
private String password;
private Logger logger = LoggerFactory.getLogger(this.getClass());
private boolean connected;
public synchronized BaseResponse connect() {
BaseResponse result = new BaseResponse();
byte[] desc = new byte[Const.DESC_SIZE];
if (connected) {
//logic if connected
logger.info("call QLConnect");
int retVal = QuikLimit.INSTANCE.QLConnect(login, password, desc);
if (retVal == Const.Status.QL_ACTIVE.getCode()) {
//logic from response
} else if (retVal == Const.Status.QL_CONNECTEDNOTACTIVE.getCode()) {
//logic from response
}
result.setCode(retVal);
result.setDescription(Util.descToStr(desc));
QuikLimit.INSTANCE.QLDisconnect();
return result;
}
}
I understand that 1 assumption immediately falls on the fact that the type does not match in the dll. But the header file clearly states that the return type is int:
typedef __int32 ql_long;
ql_long _stdcall QLConnect(const char* lpszUserName, const char* lpszUserPassword, char* lpszError);
lpszUserName is a pointer to an ASCIIZ string containing the username.
pszUserPassword is a pointer to an ASCIIZ string containing the user's password.
lpszDesc is a pointer to a buffer into which, in case of an error, the line with its description. The minimum buffer size is 512 bytes.
What else could be the cause of the error?
Solution
The first thing to check when you get Invalid Memory Access errors is your type mapping, which you correctly started with looking at the int
return value. Turns out the String
mapping for the other arguments, which works fine on *nix systems, needs a little special treatment on Windows.
By default Windows uses UTF 16 encoding, and JNA's default type mapping under-the-hood tries to convert Java String
objects to UTF-16 encoded native strings (wchar*
). This works fine for Windows API DLLs.
However, your custom DLL specified that the User Name and Password arguments should be in ASCIIZ format (null-terminated C strings).
You can force JNA to use an ASCII String
type mapper by including W32APIOptions.ASCII_OPTIONS
as a third argument when loading the DLL, e.g.,
QuikLimit INSTANCE = (QuikLimit) Native.load(new File("").getAbsolutePath() + "\\QuikLimit.dll", QuikLimit.class, W32APIOptions.ASCII_OPTIONS);
There are other finer grained approaches dealing just with String type mapping, that don't try to also do Function mapping (if any of your mapped functions end in W or A this might break!). You can dig into the W32APIOptions
class to see how to create your own custom options to pass.
Answered By - Daniel Widdis
Answer Checked By - Clifford M. (JavaFixing Volunteer)