Jump to content

aehimself

Members
  • Content Count

    1030
  • Joined

  • Last visited

  • Days Won

    22

Posts posted by aehimself


  1. ...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


  2. 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...?


  3.   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.

    😄


  4. 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...


  5. 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

     

    😄


  6. 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.


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


  8. The window message version caused more havoc than it could solve. While collecting DDE targets worked more reliably, it interfered with sending commands.

    The finding no instances symptom was caused by the list connect, not Delphi's DDE server; adding error handling revealed that:

          convlist := DdeConnectList(ddeid, svchandle, topichandle, 0, nil);
          If convlist = 0 Then
            Raise EDelphiVersionException.Create('Retrieving the list of Delphi DDE servers failed, DDE error ' + DdeGetLastError(ddeid).ToString);

    threw DMLERR_NO_CONV_ESTABLISHED when something was "locked up".

     

    I'm now playing around with

      res := DdeInitializeW(ddeid, nil, APPCMD_CLIENTONLY, 0);

    Seems to be stable so far.


  9. 7 minutes ago, Attila Kovacs said:

    ok, here a native approach, finding the dde window with winapi and sending dde messages, no dde@user32 involved

     

    let's see, if it's more stable

    This idea won't work as intended, as you are sending all the messages to all TPUtilWindows of all instances you'll find. At least for my purpose, where I am separating Delphi IDE instances by versions.

    The theory is interesting though. You think one Delphi instance can have more DDE servers, which are constantly being destroyed / created?


  10. 1 hour ago, Attila Kovacs said:

    something is still not ok, after 4 calls the IDE won't respond anymore

    I'm using the code to start an IDE if nothing was detected and open a file in it. Once the first instance can not even be detected anymore I still can control the second just fine. This makes me believe the issue is with the DDE server of a specific instance.

     

    There are two options.

    Either we lock up Delphi's DDE server with our code, or Delphi's DDE server is buggy and crashes by itself.

     

    Once an instance locks up I'll see if I can double-click on a file in Explorer to open it.


  11. 21 hours ago, Attila Kovacs said:

    Here.

     

    This queries all the running bde's and opens a file in every one.

    This is a good start, you have to find out which is which version somehow, I have no more time for that.

    DDE-test.7z

    I took the liberty to include everything in a small, easy to use package: https://github.com/aehimself/AEFramework/blob/master/AE.DelphiVersions.pas

     

    Usage:

    Var
      dv: TDelphiVersions;
      v: TDelphiVersion;
    begin
      dv := TDelphiVersions.Create;
      Try
        For v In dv.InstalledVersions Do
          If v.IsRunning Then
            v.OpenFile('C:\a.pas');
      Finally
        FreeAndNil(dv);
      End;
    End;

    Multiple DDE queries clearly lock something up, I suspect calling .IsRunning in an infinite loop will turn from True to False after a while.

    Also, OpenFile will raise an AV if there are no instances. I'll add some error handling later.


  12. 3 minutes ago, Attila Kovacs said:

    here is the executable name

     

     

     

    
      Buffer: array [0 .. 4095] of Char;
      Modules: array [0 .. 255] of THandle;
      PID: DWORD;
      exePathLen: integer;
      hProcess: THandle;
      nResult: boolean;
      nTemp: Cardinal;
      aService, aTopic: WORD;
    
    ....    
    
    if GetWindowThreadProcessId(ci.hwndPartner, PID) <> 0 then
        begin
          hProcess := OpenProcess(PROCESS_QUERY_INFORMATION or PROCESS_VM_READ, False, PID);
          if hProcess <> 0 then
          begin
            nResult := EnumProcessModules(hProcess, @Modules[0], Length(Modules), nTemp);
            if nResult then
            begin
              nTemp := GetModuleFileNameEx(hProcess, 0, Buffer, SizeOf(Buffer));
              Memo1.Lines.Add(Buffer);
            end;
          end;
        end;
    
    ...

     

    Yep, similar to my approach. I moved this to a separate function though, but logic is the same.


  13. 55 minutes ago, Attila Kovacs said:

    make exhaustive testing, it stopped working on my system again. no clue why

    Happened to me too, however a simple Delphi restart solved it. Maybe DDE server died in Delphi, as DdeQueryNextServer found no matches.

    Anyway, I added some information gathering and now I have the full process name of each process which was found. From here, I only need to enumerate the bds paths in Registry to find which Delphi version that executable is for.

     

    Little bit more code than I expected, but at least it works!

     

    Thank you!


  14. 14 minutes ago, Attila Kovacs said:

    this opens me the file in the running IDE

    How...?! It's the same garbage here, only it appears in the IDE. I thought autoconnecting makes a difference, but that doesn't seem to be the case...

     

    image.thumb.png.67aaa2663d7e97907561c55c416e9e43.png

     

    51 minutes ago, Fr0sT.Brutal said:

    Why not try pure winapi? The functions are quite straightforward AFAICT

    I did not find the needed WinApi calls yet. I might look into it, seems something is broken in TDDEClientConv between XE4 and 11.2...


  15. I remember seeing it on a really old newslist that TDDEClientConv heavily suffers from bad coding and I tend to believe something is not right with it in this case. I already tried with simple casting, StrPCopy, sending @PAnsiString[1] instead of PAnsiChar, even like this:

    Var
     pac: PAnsiChar;
     s: String;
     tb: TBytes;
     len: Integer;
    begin
      s := '[open("' + Edit1.Text + '")]';
      tb := TEncoding.Default.GetBytes(s);
      len := Length(tb);
    
      GetMem(pac, len + 1);
      ZeroMemory(pac, len + 1);
      Move(tb[0], pac^, len);

    ... but BDSLauncher always replies with some Chinese characters what it tried to execute as a command. I can not get more PAnsiChar than this.

     

    I'll try to find a different component to check.


  16. When stepping in .ExecuteMacro, a message box actually pops up but disappears right after the line

     

      hdata := DdeClientTransaction(Pointer(hszCmd), DWORD(-1), FConv, 0, FDdeFmt,
         XTYP_EXECUTE, TIMEOUT_ASYNC, @ddeRslt);
     

    What it said:

    image.png.289e19636ae1234d12f28e6d113302f1.png

     

    So I guess the command is incorrect, Delphi really didn't like the casting. Now attempting to properly build my command, because with

    Var
      pac: PAnsiChar;
      s: AnsiString;
    begin
      s := '[open("' + Edit1.Text + '")]';
      pac := PAnsiChar(s);
    
      If Not DDEClientConv1.ExecuteMacro(pac, False) Then Begin

    my result is:

     

    image.png.6b44a3f9824cbca8eb49612aa2182488.png

     

    🙂


  17. Getting close 🙂 Never worked with DDE so it'll take some attempts to succeed I believe.

     

    So far I have the following code:

     Memo1.Lines.Add(sLineBreak + '----------------------------------------');
     Memo1.Lines.Add('Opening ' + Edit1.Text);
     // DDEClientConv1.ServiceApplication := '"C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bdsLauncher.exe" "C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bds.exe" /np';
     DDEClientConv1.ServiceApplication := 'C:\Program Files (x86)\Embarcadero\Studio\22.0\bin\bdsLauncher.exe';
    
     Memo1.Lines.Add('Setting DDE link...');
     If Not DDEClientConv1.SetLink('bdsLauncher', 'system') Then
     Begin
       Memo1.Lines.Add('Setting link failed!');
       Exit;
     End;
    
     Memo1.Lines.Add('Opening DDE link...');
     If Not DDEClientConv1.OpenLink Then
     Begin
       Memo1.Lines.Add('Opening link failed!');
       Exit;
     End;
    
     Try
       Memo1.Lines.Add('Invoking DDE command...');
    
       If Not DDEClientConv1.ExecuteMacro(PAnsiChar('[open("' + Edit1.Text + '")]'), False) Then
       Begin
         Memo1.Lines.Add('Invoking command failed!');
         Exit;
       End;
    
     Finally
       Memo1.Lines.Add('Closing DDE link...');
       DDEClientConv1.CloseLink;
     End;

    No errors are shown but nothing gets executed and I get a ding sound. No popups, no entries in the event log.

    Anyone has experience debugging DDE, where should I look for errors?

     

    I took all the keywords from the registry, HKCU\BDE.pas. ServiceApplication came from Command\Default, SetLink parameters from ddeexec\application\Default and ddeexec\topic\Default, the macro from ddeexec\default.


  18. 4 minutes ago, David Heffernan said:

    Is it possible that DDE is being used? That was how this would have been done in the old days. 

    My guess was on some tricky Windows messages, but DDE is a valid option as well. As I have no source for BDSLauncher I can not say.

    I took a peek at @dummzeuch's dzBdsLauncher but that also is executing a direct bds.exe call, probably resulting in a new IDE.

     

    Finding the method and replicating it would be the goal of this topic.

×