Skip to main content
7-Bedrock
July 15, 2022
Question

Creating DLL for ShellExecute

  • July 15, 2022
  • 1 reply
  • 2396 views

Good afternoon,

 

As titled, I am crafting a general purpose ShellExecute that will run inside Prime.  I have it working.  But there is an issue with a null pointer. 

 

My approach was to pass strings.  These are strings that define the EXE, and the command line. 

 

An example would be like this:

 

ShellExecute ("Notepad.exe", "c:\\data\\test.txt", "Null")

 

The problem is that I had an issue with a null pointer being passed, so I had to create three MCAD strings like this:

 

 

LRESULT ShellExecute_MCAD(MCSTRING* pMCS_Empty, MCSTRING* pMCS_Process_EXE, MCSTRING* pMCS_Process_CMD)

 

 

here is the Visual Studio cpp file.  I can also attach it if needed.

 

 

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "MCADINCL.H"


LRESULT ShellExecute_MCAD(MCSTRING* pMCS_Empty, MCSTRING* pMCS_Process_EXE, MCSTRING* pMCS_Process_CMD)
{
 // Routine performs a MCAD shell execute

 HINSTANCE L_Result = 0;
 wchar_t LpA_Process_EXE[MAX_PATH];
 wchar_t LpA_Process_CMD[MAX_PATH];
 wchar_t LpA_Empty[MAX_PATH];

 char pA_Process_EXE[MAX_PATH] = "";
 char pA_Process_CMD[MAX_PATH] = "";
 char pA_Empty[MAX_PATH] = "";

 // Define char arrays for process name and file name for process
 strcpy_s(pA_Process_EXE, pMCS_Process_EXE->str);
 strcpy_s(pA_Process_CMD, pMCS_Process_CMD->str);
 //strcpy_s(pA_Empty, pMCS_Empty->str);

 // Derived from https://cplusplus.com/forum/beginner/23378/
 std::copy(pA_Process_EXE, pA_Process_EXE + lstrlenA(pA_Process_EXE) + 1, LpA_Process_EXE);
 std::copy(pA_Process_CMD, pA_Process_CMD + lstrlenA(pA_Process_CMD) + 1, LpA_Process_CMD);
 std::copy(pA_Empty, pA_Empty + lstrlenA(pA_Empty) + 1, LpA_Empty);

 //MessageBox(NULL, LpA_Process_EXE, (LPCWSTR)L"MCAD ShellExecute EXE", MB_ICONINFORMATION);
 //MessageBox(NULL, LpA_Process_CMD, (LPCWSTR)L"MCAD ShellExecute CMD", MB_ICONINFORMATION);
 //MessageBox(NULL, LpA_Empty, (LPCWSTR)L"MCAD ShellExecute Empty", MB_ICONINFORMATION);
 //MessageBox(NULL, (LPCWSTR)L"Preparing a test of ShellExecute", (LPCWSTR)L"MCAD ShellExecute", MB_ICONINFORMATION);

 L_Result = ShellExecute(NULL, TEXT("open"), LpA_Process_EXE, LpA_Process_CMD, NULL, SW_SHOWNORMAL);


 // Return normal
 return 0;

}



// fill out a FUNCTIONINFO structure with the information needed for registering the function with Mathcad
FUNCTIONINFO FI_ShellExecute_MCAD =
{
 // Name by which mathcad will recognize the function
 "ShellExecute",

 // description of "shell" parameters to be used by the Insert Function dialog box
 "S_Process_EXE, S_Process_CMD",

 // description of the function for the Insert Function dialog box 
 "Routine runs performs ShellExecute with the command line",

 // pointer to the executible code i.e. code that should be executed when a user types in "multiply(a,M)="
 (LPCFUNCTION)ShellExecute_MCAD,

 // ShellExecute(S, S) returns a string
 STRING,

 // ShellExecute(S, S) takes on two string arguments
 3,

 // The first is the executable name, the second is the command line 
 {STRING, STRING, STRING}
};


