Jump to content
aehimself

How to open a file in the already running IDE?

Recommended Posts

Hehe, you are right. Just this moment I entered a locked-up state again. 16394 until restarting the IDE. Starting the application from within our outside the IDE makes no difference. Double-clicking a file in explorer indeed does open the file in Delphi.

It's strange but I THINK it started with the usual command-sending issue: WM_DDE_EXECUTE goes out but file is NOT opened in the IDE.

Share this post


Link to post
7 minutes ago, aehimself said:

It's strange but I THINK it started with the usual command-sending issue: WM_DDE_EXECUTE goes out but file is NOT opened in the IDE.

"When processing the WM_DDE_ACK message that the server posts in reply to a WM_DDE_EXECUTE message, the client application must delete the object returned by the WM_DDE_ACK message."

 

This could cause that eventually, freeing up that global right after the execute instead of waiting for the ACK.

This could also be the problem for the user32 version.

 

Edited by Attila Kovacs

Share this post


Link to post

My code is your initial version, I'm not processing anything 🙂

  msghwnd := AllocateHwnd(nil);
  Try
    atomservice := GlobalAddAtom(PChar(DDESERVICE));
    atomtopic := GlobalAddAtom(PChar(DDETOPIC));
    Try
      SendMessage(_ddehwnd, WM_DDE_INITIATE, msghwnd, Makelong(atomservice, atomtopic));
    Finally
      GlobalDeleteAtom(atomservice);
      GlobalDeleteAtom(atomtopic);
    End;

    cmd := '[open("' + inFileName + '")]';
    commandhandle := GlobalLockString(cmd, GMEM_DDESHARE);
    Try
      PostMessage(_ddehwnd, WM_DDE_EXECUTE, msghwnd, commandhandle);
    Finally
      GlobalUnlock(commandhandle);
      GlobalFree(commandhandle);
    End;
  Finally
    DeAllocateHwnd(msghwnd);
  End;

Maybe I need to switch up that PostMessage to SendMessage. I'll give that modification a spin.

Share this post


Link to post

Sorry, got a little intermezzo, I broke my IDE had to debug it to make it work again....

 

Well, here is the thing, user32 version with ~proper ACK handling, seems to work. Could you check it?

 

(It's still just ~proper as the command global won't be released if there is no ACK. But I would implement it only if it became stable.)

 

 

uBdsLauncher2.pas

Edited by Attila Kovacs

Share this post


Link to post

So basically the difference is the cleanup in the message handler? Afaik ackINIT does nothing, as the window is not even alive in that section.

 

Question, why are you reading back out the partner and service name? In theory they will always come back as you specified in DdeConnectList because you are defining both parameters.

 

Edit: first test, after PC restart, no IDE was opened yet:

 

image.png.3ded15bbc70a547761898291c5154d1a.png

 

😄

Edited by aehimself

Share this post


Link to post

