Nur1
Nur1

Reputation: 480

How to send a string from c++ app to a message only window in java (LPARAM to String conversion)

I am trying to send a String to a message-only window that I have created in java using JNA.

I am using SendMessage to send a message. (I know that it is not recommended to use FindWindow every time I send a message, but this is just for this example here)

SendMessage(FindWindow(NULL,"jnaWnd"), WM_USER + 10, 0, (LPARAM)L"Test");

On the Java (JNA) side, I had to use this interface for a proper WindowProc implementation

Besides that, I also made my way through creating a message-only hwnd on the java side. But this is not relevant for this problem.

Anyway, this is the implementation of the interface.

@Override
public LRESULT callback(HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case 1034: { //1034 is WM_USER + 10
            //These lines doesn't work.... :(

            //java.lang.UnsupportedOperationException: This pointer is opaque
            String s = lParam.toPointer().getString(0);

            // java.lang.Error: Invalid memory access
            String s = lParam.toPointer().getWideString(0);
            return new LRESULT(0);
        }
        default:
            return JNA.USER32.INSTANCE.DefWindowProc(hwnd, uMsg, wParam, lParam);
    }
}

Everything works fine, and I do receive the messages. Working with Integers works fine, and conversion is also easy.

But I have no clue how to parse the String out of the LPARAM value.

JNA is very well known for converting types on the fly by using the java equivalent type in the methods.

The first problem is that I use this interface, and therefore I need to stick to the params defined in the interface.

The second problem is that I also want to work with Integer values as well, which means that I cannot just replace LPARAM lParam with String lParam

Even tho I could try to dig deeper on the JNA side to create my own interface and such... I just wanted to ask here before I move forward on this topic.

Maybe there is a simple way to get the String value out of the LPARAM

Any tips are appreciated.

EDIT_1:

I tried to use the WM_COPYDATA method, but this still leads to an invalid memory access exception...

This is what I have tried so far:

I defined a new struct named STRMSG struct on both sides;

Java:

public class STRMSG extends Structure{

    public STRMSG() {
        super();
    }

    public STRMSG(Pointer p) {
        super(p);
        read();
    }

    public String message;

    protected List<String> getFieldOrder() {
        return Arrays.asList("message");
    }
}

C++:

typedef struct STRMSG {
    LPCSTR message;
};

And here is the new SendMessage procedure:

STRMSG msg;
msg.message = "Some message";

COPYDATASTRUCT cds;
cds.dwData = 10;
cds.cbData = sizeof(STRMSG);
cds.lpData = &msg;
                    
SendMessage(FindWindow(NULL, L"jnaWnd"), WM_COPYDATA, 0, (LPARAM)&cds);

I also added the following case in my callback method on the java side:

case WinUser.WM_COPYDATA: {
    COPYDATASTRUCT cds = new COPYDATASTRUCT(new Pointer(lParam.longValue()));
    ULONG_PTR uMsg1 = cds.dwData;
    Pointer lParam1 = cds.lpData;
    int wParam1 = cds.cbData;

    switch(uMsg1.intValue()){
        case 10: {
            STRMSG msg = new STRMSG(lParam1); // Error: Invalid memory access here
            Logger.debug(msg.message);
        }
    }
    return new LRESULT(0);
}

The invalid memory error happens anyway :(...

Upvotes: 1

Views: 738

Answers (1)

Remy Lebeau
Remy Lebeau

Reputation: 595392

If the message-only window exists in a different process, then you can't send a string via a raw pointer across process boundaries in the manner you are trying. The sent pointer is only valid in the context of the sending process, it will not point at valid memory which exists in the context of the target process. So, you must marshal the string data instead (ie, actually copy the characters into the memory of the target process), such as with WM_COPYDATA (or any other IPC mechanism, such as sockets, pipes, ActiveX/COM, etc). You can't just send a raw pointer, as you are trying to do (unless you use VirtualAllocEx() to allocate the pointed-at memory in the target process, so the raw pointer is valid in that process. But don't go this way in this situation, there are better ways).

In your EDIT, your WM_COPYDATA is suffering from the exact same problem as your orignal code. Your sender is still sending a raw pointer as-is, just wrapped inside of a STRMSG struct, and then your receiver is trying to use the received pointer to access memory which does not exist in the receiving process. You didn't marshal the string data at all. Get rid of STRMSG and point the COPYDATASTRUCT directly at the string data so WM_COPYDATA will copy the actual characters.

Also, Pointer.getString() likely won't work in this situation, as the string being sent is a wchar_t string, so you should use Pointer.getWideString() instead.

With all of that said, try something more like this instead:

const UINT myStringId = RegisterWindowMessage(L"MyStringDataID");

...

HWND hwnd = FindWindow(NULL, L"jnaWnd");
if ((hwnd != NULL) && (myStringId != 0))
{
    std::wstring msg = L"Test";

    COPYDATASTRUCT cds;
    cds.dwData = myStringId;
    cds.cbData = (msg.size() + 1) * sizeof(wchar_t);
    cds.lpData = const_cast<wchar_t*>(msg.c_str());
                    
    SendMessage(hwnd, WM_COPYDATA, 0, reinterpret_cast<LPARAM>(&cds));
}
final int myStringId = JNA.USER32.INSTANCE.RegisterWindowMessage("MyStringDataID");

...

@Override
public LRESULT callback(HWND hwnd, int uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {
        case WinUser.WM_COPYDATA: {
            COPYDATASTRUCT cds = new COPYDATASTRUCT(new Pointer(lParam.longValue()));
            if ((myStringId != 0) && (cds.dwData.intValue() == myStringId)) {
                String s = cds.lpData.getWideString(0);
                Logger.debug(s);
                return new LRESULT(0);
            }
            break;
        }
    }

    return JNA.USER32.INSTANCE.DefWindowProc(hwnd, uMsg, wParam, lParam);
}

Upvotes: 1

Related Questions