BOOL APIENTRY DllMain( HMODULE hModule,
 DWORD ul_reason_for_call,
 LPVOID lpReserved
 )
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
 CreateUserFunction(hModule, &FI_ShellExecute_MCAD);
 case DLL_THREAD_ATTACH:
 case DLL_THREAD_DETACH:
 case DLL_PROCESS_DETACH:
 break;
 }
 return TRUE;
}


// table of error messages if your function never returns an error

const char* myErrorMessageTable[NUMBER_OF_ERRORS] =
{ "interrupted",
 "insufficient memory",
 "must be real"
};


#undef INTERRUPTED 
#undef INSUFFICIENT_MEMORY
#undef MUST_BE_REAL 
#undef NUMBER_OF_ERRORS 

 

 

When I run this, the only way this works is to place a sacrificial string at the end of the call.  Here is a screenshot:

 

MCAD Posting Shell Execute.png

 

Can anyone provide guidance as to why this is happening.

 

Thank you.

 

Regards, 

1 reply

23-Emerald IV
July 16, 2022

"Can anyone provide guidance as to why this is happening."

Potentially...

I've understood that the LRESULT 'function' takes as first argument a specification of the function result.

In your case that would be a string. The next arguments to LRESULT define the parameters to the function, you'd like two parameters, both strings.

Then it should look like:

LRESULT ShellExecute_MCAD(LPMCSTRING pMCS_empty, LPCMCSTRING pMCS_Process_EXE, LPCMCSTRING pMCS_Process_CMD)

 

Note the difference between LPMCSTRING (for the return value) and LPCMCSTRING for the arguments.

 

The the FUNCTIONINFO 'function' would not specify 3, but 2 arguments with { STRING, STRING } instead of {STRING, STRING, STRING } on the following line.

I see that your function uses a variable L_Result, that is not used elsewhere. I should think that your function might return an indication of the success or failure of the shell command execution, in the form of an integer value; in that case you'd want to return a scalar instead of a string to Mathcad.

 

Hope this helps.

 

Success!
Luc

 

 

 

7-Bedrock
July 19, 2022

Good morning Luc,

 

Thank you for your reply. Using this, I found more documentation based on your explanation and was able to move this further and include error codes.  It appears to be working nicely.  I will note that I cannot seem to trigger any error codes other that file not found. Anyway, I do appreciate your help. 

 

Regards, 

 

// dllmain.cpp : Defines the entry point for the DLL application.
#include "pch.h"
#include "MCADINCL.H"


