Clarion Run vs CreateProcessA

I have some old c5.5 code that uses CreateProcessA winapi vs Run. I can’t remember why, but RUN in earlier versions did not work too well.

In C11 the same code throws an access exception.

Is RUN safe now in c11?

Anyone have working c10+ code that calls createprocessA that they are willing to share?

Here is what I have now: (which fails in c11)

  program
  map
   module('')
 WaitForSingleObject(long hHandle, long  dwMilliseconds ),long,pascal,raw

 CreateProcessA(long lpApplicationName, long lpCommandLine, |
      long lpProcessAttributes , long lpThreadAttributes ,|
      long bInheritHandles , long dwCreationFlags , |
      long lpEnvironment, long lpCurrentDirectory, |
      StartupGroupType , ProcessGroupType),long,pascal,raw

 CloseHandle(long hObject),long,pascal,proc,raw

 GetExitCodeProcess(long hProcess, *long lpExitCode),long,pascal,proc,raw



     GetTickCount(),ULONG,RAW,PASCAL
   end

  end


  include('runexe.inc')

NORMAL_PRIORITY_CLASS long(20h)
INFINITE long(-1)
CREATE_SEPARATE_WOW_VDM     EQUATE(00000800h)

StartupGroupType  group,type
cb         Long
lpReserved long
lpDesktop   long
lpTitle   long
dwX   Long
dwY   Long
dwXSize   Long
dwYSize   Long
dwXCountChars   Long
dwYCountChars   Long
dwFillAttribute   Long
dwFlags   Long
wShowWindow   long  !Word
cbReserved2   long !WORD
lpReserved2   Long
hStdInput   Long
hStdOutput   Long
hStdError   Long
   End 

ProcessGroupType  group,type
hProcess   Long
hThread   Long
dwProcessID   Long
dwThreadID   Long
   End

  code
  return



RunThisClass.RunThis procedure(string command, string directory, short wait=0)
proc like(ProcessGroupType)
start like(StartupGroupType)
commandC cstring(300)
directoryC cstring(300)
ret long
creationFlag  long
WAIT_TIMEOUT equate(0102h)
   code
     ! ' Initialize the STARTUPINFO structure:
     
      start.cb = size(start)
      commandC = clip(command)
      directoryC = clip(directory)
 !     message(commandC)
 !     message(directoryC)
      creationFlag = NORMAL_PRIORITY_CLASS
      if SELF.separateFlag
         creationFlag += CREATE_SEPARATE_WOW_VDM
      end


      if (self.hideThis = true)
        start.dwFlags = 1 ! use wShowWindow
        start.wShowWindow =     0 ! hide
      end


      !' Start the shelled application:
      ret = CreateProcessA(0, address(commandC), 0, 0, 1, |
           creationFlag , 0, choose(len(directoryC)=0,0,address(directoryC)), start, proc)


    !  ' Wait for the shelled application to finish:

 

      if wait
       accept
        if event() = event:timer
         if WaitForSingleObject(proc.hProcess, 1 ) <> WAIT_TIMEOUT
           break
         end
        end
       end

       GetExitCodeProcess(proc.hProcess, ret)
       CloseHandle(proc.hThread)
       CloseHandle(proc.hProcess)
      end
   return ret



RunThisClass.Init    procedure(short separateFlag)
  code
    SELF.separateFlag = separateFlag
    SELF.hideThis = false
  return

RunThisClass.SetSeparate   procedure()
  code
    SELF.separateFlag = true
  return

RunThisClass.ClearSeparate procedure()
  code
    SELF.separateFlag = false
  return


RunThisClass.HideOutput   procedure()
  code
    SELF.hideThis = true
  return

Looks like the createproccess definition that worked in c55 needed some updates for c11.

shows an alternative api declaration. (long -> ulong) and changing the last two from types to ulongs.

I believe the Run() in clarion 10/11 is fixed. But using CreateProcessA should also work.

There are several templates that were used to do that, around. Looking at them should tell you what you have wrong (I don’t see it off hand).

HTH,

