viliam_barankin
viliam_barankin

Reputation: 23

C# wrapper for unmanaged DLL

First of all - I know how to call unmanaged functions. I'm stuck with interface methods in unmanaged dll. I think, writing wrapper - is what I need.

Please, help me with real code.

I have:

Here is some code in .h file (point of interest - DECLARE INTERFACE):

#ifdef  __BUILD_DLL
#define BLNETPASS_EXPORT __declspec(dllexport)
#else
#define BLNETPASS_EXPORT
#endif

#include "BlNetpassUid.h"
extern "C"
{
 BLNETPASS_EXPORT HRESULT WINAPI BlNetPassCreateA
                 (LPCSTR pHostName, const GUID *, VOID **);

 BLNETPASS_EXPORT HRESULT WINAPI BlNetPassCreateW
                 (LPCWSTR pHostName, const GUID *, VOID **);

 BLNETPASS_EXPORT HRESULT WINAPI CanBeUnload (DWORD dTimeout );


...

#ifdef  UNICODE
#define BlNetPassCreate BlNetPassCreateW
#else
#define BlNetPassCreate BlNetPassCreateA
#endif

#undef INTERFACE
#define INTERFACE INetPass
DECLARE_INTERFACE_ (INetPass, IUnknown)
{
// IUnknown methods

 STDMETHOD  (    QueryInterface)(REFIID, VOID **)   PURE;
 STDMETHOD_ (ULONG,      AddRef)()          PURE;
 STDMETHOD_ (ULONG,     Release)()          PURE;

// INetPass methods

 STDMETHOD  ( CreateNetPassEnum)(VOID **, REFIID cid,
                         REFIID iid)    PURE;
};

It's not a COM object, i think, i can't use type library import for this dll

What I need:

That's how i did it before (it's PInvoke):

[DllImport("BlNetpassApi")] 
public static extern int BlNetPassCreateA(string pHostName, ref Guid GUID, out IntPtr rINetPass);

...
...

 IntPtr something; 
 Guid temp_guid = Guid.Empty;

 int temp_int = BlNetPassCreateA(null, ref temp_guid, out something);

Ok! IntPtr contains some data, but what's next - how do I describe the interface that is behind and call its method?

Upvotes: 2

Views: 9449

Answers (2)

allansson
allansson

Reputation: 106

I would create a C# equivalent of the C-interface, with dll-imports for all the corresponding functions. You can even define structs that corresponds to the ones on the C-side. An example of how I would do it:

    // This defines a custom datatype present in our dll.
    [StructLayout(LayoutKind.Sequential)]
    public struct MyDllStruct
    {
        int aValue;

        [MarshalAs(UnmanagedType.Bool)]
        bool anotherValue;
    }

    public static class MyDllInterface
    {
        // The function in the interface can be customized by instead             
        // specifying the EntryPoint in the DllImport-attribute.
        [DllImport("MyDll.dll", EntryPoint = "mydll_function")]
        public static extern int MyDllFunction(
                int param1,
            // We want this to become a C-style boolean 
            // (false == 0, true != 0)
                [MarshalAs(UnmanagedType.Bool)]
                bool param2,
            // We want the dll to write to our struct, and to be  
            // able to get the data written to it back we need to 
            // specify the Out-attribute. If we were just feeding the 
            // parameter to the dll we could just use the In-attribute
            // instead, or combine them if we want to achieve both.
                [Out, MarshalAs(UnmanagedType.LPStruct)]
                MyDllStruct resultStruct
            );

        [DllImport("MyDll.dll")]
        // Sometime we need to do a little magic on our own so we let 
        // this function return an IntPtr, and not the struct.
        public static extern IntPtr get_a_pointer_struct();

        // By not defining the EntryPoint-parameter the application 
        // expects that the dll function is named just as the
        // C#-function declaration.
        [DllImport("MyDll.dll")]
        // Here we specify that we are expecting a char-array 
        // back from the function, so the application should
        // marshal it as such.
        [return: MarshalAs(UnmanagedType.LPStr)]
        public static extern string mydll_get_errormsg();
    }

    // This class "converts" the C-style functions
    // into something more similar to the common
    // way C# is usually written.
    public class MyDllWrapper
    {
        // Calls the MyDllInterface.MyDllFunction and returns
        // the resulting struct. If an error occured, an
        // exception is thrown.
        public MyDllStruct CallFunction(int param1, bool param2)
        {
            MyDllStruct result = new MyDllStruct();

            int errorCode = MyDllInterface.MyDllFunction(
                                param1, param2, result);

            if (errorCode < 0)
                throw new Exception(String.Format(
                    "We got an error with code {0}. Message: ", 
                    errorCode, MyDllInterface.mydll_get_errormsg()));

            return result;
        }

        // Gets a pointer to a struct, and converts it 
        // into a structure.
        public MyDllStruct GetStruct()
        {
            IntPtr structPtr = MyDllInterface.get_a_pointer_struct();

            return (MyDllStruct)Marshal.PtrToStructure(
                structPtr, typeof(MyDllStruct));
        }
    }

The above code probably has a bunch of errors in it and the marshaling is most likely all wrong. The purpose was mainly to display a pattern for how I usually implement C-interfaces in C#.

EDIT: I am not really sure how your C-code works, but looking at the header I would say that the IntPtr that you got from the call to BlNetPassCreateA is your this-pointer in subsequent calls to the interface. Calling CreateNetPassEnum would probably be done in this way:

    [DllImport("BlNetPassApi")]
    public static extern void CreateNetPassEnum(IntPtr this, int cid, int reffid);

    ...
    IntPtr something; 
    Guid temp_guid = Guid.Empty;

    int temp_int = BlNetPassCreateA(null, ref temp_guid, out something);

    CreateNetPassEnum(in something, 10, 10);

I don't really know what type the REFIID are, our how you get the appropriate values for them, but I hope that this gets you started on the solution at least.

Upvotes: 1

Vladimir Perevalov
Vladimir Perevalov

Reputation: 4157

You should write a managed C++ wrapper to call c++ functions and then use in your .Net project. Because there is no normal way to call functions on unmanaged object instances from .Net.

Upvotes: 1

Related Questions