LRESULT ShellExecute_MCAD(MCSTRING* pMCS_ReturnValue, MCSTRING* pMCS_Process_EXE, MCSTRING* pMCS_Process_CMD)
{
 // Routine performs a MCAD ShellExecute

 HINSTANCE L_Result = 0;
 wchar_t LpA_Process_EXE[MAX_PATH];
 wchar_t LpA_Process_CMD[MAX_PATH];

 // Supports MathCAD return error codes
 std::string S_ReturnValue;
 char* pA_ReturnValue;

 // Support process and command names
 char pA_Process_EXE[MAX_PATH] = "";
 char pA_Process_CMD[MAX_PATH] = "";

 // Define char arrays for process name and file name for process
 strcpy_s(pA_Process_EXE, pMCS_Process_EXE->str);
 strcpy_s(pA_Process_CMD, pMCS_Process_CMD->str);

 // Derived from https://cplusplus.com/forum/beginner/23378/
 std::copy(pA_Process_EXE, pA_Process_EXE + lstrlenA(pA_Process_EXE) + 1, LpA_Process_EXE);
 std::copy(pA_Process_CMD, pA_Process_CMD + lstrlenA(pA_Process_CMD) + 1, LpA_Process_CMD);

 // Returns message boxes for debugging
 #if false
 MessageBox(NULL, (LPCWSTR)L"Preparing a test of ShellExecute", (LPCWSTR)L"MCAD ShellExecute", MB_ICONINFORMATION);
 MessageBox(NULL, LpA_Process_EXE, (LPCWSTR)L"MCAD ShellExecute EXE", MB_ICONINFORMATION);
 MessageBox(NULL, LpA_Process_CMD, (LPCWSTR)L"MCAD ShellExecute CMD", MB_ICONINFORMATION);
 #endif

 // Process the Windows shell execute command
 L_Result = ShellExecute(NULL, TEXT("open"), LpA_Process_EXE, LpA_Process_CMD, NULL, SW_SHOWNORMAL);


 // Process the error codes to create the return string
 switch ((long)L_Result)
 {
 case 0:
 S_ReturnValue = "The operating system is out of memory or resources";
 break;

 case ERROR_FILE_NOT_FOUND:
 S_ReturnValue = "The specified file was not found";
 break;

 case ERROR_PATH_NOT_FOUND:
 S_ReturnValue = "The specified path was not found";
 break;

 case ERROR_BAD_FORMAT:
 S_ReturnValue = "The.exe file is invalid (non - Win32.exe or error in.exe image)";
 break;

 case SE_ERR_ACCESSDENIED:
 S_ReturnValue = "The operating system denied access to the specified file";
 break;

 case SE_ERR_ASSOCINCOMPLETE:
 S_ReturnValue = "The file name association is incomplete or invalid";
 break;

 case SE_ERR_DDEBUSY:
 S_ReturnValue = "The DDE transaction could not be completed because other DDE transactions were being processed";
 break;

 case SE_ERR_DDEFAIL:
 S_ReturnValue = "The DDE transaction failed";
 break;

 case SE_ERR_DDETIMEOUT:
 S_ReturnValue = "The DDE transaction could not be completed because the request timed out";
 break;

 case SE_ERR_DLLNOTFOUND:
 S_ReturnValue = "The specified DLL was not found";
 break;

 case SE_ERR_NOASSOC:
 S_ReturnValue = "There is no application associated with the given file name extension";
 break;

 case SE_ERR_OOM:
 S_ReturnValue = "There was not enough memory to complete the operation";
 break;

 case SE_ERR_SHARE:
 S_ReturnValue = "A sharing violation occurred";
 break;

 default:
 S_ReturnValue = "MathCAD ShellExecute normal";
 break;

 }

 // Process the return status string
 // Derived from https://community.ptc.com/t5/Mathcad/Prime-3-0-Custom-Functions-string-arguments/td-p/364978
 pA_ReturnValue = MathcadAllocate(S_ReturnValue.size() + 1);
 std::copy(S_ReturnValue.begin(), S_ReturnValue.end(), pA_ReturnValue);
 pA_ReturnValue[S_ReturnValue.size()] = '\0';
 pMCS_ReturnValue->str = pA_ReturnValue;

 // Return normal
 return 0;

}



// fill out a FUNCTIONINFO structure with the information needed for registering the function with Mathcad
FUNCTIONINFO FI_ShellExecute_MCAD =
{
 // Name by which mathcad will recognize the function
 "ShellExecute",

 // description of "shell" parameters to be used by the Insert Function dialog box
 "S_Process_EXE, S_Process_CMD",

 // description of the function for the Insert Function dialog box 
 "Routine runs performs ShellExecute with the command line",

 // pointer to the executible code i.e. code that should be executed when a user types in "multiply(a,M)="
 (LPCFUNCTION)ShellExecute_MCAD,

 // ShellExecute(S, S) returns a string
 STRING,

 // ShellExecute(S, S) takes on two string arguments
 2,

 // The first is the executable name, the second is the command line 
 {STRING, STRING}

};


BOOL APIENTRY DllMain( HMODULE hModule,
 DWORD ul_reason_for_call,
 LPVOID lpReserved
 )
{
 switch (ul_reason_for_call)
 {
 case DLL_PROCESS_ATTACH:
 CreateUserFunction(hModule, &FI_ShellExecute_MCAD);
 case DLL_THREAD_ATTACH:
 case DLL_THREAD_DETACH:
 case DLL_PROCESS_DETACH:
 break;
 }
 return TRUE;
}


// table of error messages if your function never returns an error

const char* myErrorMessageTable[NUMBER_OF_ERRORS] =
{ "interrupted",
 "insufficient memory",
 "must be real"
};


#undef INTERRUPTED 
#undef INSUFFICIENT_MEMORY
#undef MUST_BE_REAL 
#undef NUMBER_OF_ERRORS