P.S. I did find this in my notes:
“But it always depends on the API and the use of that API. So even if you
are using only non-administrative APIs, you are not safe. For example, a
simple CreateProcess API to launch an external application. It is not an
administrative API per-se. But if you try to launch a program that itself
requests administrator execution level privileges from an non-elevated
running app, then CreateProcess will fail with error 740. It will succeed
if the calling program is running elevated.”

CreateProcessA does work in C11, I have it running but I noticed the CML Classes and I wondered why Tom H (is it Hebrestein?) uses ulong’s instead of long’s in this createprocess api prototype?

Its a nice class though!

If anyone is wondering, MS specify the simple data types here:
Windows Data Types (BaseTsd.h) - Win32 apps | Microsoft Docs
and the integer limits which makes it easy to marry up to clarion simple data types are here:
Integer Limits | Microsoft Docs
and the __ptr32 are an int which is a clarion Long.
__ptr32, __ptr64 | Microsoft Docs

Another option is to use ShellExecuteExA to run an EXE. This is the only way if the EXE needs Elevation to Supervisor. Since you may not know if Elevation is required it is the safe choice. RUN() will fail and in the past returned an odd ErrorCode().

There is the SEE_MASK_NOCLOSEPROCESS option in SHELLEXECUTEINFOA to return the Handle to the new Process. You can Wait on that Handle and/or call GetExitCodeProcess(), don’t forget to CloseHandle(). So ShellEx can work like CreateProcess() plus support Elevation of rights.

In the code posted I don’t think the 1 miilisecond WaitForSingleObject(,hProcess,1) is the best code for checking if the process closed. You can only call GetExitCodeProcess(). If the Process is still running the Exit Code will be STILL_ACTIVE = 259 so you can continue waiting.

The CreateProcessAsUser would also allow a process to run as a different user, like the Administrator.
CreateProcessAsUserA function (processthreadsapi.h) - Win32 apps | Microsoft Docs

And if the app has code which needs to be run elevated, I can use BCM_SETSHIELD message (Commctrl.h) - Win32 apps | Microsoft Docs to add the UAC shield to a button control or link and it looks like, but havent tested yet, that modifying the environment block could create a process that is elevated.

So if UAC is enabled, it looks like the so called TOKEN_LINKED_TOKEN is required when TokenElevationTypeLimited is returned by GetTokenInformation.

WTSQueryUserToken function (wtsapi32.h) - Win32 apps | Microsoft Docs to get the logged on user’s token
GetTokenInformation function (securitybaseapi.h) - Win32 apps | Microsoft Docs
TOKEN_LINKED_TOKEN (winnt.h) - Win32 apps | Microsoft Docs
TOKEN_ELEVATION_TYPE (winnt.h) - Win32 apps | Microsoft Docs

I havent tried this yet so its only theory!

So there is a a BOOL parameter called bInheritHandles in the CreateProcessA api, the first image shows it Off/0/false the second image shows it On/1/true, the main point being when its Off/0/false, you dont have a clue what program started it, at least I cant find anything from ProcessExplorer! :innocent:
Although the images dont show it in the tooltip, when you only have notepad.exe ie no path in the applicationname or commandline, the tooltip will only show CommandLine as “notepad.exe”

InheritedFlag = 0 !Off/false Notepad appears unconnected to any other app
InheritedFlaggOff

InheritedFlag = 1 !On/true notepad can be seen connected to C11\Library.exe alongside debugview.exe
InheritedFlagOn

These combinations of lpApplicationName, lpCommandLine & lpCurrentDirectory work

Work

lpApplicationName = ''
lpCommandLine = 'notepad.exe'
lpCurrentDirectory = ''

lpApplicationName = ''
lpCommandLine = 'notepad.exe'
lpCurrentDirectory = 'c:\windows\system32\'

lpApplicationName = 'c:\windows\system32\notepad.exe'
lpCommandLine = ''
lpCurrentDirectory = ''

lpApplicationName = ''
lpCommandLine = 'c:\windows\system32\notepad.exe'
lpCurrentDirectory = ''

Not Work

