Accessing an external DLL from a Timer - should there be a problem?
-
I want to download data every five minutes from a Davis Weather Station. The code that actually talks to the hardware is in a DLL, and I don't have access to the source (or to terribly good documentation, sadly!). My code that does the downloading (below) works reliably and without problems if called in the normal way. If it is called from a System.Timer OnElapsed thread, the program (run in Debug mode) crashes immediately without reporting what is going wrong. The variables and structures starting with g are defined outside any Functions in a Module, so they have global scope, as do WSInitialized and USBSerialNumber. I think that everything else is fairly obvious. Commenting out the one line which retrieves text data into a StringBuilder seems to fix the problem, and I can do without that data, but I would like to know what is going on, since I may want to do something in the future where the use of StringBuilders to receive data (if that is what is causing the problem) is less optional.
Private Sub Get_Davis_WeatherData(ByRef gwdcurrent As WeatherData)
Dim dtsTemp As New DateTimeStamp
Dim sbWindDir As New StringBuilder(5)
If Not WSInitialized Then
CloseUSBPort_V()
USBSerialNumber = GetUSBDevSerialNumber_V()
If (OpenUSBPort_V(USBSerialNumber) = 0) Then
If (InitStation_V() = COM_ERROR) Then
MsgBox("Cannot initialize the USB port for the Weather Station")
Exit Sub
Else
SetCommTimeoutVal_V(100, 100)
WSInitialized = True
End If
Else
MsgBox("Cannot open the USB port for the Weather Station")
Exit Sub
End If
End If
If (GetStationTime_V(dtsTemp) = 0) Then gwdcurrent.Time = DateStamp2UnixTime(dtsTemp)
If LoadCurrentVantageData_V() = 0 Then
gwdcurrent.OutTempF = GetOutsideTemp_V()
gwdcurrent.OutHum = GetOutsideHumidity_V()
gwdcurrent.BPressure = GetBarometer_V()
gwdcurrent.WindChillF = GetWindChill_V()
gwdcurrent.DewPointF = GetDewPt_V()
gwdcurrent.RainTotal = GetTotalRain_V()
gwdcurrent.Rain24h = GetDailyRain_V()
gwdcurrent.Rain1h = GetRainRate_V()
gwdcurrent.WindSpeedMPH = GetWindSpeed_V()
gwdcurrent.WindRDir = GetWindDir_V()
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToString ' This is where things go badly wrong
gsngRainMTD = GetMonthlyRain_V()
End If
If gsns -
I want to download data every five minutes from a Davis Weather Station. The code that actually talks to the hardware is in a DLL, and I don't have access to the source (or to terribly good documentation, sadly!). My code that does the downloading (below) works reliably and without problems if called in the normal way. If it is called from a System.Timer OnElapsed thread, the program (run in Debug mode) crashes immediately without reporting what is going wrong. The variables and structures starting with g are defined outside any Functions in a Module, so they have global scope, as do WSInitialized and USBSerialNumber. I think that everything else is fairly obvious. Commenting out the one line which retrieves text data into a StringBuilder seems to fix the problem, and I can do without that data, but I would like to know what is going on, since I may want to do something in the future where the use of StringBuilders to receive data (if that is what is causing the problem) is less optional.
Private Sub Get_Davis_WeatherData(ByRef gwdcurrent As WeatherData)
Dim dtsTemp As New DateTimeStamp
Dim sbWindDir As New StringBuilder(5)
If Not WSInitialized Then
CloseUSBPort_V()
USBSerialNumber = GetUSBDevSerialNumber_V()
If (OpenUSBPort_V(USBSerialNumber) = 0) Then
If (InitStation_V() = COM_ERROR) Then
MsgBox("Cannot initialize the USB port for the Weather Station")
Exit Sub
Else
SetCommTimeoutVal_V(100, 100)
WSInitialized = True
End If
Else
MsgBox("Cannot open the USB port for the Weather Station")
Exit Sub
End If
End If
If (GetStationTime_V(dtsTemp) = 0) Then gwdcurrent.Time = DateStamp2UnixTime(dtsTemp)
If LoadCurrentVantageData_V() = 0 Then
gwdcurrent.OutTempF = GetOutsideTemp_V()
gwdcurrent.OutHum = GetOutsideHumidity_V()
gwdcurrent.BPressure = GetBarometer_V()
gwdcurrent.WindChillF = GetWindChill_V()
gwdcurrent.DewPointF = GetDewPt_V()
gwdcurrent.RainTotal = GetTotalRain_V()
gwdcurrent.Rain24h = GetDailyRain_V()
gwdcurrent.Rain1h = GetRainRate_V()
gwdcurrent.WindSpeedMPH = GetWindSpeed_V()
gwdcurrent.WindRDir = GetWindDir_V()
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToString ' This is where things go badly wrong
gsngRainMTD = GetMonthlyRain_V()
End If
If gsnsIf you're using the System.Timers.Timer (there is no System.Timer!), the Elapsed callback is done on a background thread. If the library you're using is not written to handle that, it may screw up the library like you're seeing. You'll have to check with the people who wrote that library to find out. If possible, you can swap out the System.Timers.Timer with the Timer object in the Toolbox. That's the Forms-based Timer and runs on windows messaging Events, but needs to be used in a Windows Forms app, not a Console app or WPF. These events are always raised on the UI (startup) thread and shouldn't give you the problem that you're seeing.
A guide to posting questions on CodeProject
How to debug small programs
Dave Kreskowiak -
If you're using the System.Timers.Timer (there is no System.Timer!), the Elapsed callback is done on a background thread. If the library you're using is not written to handle that, it may screw up the library like you're seeing. You'll have to check with the people who wrote that library to find out. If possible, you can swap out the System.Timers.Timer with the Timer object in the Toolbox. That's the Forms-based Timer and runs on windows messaging Events, but needs to be used in a Windows Forms app, not a Console app or WPF. These events are always raised on the UI (startup) thread and shouldn't give you the problem that you're seeing.
A guide to posting questions on CodeProject
How to debug small programs
Dave KreskowiakYes, it is/was a System.Timers.Timer. I had a vague memory of this sort of problem responding to switching to a Forms timer, so I did try that (my app normally keeps a main Form instantiated, even though it was written as a Console App). This, however, also resulted in a crash when the timer fired and the code executed - I left the Form open for the test.
-
I want to download data every five minutes from a Davis Weather Station. The code that actually talks to the hardware is in a DLL, and I don't have access to the source (or to terribly good documentation, sadly!). My code that does the downloading (below) works reliably and without problems if called in the normal way. If it is called from a System.Timer OnElapsed thread, the program (run in Debug mode) crashes immediately without reporting what is going wrong. The variables and structures starting with g are defined outside any Functions in a Module, so they have global scope, as do WSInitialized and USBSerialNumber. I think that everything else is fairly obvious. Commenting out the one line which retrieves text data into a StringBuilder seems to fix the problem, and I can do without that data, but I would like to know what is going on, since I may want to do something in the future where the use of StringBuilders to receive data (if that is what is causing the problem) is less optional.
Private Sub Get_Davis_WeatherData(ByRef gwdcurrent As WeatherData)
Dim dtsTemp As New DateTimeStamp
Dim sbWindDir As New StringBuilder(5)
If Not WSInitialized Then
CloseUSBPort_V()
USBSerialNumber = GetUSBDevSerialNumber_V()
If (OpenUSBPort_V(USBSerialNumber) = 0) Then
If (InitStation_V() = COM_ERROR) Then
MsgBox("Cannot initialize the USB port for the Weather Station")
Exit Sub
Else
SetCommTimeoutVal_V(100, 100)
WSInitialized = True
End If
Else
MsgBox("Cannot open the USB port for the Weather Station")
Exit Sub
End If
End If
If (GetStationTime_V(dtsTemp) = 0) Then gwdcurrent.Time = DateStamp2UnixTime(dtsTemp)
If LoadCurrentVantageData_V() = 0 Then
gwdcurrent.OutTempF = GetOutsideTemp_V()
gwdcurrent.OutHum = GetOutsideHumidity_V()
gwdcurrent.BPressure = GetBarometer_V()
gwdcurrent.WindChillF = GetWindChill_V()
gwdcurrent.DewPointF = GetDewPt_V()
gwdcurrent.RainTotal = GetTotalRain_V()
gwdcurrent.Rain24h = GetDailyRain_V()
gwdcurrent.Rain1h = GetRainRate_V()
gwdcurrent.WindSpeedMPH = GetWindSpeed_V()
gwdcurrent.WindRDir = GetWindDir_V()
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToString ' This is where things go badly wrong
gsngRainMTD = GetMonthlyRain_V()
End If
If gsnsDim sbWindDir As New StringBuilder(5)
'nothing happens with that StringBuilder, and then
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToStringSo you pass an empty StringBuilder instance with a capacity of 5 characters to the GetWindDirStr_V function. What does happen in that function? How does the signature of the function of the thirdparty dll look like, and how does your DllImport statement look like? Other values called seem to be numeric types, and this one is a string type. Hence I guess the problem is actually in the marshalling of the non-managed "string" to the managed string. Also the length of the string could be wrong: what about "SouthEast" with 9 characters or "NorthNorthWest" with 14? I.e. a simple buffer overflow, actually independent from threading issues.
-
Dim sbWindDir As New StringBuilder(5)
'nothing happens with that StringBuilder, and then
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToStringSo you pass an empty StringBuilder instance with a capacity of 5 characters to the GetWindDirStr_V function. What does happen in that function? How does the signature of the function of the thirdparty dll look like, and how does your DllImport statement look like? Other values called seem to be numeric types, and this one is a string type. Hence I guess the problem is actually in the marshalling of the non-managed "string" to the managed string. Also the length of the string could be wrong: what about "SouthEast" with 9 characters or "NorthNorthWest" with 14? I.e. a simple buffer overflow, actually independent from threading issues.
The documentation of the DLL is (to put it politely) somewhat sketchy - for this routine: 'char* GetWindDirStr_V (char* dirStr) Description This function gets the currenct (sic!) wind direction in string representation. Return Values current wind direction "---" represents no data available.' The DllImport statement is:
<DllImport("VantagePro.dll", CharSet:=CharSet.Ansi, CallingConvention:=CallingConvention.StdCall)> _
Public Function GetWindDirStr_V(dirStr As StringBuilder) As StringBuilder
End FunctionWhile string length overflow is an obvious possibility, the longest string actually passed is three characters (e.g. 'NNE'), and buffers for text wind direction strings which are explicitly sized elsewhere in Structures passed to and from the DLL are always char(5). In addition, the code was extensively exercised without any problems outside timer OnElapsed routines and worked fine - it only fails when called from interrupt code.
-
I want to download data every five minutes from a Davis Weather Station. The code that actually talks to the hardware is in a DLL, and I don't have access to the source (or to terribly good documentation, sadly!). My code that does the downloading (below) works reliably and without problems if called in the normal way. If it is called from a System.Timer OnElapsed thread, the program (run in Debug mode) crashes immediately without reporting what is going wrong. The variables and structures starting with g are defined outside any Functions in a Module, so they have global scope, as do WSInitialized and USBSerialNumber. I think that everything else is fairly obvious. Commenting out the one line which retrieves text data into a StringBuilder seems to fix the problem, and I can do without that data, but I would like to know what is going on, since I may want to do something in the future where the use of StringBuilders to receive data (if that is what is causing the problem) is less optional.
Private Sub Get_Davis_WeatherData(ByRef gwdcurrent As WeatherData)
Dim dtsTemp As New DateTimeStamp
Dim sbWindDir As New StringBuilder(5)
If Not WSInitialized Then
CloseUSBPort_V()
USBSerialNumber = GetUSBDevSerialNumber_V()
If (OpenUSBPort_V(USBSerialNumber) = 0) Then
If (InitStation_V() = COM_ERROR) Then
MsgBox("Cannot initialize the USB port for the Weather Station")
Exit Sub
Else
SetCommTimeoutVal_V(100, 100)
WSInitialized = True
End If
Else
MsgBox("Cannot open the USB port for the Weather Station")
Exit Sub
End If
End If
If (GetStationTime_V(dtsTemp) = 0) Then gwdcurrent.Time = DateStamp2UnixTime(dtsTemp)
If LoadCurrentVantageData_V() = 0 Then
gwdcurrent.OutTempF = GetOutsideTemp_V()
gwdcurrent.OutHum = GetOutsideHumidity_V()
gwdcurrent.BPressure = GetBarometer_V()
gwdcurrent.WindChillF = GetWindChill_V()
gwdcurrent.DewPointF = GetDewPt_V()
gwdcurrent.RainTotal = GetTotalRain_V()
gwdcurrent.Rain24h = GetDailyRain_V()
gwdcurrent.Rain1h = GetRainRate_V()
gwdcurrent.WindSpeedMPH = GetWindSpeed_V()
gwdcurrent.WindRDir = GetWindDir_V()
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToString ' This is where things go badly wrong
gsngRainMTD = GetMonthlyRain_V()
End If
If gsnsIf you enclose the line
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToString
in a try-catch block, can you catch an exception? If so, what's the message - and GetLastError's message? -
If you enclose the line
gwdcurrent.WindDir = GetWindDirStr_V(sbWindDir).ToString
in a try-catch block, can you catch an exception? If so, what's the message - and GetLastError's message?I had not previously tried that, but I did, with Debug.Print commands inserted to display the information. Unfortunately, the crash is a Windows "This application has stopped working" one, which is apparently not prevented by exception-trapping the line of code that provokes it, and it won't let me 'Debug' it with the open instance of VS.