Welcome to Part 2 of the SNMP component series! The first article explained the background and architecture of the Simple Network Management Protocol, and included source code for a component written in Visual C++ 6.0. Listed below is an outline of what will be discussed in this article:
Ported version of NetMgr component using the wsnmp32 library instead of the SnmpAPI and MgmtAPI libraries
Added GetNext method
This article has made the following assumptions:
The reader has a good understanding of:
Visual C++ 6.0
Visual Basic 6.0
Component Object Model
The SNMP version being used is 1.0
Only Get, GetNext and Set operations are being demonstrated; therefore, only code that corresponds to those operations are listed
The source code provided shows how to communicate with SNMP agents.
NetMgr Component Updates
As stated above, GetNext and passing multiple OIDs to the Get/Set methods have been added to this version of NetMgr. The entire object will be not laid out as in the last article. Instead, only the major differences between the two will be explained in detail. Also, the entire project including a pre-compiled DLL will be provided with this article.
SNMPv1.cpp
All major changes have occurred in the SNMPv1.cpp file. Granted that changes had to made to NetMgr.idl and SNMPv1.h, but they've been categorized as minor changes and will not be listed in detail.
Get Method
The previous version would only allow a single OID to be passed in the oidStr argument. Now we're allowed to choose between passing a single OID or an array of OIDs. Let's begin to dissect the new Get method.
First we have to change the function definition to accommodate arrays being passed to and from Visual Basic and VBScript.
Next the local variable declarations will change slightly to help with the SAFEARRAY type.
// Local variable declarations -- P1
RFC1157VarBindList vb; // Variable Binding List; Binds OID to value
AsnObjectIdentifier objID; // The actual OID itself
AsnInteger errorStatus; // Error type that is returned if encountered
AsnInteger errorIndex; // Works with variable above
// Local variable declarations -- P2
VARIANT *lvarArr; // place holder
BSTR *bstrArr; // place holder
SAFEARRAY *psa; // temporary array
HRESULT hr;
// Local variable declarations -- P3
char asciiStr[255]; // Utility variable
char lcOID[255]; // Local char reference to the oidStr parameter
int ret; // Used for return values
unsigned int i; // Used as a counter
// Initialize VariableBinding List
vb.list = NULL;
vb.len = 1;
In this project the actual ASN parsing was moved to different function simply because both this and the GetNext functions use identical statements.
After local variable declarations and variable binding initialization, the next step is to test the varOID argument to determine if it is an array or a string. If it is an array and it has equal to or greater than 1 element, psa, lvarArr and bstrArr are initialized for use.
// Verify what our varOID variable contains
if (varOID->vt == (VT_ARRAY | VT_BSTR))
{
// Validate that more than 1 element has been passed
if ((vb.len = varOID->parray->rgsabound[0].cElements) < 1)
{
// Return Error
varRetVal->vt = VT_BSTR;
varRetVal->bstrVal = SysAllocString(L"Error: Array size less than 1");
return S_OK;
}
// Create SafeArray element bounds by using length of
// VariableBinding List
SAFEARRAYBOUND bounds[] = {(int)vb.len, 0};
// Create a Single-Dimensional SafeArray with a type
// of Variant
psa = SafeArrayCreate(VT_VARIANT, 1, bounds);
// Assign pointer lvarArr to psa
SafeArrayAccessData(psa, (LPVOID*) &lvarArr);
// Assign varOID array elements to bstrArr
bstrArr = (BSTR *) (*varOID).parray->pvData;
}
It's worth mentioning the purpose that psa, lvarArr and bstrArr serve here, as well as in other functions. The variable psa represents the actual array that will contain our return values from the agent that correlate to their respective OIDs. Using the SafeArrayAccessData function, lvarArr becomes the pointer to the psa SafeArray. So element manipulation occurs through lvarArr and not psa directly. To get the current values out of the varOID variable, bstrArr is used by acting as a container for the array elements.
// VariableBinding List Allocation
if ((vb.list = (RFC1157VarBind *)SNMP_realloc(vb.list, sizeof(RFC1157VarBind) *vb.len)) == NULL)
{
// Return Error
varRetVal->vt = VT_BSTR;
varRetVal->bstrVal = SysAllocString(L"Error: Error allocating OID");
// Remove binding to lvarArr if varOID is an Array
if (varOID->vt == (VT_ARRAY | VT_BSTR))
hr = SafeArrayUnaccessData(psa);
return S_OK;
}
// Assign values to VariableBinding List
for (i = 0; i < vb.len; i++)
{
// Validate varOID type and convert
// appropriate reference to char
if (varOID->vt == (VT_ARRAY | VT_BSTR))
sprintf(lcOID, "%ls", bstrArr[i]);
else
sprintf(lcOID, "%ls", (*varOID).bstrVal);
// Convert the string OID to the AsnObjectIdentifier data type
ret = SnmpMgrStrToOid((LPSTR)lcOID, &objID);
// Assigning OID to variable bindings list
vb.list[i].name = objID;
vb.list[i].value.asnType = ASN_NULL;
}
If a string is passed, vb.len is equal to 1 and varOID->vt is equal to VT_BSTR. So only one iteration will occur and (*varOID).bstrVal is used instead of bstrArr[i].
// Make request to agent
if (!SnmpMgrRequest(session, ASN_RFC1157_GETREQUEST, &vb, &errorStatus, &errorIndex))
{
// Return Error
sprintf(asciiStr,"Error: SnmpMgrRequest could not carry out desired option
(Error %d)", errorStatus);
lpVal = (LPSTR)asciiStr;
varRetVal->vt = VT_BSTR;
varRetVal->bstrVal = lpVal.AllocSysString();
// Remove binding to lvarArr if varOID is an Array
if (varOID->vt == (VT_ARRAY | VT_BSTR))
hr = SafeArrayUnaccessData(psa);
}
else
{
// Verify that no error exists
if (errorStatus > 0)
{
// Return Error
varRetVal->vt = VT_BSTR;
varRetVal->bstrVal = SysAllocString(L"Error: Error with Agent Process");
// Remove binding to lvarArr if varOID is an Array
if (varOID->vt == (VT_ARRAY | VT_BSTR))
hr = SafeArrayUnaccessData(psa);
}
else
{
// Retrieve ASN value
if (varOID->vt == (VT_ARRAY | VT_BSTR))
{
// Loop through all elements in the VariableBinding
// List
for (i = 0; i < vb.len; i++)
{
// Initialize variant array element
VariantInit(&lvarArr[i]);
// Set VarType to BSTR
lvarArr[i].vt = VT_BSTR;
// Extract ASN Value from VB List Element
lvarArr[i].bstrVal = extractAsnValue(vb.list[i]);
}
Within the For...Next loop we initialize lvarArr[i]as a variant, then set its type to VT_BSTR (in this component, all values passed back are defined as strings [BSTR]). Once that is completed, extractAsnValue(...) has its return value assigned to lvarArr[i]. Function extractAsnValue(...) contains the actual parsing of the ASN value (vb.list[i].value is defined as an AsnAny type).
// Remove binding to lvarArr
SafeArrayUnaccessData(psa);
// Initialize varRetVal
VariantInit(varRetVal);
// Set VarType to a Variant Array
varRetVal->vt = VT_ARRAY | VT_VARIANT;
// Assign local SafeArray psa to return variable
varRetVal->parray = psa;
}
else
{
// Initialize varRetVal
VariantInit(varRetVal);
// Set VarType to BSTR
varRetVal->vt = VT_BSTR;
// Assign ASN Value to return variable
varRetVal->bstrVal = extractAsnValue(vb.list[0]);
}
}
}
// Release variable binding list
SnmpUtilVarBindListFree(&vb);
// Done
return S_OK;
}
Set Method
As with the Get Method, we've modified this to allow manipulation of arrays in the event that multiple sets must occur in a single transmission.
STDMETHODIMP CSNMPv1::Set(VARIANT *varOID, VARIANT *value, BSTR *statusCode)
{
...
// Local variable declarations -- P2
BSTR *bstrArr, *bstrValue; // Pointers to string arrays
// Local variable declarations -- P2
...
long *lngArr; // Pointer to long array
...
This is similar to the Get Method with the difference being that no SAFEARRAY declarations have occurred. The reason being that an array is not going to be passed back, only a status message reporting success or type of failure. The bstrArr pointer represents the array of elements from the varOID variable, while bstrValue and lngArr deal with the array of elements from the value variable (value->vt determines which pointer is used).
...
// Verify what our varOID variable contains
if (varOID->vt == (VT_ARRAY | VT_BSTR))
{
// Validate that more than 1 element has been passed
if ((vb.len = varOID->parray->rgsabound[0].cElements) < 1)
{
// Return Error
(*statusCode) = SysAllocString(L"Error: Array size less than 1");
return S_OK;
}
// Assign varOID array elements to bstrArr
bstrArr = (BSTR *) (*varOID).parray->pvData;
// Validate Array VarType and assign to appropriate variable pointer
if (value->vt == (VT_ARRAY | VT_BSTR))
bstrValue = (BSTR *) (*value).parray->pvData;
else
lngArr = (long *) (*value).parray->pvData;
}
// Assign values to VariableBinding List
for (i = 0; i < vb.len; i++)
{
// Validate varOID type and convert
// appropriate reference to char
if (varOID->vt == (VT_ARRAY | VT_BSTR))
sprintf(lcOID, "%ls", bstrArr[i]);
else
sprintf(lcOID, "%ls", (*varOID).bstrVal);
// Convert the string OID to the AsnObjectIdentifier data type
ret = SnmpMgrStrToOid((LPSTR)lcOID, &objID);
// Assigning OID to variable bindings list
vb.list[i].name = objID;
// Test to see if we're dealing with an integer or a string
switch (value->vt)
{
case (VT_ARRAY | VT_I4):
// Long Array type -- lngArr[]
vb.list[i].value.asnType = ASN_INTEGER32;
vb.list[i].value.asnValue.number = lngArr[i];
break;
case 2:
case 3:
// Long non-array type -- lVal
vb.list[i].value.asnType = ASN_INTEGER32;
vb.list[i].value.asnValue.number = (*value).lVal;
break;
case (VT_ARRAY | VT_BSTR):
case (VT_BSTR):
// String -- things are more complex here
// First let's define the asn type
vb.list[i].value.asnType = ASN_OCTETSTRING;
// wide character string format type
// Determine which variable to evaluate
if (value->vt == VT_BSTR)
sprintf(tcVar,"%ls",(*value).bstrVal);
else
sprintf(tcVar,"%ls",bstrValue[i]);
// Next we set the string length (remember to add 1 for \0)
vb.list[i].value.asnValue.string.length = strlen(tcVar) + 1;
// From here we basically allocate an empty string into the stream
vb.list[i].value.asnValue.string.stream =
(unsigned char*)malloc(vb.list[i].value.asnValue.string.length);
// then we copy the string from tcVar into stream
// (NOTE: All empty spaces are trim'd off)
strcpy((char *)vb.list[i].value.asnValue.string.stream, tcVar);
// Set dynamic to false because we're specifying a length
vb.list[i].value.asnValue.string.dynamic = FALSE;
}
}
Above is a For...Next loop that converts the OID(s) from varOID, assigns it to vb.list, and goes into a switch statement to determine what type of value it is. Once the loop is complete, a request to the Agent is made and the return value is either Successful or Failure with error details. This section was shorter than the last because there was some duplication in code/methods, so to eliminate redundancy they were not shown.
GetNext Method
Note: varOID is defined in NetMgr.idl as [in,out]
STDMETHODIMP CSNMPv1::GetNext(VARIANT *varOID, VARIANT *varRetVal)
{
...
// Local variable declarations - P2
...
LPSTR lpOID; // Return OID placeholder
...
// Initialize varRetVal
VariantInit(varRetVal);
// Set VarType to BSTR
varRetVal->vt = VT_BSTR;
// Assign ASN Value to return variable
varRetVal->bstrVal = extractAsnValue(vb.list[0]);
// Assign OID to out variable [varOID]
// == If you wanted the actual OID in "dotted numeric string format",
// you would use the SnmpOidToStr function. Include winsnmp.h and
// add wsnmp32.lib to Project->Settings->Link-
// >Object/library Modules
ret = SnmpMgrOidToStr(&vb.list[0].name, &lpOID);
lpVal = lpOID;
varOID->bstrVal = lpVal.AllocSysString();
We've set the return value to the value of the OID that comes after varOID. Once the value has been extracted, we set varOID to the OID of the value being returned.
NetMgrVB Component
The NetMgrVB object uses the wsnmp32 library as its primary API. Rather than re-inventing NetMgr, it was decided that it would be more beneficial to illustrate, for example, what code is behind some of the SnmpMgr[x] functions. In addition, it demonstrates a different way of getting the same results.
Of course with every new venture comes new challenges, and this component was no exception. As you'll see below, the third argument of the SnmpCreateSession function represents a callback function (SNMPAPI_CALLBACK). By using AddressOf [module.function] in this argument, that function becomes bound to the all SNMP Agent Responses. Because this object is not going to be sending or receiving traps, all of the communication with the agent is Get/GetNext/Set actions. One of the major issues that were encountered during development dealt with API calls within callback functions. The first API call that was executed within the callback function would automatically crash the application with an Access Violation error when it ran as a stand-alone.
Further research into the issue uncovered that this happens when a thread that was not specifically created by Visual Basic executes the callback function. So the ultimate challenge was how to process the request from the agent without calling any API functions from within the callback function and keep CPU/memory utilization very low within the confines of COM. A work-around was found and is shown here, but will not be discussed in detail because this article is geared around SNMP component building. Another article is currently in the works that will explain in detail what the issue is and a resolution for COM objects.
Function Process Outline
Let's briefly discuss what major API functions are necessary in order to replicate the functionality that you get with the SnmpAPI and MgmtAPI libraries:
SnmpMgrOpen - Opens a session and "assigns" the agent (snmp host), community string, timeout and retry values to it
SnmpStartup
SnmpSetTranslateMode
SnmpSetRetransmitMode
SnmpCreateSession
SnmpMgrClose - Closes session and performs clean up
SnmpClose
SnmpCleanUp
SnmpMgrRequest - Initiates request and gathers response from Agent
SnmpCreatePdu
SnmpSendMsg
[SNMPAPI_CallBack]
SnmpRecvMsg
SnmpGetPduData
SnmpGetVb
CopyMemory (kernel32.dll)
Even though in the code below there will be explanations on the functions listed above, it's nice to see how the functions correlate to one another.
SnmpV1.cls / modSNMPAPI.mod
The following procedures will alternate between the exposed method in the SnmpV1 class document and the module functions that reside in modSNMPAPI.mod.
' ==========================================================
' "Open" session to agent
' ==========================================================
Public Function SnmpMgrOpen() As String
' Validate session isn't open and appropriate data has
' been assigned to agentSettings struct
Select Case True
' Properties haven't been initialized
Case (agentSettings.agtAddr = "" Or agentSettings.CommStr = "")
SnmpMgrOpen = "[NetMgrVB.SnmpV1.SnmpMgrOpen][MV]=You must set both
the Agent and CommStr properties before you can open a session."
' Pre-existing session encountered
Case (lSession <> 0)
SnmpMgrOpen = "[NetMgrVB.SnmpV1.SnmpMgrOpen][OS]=
You must close your existing session before opening another one."
' Good to go!
Case Else
SnmpMgrOpen = SnmpOpenSession(lcSession)
End Select
End Function
Before SnmpOpenSession is called, checks are made to ensure that a session isn't currently open and that the necessary properties have been assigned values.
' ==========================================================
' Initalizes WinSNMP API and SNMP session
' ==========================================================
Public Function SnmpOpenSession(ByRef lSession As Long) As String
On Error GoTo errorHandler
' Local variable declarations
' ---------------------------
Dim lngRetVal As Long
Dim LPVOID As Long
' "Startup" API
lngRetVal = SnmpStartup(LPVOID, LPVOID, LPVOID, LPVOID, LPVOID)
' Set translate mode to Translated
lngRetVal = SnmpSetTranslateMode(0)
' Set to On
lngRetVal = SnmpSetRetransmitMode(1)
' Create session
lSession = SnmpCreateSession(0, 0, AddressOf SnmpAgentResponse, 0)
' Done!
SnmpOpenSession = "Successful"
Exit Function
errorHandler:
SnmpOpenSession = "[NetMgrVB.Mod.SnmpOpenSession][WAO" & Err.Number & "]=" &_
Err.Description
End Function
SnmpStartup must be called before any other API calls can be used (much like WSAStartup in wsock32 library). According to MSDN, SnmpSetTranslateMode affects the interpretation and return of WinSNMP I/O string parameters, while SnmpSetRetransmitMode is responsible for setting timeout and retry attempts. Within SnmpCreateSession we're passing in the function pointer of SnmpAgentResponse.
' ==========================================================
' "Close" session to agent
' ==========================================================
Public Function SnmpMgrClose() As String
' Verify if the session is already closed
If lcSession = 0 Then
' Session is already closed -- no need to run through process
SnmpMgrClose = "[NetMgrVB.SnmpV1.SnmpMgrClose][CS]=No session currently initialized to close."
Else
' Session is active -- run SnmpCloseSession
SnmpMgrClose = SnmpCloseSession(lcSession)
End If
End Function
' ==========================================================
' Closes session
' ==========================================================
Public Function SnmpCloseSession(ByRef lSession As Long) As String
On Error GoTo errorHandler
' Local variable declarations
' ---------------------------
Dim lngRetVal As Long
' Close SNMP Session
lngRetVal = SnmpClose(lSession)
lSession = 0
' Initiate CleanUp mode
lngRetVal = SnmpCleanup()
' Done!
SnmpCloseSession = "Successful"
Exit Function
errorHandler:
SnmpCloseSession = "[NetMgrVB.Mod.SnmpCloseSession][WA0" & Err.Number & "]=" & Err.Description
End Function
' ==========================================================
' Request OID value from Agent and return response
' ==========================================================
Public Function SnmpGet(ByVal strOID As String) As String
' Local variable declarations
' ---------------------------
Dim strRetVal As String
' Make request to agent
strRetVal = initAgentProc(lcSession, SNMP_PDU_GET, strOID, "")
' Return agent response
SnmpGet = strRetVal
End Function
' ==========================================================
' Set OID value to Agent and return response
' ==========================================================
Public Function SnmpGetNext(strOID As String) As String
' Local variable declarations
' ---------------------------
Dim strRetVal As String
' Make request to agent
strRetVal = initAgentProc(lcSession, SNMP_PDU_GETNEXT, strOID, "")
' =Re-Assign Next OID
' == If preservation of the incoming oid is an issue then you can do something like:
' SnmpGetNext(byVal strInOID as String, strOutOID as String) as String
strOID = strReturnOID
' Return agent response
SnmpGetNext = strRetVal
Actual request/response actions occur within the module-defined functions. SnmpGet, SnmpGetNext and SnmpSet simply returns what it gets from initAgentProc. SnmpGetNext does one additional step, which is assigning the next OID back to strOID.
' ==========================================================
' Request that OID value change and return response
' ==========================================================
Public Function SnmpSet(ByVal strOID As String, ByVal varOIDValue As Variant,
Optional ByVal intSetTypeOverride As Integer = 99) As String
On Error GoTo errorHandler
' Local variable declarations
' ---------------------------
Dim strRetVal As String
' Make request to agent
Select Case intSetTypeOverride
Case 0 ' Setting oid set value to string
strRetVal = initAgentProc(lcSession, SNMP_PDU_SET, strOID, CStr(varOIDValue))
Case 1 ' Setting oid set value to numeric
strRetVal = initAgentProc(lcSession, SNMP_PDU_SET, strOID, CLng(varOIDValue))
Case 99 ' Auto oid set value detection put in place
strRetVal = initAgentProc(lcSession, SNMP_PDU_SET, strOID, varOIDValue)
Case Else ' Invalid argument passed
strRetVal = "[NetMgrVB.SnmpV1.SnmpSet][IST]=An invalid Set Type Override value
has been encountered. Valid Values are 0 for String Override and 1 for Numeric
Override."
End Select
' Return agent response
SnmpSet = strRetVal
' Done!
Exit Function
errorHandler:
SnmpSet = "[NetMgrVB.SnmpV1.SnmpSet][" & Err.Number & "]=" & Err.Description
End Function
Before initAgentProc is executed, data type checks are ran to ensure that the right type is getting passed through. If intSetTypeOverride is allowed to default to 99, it will allow the functions that utilize those variables to automatically determine what type variable type is. Listed below are functions that come directly from modSNMPAPI.mod.
' ==========================================================
' Centralized point for request/response between module and class
' ==========================================================
Public Function initAgentProc(ByVal lSession As Long, ByVal lngPDUType As Long,
ByVal strOID As String, ByVal varOIDValue As Variant) As String
' Local variable declarations
' --------------------------------
Dim strRetVal As String
Dim lngRetVal As String
' Make request to agent
strRetVal = SnmpAgentRequest(lSession, lngPDUType, strOID, varOIDValue)
' Check to see if an error was returned
If InStr(1, strRetVal, "[NetMgrVB.Mod.", 1) > 0 Then
' Return error message
initAgentProc = strRetVal
Else
' Wait for SnmpAgentResponse to be executed
Do
DoEvents
Sleep APP_RESPONSE_TIME
Loop Until (boolResponse)
' Return response for request value
initAgentProc = SnmpResponseProcess(lSession)
End If
End Function
SnmpAgentRequest is responsible for creating and sending the request to the agent for processing, while SnmpResponseProcess parses the response from the agent.
' ==========================================================
' Generates and sends the SNMP packet to the agent
' ==========================================================
Public Function SnmpAgentRequest(ByVal lSession As Long, ByVal lngPDUType As
Long, ByVal strOID As String, ByVal varOIDValue As Variant) As String
On Error GoTo errorHandler
' Local variable declarations
' ------------------------------------
Dim vbListOctValue As vbOctetValue
Dim vbListIntValue As vbIntValue
Dim vbOID As AsnVar
Dim ctxOctetVal As ContextOctetValue
Dim lngRetVal As Long
Dim lngSrcEntity As Long
Dim lngDstEntity As Long
Dim lngVBL As Long
Dim lngPDU As Long
Dim lngContext As Long
' Recycle to false
boolResponse = False
' Set Translate mode to Translated
lngRetVal = SnmpSetTranslateMode(0)
' Convert source (mgr) string to entity (long)
lngSrcEntity = SnmpStrToEntity(lSession, agentSettings.mgrAddr)
' Verify that conversion happened appropriately
If lngSrcEntity = SNMPAPI_FAILURE Then
' Translation did not occur -- set translation to
' Untranslated for v1
lngRetVal = SnmpSetTranslateMode(1)
' Retry assignment
lngSrcEntity = SnmpStrToEntity(lSession, agentSettings.mgrAddr)
' Verify that assignment occured
If lngSrcEntity = SNMPAPI_FAILURE Then
' Translation did not occur -- return error
SnmpAgentRequest = "[NetMgrVB.Mod.SnmpAgentRequest][US]=Unknown Source"
Exit Function
End If
End If
' Reset translation mode to translated
lngRetVal = SnmpSetTranslateMode(0)
' Convert target (agent) string to entity (long
lngDstEntity = SnmpStrToEntity(lSession, agentSettings.agtAddr)
' Verify that conversation happened appropriately
If lngDstEntity = SNMPAPI_FAILURE Then
' Translation did not occur -- set translation to
' Untranslated for v1
lngRetVal = SnmpSetTranslateMode(1)
' Retry assignment
lngDstEntity = SnmpStrToEntity(lSession, agentSettings.agtAddr)
' Verify that assignment occured
If lngDstEntity = SNMPAPI_FAILURE Then
' Free up any resources up until this point
lngRetVal = SnmpFreeEntity(lngSrcEntity)
' Translation did not occur -- return error
SnmpAgentRequest =
"[NetMgrVB.Mod.SnmpAgentRequest][UT]=Unknown Target"
Exit Function
End If
End If
' Assign community string to context type
ctxOctetVal.len = Len(agentSettings.CommStr)
ctxOctetVal.ptr = agentSettings.CommStr
' Reset translation mode to translated
lngRetVal = SnmpSetTranslateMode(0)
' Convert context from string to long
lngContext = SnmpStrToContext(lSession, ctxOctetVal)
' Verify that assignment occured
If lngContext = SNMPAPI_FAILURE Then
' Translation did not occur -- set translation to
' Untranslated for v1
lngRetVal = SnmpSetTranslateMode(1)
' Reassign context
lngContext = SnmpStrToContext(lSession, ctxOctetVal)
' Verify that assignment occured
If lngContext = SNMPAPI_FAILURE Then
' Free any resources up until this point
lngRetVal = SnmpFreeEntity(lngSrcEntity)
lngRetVal = SnmpFreeEntity(lngDstEntity)
'Translation did not occur -- return error
SnmpAgentRequest =
"[NetMgrVB.Mod.SnmpAgentRequest][CSTR]=Invalid community string"
Exit Function
End If
End If
' Convert OID string to vbOID
lngRetVal = SnmpStrToOid(strOID, vbOID)
' Check to see if any errors may have been encountered
If lngRetVal = SNMPAPI_FAILURE Then
' Free any resources up until this point
lngRetVal = SnmpFreeContext(lngContext)
' Return Error
SnmpAgentRequest = "[NetMgrVB.Mod.SnmpAgentRequest][AP0" & _
SnmpGetLastError(lSession) & "]=Error during String to OID conversion"
' No more processing is necessary
Exit Function
End If
' Create variable binding list
' Initialize null value
'OutputDebugString CStr(IsNumeric(varOIDValue)) & ":" & CStr(lngPDUType) &
":" & CStr(SNMP_PDU_SET)
If lngPDUType = SNMP_PDU_SET Then
If IsNumeric(varOIDValue) Then
vbListIntValue.vbType = ASN_INTEGER32
CopyMemory vbListIntValue.lngVal, CLng(varOIDValue), 4
lngVBL = SnmpCreateVbl(lSession, vbOID, vbListIntValue)
Else
vbListOctValue.vbType = ASN_OCTETSTRING
vbListOctValue.octetRef.len = Len(varOIDValue)
CopyMemory vbListOctValue.octetRef.ptr, CStr(varOIDValue),
Len(varOIDValue)
lngVBL = SnmpCreateVbl(lSession, vbOID, vbListOctValue)
End If
Else
vbListIntValue.vbType = ASN_NULL
lngVBL = SnmpCreateVbl(lSession, vbOID, vbListIntValue)
End If
' Check to see if any errors may have been encountered
If lngVBL = SNMPAPI_FAILURE Then
' Return Error
SnmpAgentRequest = "[NetMgrVB.Mod.SnmpAgentRequest][AP0" &
SnmpGetLastError(lSession) & "]=Error during VariableBind object creation"
' Free any resources up until this point
lngRetVal = SnmpFreeDescVar(ASN_OBJECTIDENTIFIER, vbOID)
lngRetVal = SnmpFreeEntity(lngSrcEntity)
lngRetVal = SnmpFreeEntity(lngDstEntity)
lngRetVal = SnmpFreeContext(lngContext)
' No more processing is necessary
Exit Function
End If
' Randomize Timer for Request ID Generation
Randomize Timer
' Generate PDU
lngPDU = SnmpCreatePdu(lSession, lngPDUType, 0, 0, 0, lngVBL)
' Check to see if any errors may have been encountered
If lngPDU = SNMPAPI_FAILURE Then
' Return Error
SnmpAgentRequest = "[NetMgrVB.Mod.SnmpAgentRequest][AP0" &
SnmpGetLastError(lSession) & "]=Error during PDU creation"
' Free any resources up until this point
lngRetVal = SnmpFreeDescVar(ASN_OBJECTIDENTIFIER, vbOID)
lngRetVal = SnmpFreeEntity(lngSrcEntity)
lngRetVal = SnmpFreeEntity(lngDstEntity)
lngRetVal = SnmpFreeVbl(lngVBL)
lngRetVal = SnmpFreeContext(lngContext)
' No more processing is necessary
Exit Function
End If
' Send request to Agent
lngRetVal = SnmpSendMsg(lSession, lngSrcEntity, lngDstEntity, lngContext,
lngPDU)
' Verify that no errors occured
If lngRetVal = SNMPAPI_FAILURE Then
' Report error
SnmpAgentRequest = "[NetMgrVB.Mod.SnmpAgentRequest][WAPI]=" &
SnmpGetLastError(lSession)
End If
' Free local variables
lngRetVal = SnmpFreeDescVar(ASN_OBJECTIDENTIFIER, vbOID)
lngRetVal = SnmpFreeEntity(lngSrcEntity)
lngRetVal = SnmpFreeEntity(lngDstEntity)
lngRetVal = SnmpFreeVbl(lngVBL)
lngRetVal = SnmpFreePdu(lngPDU)
lngRetVal = SnmpFreeContext(lngContext)
' Done!
Exit Function
errorHandler:
SnmpAgentRequest = "[NetMgrVB.Mod.SnmpAgentRequest][WA0" & Err.Number & "]=" & Err.Description
End Function
Once the SnmpSendMsg command is initiated and returns SNMPAPI_SUCCESS (or 1), the function that was passed as a pointer in the SnmpCreateSession function will be executed once a response has been received from the agent. This means that execution of code after SnmpSendMsg will continue and NOT wait for that response. Within the initAgentProc function, SnmpAgentRequest is executed and then, if successful, waits for variables to be set by the SNMPAPI_CallBack function (in our case SnmpAgentResponse). Once completed, it will then execute the SnmpResponseProcess listed below.
' ==========================================================
' Processes response from agent
' ==========================================================
Public Function SnmpResponseProcess(ByVal lSession As Long) As String
On Error GoTo errorHandler
' Local variable declarations
' --------------------------------
Dim vbValue As vbOctetValue
Dim vbValueTmp As vbOctetValue
Dim vbOID As AsnVar
Dim strOctetValueT As String
Dim strRequestValue As String
Dim lngRetVal As Long
Dim lngSrcEntity As Long
Dim lngDstEntity As Long
Dim lngContext As Long
Dim lngPDU As Long
Dim lngVBL As Long
Dim lngPDUType As Long
Dim lngRequestID As Long
Dim lngErrorStatus As Long
Dim lngErrorIndex As Long
' Verify that no error is being passed
If lngWParam = SNMP_ERRORSTATUS_NOERROR Then
' Assign RequestID
lngRequestID = lngLParam
' Get the message from the agent
lngRetVal = SnmpRecvMsg(lSession, lngSrcEntity, lngDstEntity,
lngContext, lngPDU)
' Check to see if any errors may have been encountered
If lngRetVal = SNMPAPI_FAILURE Then
' Return Error
SnmpResponseProcess =
"[NetMgrVB.Mod.SnmpResponseProcess][AP0" & SnmpGetLastError(lSession) & "]=Error
during message retrieval"
' No more processing is necessary
Exit Function
End If
' Process data within the PDU
lngRetVal = SnmpGetPduData(lngPDU, lngPDUType, lngRequestID,
lngErrorStatus, lngErrorIndex, lngVBL)
' =Check to see if any errors may have been encountered
' ==Check return value from SnmpRecvMsg
If lngRetVal = SNMPAPI_FAILURE Then
' Return Error
SnmpResponseProcess =
"[NetMgrVB.Mod.SnmpResponseProcess][AP0" & SnmpGetLastError(lSession) & "]=Error
during PDU parsing"
' No more processing is necessary
Exit Function
End If
' ==Check ErrorStatus to get Agent Specific error
If lngErrorStatus > 0 Then
SnmpResponseProcess =
"[NetMgrVB.Mod.SnmpResponseProcess][AP0" & CStr(lngErrorStatus) & "C" &
CStr(lngErrorIndex) & "]=Error encountered during Agent Process"
' No more processing is necessary
Exit Function
End If
' Get the variable binding list
lngRetVal = SnmpGetVb(lngVBL, 1, vbOID, vbValue)
' Check to see if any errors may have been encountered
If lngRetVal = SNMPAPI_FAILURE Then
' Return Error
SnmpResponseProcess = "[NetMgrVB.Mod.SnmpResponseProcess][AP0" &
SnmpGetLastError(lSession) & "]=Error during VariableBind parsing"
' No more processing is necessary
Exit Function
End If
' Determine how to process response from agent
Select Case vbValue.vbType
Case ASN_OCTETSTRING ' String value
' Initiliaze variable
strOctetValueT = String$(vbValue.octetRef.len, 0)
CopyMemory ByVal strOctetValueT, ByVal vbValue.octetRef.ptr,
vbValue.octetRef.len
' What kind of data are we dealing with?
If Asc(Mid(strOctetValueT, 1, 1)) > 0 Then
' Strict string -- return value
strRequestValue = strOctetValueT
Else
' loop through octet stream and apply appropriate
' conversions
For X = 1 To vbValue.octetRef.len
If Hex(Asc(Mid(strOctetValueT, X, 1))) = "0" Then
strRequestValue = strRequestValue & "0"
End If
strRequestValue = strRequestValue &
Hex(Asc(Mid(strOctetValueT, X, 1)))
Next
End If
Case ASN_OBJECTIDENTIFIER ' OID type
' Initialize variable
strOctetValueT = String(1408, 0)
CopyMemory vbValueTmp, vbValue, 12
' Convert OID to String
lngRetVal = SnmpOidToStr(vbValueTmp.octetRef, 1408,
strOctetValueT)
' Accommodate \0 character
If lngRetVal > 0 Then lngRetVal = lngRetVal - 1
' Return agent response value
strRequestValue = Left(strOctetValueT, lngRetVal)
Case Else
' All others
strRequestValue = CStr(vbValue.octetRef.len)
End Select
' This is used primarily for GetNext operations
strReturnOID = String$(1408, 0)
lngRetVal = SnmpOidToStr(vbOID, 1408, strReturnOID)
If lngRetVal = SNMPAPI_FAILURE Then
strReturnOID = "[NetMgrVB.Mod.SnmpResponseProcess][AP2" &
lngLParam & "]=Unable to retrieve OID from VariableBinding List"
End If
strReturnOID = Trim(Replace(strReturnOID, Chr(0), ""))
' Free local variables
lngRetVal = SnmpFreeDescVar(ASN_OBJECTIDENTIFIER, vbOID)
lngRetVal = SnmpFreeDescVar(vbValue.vbType, vbValue.octetRef)
lngRetVal = SnmpFreeVbl(lngVBL)
lngRetVal = SnmpFreePdu(lngPDU)
lngRetVal = SnmpFreeEntity(lngSrcEntity)
lngRetVal = SnmpFreeEntity(lngDstEntity)
lngRetVal = SnmpFreeContext(lngContext)
' Return Value
SnmpResponseProcess = strRequestValue
Else
' Return Error Message
SnmpResponseProcess = "[NetMgrVB.Mod.SnmpAgentResponse][AP1" & lngWParam
& "]=Request ID " & lngLParam & " has encountered an error"
strReturnOID = ""
End If
' Done!
Exit Function
errorHandler:
strRequestValue = "[NetMgrVB.Mod.SnmpResponseProcess][WA0" & Err.Number &
"]=" & Err.Description
strReturnOID = ""
End Function
The reason why this and SnmpAgentResponse are separated is because of limitations within VB 6.0, which is explained briefly in the comments below.
' ==========================================================
' Adheres to the SNMPAPI_Callback function layout but does
' NOT HANDLE API CALLS because of limitations of Visual
' Basic 6.0 -- If an API function is placed in this function
' an Access Violation error will occur within any
' stand-alone application that tries to use this component
' ==========================================================
Private Function SnmpAgentResponse(ByVal lSession As Long, ByVal hWnd As Long, ByVal wMsg As Long,
ByVal wParam As Long, ByVal lParam As Long, ByVal lpClientData As Long) As Long
' Assign values to global variables
lngWParam = wParam ' If wParam = 0 then a SNMP message is available
lngLParam = lParam ' Specifies the request id for the pdu being processed
boolResponse = True ' Set global to recognize response has been received
' Return Success -- must be returned
SnmpAgentResponse = SNMPAPI_SUCCESS
End Function
By doing this, it not only allows the application not to crash (a big plus), but also helps separate out functionality. So all request related functionality resides in SnmpAgentRequest, while all response parsing is primarily done in SnmpResponseProcess (triggered by SnmpAgentResponse). This model allows the ability to introduce trap functionality.
Sample Application
In addition to the source code for both the NetMgr (VC++) and NetMgrVB components, attached is also source code for a sample application that shows how to use both components.
There are three different areas to choose from - each one is aimed at a specific example. Samples 1 and 3 demonstrate how to perform an SNMP Walk using their respective objects.
Sample 1 shows how to use the NetMgr component and Sample 3 shows how to use the NetMgrVB component.
Sample 2 shows how to do Multiple Get/Sets with the NetMgr component.
Conclusion
This article has shown how to create an SNMP component in both Visual C++ 6.0 and Visual Basic 6.0. Even though each component utilizes different APIs, they accomplish the same goals in the end. Developers should be able to use the source code provided and build upon it to suit their needs. Please review the source code for the components as not everything was placed within the article (i.e. API function declarations, type definitions, etc.).
Q198607 - PRB: Access Violation in VB Run-Time Using AddressOf, Microsoft Knowledge Base
About the Author
Ben Garcia is the President and Co-founder of Adaptive Innovations, LLC., a solution provider specializing in integration and artificial intelligence. He has more than 13 years of software development experience in many different facets of the industry. Please feel free to contact Ben at bgarcia@personify.ws.
Tool Parts provide an interface for Web Part properties well beyond the capabilities of the default property pane. In this article Gayan Peiris shows how to customize Web Parts with custom Tool Parts. [Read This Article][Top]
This article demonstrates how to create a reusable component in ASP.NET 2.0 and then consume it from an ASP.NET page. Also learn how the ObjectDataSource control can be used to directly bind the output of an object to the controls in an ASP.NET page and how precompilation can be used to increase the performance of the Web application and catch compilation errors. [Read This Article][Top]
Browser Helper Objects (BHOs) are COM components that communicate with Internet Explorer to enrich the browsing experience. Michele Leroux Bustamante returns to the world of COM to show you how to build a managed BHO with the help of the .NET Framework's COM interoperability features. [Read This Article][Top]
In addition to creating custom Web Parts for SharePoint Portal Server, developers can actually create their own custom properties to further enhance Web Part appearance and behavior. Gayan Peiris explains the process and provides all the necessary code. [Read This Article][Top]
Accessing shared resources is a challenge for many ASP.NET developers. Tony Arslan explains how a simple serviced component can solve this infamous problem. [Read This Article][Top]
Using callbacks and function pointers in VB can be risky and complicated. Ben Garcia explains his work-around for the function pointer issue he encountered while creating the VB version of his SNMP component. [Read This Article][Top]
Ben Garcia sheds some light on the Simple Network Management Protocol
(SNMP). First he provides a history of SNMP, then he dives right into its
architecture. Finally, he shows how to build a COM component that
communicates with SNMP-enabled devices. [Read This Article][Top]
Paul Apostolos begins his series on using Web services and the MSComm32.OCX
component to access caller id information from a Web page. In part 1, learn how to write the Visual Basic program that runs on the server and updates a database with incoming callers.
[Read This Article][Top]
Doug Dean explains different methods of retrieving and manipulating data from a database in a VB DLL so that it is ready to be rendered in a browser. [Read This Article][Top]
In this article, S.S. Ahmed shows you how to create VB add-ins. Programmers always feel that they are short of several features while working with development tools. Since Microsoft made Visual Basic an extensible product, VB developers can create their own features in VB. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.