lpApplicationName = 'notepad.exe'
lpCommandLine =  ''
lpCurrentDirectory = 'c:\windows\system32\'

lpApplicationName = 'notepad.exe'
lpCommandLine =  ''
lpCurrentDirectory = ''

There are notes in the MS createprocessa webpage which explains why for the above.

StartupInfo minimum info to start a process

Module('WinAPI')       
    IS_CreateProcess(long,Long,Long,Long,Bool,ULong,Long,long,Long,Long),Bool,Raw,Pascal,Name('CreateProcessA')
    IS_GetLastError(),Long,Raw,Pascal,Name('GetLastError')
End

CREATE_DEFAULT_ERROR_MODE Equate(04000000h)

STARTUPINFOA                Group
    cb                              ULong   !Dword
    lpReserved                      Long    !LP STR     __PTR32 = INT Int = Long
    lpDesktop                       Long    !LP STR
    lpTitle                         Long    !LP STR
    dwX                             ULong   !Dword
    dwY                             ULong   !Dword
    dwXSize                         ULong   !Dword
    dwYSize                         ULong   !Dword
    dwXCountChars                   ULong   !Dword
    dwYCountChars                   ULong   !Dword
    dwFillAttribute                 ULong   !Dword
    dwFlags                         ULong   !Dword
    wShowWindow                     UShort  !0 to 65,535       WORD	A 16-bit unsigned integer. The range is 0 through 65535 decimal.
    cbReserved2                     UShort  !0 to 65,535       WORD	A 16-bit unsigned integer. The range is 0 through 65535 decimal.
    lpReserved2                     Long    !LP Byte    __PTR32 = INT Int = Long
    hStdInput                       Long    !Handle = pVoid  pVoid = Pointer to any type
    hStdOutput                      Long    !Handle
    hStdError                       Long    !Handle
                                End

Loc:StartupInfo             Group(STARTUPINFOA)
                            End

Loc:Desktop                 Cstring(1024)`

code
    Loc:Desktop                             = 'default'
    Clear(Loc:StartupInfo)                                                          !Zero Loc:StartupInfo
    Loc:StartupInfo.cb                      = Size(Loc:StartupInfo)                         !Set size of Loc:StartupInfo in cb
    Loc:Desktop                             = 'default'
    or
    Loc:Desktop                             = 'Winsta0\default'
    Loc:StartupInfo.lpDesktop               = Address(Loc:Desktop)

Loc:lpApplicationName                   = 0
Loc:lpCommandLine                       = Address(Loc:CommandLine)
Loc:lpProcessAttributes                 = 0
Loc:lpThreadAttributes                  = 0
Loc:InheritHandles                      = 0
Loc:CreationFlags                       = CREATE_DEFAULT_ERROR_MODE 
Loc:lpEnvironment                       = 0
Loc:lpCurrentDirectory                  = 0
Loc:lpStartupInfo                       = Address(Loc:StartupInfo)
Loc:lpProcessInformation                = Address(Loc:ProcessInformation)


Loc:ReturnValue         = IS_CreateProcess( Loc:lpApplicationName,|                                
                                            Loc:lpCommandLine,|         
                                            Loc:lpProcessAttributes,|                                  
                                            Loc:lpThreadAttributes,|                                  
                                            Loc:InheritHandles,|                
                                            Loc:CreationFlags,|                 
                                            Loc:lpEnvironment,|                                  
                                            Loc:lpCurrentDirectory,|                            
                                            Loc:lpStartupInfo,|          
                                            Loc:lpProcessInformation)

Anyway that should get most clarioneers going with this api because the code examples listed in the results by a couple of the most popular search engines, take you to websites like overing flowing stack , exchanging experts, various blogs and their code examples dont work just like the MS code example does not work out the box because of flag settings.

I’ll bang this link on here ( [MS-DTYP]: Windows Data Types | Microsoft Docs)

Its where all the latest data types and protocols are specified in a PDF format for handy off line use.

This is the link to the most recent MS specs from 25/6/21 rev 37.

Its got SACL’s, DACL’s, ACE’s, LUID’s, GUID’s, IDL’s and plenty more.