Static unmanaged dll C# wrapper and multithreading, multidomains

1.9k Views Asked by At

Good morning.

This is my scenario: I've got a 3rd party unmanaged foo.dll that interacts with an automatic fast payback device, call it FooDevice. I wrote a wrapper around methods of foo.dll, call it FooWrapper, and with marshaling and a bit of hammering I finally make it work; as you may know, when using DllImport all methods exposed need to be marked as static and extern foo.dll exposes some methods and a callback function pointer; when I try to connect two devices at same time in diffrent threads, my wrapper hangs when tryng to hook this callback function. I know that static stuff is thread shared, so I thinked about using different AppDomain for each FooWrapper istances. Do you think is that the right way to do this kind of work?

Here a bit of my FooWrapper:


    [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
    public delegate void FOO_EventHandlerFunc([In] UInt16 event_id, [In, Out] ref IntPtr data, [In] IntPtr param);

    [SuppressUnmanagedCodeSecurity]
    internal static class FOO
    {
        static FOO()
        {
            //...
        }

        ///
        ///    FOO_RegisterEventHandler 
        ///    The FOO_RegisterEventHandler function registers an application-defined callback 
        ///    function, which will subsequently be called for all FooDevice generated events. 
        /// 
        ///    long FOO_RegisterEventHandler(FOO_EventHandlerFunc handler, BYTE evmask, LONG param); 
        /// 
        ///    Parameters 
        ///    handler 
        ///    [in] Pointer to an application-defined callback function (see below). 
        ///    evmask 
        ///    [in] Specify which events to enable (see EnableEvents). 
        ///    param 
        ///    [in] An application-defined value to be passed to the callback function 
        /// 
        ///    Return Values 
        ///    If the function succeeds, the return value is zero. 
        ///    If the function fails, the return value is nonzero. 
        /// 
        ///    Remarks 
        ///    The FOO_EventHandlerFunc type defines a pointer to a callback function, which must 
        ///    comply with the following, where FOO_EventHandlerFunc is a placeholder for the 
        ///    application-defined function name. 
        /// 
        ///    void FOO_EventHandlerFunc(WORD event_id, LPVOID data, LONG param); 
        /// 
        ///    Parameters 
        ///    event_id 
        ///    [in] Event index as specified by the FooDevice protocol. 
        ///    data 
        ///    [in] Event data. The type of data depends on event_id. 
        ///    (See the event specifications for FooDevice). 
        ///    param 
        ///    The application-defined value passed during registration. 
        /// 
        ///    Remarks 
        ///    Avoid lengthy callback functions, since it will stall the underlying protocol, 
        ///    thereby interrupting a steady communications flow. 
        ///    FooDevice will only be generating events during operation. 
        ///    That is - between FOO_LogIn and FOO_LogOut.
        ///
        ///The handler.
        ///The evmask.
        ///The param.
        ///
        [DllImport("foo.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi)]
        public static extern UInt32 FOO_RegisterEventHandler([In] [MarshalAs(UnmanagedType.FunctionPtr)] FOO_EventHandlerFunc handler, [In] byte evmask, [In] IntPtr param);

        ///
        ///    FOO_LogIn
        ///    The FOO_LogIn function opens FooDevice for normal operation.
        ///
        ///    long FOO_LogIn(LPSTR oper, LPSTR datetime);
        /// 
        ///    Parameters
        ///    oper
        ///    [in] Pointer to a null-terminated string identifying the cashier.
        ///    The string can have any content, but a maximum of 50 characters will be used.
        ///    datetime
        ///    [in] Pointer to a null-terminated string indicating the current date and time.
        ///    The string must have 'YYYYMMDDhhmmss' format to take effect.
        ///    Return Values
        ///    If the function succeeds, the return value is zero.
        ///    If the function fails, the return value is nonzero.
        ///
        ///The oper.
        ///The datetime.
        ///
        [DllImport("foo.dll", CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Ansi)]
        public static extern UInt32 FOO_LogIn([In] string oper, [In] string datetime);

        //... and so on ...
    }
}

Can you suggest me a way to istantiate correctly FooWrapper for more than one time (in same or differente thread or AppDomain)?

Thank you guys. Cheers, Nando

1

There are 1 best solutions below

2
On

I understand your problem completely. These are the options I would try, choose one that suits your particular situation

  1. I would try to contact the vendor of Foo.dll and get a version that is thread safe.

  2. If calling methods on the DLL does not affect performance (they take very little time), I would make the wrapper thread-safe by locking, logging in, setting up state, performing the operation and logging out on each call. This is a clean solution that can be replaced later with a thread-safe foo.dll or even new C# based implementation. It is also easy to test and maintain.

  3. The third, messy but easy option - is to wrap the P/Invoke class wrapper into an executable and launch one process per thread and use remoting to talk to the actual instance of the class wrapper. You could use ThreadId to determine which process was launched for which thread and separate calls that way.

Hope one of these options help!