The ackINIT was for the other version, (see attachment), I just left it there, you can put (FAckMode := ackINIT;) into the initialization, instead. (It won't affect anything though)

 

Double-check your implementation, both versions are stable on my PC.

The problem was calling GlobalUnlock(ddeCommandH) and GlobalFree(ddeCommandH) right after WM_DDE_EXECUTE.

If you are still getting the 16394, I suspect, you still have this in the code.

 

 

uBdsLauncher2.pas

Edited by Attila Kovacs

Share this post


Link to post

It only fails on one machine, on mine it works. Maybe it's an AV-related thing...

 

Edit: DDE version locks up the IDE completely if ran on the machine which is affected, also throws an AV here after the WM_DDE_INITIATE message is sent:

 

image.thumb.png.46b9bc007a315c88ac0921faad4a8948.png

 

image.thumb.png.cb97c2f29781bf0720fbdea54d2e459b.png

 

When starting it outside from the IDE, reports the same DDE error:

image.png.a5c148f1d2cf6a42dcd9e4d361df107b.png

 

I also can confirm, running my program on my machine outside of the IDE also fails to join the conversation.

 

Crap, I remember reading something about DDE behaving differently from the IDE somewhere...

Edited by aehimself

Share this post


Link to post

Well, the whole ackINIT / ackEXEC thing is just a crap because I have no idea who sent the ACK.... also, there is difference between posted and sent messages, which I also cant distinguish.

The whole DDE fuck is just some botching from MS. I'm sure they can't even document it as they don't know why is it working.

 

So it's possible that I get an ACK message and I wan't to free the globals from it's parameters, but it was not an ACK for the EXEC, so AV on nil handle raises.

 

You could try to check the return value of "UnpackDDElParam" and only free the globals if it's != 0.

          if UnpackDDElParam(AMsg.Msg, AMsg.lParam, @pLo, @pHi) <> False then
          begin
            GlobalUnlock(pHi);
            GlobalFree(pHi);
            FreeDDElParam(AMsg.Msg, AMsg.lParam);
          end;

this would eventually solve the AV but not the problem

Edited by Attila Kovacs

Share this post


Link to post
  If HWND(inMessage.WParam) = _ddehwnd Then
    If UnpackDDElParam(inMessage.Msg, inMessage.lParam, @pLo, @pHi) Then
    Begin
      GlobalUnlock(pHi);
      GlobalFree(pHi);
      FreeDDElParam(inMessage.Msg, inMessage.lParam);
    End;

The innermost section never gets executed, so yes, it indeed stops the AV from happening 🙂 Now only unable to connect from outside the IDE remains.

I'm still trying to find that article.

 

33 minutes ago, Attila Kovacs said:

The whole DDE fuck is just some botching from MS. I'm sure they can't even document it as they don't know why is it working.

😄

Share this post


Link to post

I made a discovery...

 

At the moment there are 3 possible outcomes:

- You start it from the IDE, works. Period.

- You start it from outside of the IDE, WHILE the IDE is running -> Getting the lust succeeds but file is not opened:

image.thumb.png.dd9f32978d84fa0b3145d6fb195ca022.png

- You start it from outside of the IDE, and NO IDE is running -> 16349.

 

This makes me believe that 16349 should be handled separately, it means there is no list to be gathered... which equals to no Delphi instances running. Does this make sense...?

Share this post


Link to post

Ok, I just tested this on 4 different pc's under 3 different os's and 3 different IDE's, no errors or lockups. (Could still contain bugs)

I'll just leave it there for now, if someone has a better implementation just share with us.

uBdsLauncher2.pas

Share this post


Link to post
1 minute ago, aehimself said:

- You start it from outside of the IDE, and NO IDE is running -> 16349.

 

of course, if no IDE is running there is no DDE to answer the requests

Share this post


Link to post

There is no such thing as a central DDE server or anything, the DDE lib sends a message to every enumerated main window a WM_DDE_INITIALIZE with Service and Topic and the application

will either answer with a WM_DDE_ACK or not. That said, on 16349, or if the list is zero, you have to start bds.exe /np filename.pas. That is what's in the registry for bdsLauncher.

Share this post


Link to post

Okay, this version works! Probably the delayed freeup of resources, in my case when the message arrived the handles were already invalid.

 

Now, just to get rid of the GetMessage loop, as it breaks the ability to be run in a thread. Or I need a message pump 🙂

Share this post


Link to post

...any idea why this throws an AV? DDE is initialized inside the thread's context, I think the PeekMessage's section runs in the thread's context too...

The message goes through though, file is opened in the IDE, msg.WParam indeed contains the handle of the DDE server, msg.hwnd is equal to the handle of the window created in the beginning... I don't know what else to check 😞

 

  msghwnd := AllocateHWnd(nil);
  Try
    [...]

    PostMessage(inDDEServerHWND, WM_DDE_EXECUTE, msghwnd, commandhandle);
    SetTimer(msghwnd, 1, inTimeOutInMs, nil);

    Repeat
      If PeekMessage(msg, 0, 0, 0, PM_REMOVE) Then
      Begin
        If msg.message = WM_TIMER Then
          Break;

        If msg.message = WM_DDE_ACK Then
        Begin
          If UnpackDDElParam(msg.message, msg.lParam, @pLo, @pHi) Then // AV is thrown on this line
          Begin
            GlobalUnlock(pHi);
            GlobalFree(pHi);
            FreeDDElParam(msg.message, msg.lParam);

            PostMessage(msg.wParam, WM_DDE_TERMINATE, msghwnd, 0);

            Exit;
          End;
        End;

        TranslateMessage(msg);
        DispatchMessage(msg);
      End;

      Sleep(200);
      Inc(wait, 200);
    Until wait >= inTimeOutInMs;
Finally
 DeallocateHWnd(msghwnd);
End;

image.png.412d082f5fc7b1fe2466a48990c1d42e.png

Share this post


Link to post

first of all, PeekMessage is blocking, therefor the repeat until is not just superfluous but could be a problem, do you know where exactly the AV happens?

 

sorry, that was getmessage, the blocking one. well, then I don't think I can comment on that 

Edited by Attila Kovacs

Share this post


Link to post

PeekMessage "Dispatches incoming nonqueued messages, checks the thread message queue for a posted message, and retrieves the message (if any exist)."

 

"During this call, the system dispatches (DispatchMessage) pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved."

 

ok, that means, it should be fine

Edited by Attila Kovacs

Share this post


Link to post

first, "I think"  .... is not acceptable on programming!  Or, it is, or not!

second, using an "asynchronism" here (on message) I think that is complicated, mainly if you are using another thread to do the tasks... how many messages and what ordem it is? what ordem you expect?

it's complicated if you dont domains the memory flow!

of course, exists many way to do it, but I think that you are going in a complicated scenary! but if works, then, works!

in time, I dont know very so much about DDE flow, ok? unfortunatelly, I cannot help so much!

Share this post


Link to post

Your original idea was good, I'm indeed sending WM_DDE_INITIATE first. However attempts to purge the queue is unsuccessful, as there is nothing in the message queue:

  msghwnd := AllocateHWnd(nil);
  Try
    atomservice := GlobalAddAtom(PChar(_service));
    atomtopic := GlobalAddAtom(PChar(_topic));
    Try
      SendMessage(inDDEServerHWND, WM_DDE_INITIATE, msghwnd, Makelong(atomservice, atomtopic));
    Finally
      GlobalDeleteAtom(atomservice);
      GlobalDeleteAtom(atomtopic);
    End;

    While PeekMessage(msg, msghwnd, 0, 0, PM_REMOVE) Do
     Begin
       Sleep(20);
     End;

I split LParam by LoWord and HiWord, in that case FreeDDElParam(msg.message, msg.lParam) throws an AV...

it's like LParam is malformed somehow...

Share this post


Link to post

It's not a threading issue, it's 64-bit issue!

 

According to MSDN, it's declared as:

 

BOOL UnpackDDElParam(
  [in]  UINT      msg,
  [in]  LPARAM    lParam,
  [out] PUINT_PTR puiLo,
  [out] PUINT_PTR puiHi
);

 

In Delphi, however:

 

image.png.fc555a6f5ca967051c1c0d0113bb5258.png

 

Now, we are feeding it an LParam, which is NativeInt.

 

Time to re-import it, correctly this time 🙂

  • Like 1

Share this post


Link to post

@Attila Kovacs

 

// Need to reimport these two

function UnpackDDElParam(msg: UINT; lParam: LPARAM; puiLo, puiHi: PUINT_PTR): BOOL; stdcall; external user32;
function FreeDDElParam(msg: UINT; lParam: LPARAM): BOOL; stdcall; external user32;

// class procedure TDdeHelper.DdeWndProc
  pLo, pHi: PUINT_PTR; // NOT IntPtr

  GlobalUnlock(pHi^);
  GlobalFree(pHi^);

And your code should be 64-bit compatible 🙂

 

I'll run some more tests but it seems that now it is finally working.

  • Like 1

Share this post


Link to post

 

 pLo, pHi: UIntPtr;
..
 if UnpackDDElParam(AMsg.msg, AMsg.lParam, @pLo, @pHi) then
..
 GlobalUnlock(pHi);
 GlobalFree(pHi);

 

Edited by Attila Kovacs

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×