2

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);
user692942
  • 16,398
  • 7
  • 76
  • 175
  • It seems to me having to use switch statements for every API call I make is really untidy, my question is aimed at how I can tidy my code so I store say a reference the class type and call the unmanaged calls from there, thing is not sure how I can do that. Tried using a singleton class but had too many problems. – user692942 Jun 19 '12 at 18:09

2 Answers2

2

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.
Community
  • 1
  • 1
Regfor
  • 8,515
  • 1
  • 38
  • 51
  • 1
    But I mean c#. Implement pattern Strategy on C#. You will have 2 Strategies - for each versions and you can avoid also unnecessary switches that. And which version execute could be stored in app.config – Regfor Jun 19 '12 at 18:28
  • I found a this about Abstract Factory Patterns implementation in C# - [link](http://www.codeproject.com/Articles/328373/Understanding-and-Implementing-Abstract-Factory-Pa). – user692942 Jun 20 '12 at 09:25
  • Your showing me part of the puzzle and it looks promising but it seems I'm missing key steps in your example, how do I get from a NativeMethodFactory to my SDK instance? – user692942 Jun 20 '12 at 09:37
  • NativeMethodsFactory is that a static class?, as I'm getting problems with not being able to return an instance member from a static class. – user692942 Jun 20 '12 at 10:45
  • I've looked at changes. Yes it'pretty similar to changes that I mean. With Factory main difference that you will have switch only on one place, and all other your code will depend from interface. And execute the same method but depending to your needs with different implementation. NativeMethodsFactory could be static and could be not static. Implement factory as you wish and need. Main goal of factory to construct objects based on version. Only factory should know concrete implementation, all other code should use factory and interface. – Regfor Jun 20 '12 at 11:15
  • The point is that it doesn't solve (so as my answer) the main problem of differences between the versions SDKs... – dzendras Jun 20 '12 at 18:13
1

I'd suggest changing your managed API. Calling C code (from the DLL) shouldn't force you to use structural paradigm. Your wrappers should be fully objective. For instance:

internal static extern int CV_SetCommunicationType(byte aType);
  1. Returned int is 0 in case of success, and any other value indicating error, I suppose. Return void and internally translate the code from the DLL call to exceptions.

    public void SetCommunicationType(byte type) 
    {
         int result = CV_SetCommunicationType(type);
         switch (result)
         {
             case 0:
                 return;
             case 1:
                 throw new NotSupportedException("Communication type not supported");
             case 2:
                 throw AnyOtherMeaningfulException("Its message");
         }
     }
    
  2. Create an enum for aType.

  3. Seek for classes. Static methods are pure evil. Analyze your domain and seek for objects and behaviours. For instance this could be the method of (stupid name, I know...) ConnectionManager.

  4. Depend on contracts. Let your classes (for different SDK) implement some common interfaces.

  5. Do not use hungarian notation in the way you've shown in the example method.

Community
  • 1
  • 1
dzendras
  • 4,721
  • 1
  • 25
  • 20
  • Thanks dzendras that is very helpful. I don't usually use hungarian notation but in this case I never got around to changing the declarations that came with the SDK samples (there are quite a few). – user692942 Jun 20 '12 at 08:31
  • How does this help me with wrappering of multiple versions of the calls though? I know I'm missing something could you connect the dots for me? Could you expand on 3. Seek for classes... – user692942 Jun 20 '12 at 08:37
  • Setting return values to exceptions would be a big job, the values get returned as a single byte and there are a lot of them, but I get your point, thank you. – user692942 Jun 20 '12 at 10:33
  • You can always upvote my answer :) The solution you came up with is nice. However if the API changes (either some arguments or new methods), your code will degrade... Without broader context I can't find any solution. – dzendras Jun 20 '12 at 18:19