How to pass a null pointer to a DLL?

 

I'm trying to call CreateProcess() from MQL, without success. Following is the import declaration:

#import "kernel32.dll"
   int CreateProcessA(string lpApplicationName,
                      string lpCommandLine,
                      int lpProcessAttributes[],
                      int lpThreadAttributes[],
                      int bInheritHandles,
                      int dwCreationFlags,
                      int lpEnvironment[],
                      string lpCurrentDirectory,
                      int lpStartupInfo[],
                      int lpProcessInformation[]);
#import

lpProcessAttributes expexts a pointer to a SECURITY_ATTRIBUTES structure, usually NULL is passed. Same for lpThreadAttributes and lpEnvironment. But how to pass this NULL pointer. I know how to do it with strings.

Changing the declaration to

int CreateProcessA(..., int lpProcessAttributes, int lpThreadAttributes, ...)
and passing 0 as pointer doesn't work. I know I can use WinExec() or ShellExecute() etc. to run an application but later I'm dependent on WaitForSingleObject(), so these are no options.

Anybody any idea?

 

maybe this can help i dono

 
qjol:

maybe this can help i dono

No, unfortunately not. The example stringo pointed to is written in C++, not in MQL. In a native language it's not a problem at all. How to do this in MQL?

If MQL provides the use of native libraries it should be possible to pass a NULL pointer. So how?

 
Simply pass 0 as int. Change the declaration of the import so that it accepts an int instead of an array and then pass the value 0 to it. int is a 32 bit integer, the same size as a pointer on i386, so the function signature would stay exactly the same and the number 0 would be interpreted as a null pointer.
 
Yes, of course passing directly 0 instead of an array works in this case. But what should I do in my next function call, where I indeed need to pass a valid struct. It's not possible to change back and forth declarations (if you need a NULL pointer change it to int, if you need to pass a struct/array change it to array).

Isn't there a solution with a single declaration?
 
paulepanke:
Yes, of course passing directly 0 instead of an array works in this case. But what should I do in my next function call, where I indeed need to pass a valid struct. It's not possible to change back and forth declarations (if you need a NULL pointer change it to int, if you need to pass a struct/array change it to array).

Isn't there a solution with a single declaration?

To sum up: you can pass lpProcessAttributes and lpEnvironment as NULL by declaring them simply as int, and supplying 0 for these values. Your problem is that CreateProcess requires a non-null value for STARTUPINFO and PROCESS_INFORMATION. For example:

#import "kernel32.dll"
   int CreateProcessA(int lpApplicationName,
                      string lpCommandLine,
                      int lpProcessAttributes,
                      int lpThreadAttributes,
                      int bInheritHandles,
                      int dwCreationFlags,
                      int lpEnvironment,
                      int lpCurrentDirectory,
                      int & lpStartupInfo[],
                      int & lpProcessInformation[]);

   int WaitForSingleObject(int Handle, int Timeout);                      
#import

int start()
{
   // Create an array to mimic a STARTUPINFO structure, which is 68 bytes long 
   int startupinfo[17];
   
   // Set the cb value of the STARTUPINFO structure. All the other values can
   // be left set to zero
   startupinfo[0] = 17 * 4;
   
   // Create an array to mimic a PROCESS_INFORMATION structure. Doesn't need
   // initialisation
   int processinfo[4];
   
   // Call create process
   if (CreateProcessA(0, "c:\windows\system32\calc.exe", 0, 0, 0, 0, 0, 0, startupinfo, processinfo) != 0) {
      // Wait for calc.exe to be closed 
      // Handle is in processinfo[0];
      WaitForSingleObject(processinfo[0], 60 * 1000);
      
      MessageBox("Calc.exe started and closed...");
   } else {
      MessageBox("CreateProcess() failed");
   }
}
 

The structs are not a problem. Structs are easy in MQL. The problems are lpApplicationName, lpCommandLine and lpDirectory. To get a working solution you have to define them as int and pass 0 or you define them as string. There is no unique solution for both ways.

Whenever your code indeed needs to specify module name or default directory your code breaks because you would have to change the declaration, and this is not possible. My simplified solution so far looked like this:

#import "kernel32.dll"
   int CreateProcessA(int    lpApplicationName,
                      string lpCommandLine,
                      int    lpProcessAttributes[],
                      int    lpThreadAttributes[],
                      int    bInheritHandles,
                      int    dwCreationFlags,
                      int    lpEnvironment,
                      int    lpCurrentDirectory,
                      int    lpStartupInfo[],
                      int    lpProcessInformation[]);
#import

/**
 *
 */
int start() {
   string lpCommandLine = "calc.exe";
   int /*SECURITY_ATTRIBUTES*/ sa[ 3] = {12};
   int /*STARTUPINFO*/         si[17] = {68};
   int /*PROCESS_INFORMATION*/ pi[ 4];

   int result = CreateProcessA(NULL,            // module name
                               lpCommandLine,   // command line
                               sa,              // process attributes
                               sa,              // thread attributes
                               false,           // handle inheritance
                               0,               // creation flags
                               NULL,            // environment block
                               NULL,            // starting directory
                               si,              // startup info
                               pi               // process info
   );

   if (result == 0) {
      Print("CreateProcess() failed with error = "+ error);
   }
   else {
      Print("CreateProcess() success");
   }
   return(0);
}

I tried to keep the declaration as close as possible to the originial. I don't use NULL pointers for the structs, this way at least I can use/modify them.

So finally, as a clean and reusable solution I would recommend to implement the process creation either in a separate DLL or via ShellExecuteEx(), where we have no NULL pointers as parameters, only a struct that we can manipulate to our liking.


One more thing: Don't forget to close process and/or thread handles if you use CreateProcess() or ShellExecuteEx(). You asked for the references, so you are responsible to clean up afterwards.

@see CloseHandle()

 
If you want to use your own environment block, the single declaration again breaks.
 
paulepanke:

Whenever your code indeed needs to specify module name or default directory your code breaks because you would have to change the declaration, and this is not possible.

I can't immediately think of a reason from MT4 why you might need to specify both lpApplicationName and lpCommandLine. Similarly, if you sometimes need to specify lpDirectory and sometimes not, then why can't you just declare it as string, and fill in the current directory when you would otherwise pass the value as null? What actual requirement do you have other than the one you've specified about needing a handle in order to call WaitForSingleObject()?

paulepanke:

There is no unique solution for both ways

Yes, there is. It's just ridiculously and unnecessarily complex. You declare all the parameters as int, including the strings, and then get the addresses for the strings you want to pass and specify the others as zero. See https://www.mql5.com/en/forum/118999 for an example. But I can't see any actual reason why you'd need to go down this route.

paulepanke:

So finally, as a clean and reusable solution I would recommend to implement the process creation either in a separate DLL or via ShellExecuteEx(), where we have no NULL pointers as parameters, only a struct that we can manipulate to our liking.

You can indeed use ShellExecuteEx(), but selectively populating the SHELLEXECUTEINFO from MT4 is very messy and again needs some nasty tweaks to get the addresses of the strings. Again, I'm struggling to see a plausible reason why, from MT4, you might need to do anything other than starting a process and getting back a handle.

 

solution for real NULL pointers:

https://www.mql5.com/en/forum/141796

first answer

Reason: