How can I prevent a DLL from causing problems when it is used more than once?

谁说胖子不能爱 提交于 2019-12-13 05:09:59

问题


As Peter Duniho points out in a comment here, I was fixated on a red herring when I should have been focusing on something else altogether.

When I use Symbol.Barcode.Reader and Symbol.Barcode.ReaderData in one form, they work fine. I use them as I document here.

However, when I go from one form that uses the barcode scanning code to another one that also does, all Dallas breaks loose. I get the following exception in the second form, on startup:

Symbol.Exceptions.OperationFailureException: SCAN_GetInterfaceParams
   at Symbol.Barcode.InterfaceParams.GetInterfaceParams()
   at Symbol.Barcode.InterfaceParams..ctor(Reader reader)
   at Symbol.Barcode.Actions.Enable()
   at HHS.frmFind.InitReader()
   at HHS.frmFind.textBoxScan_GotFocus(Object sender, EventArgs e)
   at System.Windows.Forms.Control.OnGotFocus(EventArgs e)
   at System.Windows.Forms.Control.WnProc(WM wm, Int32 wParam, Int32 lParam)
   at System.Windows.Forms.Control._InternalWnProc(WM wm, Int32 wParam, Int32 lParam)
   at Microsoft.AGL.Forms.WL.SetVis(IntPtr hwnThis, BOOL fVis)
   at System.Windows.Forms.Control.set_Visible(Boolean value)
   at System.Windows.Forms.Form.ShowDialog()

The barcode scanning code between the two forms is identical, so it's not the code itself (it works fine the first time, in the first form).

The exception occurs immediately in the second form when the textbox that is set up for scanning is entered. It's GotFocus() event fires because the textbox gets focus when the form is displayed; OnGotFocus() calls InitReader(), which then fails. InitReader() is:

private bool InitReader()
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader");
    // If reader is already present then retreat
    if (this.barcodeReader != null)
    {
        return false;
    }

    // Create new reader, first available reader will be used.
    this.barcodeReader = new Symbol.Barcode.Reader();

    // Create reader data
    this.barcodeReaderData = new Symbol.Barcode.ReaderData(
        Symbol.Barcode.ReaderDataTypes.Text,
        Symbol.Barcode.ReaderDataLengths.MaximumLabel);

    // Create event handler delegate
    this.barcodeEventHandler = this.BarcodeReader_ReadNotify;

    // Enable reader, with wait cursor
    this.barcodeReader.Actions.Enable();

    this.barcodeReader.Parameters.Feedback.Success.BeepTime = 0;
    this.barcodeReader.Parameters.Feedback.Success.WaveFile = "\\windows\\alarm3.wav";

    // Attach to activate and deactivate events
    this.Activated += ReaderForm_Activated;
    this.Deactivate += ReaderForm_Deactivate;

    return true;
}

The objects being dealt with there are in Symbol.Barcode.dll, from Motorola. They are declared in the form like so:

private Symbol.Barcode.Reader barcodeReader;
private Symbol.Barcode.ReaderData barcodeReaderData;

If I bypass the first form, which has the same type of barcode scannig code, and go straight to this form, it doesn't crash.

So apparently it's that the closely related bits of code can't coexist. Why not and, more importantly, how can I prevent this revolting development?

UPDATE

With this logging added to InitReader:

private bool InitReader()
{
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader");
    // If reader is already present then retreat
    if (this.barcodeReader != null)
    {
        return false;
    }

    // Create new reader, first available reader will be used.
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader #2");
    this.barcodeReader = new Symbol.Barcode.Reader();

    // Create reader data
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader #3");
    this.barcodeReaderData = new Symbol.Barcode.ReaderData(
        Symbol.Barcode.ReaderDataTypes.Text,
        Symbol.Barcode.ReaderDataLengths.MaximumLabel);

    // Create event handler delegate
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader #4");
    this.barcodeEventHandler = this.BarcodeReader_ReadNotify;

    // Enable reader, with wait cursor
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader #5");
    this.barcodeReader.Actions.Enable();
    this.barcodeReader.Parameters.Feedback.Success.BeepTime = 0;
    this.barcodeReader.Parameters.Feedback.Success.WaveFile = "\\windows\\alarm3.wav";

    // Attach to activate and deactivate events
    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader #6");
    this.Activated += ReaderForm_Activated;
    this.Deactivate += ReaderForm_Deactivate;

    ExceptionLoggingService.Instance.WriteLog("Reached frmFind.InitReader #7");
    return true;
}

...I see this in the log file after opening the Find form and crashing the app (it hangs for about 20 seconds before disappearing):

Date: 3/19/2009 11:43:38 PM
Message: Reached frmFind.InitReader

Date: 3/19/2009 11:43:38 PM
Message: Reached frmFind.I

...so it's crashing almost instantaneously after this block of code:

if (this.barcodeReader != null)
{
    return false;
}

...as it only gets through half of the next logging line before rudely interrupting itself.

UPDATE 2

Fejesjoco may still be right (in his comment below), but I submit this logging code as exhibit "A":

public void WriteLog(string message)
{
    if (!HHSConsts.Logging) return;
    StringBuilder formattedMessage = new StringBuilder();
    formattedMessage.AppendLine("Date: " + DateTime.Now.ToString());
    formattedMessage.AppendLine("Message: " + message);
    _streamWriter.WriteLine(formattedMessage.ToString());
    _streamWriter.Flush();
}

The stream should get flushed after each line is written to the log.

UPDATE 3

The failure is always very consistent as to how long it "hangs" before it crashes; I can see the cursor blinking in the bar code text box of the find form, and it pulsates about 20 times (I actually counted it last time: 24).

This also is a bit odd; in Update 2, I showed the contents of the log file with all the log entries sprinkled into the InitReader method. With those commented out (except for the first one), the log file ends with:

Date: 3/20/2009 12:01:22 AM
Message: Reached frmFind.InitReader

Date: 3/20/2009 12:01:22 AM
Message: From application-wide exception handler: Symbol.Exceptions.OperationFailureException: SCAN_GetInterfaceParams
   at Symbol.Barcode.InterfaceParams.GetInterfaceParams()
   at Symbol.Barcode.InterfaceParams..ctor(Reader reader)
   at Symbol.Barcode.Actions.Enable()
   at HHS.frmFind.InitReader()

...so the additional log file entries were preventing the exception msg from getting logged.

UPDATE 4

Actions.Enable?

I was unfamiliar with this, and when I added "Actions." I got a choice between Symbol.Barcode.Actions and Symbol.Generic.Actions.

I chose the first first, but it has no "Enable" method. Adding it scolded me with, "An object reference is required for the non-static field, method, or property 'Symbol.Generic.Actions.Enable()'"

I then commented out the using that was added, entered "Actions." again, and this time chose Symbol.Generic.Actions (and got the same err msg for my troubles).

How can I use Actions.Enable()?

UPDATE 5

C.Evenhuis: by "some events require re-attaching each time they occur" do you mean this:

private void StartRead()
{
    // If we have both a reader and a reader data
    if ((this.barcodeReader != null) && (this.barcodeReaderData != null))
    {
        // Submit a read
        this.barcodeReader.ReadNotify += this.barcodeEventHandler;
        this.barcodeReader.Actions.Read(this.barcodeReaderData);
    }
}

private void StopRead()
{
    // If we have a reader
    if (this.barcodeReader != null)
    {
        // Flush (Cancel all pending reads)
        this.barcodeReader.ReadNotify -= this.barcodeEventHandler;
        this.barcodeReader.Actions.Flush();
    }
}

...(barcodeReader.ReadNotify is attached in StartRead and detached in StopRead), or is more necessary?

I also have, in InitReader():

this.Activated += ReaderForm_Activated;
this.Deactivate += ReaderForm_Deactivate;

...which are implemented like so:

private void ReaderForm_Activated(object sender, EventArgs e)
{
    // If there are no reads pending on barcodeReader start a new read
    if (!this.barcodeReaderData.IsPending)
    {
        this.StartRead();
    }
}

private void ReaderForm_Deactivate(object sender, EventArgs e)
{
    this.StopRead();
}

InitReader() is called from textBoxScan_GotFocus(); textBoxScan has the focus when the form displays.

UPDATE 6

As to "Explicitly Close() classes before you Dispose() them", I call Dispose on two things, Symbol.Barcode.Reader and Symbol.Barcode.ReaderData, and neither one allows a Close() call.

UPDATE 7

This statement from C. Evenhuis: "you can't have two (foreground) readers enabled" led me to try the following:

private void FrmDelivery_Closing(object sender, System.ComponentModel.CancelEventArgs e)
{
    this.barcodeReader.Actions.Disable();
}

...which seems to have pretty much done the trick - I can open the Find form without a hang resulting in an eventual crash. I can scan into the Find form after having scanned into the Delivery form. The only (unrelated?) problem that I still see is that my Find form is still partially obscured for some reason...


回答1:


Symbol Motorola Zebra is building on a legacy C++ library with a legacy .NET wrapper. There are quite some pitfalls (some events require re-attaching each time they occur, some classes require explicit Close() before you Dispose() them, etc).

As you may have found out, you can invoke members of the Symbol classes from multiple threads / forms, but you can't have two (foreground) readers enabled, and some properties can only be set if no background readers are enabled, and there is no way to determine whether there are background readers enabled (ie DataWedge).

At our company we chose to initialize the scanner in the Form.Activated event and deinitialize it in the Deactivated event. Then when scanning was required, a call to Actions.Enable() would do the trick. Setting reader properties is done in try catch blocks :(

Quite odd that your application crashes at GetInterfaceParams, I would expect Setting them to cause trouble. Try your application with the bare minimum; don't set the BeepTime or WaveFile properties.




回答2:


I'm not familiar with this type of scanner, but I can imagine initreader will do something at a hardware level. It will probably try to open some port. Because the port is already open it fails.

You need to implement a singleton service for your scanner instead of addressing it directly in your form. Then you can initialize the reader once for the complete application and use it wherever you'd like.

This will be a lot more 'SOLID' and thus more maintainable.



来源:https://stackoverflow.com/questions/27712999/how-can-i-prevent-a-dll-from-causing-problems-when-it-is-used-more-than-once

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!