-
Content Count
1030 -
Joined
-
Last visited
-
Days Won
22
Posts posted by aehimself
-
-
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 🙂
-
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:
- 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...?
-
 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.
😄
-
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:
Â
Â
Â
When starting it outside from the IDE, reports the same DDE error:
Â
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...
-
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:
Â
Â
😄
-
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.
-
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.
-
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.
-
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?
-
7 hours ago, Attila Kovacs said:also, I dropped vcl.ddeman and initialize DDE myself
And with this, you made all calls work fine from a background thread!
- 1
-
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.
-
Btw, stability issue seem to be solved by calling DdeDisconnectList when finished. I'll push an update soon.
- 2
-
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.
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.
-
Had that in mind but I'm afraid of using version numbers, as you have to consider patch levels in each version. I guess it might change bds.exe version number too.
-
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.
-
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!
-
4 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.
Perfection, works like a charm. It triggered my antivirus though, hope won't happen in the real application 🙂
-
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...
Â
Â
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...
-
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.
-
2 minutes ago, Attila Kovacs said:are you sure its PAnsiChar?
Strange to me too, but yes, I am:
-
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:
Â
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:
Â
Â
🙂
-
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.
-
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.
-
1 minute ago, David Heffernan said:Of course, that would be assuming one's gender which isn't great. The French to their great credit have extended their language to include non-gendered pronouns.
I do believe that's more than enough, please try not to steer the conversation away. Can we get back to the topic, please?
How to open a file in the already running IDE?
in Delphi IDE and APIs
Posted
...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 😞
Â