cancel
Showing results for 
Search instead for 
Did you mean: 
cancel
Showing results for 
Search instead for 
Did you mean: 

Community Tip - Stay updated on what is happening on the PTC Community by subscribing to PTC Community Announcements. X

Creating DLL for ShellExecute

DS_10314450
6-Contributor

Creating DLL for ShellExecute

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, 

4 REPLIES 4
LucMeekes
23-Emerald III
(To:DS_10314450)

"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

 

 

 

DS_10314450
6-Contributor
(To:LucMeekes)

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     

 

 

DS_10314450
6-Contributor
(To:LucMeekes)

here is the updated MathCAD include file

#ifndef _MCADINCL_H_
#define _MCADINCL_H_

#include <windows.h>
#include <iostream>
#include <math.h>
#include <string.h>
#include <time.h>
#include <ShellApi.h>
#include <tchar.h>

#define  INTERRUPTED            1
#define  INSUFFICIENT_MEMORY    2
#define  MUST_BE_REAL           3
#define  NUMBER_OF_ERRORS       3   

#ifdef __cplusplus
extern "C"
{
#endif // __cplusplus
                           
// complex scalar type
typedef struct tagCOMPLEXSCALAR {
    double real;
    double imag;
} COMPLEXSCALAR;
    
// this is the complex scalar type received from mathcad
typedef const COMPLEXSCALAR * const LPCCOMPLEXSCALAR; 
// this is the complex scalar type that should be returned to mathcad
typedef COMPLEXSCALAR * const LPCOMPLEXSCALAR;
    

// complex array type
typedef struct tagCOMPLEXARRAY  {
    unsigned int rows;
    unsigned int cols;
    double **hReal; // hReal[cols][rows],  == NULL when the real part is zero 
    double **hImag; // hImag[cols][rows],  == NULL when the imaginary part is zero
} COMPLEXARRAY;
    
// readonly complex array type
typedef struct tagReadOnlyCOMPLEXARRAY  {
    const unsigned int rows;
    const unsigned int cols;
    const double *const *const hReal; // hReal[cols][rows],  == NULL when the real part is zero 
    const double *const *const hImag; // hImag[cols][rows],  == NULL when the imaginary part is zero
} ReadOnlyCOMPLEXARRAY;
    
// this is the complex array type received from mathcad
typedef const ReadOnlyCOMPLEXARRAY * const LPCCOMPLEXARRAY;    

// this is the complex array type that should be returned to mathcad
typedef COMPLEXARRAY * const LPCOMPLEXARRAY;


typedef struct tagMCSTRING {
	char *str;
}MCSTRING;

typedef const MCSTRING * const LPCMCSTRING;
typedef MCSTRING * const LPMCSTRING;


// types to be used in declaration of the function's arguments and of the return value
#define COMPLEX_SCALAR  1
#define COMPLEX_ARRAY   2
#define STRING			8


//
// File name variables. These are passed as const char *pointers if the string doesn't look like it has a path in it then the current working directory will be prepended to the string 
// before it is passed to the user function 

// your function will be passed a const char * pointer
#define INFILE			13

// an OUTFILE is like an INFILE except it allows you to put your function on the left side of a := like the WRITEPRN() builtin
#define OUTFILE			14

// use this structure to create a function
#define MAX_ARGS        10

typedef LRESULT (* LPCFUNCTION ) ( void * const, const void * const, ... );    

typedef struct tagFUNCTIONINFO {
    const char *  lpstrName;
    const char *  lpstrParameters; 
    const char *  lpstrDescription;
    LPCFUNCTION lpfnMyCFunction;
    long unsigned int returnType;
    unsigned int nArgs;
    long unsigned int argType[MAX_ARGS];
} FUNCTIONINFO;

const void * CreateUserFunction( HINSTANCE, FUNCTIONINFO * );
    
BOOL CreateUserErrorMessageTable(   HINSTANCE, 
                                    unsigned int nErrorMessages,
                                    const char  * ErrorMessageTable[] );
                                       
// memory management routines
char * MathcadAllocate( unsigned int size );
void MathcadFree( char * address );


// array allocation -- should be used to allocate return array
BOOL    MathcadArrayAllocate(   COMPLEXARRAY * const, 
                                unsigned int rows,  
                                unsigned int cols,
                                BOOL allocateReal, 
                                BOOL allocateImag );

// should be used to free (in case of an error), Mathcad allocated return array
void MathcadArrayFree( COMPLEXARRAY * const );
    
    
// this routine can be used to find out whether the user has attempted to interrupt Mathcad this routine slows down the execution -- so use judiciously
BOOL isUserInterrupted( void );
    
#ifdef __cplusplus
}
#endif // __cplusplus
    
#endif // _MCADINCL_H_  

Here are some compilation notes.

  • Compile x64
  • Must include the mcadincl.h file with the updated headers
  • Add the Mcaduser.lib as shown in the project tree

MCAD ShellExecute VS Compile.png

 

Here are some notes on the DLL placement:

 

MCAD ShellExecute DLL Placement.png

 

Here is a simple example.  You highlight the function line and hit F9 to execute the content.  Since the shell is not topmost and is modeless, it will create multiple instances.  There may be a way to redirect the content to the same instance using mutex.  

MCAD ShellExecute Example.png

 

Top Tags