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

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

Related

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.

Controlling Program Flow Between VB and 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);

Get record set from stored procedure using C++ ADODB CommandPtr::Execute()

I'm trying to get a recordset from a stored procedure using ADODB.
Stored procedures get executeed successfully (Doing everything written in the SP), but the recordset count is "-1".
Here is what I'm doing (stored procedure has no parameters):
hr = ptrCom.CreateInstance(__uuidof(Command));
ptrCom->ActiveConnection = _connection;
ptrCom->CommandType = adCmdStoredProc;
ptrCom->CommandText = _bstr_t(_T("get_all_users"));
_variant_t vtEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR);
ADODB::_RecordsetPtr record_set;
HRESULT normal_hr = ptrCom->raw_Execute(&vtEmpty, &vtEmpty, adCmdStoredProc, &record_set);
int cnt = record_set->RecordCount; // PROBLEM: cnt == -1 :-(
Can anyone point out what am I doing wrong?
thanks!
Have you tried just calling Execute?
ptrCom->Execute(NULL, NULL, ADODB::adCmdStoredProc);
returns _RecordSetPtr.
You might also want to try:
after you set the connection.
ptrCom->ActiveConnection->PutCursorLocation(ADODB::adUseClient);

return array from com object

I want to pass a list of alarm names from COM to VBScript used in ASP pages. If the method name is GetAlarms, What would be the signature of the method?. The number of alarms returned by GetAlarms will vary.
Does VBScrip support Safe Array?
The declaration in the *.idl file would look like this:
[id(1)] HRESULT GetAlarms([out,retval] SAFEARRAY(VARIANT)* pAlarms);
The corresponding C++ method would look like this:
STDMETHODIMP CMyClass::GetAlarms(SAFEARRAY** pAlarms)
{
CComSafeArray<VARIANT> alarms(3);
CComVariant value;
value = L"First Alarm";
alarms.SetAt(0, value);
value = L"Second Alarm";
alarms.SetAt(1, value);
value = L"Third Alarm";
alarms.SetAt(2, value);
*pAlarms = alarms.Detach();
return S_OK;
}
And finally, here is a sample VBScript that uses the above method:
Set obj = CreateObject("MyLib.MyClass")
a = obj.GetAlarms
For i = 0 To UBound(a)
MsgBox a(i)
Next
In ASP, of course, you would use something else instead of MsgBox.

Word automation - SaveAs

I try to write a simple MFC - Word Automation to save for every 1 minute.
I follow this article : http://www.codeproject.com/KB/office/MSOfficeAuto.aspx
And this is what Im trying to implement , I'm new to COM so I think there's problem here:
my VBA is generated by Word 2010:
ActiveDocument.SaveAs2 FileName:="1.docx", FileFormat:=wdFormatXMLDocument _
, LockComments:=False, Password:="", AddToRecentFiles:=True, _
WritePassword:="", ReadOnlyRecommended:=False, EmbedTrueTypeFonts:=False, _
SaveNativePictureFormat:=False, SaveFormsData:=False, SaveAsAOCELetter:= _
False, CompatibilityMode:=14
And my code to implement VBA code above :
{
COleVariant varName(L"b.docx");
COleVariant varFormat(L"wdFormatXMLDocument");
COleVariant varLockCmt((BYTE)0);
COleVariant varPass(L"");
COleVariant varReadOnly((BYTE)0);
COleVariant varEmbedFont((BYTE)0);
COleVariant varSaveNativePicFormat((BYTE)0);
COleVariant varForms((BYTE)0);
COleVariant varAOCE((BYTE)0);
VARIANT x;
x.vt = VT_I4;
x.lVal = 14;
COleVariant varCompability(&x);;
VARIANT result;
VariantInit(&result);
_hr=OLEMethod( DISPATCH_METHOD, &result, pDocApp, L"SaveAs2",10,
varName.Detach(),varFormat.Detach(),varLockCmt.Detach(),varPass.Detach(),varReadOnly.Detach(),
varEmbedFont.Detach(),varSaveNativePicFormat.Detach(),varForms.Detach(),varAOCE.Detach(),varCompability.Detach()
);
}
I get no error from this one, but it doesn't work.
The VBA syntax uses named parameters where the order and count of the parameters do not matter. However, when calling from C++ you need to pass the required number of parameters in the right order.
SaveAs2 is defined as:
void SaveAs2(
ref Object FileName,
ref Object FileFormat,
ref Object LockComments,
ref Object Password,
ref Object AddToRecentFiles,
ref Object WritePassword,
ref Object ReadOnlyRecommended,
ref Object EmbedTrueTypeFonts,
ref Object SaveNativePictureFormat,
ref Object SaveFormsData,
ref Object SaveAsAOCELetter,
ref Object Encoding,
ref Object InsertLineBreaks,
ref Object AllowSubstitutions,
ref Object LineEnding,
ref Object AddBiDiMarks,
ref Object CompatibilityMode
)
So, it has 17 parameters which means you should specify them all if you are going to pass CompatibilityMode, which was specified as 10th parameter in your example (that corressponds to SaveFormsData).
If you do not need all the parameters, or just for testing, you can try a simpler code:
_hr = OLEMethod(DISPATCH_METHOD, &result, pDocApp, L"SaveAs2", 2, varName.Detach(), varFormat.Detach());
If you need the rest of the parameters, you need to pass all the parameters up to the parameter you need set. In that case, you can pass
COleVariant vtOptional((long)DISP_E_PARAMNOTFOUND, VT_ERROR);
for the parameters you do not like to set.
Edit - Test Code
This works for me:
CoInitialize(NULL);
CLSID clsid;
IDispatch *pWApp;
HRESULT hr = CLSIDFromProgID(L"Word.Application", &clsid);
hr = CoCreateInstance(clsid, NULL, CLSCTX_LOCAL_SERVER, IID_IDispatch, (void **)&pWApp);
hr = OLEMethod(DISPATCH_PROPERTYPUT, NULL, pWApp, L"Visible", 1, COleVariant((long)1));
VARIANT result;
VariantInit(&result);
hr = OLEMethod(DISPATCH_PROPERTYGET, &result, pWApp, L"Documents", 0);
IDispatch *pDocs = result.pdispVal;
VARIANT result2;
VariantInit(&result2);
hr = OLEMethod(DISPATCH_METHOD, &result2, pDocs, L"Open", 1, COleVariant(L"D:\\Archive\\t1.docx"));
IDispatch *pDoc = result2.pdispVal;
VARIANT result3;
VariantInit(&result3);
hr = OLEMethod(DISPATCH_METHOD, &result3, pDoc, L"SaveAs2", 1, COleVariant(L"D:\\Archive\\t2.docx"));
CoUninitialize();
Change your variable varFormat from wdFormatXMLDocument to an integer of 12 (probably like you've done the varCompability variable). Also, what's the 10 for after "SaveAs2"?
Guess I'll start over since you opened the bounty.
Change your variable varFormat from wdFormatXMLDocument to an integer of 12 (probably like you've done the varCompability variable). wdFormatXMLDocument is an enum of WdSaveFormat and was introduced in Word 2003. There is no need to send in an L"name" - just send in the integer of 12.
If these are previously saved documents (i.e. not new ones), perform a conversion first to get it to the right format (like ActiveDocument.Convert)