Controlling Program Flow Between VB and C++ - c++

I have a program that runs from VB/Excel and executes a C++ program in the middle of it. I have two (I think related) questions:
I capture the return value from the C++ program when it executes, but the number I get isn't zero (it's a 4-digit integer value instead, sample values I've received are 8156, 5844, 6100, 5528). I am certain the program exits normally with code 0, but I think VB is getting its value before the C++ program has completed its execution - would that explain why I am not getting a value of zero, and how I can get the final, correct return value from my C++ program?
[Probably as a solution to #1] How can I make the VB program "pause" until the C++ model has completed its execution? I need to do some additional VB work (output configuration based on the C++ model run) once the model is complete
Here is my VB code for how the model call. I am running a full-compiled C++ program through the windows shell.
'---------------------------------------------------------
' SECTION III - RUN THE MODEL AS C++ EXECUTABLE
'---------------------------------------------------------
Dim ModelDirectoryPath As String
Dim ModelExecutableName As String
Dim ModelFullString As String
Dim ret As Long
ModelDirectoryPath = Range("ModelFilePath").value
ModelExecutableName = Range("ModelFileName").value
ModelFullString = ModelDirectoryPath & ModelExecutableName
' Call the model to run
Application.StatusBar = "Running C Model..."
ModelFullString = ModelFullString & " " & ScenarioCounter & " " & NumDeals _
& " " & ModelRunTimeStamp
ret = Shell(ModelFullString)
' Add error checking based on return value
' This is where I want to do some checks on the return value and then start more VB code

1) You are capturing the Task ID of the program (this is what Shell() returns) not any return from the opened programme - that is why it is a 4 digit number
2) Shell() runs all programs asychronously.
To run a program synchronously or to run it and wait for the return, either:
Use a Windows API function (I refer you to https://stackoverflow.com/a/5686052/1101846 for a list of options / API calls you could use)
Much more easily, use the WshShell object provided by Windows Scripting Host (see https://stackoverflow.com/a/8906912/1101846 for more examples than what I give below). See Microsoft documentation of the Run method at http://msdn.microsoft.com/en-us/library/d5fk67ky
Essentially, do something like:
Set o = CreateObject("WScript.Shell")
valueReturnedFromYourProgram = o.Run( _
strCommand:="notepad", _
intWindowStyle:=1,
bWaitOnReturn:=true)
Debug.Print valueReturnedFromYourProgram

I'm not sure what the VBA code would be but have you checked out ShellExecuteEx? Below is the C/C++ code:
SHELLEXECUTEINFO ShExecInfo = {0};
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile = "c:\\MyProgram.exe";
ShExecInfo.lpParameters = "";
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_SHOW;
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo);
WaitForSingleObject(ShExecInfo.hProcess,INFINITE);

Related

Sending argument to an .exe via SHELLEXECUTEINFO

I have an .exe which takes 2 arguments. I need to run the .exe file between my program in runtime. I have used ShellExecuteEx to run the .exe file but it is not taking the arguments. Please help where did i went wrong. I am posting my code below.
void StartProgram() {
SHELLEXECUTEINFO lpExecInfo;
lpExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
lpExecInfo.lpFile = L"F:\\EXEFolder\\RunMe.exe"; // Path to the .exe
lpExecInfo.fMask = SEE_MASK_DOENVSUBST | SEE_MASK_NOCLOSEPROCESS;
lpExecInfo.hwnd = NULL;
lpExecInfo.lpVerb = L"open"; // to open program
lpExecInfo.lpParameters = L"D:/input.csv" "F:/output.csv;"; //Arguments to be passed to .exe
lpExecInfo.lpDirectory = NULL;
lpExecInfo.nShow = SW_SHOW;
lpExecInfo.hInstApp = (HINSTANCE)SE_ERR_DDEFAIL;
ShellExecuteEx(&lpExecInfo);
//wait until a file is finished printing
if (lpExecInfo.hProcess != NULL)
{
::WaitForSingleObject(lpExecInfo.hProcess, INFINITE);
::CloseHandle(lpExecInfo.hProcess);
}
}
The lpParameters member is a pointer to a space-delimited string of arguments, just like you would pass them in a command-line environment.
If you want to pass two arguments to the program, you do it like
lpExecInfo.lpParameters = L"argument1 argument2";
For your case it should be like
lpExecInfo.lpParameters = L"D:/input.csv F:/output.csv";
To explain what you do wrong, the C++ compiler have a phase where it concatenate adjacent string literals into a single string. That means L"D:/input.csv" "F:/output.csv;" will be concatenated into L"D:/input.csvF:/output.csv;" which is a single argument.

Call a python code from WCF

I need to make a python code available as WCF for another application to access it. The python code was build by the data science team and have no ability to change it. I tried running the program as a process shell but it gives 'System.InvalidOperationException' exception.
I created the same program as C# console application and it works fine. The question is
a. Is this the right way to go about making python code available to another application (REST API is not an option).
b. What is the issue with my code.
public string ClassifyText(string value)
{
string textoutput = "";
string exeFileName = HttpContext.Current.Server.MapPath("~/python.exe");
string argName = HttpContext.Current.Server.MapPath("~/predictionscript.py");
ProcessStartInfo start = new ProcessStartInfo();
start.FileName = exeFileName;
start.Arguments = argName;
start.UseShellExecute = false;
start.RedirectStandardOutput = true;
using (Process process = Process.Start(start))
{
using (StreamReader reader = process.StandardOutput)
{
string result = reader.ReadToEnd();
textoutput = result;
}
}
return textoutput;
}

Converting C++ code to VB.net ( using while )

I'm having trouble parsing some C++ sample code to VB.net ( and yes, i know you won't paste code == do it for me), but i need some guidance.
C++ sample code:
int busy=1;
while (busy == 1)
{
hr = pMarker ->GetBusyStatus (cardnum ,&busy);
}
busy=1;
hr = pMarker ->MarkObj (cardnum,i,90.0);
VB.net code ( that i have, not working properly)
Dim busy As Integer
hr = pMarker.GetBusyStatus(cardnum, busy)
While busy = 1
hr = pMarker.GetBusyStatus(cardnum, busy)
hr = pMarker.MarkObj(cardnum, i, 90.0)
End While
Thank you for your help!
EDIT:
Dim busy As Integer=1
While busy = 1
hr = pMarker.GetBusyStatus(cardnum, busy)
End While
hr = pMarker.MarkObj(cardnum, i, 90.0)
I know that i have to use busy as ByRef, in order to use it like a pointer... So, how can i manage that?
For a parameter to be passed by reference in VB.NET, you need to define it as ByRef in the method definition - there isn't anything in the calling code that indicates it is passed by reference (unlike the C++ & or the C# ref). You didn't show the definition of GetBusyStatus, but it should be something like this:
Public Function GetBusyStatus(cardnum As String, ByRef busy As Integer) As Integer
'...
If someCondition Then
busy = 0
End If
End Function

