Search code examples
c#dllunmanageddllimportmultiple-versions

How do I managing usage of multiple versions of unmanaged DLL calls in C#?


I wonder if anyone can point me in the right direction I'm fairly new to c# so go easy on me.

My code uses an unmanaged DLL that is a provided API for interfacing with a SmartCard Reader, I don't have control of the DLL I just want to wrap it in c# to make it easier to use. So far I've managed to do this but I'm finding now that there is a requirement to test multiple versions of this DLL which has EntryPoints that are the same but parameters that can be different.

I tried something along these lines to organise my wrapper classes;

internal static class UnSafeNativeMethods
{
    internal static class READER
    {
        internal static class SDK20
        {
            [DllImport(dllpath, EntryPoint = "CV_SetCommunicationType")]
            internal static extern int CV_SetCommunicationType(byte type);
            ...
        }

        internal static class SDK21
        {
            [DllImport(dllpath, EntryPoint = "CV_SetCommunicationType")]
            internal static extern int CV_SetCommunicationType(byte type);
            ...
        }
    }
}

But it makes for really unsightly code when checking which call to use;

ReaderSDK sdk = ReaderSDK.SDK20  //Could come from a argument passed in or set in an 
                                 //instantiated class
...
switch (sdk)
{
    case ReaderSDK.SDK20:
        UnSafeNativeMethods.READER.SDK20.CV_SetCommunicationType(0x0);
        break;
    case ReaderSDK.SDK21:
        UnSafeNativeMethods.READER.SDK21.CV_SetCommunicationType(0x0);
        break;
    ...
}

This seems messy to me and wondering if anyone could point me in the right direction...


EDIT : Going off comments below, I've come up with some sample code still not sure if I'm on the right track as my switch still remains but it is now part of a Concrete Factory Class.

public enum ConnectionType
{
    RS232 = 0x0,
    USB = 0x1,
    UDP = 0x2
}

interface INativeMethods
{
    string Name();
    void SetCommunicationType(ConnectionType type);
}

class SDK20 : INativeMethods
{
    public string Name()
    {
        return "SDK Version 2.0";
    }

    // Thanks to @dzendras for this!!
    public void SetCommunicationType(ConnectionType type)
    {
        int result = UnSafeNativeMethods.READER.SDK20.CV_SetCommunicationType((byte)type);
        switch (result)
        {
            case 0:
                return;
            case 1:
                throw new NotSupportedException("Communication type not supported");
            case 2:
                throw AnyOtherMeaningfulException("Its message");
        }
    }


}

class SDK21 : INativeMethods
{
    public string Name()
    {
        return "SDK Version 2.1";
    }

    // Thanks to @dzendras for this!!
    public void SetCommunicationType(ConnectionType type)
    {
        int result = UnSafeNativeMethods.READER.SDK21.CV_SetCommunicationType((byte)type);
        switch (result)
        {
            case 0:
                return;
            case 1:
                throw new NotSupportedException("Communication type not supported");
            case 2:
                throw AnyOtherMeaningfulException("Its message");
        }
    }
}

class NativeMethodsFactory
{
    private static NativeMethodsFactory instance = new NativeMethodsFactory();
    private NativeMethodsFactory()
    {

    }

    public static NativeMethodsFactory Instance
    {
         get { return NativeMethodsFactory.instance; }
    }
    public INativeMethods Get(ReaderSDK version)
    {
        switch (version)
        {
            case ReaderSDK.SDK20:
                return new SDK20();

            case ReaderSDK.SDK21:
                return new SDK21();

            default:
                return new SDK20();
        }
    }
}

Am I on the right track with this?

This is how I implement calls to the SDKs now...

// sdk passed in as enum, NativeMethods stored as class member.
NativeMethods = NativeMethodsFactory.Instance.Get(sdk);
...
NativeMethods.SetCommunicationType(ConnectionType.USB);

Solution

  • Use pattern Strategy

    Some links about pattern:

    http://www.oodesign.com/strategy-pattern.html

    Real World Example of the Strategy Pattern

    Some sample of how it could be in final with Strategy pattern with help of Factory pattern.

    INativeMethods nativeMethods = NativeMethodsFactory.Get(UnsafeSdkVersion.V1);
    nativeMethods.CV_SetCommunicationType(aType);
    

    Advantages:

    1. Decoupling through interface and factory
    2. No switches
    3. Easy to add new version, all other code is independent from wich version to use.