When we do some difficult things we end with a hang process.
This is the right program which is not hang the M2000 Envirnoment:
Declare global GetMem2 lib "msvbvm60.GetMem2" {Long addr, Long retValue} Variant A=1000& Integer B=0 Call Void GetMem2(varptr(A), varptr(B)) Print B
So now we think that maybe can work using different signature:
// this is the wrong function/signature declare global GetMem2_Bad lib "msvbvm60.GetMem2" {Long addr, &retValue} Variant A=1000& Integer B=0 Call void GetMem2_Bad(varptr(A), &B) ? B
This hang the Interpreter/Environment
So we run another M2000.exe and put the following code on a module, say A (Edit A then Copy this, press Esc and write A and press enter)
This program finds files with M2000 extension in TEMP folder. Every time we start M2000.exe a new PID (process ID) assigned from operating system and M2000 write an empty file with the name then number of PID and M2000 as extension. So We get the current PID using the GetCurrentProcessID of Kernel32.dll and then we get all *,M2000 files, enumerate them, excluding our PID and sending Taskkill by PID. Also we delete the associate M2000 file. Last we have to delete the TMP type file which VB6 (not the M2000.dll) make it. We can't delete these TMP file if they are locked. TMP files are not of the same type for every program. Operating system produce the names which guarantee that are not used. So we don't now from the M2000.dll which name the tmp file procuced. For this reason we delete all not locked TMP files. Ypu can ask googles AI "can we delete TMP files in TEMP directory if they ar not locked?" and you get YES, plus the story about that.
The simulated hang program before can closed from the taskbar, which we terminate the M2000.exe which use the M2000.dll as a com object. The TMP file also erased but the M2000 not erased. So the program here will delete only the file (the program are not exist as a process at this point)
The DOS statement using a semi colon at the end executed in a hidden window.
Use Win m2000 to start another M2000.exe (M2000 Interpreter)
or use
USE alfa, "paremeter", 2000
to open another M2000.exe, run alfa.gsb and put these two parameters in the stack of the that interpreter. We can send only strings and numbers from that call. We can use named PIPES, so we can send a pipe name, and we can make alfa the client, and the program which "use" it as server. Also the pipename can be as something specific and all clients known the name from the start (hardcoded).
Named pipes can be used to provide communication between processes on the same computer or between processes on different computers across a network. If the server service is running, all named pipes are accessible remotely
Write HELP USE in command line in M2000 Environment to see about the use of pipes.
This program can't kill process from another computer from the LAN.
Declare GetCurrentProcessId Lib "kernel32.GetCurrentProcessId" As Long // this is our ProcessId ourPID=GetCurrentProcessId() // get current directrory m$=dir$ // set TEMP folder as current folder dir temporary$ // empty menu array menu // using + we send filenames to menu$() array files + "m2000" if menuitems>0 else menu: exit // We get a copy (menu$() is not an array is a function) // when we use it without parameter return a an array a=menu$() menu k=each(a) while k if array(k)<>ourPID then Print "Erase "+array$(k) // now we kill the PID and erase the (pid).M2000 dos "taskkill /F /PID "+array$(k); wait 100 dos "del "+quote$(dir$+array$(k)+".m2000"); end if end while // delete all tmp files which are not locked // because one tmp file produced by the VB6 dll, and we didn't now which are dos "del /A:-R "+quote$(temporary$+"*.tmp"); dir m$
We make a form, with a context menu (see the three parallel lines at the right up corner. We place in form an Image control for using by OpenGl, three Textbox as Spinner Controls (alter values using up/down arrow or Shift up/down for fine tune)m and one "M2000.ctxNineButton" which is a ctxNineButton.ctl (included in M2000.dll) from Vladimir Vissoultchev (nickname: wqweto, https://github.com/wqweto/NinePatch). In M2000 we can use external controls, using the Delcare VarName Type "name as register" FORM name_of_form or image (image control can hold controls too).
Especially for ctxNineButton the GUIM2000 form (the basic form for user UI in M2000), hold the functionality to perform the animations (fade in out for caption change, transparent and light transitions) which you see in the video. Also I use the TabStop to include it in the list of "tab" used controls: There are 5 in total, one is the form (with down arrow we open the context menu), three more the textboxes, and one the blue button of type NineButton.
We make the controls for X,Y,Z with consolas font, and large enough to see them. Also we can change the type of decimal point (changing UTC to GTB time (for Greece), also I change the locale type). The XYZ not only used to pass values but for showing values too.
The OpenGl scene run on a Thread, not a really thread but an M2000 type of thread (based on time intrevals). Threading system use either a Concurrent or a Sequential mode, here the default Sequential mode run. So when the form is up, only threads and events run. An event has always priority over thread. But all events are run in short time. The same for controls. When we write in a control the event of a click is very short, so the control not use the cpu time, so our threads run smoothly.
The task for OpenGl is to show not only the Scene, as the 3D objects plus the HUD type text (see first line) and the 3D text (two lines - one traveling both horizontal directions in turn), but the background which we take a snapshot every time the scene constructed. So we draw lines on Form, and take the part of the form behind Image1 and place it in an OpenGl texure (the Background). Also we place a second Texture which can move and rotate everywhere (the background is always at the same viewport like the HUD text). The HUD Text has 20 lines always, and the class has ready support for latin and greek letters. The "library" (or class if you prefer) written with the help of ChatGPT although the DIBsection used for loading fast the texture are mine (I have to say I didn't ask for that, so ChatGPT maybe can give something better). The crucial part was the understanding of OpenGL which was zero for me, so for years I didn't included on M2000 code. Now there is something basic.
This is the code of cOpenGl.cls. You can see the Public interface (just search Public to see the methods (functions or subs) which tagged Public. Those methods we can call from M2000. There are other objects like Sphere Torus Pyramid Cone Cylinder.
locale 1033 cls,0 escape off back{ cls 0 } declare form1 form method form1, "move", 0,0, 12000, 6600 gColor=color(256*0.2, 256*0.6, 256*0.9) Layer form1 { cls 15 font "consolas" window mode, scale.x, scale.y; gradient gColor hold ' push the background to store (we use restore to bring back the background) form1.scaleX=scale.x form2.scaleY=scale.y }
With form1, "titleheight" as titleheight, "id" as Id(), "MenuEnabled" as MenuEnabled(), "ListSelected" as Checked() long ret module EnableRotation { MenuEnabled(tag1)=false MenuEnabled(tag1+1)=true } module DisableRotation { MenuEnabled(tag1+1)=false MenuEnabled(tag1)=true } boolean clearplease Function Form1.infoclick(new it) { select case val(id(it)) case 500 if pushbutton then call local scenedrive case 600 if pushbutton else call local scenedrive case 800 call local form1.click() case 850 Checked(tag2+1)=not Checked(tag2+1) call local opacity case 900 clearplease=true case 1000 {after 100 {ret=ask("Hello", "Help", "Ok", "")} } case 2000 method form1,"CloseNow" end select } Function Form1.InfoChecked(new it) { if val(id(it)) =800 then if checked(it)and locale<>1033 then call local form1.click() else.if not checked(it)and locale=1033 then call local form1.click() end if else.if val(id(it)) =850 then call local opacity end if } module opacity { if Checked(tag2+1)then method form1, "Opacity", 180 else method form1, "Opacity", 255
With image1, "TabStop", false, "Default", true With form1, "visible" as visible, "blink", 300, "blinktimes", 12, "NoFocus" as ReadNoFocus Boolean Free
Layer Image1 { cls color(256*0.2, 256*0.6, 256*0.9) declare og opengl With og, "Ready" as Ready } method og, "ReStartGL", Textures2d:=2 'method og, "SetLoadPicture", dir$+"myjpg.jpg", number:=1 method og, "SetLoadPicture", img$, number:=1 img$="" 'get memory back ? type$(og) angleX=13.4~ angleY=21.4~ angleZ=0~ Single PosGreek=2.6 d=-1~ Print "OpenGl ready", ready if not ready then exit Layer form1 { refresh 25 release } function button1.click { free~ if free then bCaption$="status: free rotation" else bCaption$="status: no free rotation" } function form1.unload { threads erase } function form1.click { locale 2065-locale spX$=dec1$(angleX) spY$=dec1$(angleY) spZ$=dec1$(angleZ) Checked(tag2)=locale=1033 } function form1.blink { flush if scene_run>0 then thread scene_run interval 30 } scene_run=0 pushbutton=true def dec1$()=str$((number mod 3600)/10,"#0.0") ' this is for ValidString module clearInput { local o$ o$=filter$(v$, "-1234567890"+locale$(14)) if o$<>"" then v$=filter$(v$, o$) } module commonKeyDown { if pushbutton then call local scenedrive } module commonValid (new &AngleN) { try { local single alfa alfa=val(v$, locale$(14))*10:angleN=alfa mod 3600~:alfa=angleN } if valid(alfa)then v$=dec1$(alfa)else where=1000 } ' XXXXXXXXXXXXXXXX function InpAngleX.keydown {call local commonKeydown} function InpAngleX.click { if pushbutton then call local scenedrive } function InpAngleX.SpinnerValue(new v) {spX$=dec1$(v)} function InpAngleX.ValidString(new &v$, &where) { call local clearInput call local commonValid, &angleX } 'YYYYYYYYYYYYYYYYYYYY function InpAngleY.keydown {call local commonKeydown} function InpAngleY.click { if pushbutton then call local scenedrive } function InpAngleY.SpinnerValue(new v) {spY$=dec1$(v)} function InpAngleY.ValidString(new &v$, &where) { call local clearInput call local commonValid, &angleY } 'zzzzzzzzzzzzzzzzzzz function InpAngleZ.keydown {call local commonKeydown} function InpAngleZ.click { if pushbutton then call local scenedrive } function InpAngleZ.SpinnerValue(new v) {spZ$=dec1$(v)} function InpAngleZ.ValidString(new &v$, &where) { call local clearInput call local commonValid, &angleZ } spX$ =dec1$(angleX) spY$ =dec1$(angleY) spZ$ =dec1$(angleZ) boolean skiponce function image1.click { if skiponce then skiponce=false else call local scenedrive } function Form1.enter { skiponce=true call local scenedrive } function image1.gotfocus { exit skiponce=true call local scenedrive } function image1.LostFocus { exit skiponce=false pushbutton=true call local scenedrive } boolean OnlyTitleMove module scenedrive { pushbutton~ if pushbutton then if scene_run>0 then OnlyTitleMove=false: thread scene_run restart: call local DisableRotation else if scene_run>0 then thread scene_run hold: OnlyTitleMove=true: call local EnableRotation end if } thread { Layer form1 { if clearplease then Gradient gColor:clearplease~ else release cursor 0, height-1 move random(scale.x), random(scale.y): draw to random(scale.x), random(scale.y), random(0,7) hold print part @(width-27), $(0,9),format$("{0:1:-8}°{1:1:-8}°{2:1:-8}°",angleX/10~,angleY/10~, angleZ/10~) print part time$(if(locale=1032->now, time("UTC")), locale, "hh:mm:ss tt ");if$(locale=1032->time$(),"UTC"); " press Esc to quit" rem print part time$(time("UTC"), locale, "hh:mm:ss tt");" UTC" refresh 2000 } } as clock interval 60 module resetSceneState { angleX = 0~ angleY = 0~ angleZ = 0~ method og "SetCameraPosition", 0~, 0~, -10~ method og "SetCameraRotation", 0~, 0~, 0~ } method og "ClearLights" method og "SetAmbient", 0.18~, 0.18~, 0.22~, 1~ method og "AddPointLight", 0~, 5~, 6~, 0.55~, 0.55~, 0.55~, 0~, 0~, 0~, 0.8~, 0.8~, 0.8~, 1~, 0.03~, 0~ method og "AddSpotLight", -8~, 6~, 8~, 1~, -0.6~, -1~, 25~, 12~, 1~, 0.85~, 0.7~, 0~, 0~, 0~, 1~, 1~, 1~, 1~, 0.02~, 0~ method og "AddSpotLight", 8~, 5~, 7~, -1~, -0.5~, -1~, 22~, 18~, 0.6~, 0.7~, 1~, 0~, 0~, 0~, 1~, 1~, 1~, 1~, 0.025~, 0~ method og "ApplyLights"
module sceneone { method og, "SetBackgroundColor", 0.2, 0.6, 0.9 method og, "SetBackgroundPictureUnder", form1, 0, titleheight method og, "BeginSceneEx" method og, "LightingOn" method og, "DrawBackgroundFullScreen", -25~ method og, "DrawCube", angleX/10~,angleY/10~, angleZ/10~,0~, 0~, 0~, 4~ method og, "DrawImage", number:=1, width:=10, height:=10, x:=0, y:=0, z:=-3 , angleX:=-30, angleY:=angleY/5~ method og, "DrawHudlineX20", 0,"first line", 1,1,1 method og, "DrawText3D", PosGreek, 3.2~, 1~, 0.28~, "VB6 - Ελληνικά", 1~, 1~, 1~, 0~, 0~, 0~ method og, "DrawText3D", -2.6~, -3.2~, 1~, 0.28~, "OpenGL Κείμενο", 1~, 0.8~, 0.2~, 0~, 0~, 0~ method og, "EndScene" } module titlemove { PosGreek+=0.2*d if PosGreek<-4.6 then d-! else.if PosGreek>2.6 then d-! } thread { static once as boolean if ReadNoFocus and not free then if once else one=true call local sceneone call local sceneone end if method og, "Present" else once=false if OnlyTitleMove then call local titlemove call local sceneone end if ' Layer form1 {refresh 25}
} as scene interval 30 Boolean bpass thread { if free then bpass=false else bpass=ReadNoFocus if bpass else call local titlemove spX$=dec1$(angleX+12~) spY$=dec1$(angleY+18~) spZ$=dec1$(angleZ) call local sceneone end if } as scene_run hold cls gradient 1, 5 move scale.x/2, scale.y/2 circle fill 4, scale.y/3 If ask("Hide the M2000 Console","Cube3d", "*Yes", "No")=1 then title "Cube3d", 0 method form1, "show" call local resetSceneState method form1, "Opacity", 180