Why file enumeration using DeviceIoControl is faster in VB.NET than in C++?

I am trying to read windows Master File Table (MFT) for fast enumeration of files. Till now I have seen two approaches to do this:
As suggested by Jeffrey Cooperstein and Jeffrey Richter using DeviceIoControl
Direct parsing of MFT as presented in some opensource tools and An NTFS Parser Lib
For my project I am focusing on the approach [1]. The problem I am facing is mostly related to execution time. Just to be clear, following is my system and development enviornment:
IDE - Visual Studio 2013
Language - C++
OS - Windows 7 Professional x64
32 Bit binaries are generated for C++ and .NET code.
Problem
I have compared the version mentioned in [1] (slightly modified) with a VB.NET implementation available on codeplex. The issue is if I uncomment the statement in Inner Loop the C++ code execution time increases by a factor of 7-8x. I haven't implemented the path matching in C++ code (which is available in the VB code).
Q1. Kindly suggest how to improve the performance of the C++ code.
Timings for enumerating C:\ drive on my machine:
C++ (with uncommented statement in inner loop) - 21 seconds
VB.NET (with additional path matching code) - 3.5 seconds
For more clarity following is the C++ and VB.NET snippets.
C++
bool FindAll()
{
if (m_hDrive == NULL) // Handle of, for example, "\\.\C:"
return false;
USN_JOURNAL_DATA ujd = {0};
DWORD cb = 0;
BOOL bRet = FALSE;
MFT_ENUM_DATA med = {0};
BYTE pData[sizeof(DWORDLONG) + 0x10000] = {0};
bRet = DeviceIoControl(m_hDrive, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &ujd, sizeof(USN_JOURNAL_DATA), &cb, NULL);
if (bRet == FALSE) return false;
med.StartFileReferenceNumber = 0;
med.LowUsn = 0;
med.HighUsn = ujd.NextUsn;
//Outer Loop
while (TRUE)
{
bRet = DeviceIoControl(m_hDrive, FSCTL_ENUM_USN_DATA, &med, sizeof(med), pData, sizeof(pData), &cb, NULL);
if (bRet == FALSE) {
break;
}
PUSN_RECORD pRecord = (PUSN_RECORD)&pData[sizeof(USN)];
//Inner Loop
while ((PBYTE)pRecord < (pData + cb))
{
tstring sz((LPCWSTR) ((PBYTE)pRecord + pRecord->FileNameOffset), pRecord->FileNameLength / sizeof(WCHAR));
bool isFile = ((pRecord->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) != FILE_ATTRIBUTE_DIRECTORY);
if (isFile) m_dwFiles++;
//m_nodes[pRecord->FileReferenceNumber] = new CNode(pRecord->ParentFileReferenceNumber, sz, isFile);
pRecord = (PUSN_RECORD)((PBYTE)pRecord + pRecord->RecordLength);
}
med.StartFileReferenceNumber = *(DWORDLONG *)pData;
}
return true;
}
Where m_nodes is defined as typedef std::map<DWORDLONG, CNode*> NodeMap;
VB.NET
Public Sub FindAllFiles(ByVal szDriveLetter As String, fFileFound As FileFound_Delegate, fProgress As Progress_Delegate, fMatch As IsMatch_Delegate)
Dim usnRecord As USN_RECORD
Dim mft As MFT_ENUM_DATA
Dim dwRetBytes As Integer
Dim cb As Integer
Dim dicFRNLookup As New Dictionary(Of Long, FSNode)
Dim bIsFile As Boolean
' This shouldn't be called more than once.
If m_Buffer.ToInt32 <> 0 Then
Console.WriteLine("invalid buffer")
Exit Sub
End If
' progress
If Not IsNothing(fProgress) Then fProgress.Invoke("Building file list.")
' Assign buffer size
m_BufferSize = 65536 '64KB
' Allocate a buffer to use for reading records.
m_Buffer = Marshal.AllocHGlobal(m_BufferSize)
' correct path
szDriveLetter = szDriveLetter.TrimEnd("\"c)
' Open the volume handle
m_hCJ = OpenVolume(szDriveLetter)
' Check if the volume handle is valid.
If m_hCJ = INVALID_HANDLE_VALUE Then
Console.WriteLine("Couldn't open handle to the volume.")
Cleanup()
Exit Sub
End If
mft.StartFileReferenceNumber = 0
mft.LowUsn = 0
mft.HighUsn = Long.MaxValue
Do
If DeviceIoControl(m_hCJ, FSCTL_ENUM_USN_DATA, mft, Marshal.SizeOf(mft), m_Buffer, m_BufferSize, dwRetBytes, IntPtr.Zero) Then
cb = dwRetBytes
' Pointer to the first record
Dim pUsnRecord As New IntPtr(m_Buffer.ToInt32() + 8)
While (dwRetBytes > 8)
' Copy pointer to USN_RECORD structure.
usnRecord = Marshal.PtrToStructure(pUsnRecord, usnRecord.GetType)
' The filename within the USN_RECORD.
Dim FileName As String = Marshal.PtrToStringUni(New IntPtr(pUsnRecord.ToInt32() + usnRecord.FileNameOffset), usnRecord.FileNameLength / 2)
'If Not FileName.StartsWith("$") Then
' use a delegate to determine if this file even matches our criteria
Dim bIsMatch As Boolean = True
If Not IsNothing(fMatch) Then fMatch.Invoke(FileName, usnRecord.FileAttributes, bIsMatch)
If bIsMatch Then
bIsFile = Not usnRecord.FileAttributes.HasFlag(FileAttribute.Directory)
dicFRNLookup.Add(usnRecord.FileReferenceNumber, New FSNode(usnRecord.FileReferenceNumber, usnRecord.ParentFileReferenceNumber, FileName, bIsFile))
End If
'End If
' Pointer to the next record in the buffer.
pUsnRecord = New IntPtr(pUsnRecord.ToInt32() + usnRecord.RecordLength)
dwRetBytes -= usnRecord.RecordLength
End While
' The first 8 bytes is always the start of the next USN.
mft.StartFileReferenceNumber = Marshal.ReadInt64(m_Buffer, 0)
Else
Exit Do
End If
Loop Until cb <= 8
If Not IsNothing(fProgress) Then fProgress.Invoke("Parsing file names.")
' Resolve all paths for Files
For Each oFSNode As FSNode In dicFRNLookup.Values.Where(Function(o) o.IsFile)
Dim sFullPath As String = oFSNode.FileName
Dim oParentFSNode As FSNode = oFSNode
While dicFRNLookup.TryGetValue(oParentFSNode.ParentFRN, oParentFSNode)
sFullPath = String.Concat(oParentFSNode.FileName, "\", sFullPath)
End While
sFullPath = String.Concat(szDriveLetter, "\", sFullPath)
If Not IsNothing(fFileFound) Then fFileFound.Invoke(sFullPath, 0)
Next
'// cleanup
Cleanup() '//Closes all the handles
If Not IsNothing(fProgress) Then fProgress.Invoke("Complete.")
End Sub
Where fFileFound is defined as follows:
Sub(s, l)
If s.ToLower.StartsWith(sSearchPath) Then
lCount += 1
lstFileNames.Add(s.ToLower) '// Dim lstFileNames As List(Of String)
End If
End Sub
Where FSNode & CNode has the following structure:
//C++ version
class CNode
{
public:
//DWORDLONG m_dwFRN;
DWORDLONG m_dwParentFRN;
tstring m_sFileName;
bool m_bIsFile;
public:
CNode(DWORDLONG dwParentFRN, tstring sFileName, bool bIsFile = false) :
m_dwParentFRN(dwParentFRN), m_sFileName(sFileName), m_bIsFile(bIsFile){
}
~CNode(){
}
};
Note - The VB.NET code spawns a new thread (needed as it has GUI), whereas, I am calling the c++ function in the main thread (a simple console application for testing).
Update
It was a silly mistake from my side. The DeviceIoControl API is working as expected. Though the Debug build is a bit slower than the Release build. Refer to the following article:
how-can-i-increase-the-performance-in-a-map-lookup-with-key-type-stdstring
I didn't run your code, but since you say the commented line is the issue, the problem is probably the map insertion.
In the C++ code, you are using a std::map, which is implemented as a tree (sorted by key, log(n) access time).
In the VB code, you are using a Dictionary, which is implemented as a hash table (no sorting, constant access time).
Try using a std::unordered_map in the C++ version.

TaskDialogIndirect is returning an unusual error code

I'm using TaskDialogIndirect to display prompts to the user. Normally this works just fine, but sometimes, after the program has been running for a while, it begins returning an error code that the MSDN entry does not list as being one of the error codes this function can return.
0x80040001 OLE_E_ADVF "Invalid advise flags"
I have checked all the inputs to the function against previous successful calls in the same run. Aside from differences in the string to be displayed, they are identical. (the strings are even the same length.)
// create task dialog struct
TASKDIALOGCONFIG tdc;
ZeroMemory(&tdc, sizeof(TASKDIALOGCONFIG));
tdc.cbSize = sizeof(tdc);
tdc.dwFlags = (((dwMessageBoxFlags & MB_OKCANCEL) == MB_OKCANCEL) ? TDF_ALLOW_DIALOG_CANCELLATION : 0) | TDF_POSITION_RELATIVE_TO_WINDOW;
tdc.hwndParent = hwndOwner;
tdc.hInstance = LGetHInstance();
tdc.pszContent = usrText.wsz;
tdc.pButtons = _pButtons;
tdc.cButtons = nButtons;
tdc.pszMainIcon = pszTaskDialogIcon;
tdc.pszWindowTitle = usrCaption.wsz;
tdc.nDefaultButton = nDefaultButton;
// display it now
int iButton = 0;
BOOL b = 0;
HRESULT hResult = TaskDialogIndirect(&tdc, &iButton, NULL, &b);
NEW INFORMATION
At the same time that TaskDialogIndirect stops behaving correctly, ShellExecute also stops working, as does CreateFile.
This was actually caused by an event handle leak elsewhere. When the available handles ran out, no more API calls which needed to create a handle could succeed. They did return a rather odd set of error codes though, none of which was "out of handles".