非托管DLL的C#包装器

时间:2011-08-19 07:30:34

标签: c# wrapper unmanaged

首先 - 我知道如何调用非托管函数。我在非托管DLL中遇到了接口方法。我认为,编写包装器 - 就是我需要的。

请帮我提供真实的代码。

我有:

  • 旧DLL(C);
  • 2个头文件;
  • lib文件(用于什么?)。
  • !NO SOURCES!

以下是.h文件中的一些代码(兴趣点 - 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;
};

这不是COM对象,我想,我不能为这个dll使用类型库导入

我需要什么:

  • 致电BlNetPassCreateA;
  • 获取指向接口INetPass的指针;
  • 调用接口方法CreateNetPassEnum。

我之前就是这样做的(它是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);

确定! IntPtr包含一些数据,但下一步是什么 - 如何描述后面的接口并调用其方法?

2 个答案:

答案 0 :(得分:1)

我会创建一个C#等价的C接口,所有相应的函数都使用dll-imports。您甚至可以定义与C侧的结构相对应的结构。我将如何做的一个例子:

    // 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));
        }
    }

上面的代码中可能有一堆错误,编组很可能都是错误的。目的主要是显示我通常如何在C#中实现C接口的模式。

编辑:我不确定你的C代码是如何工作的,但是看看标题,我会说你从调用BlNetPassCreateA得到的IntPtr是后续调用接口的this-pointer。调用CreateNetPassEnum可能会以这种方式完成:

    [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);

我真的不知道REFIID是什么类型,我们如何为它们获取适当的值,但我希望这至少可以让你开始解决这个问题。

答案 1 :(得分:1)

您应该编写一个托管C ++包装器来调用c ++函数,然后在.Net项目中使用。因为没有正常的方法来从.Net上调用非托管对象实例上的函数。