1

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 :(...

Nur1
  • 418
  • 4
  • 11
  • `lParam.toPointer()` -- Verify if this value is the same as the pointer passed here: `(LPARAM)L"Test"`. If not, then the issue is with `toPointer`. Also, what exactly do you mean by "doesn't work"? – PaulMcKenzie Aug 07 '21 at 23:47
  • Also, the issue will always work for integers, since that is what an LPARAM is. The real issue is how to cast that LPARAM to some Java object type -- you would have had the same trouble if it were not `String`, but some other object. – PaulMcKenzie Aug 07 '21 at 23:51
  • The pointers are the same. On lParam or wParam when using .longValue() it works for getting numerical values. This example code throws a "java.lang.UnsupportedOperationException: This pointer is opaque:..." exception. I updated the question – Nur1 Aug 08 '21 at 00:09
  • 2
    Does the message-only window exist in a different process? You can't sent strings across process boundaries in this manner, you have to use something more like `WM_COPYDATA` instead. In any case, `Pointer.getString()` likely won't work in this case anyway, the string being sent is a `wchar_t` string so use `Pointer.getWideString()` instead, eg: `(new Pointer(lParam.longValue())).getWideString(0);` – Remy Lebeau Aug 08 '21 at 00:27
  • Ok the `getWideString()` method caused an `java.lang.Error: Invalid memory access` exception. And yes they are separate processes. In this [thread](https://stackoverflow.com/questions/67811930/is-it-possible-to-send-a-window-handle-with-wm-copydata) we discussed that sending simple stuff with WM_COPYDATA is overkill therefore I don't work with them. – Nur1 Aug 08 '21 at 00:45
  • 1
    `WM_COPYDATA` is overkill when sending values that fit into either (or both) of the pointer-sized arguments of `SendMessage`. A string doesn't fit that category. It consists of two pieces of data: The pointer to the beginning of the string, and the actual string buffer in memory. You cannot simply send over the pointer (into a process where that pointer is meaningless). `WM_COPYDATA` is a convenient way to send complex data (such as a string) to a different process. – IInspectable Aug 08 '21 at 05:31
  • @Nur1 "*And yes they are separate processes*" - then you **must** *marshal* the string data across process boundaries, such as with `WM_COPYDATA` (or another IPC mechanism, like sockets, pipes, ActiveX/COM, etc). You can't just send raw pointers, 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). – Remy Lebeau Aug 08 '21 at 18:13
  • Sometimes just wrapping up the problem, describing it again here on StackOverflow, and getting some comments really helps a lot. I am on it to solve it with WM_COPYDATA. I am going to answer this question when I am done. Thanks so far. – Nur1 Aug 08 '21 at 18:22
  • @RemyLebeau WM_COPYDATA method has the same behavior, unfortunately. I described what I did in the update of this question. is there something wrong? Is there something special I need to know about marshalling? – Nur1 Aug 08 '21 at 20:30
  • @Nur1 your WM_COPYDATA is suffering from the exact same problem. Your sender is still sending a raw pointer as-is, just wrapped inside of STRMSG now, and then your receiver is trying to access the pointed-at memory in the sending 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 it. – Remy Lebeau Aug 08 '21 at 20:46
  • @RemyLebeau true at last I just wrapped the string in a struct. Time to learn marshalling I guess... – Nur1 Aug 08 '21 at 20:49
  • @Nur1 no, you just need to learn how to use `WM_COPYDATA` *correctly*. I wll write up a proper answer... – Remy Lebeau Aug 08 '21 at 20:56
  • Oh, ok, I thought this is the right way. I could also learn from sources, but an answer would be epic. Thanks in advance. – Nur1 Aug 08 '21 at 21:10

1 Answers1

1

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);
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thank you so much. One day I read about the `RegisterWindowMessage` in the docs, but didn't get a thing why it could be useful... So it's all about the uniqueness of the cds.dwData? Oh and do I need to `RegisterWindowMessage` every time I want to send a message or can I create a constant and use those throughout the lifetime of the processes? – Nur1 Aug 08 '21 at 21:44
  • 1
    "*So it's all about the uniqueness of the cds.dwData?*" - yes, `dwData` should be unique, to distinguish from other `WM_COPYDATA` messages that marshal other kinds of data. Now, if you are in full control of the `HWND` that is receiving `WM_COPYDATA`, then this is not so much an issue. But if you were ever to use an `HWND` that you are not in full control of, or if you decide to use `WM_COPYDATA` to marshal multiple types of data, then the uniqueness of `dwData` becomes more important. – Remy Lebeau Aug 08 '21 at 22:19
  • 1
    "*can I create a constant and use those throughout the lifetime of the processes?*" - yes. That is what the example in my answer is doing. Though, every time `RegisterWindowMessage()` is called with the same string value, it returns the same ID number. This is what allows multiple processes to discover common IDs whose values are not known until runtime. – Remy Lebeau Aug 08 '21 at 22:20
  • Oh and one more thing btw. I do `return new LRESULT(0);` at the end of every case. But in your case you only return it when the if statement is true. And otherwise you break and continue to return the `DefWindowProc...` Did you do that intentionally for some reason? – Nur1 Aug 09 '21 at 01:09
  • 1
    @Nur1 Yes, I did. It is to allow processing of other WM_COPYDATA messages that are not your own. All non-processed messages should be passed along to the default processor. Again, not an issue if you fully control the window, but can be an issue if you ever hook into someone else's window. – Remy Lebeau Aug 09 '21 at 05:40
  • Oh, this makes sense, of course... And yes, it's my own message-only window for IPC purposes. Thanks. – Nur1 Aug 09 '21 at 20:00