如何在运行时将一个 c++ 的 Dll 加载到 C# 应用程序中

3

我有一个C++文件中的代码,可以编译成DLL。

#include "stdafx.h"

#include "WHUU.h"

#include "stdafx.h"

typedef int (__stdcall * Callback)(const int text);

static int x , y= 0;

Callback Handler = 0;

int getX()
{
    return x;
}

void setX(int i)
{
    x = i;
}

void setY(int i)
{
    y = i;
}
int getY()
{
    return y;
}
extern "C" __declspec(dllexport)
void __stdcall SetCallback(Callback handler) {
  Handler = handler;
}


extern "C" __declspec(dllexport)
void __stdcall addX(int x) {
    setX(x);
}

extern "C" __declspec(dllexport)
void __stdcall addY(int y) {
    setY(y);


}


extern "C" __declspec(dllexport)
void __stdcall TestCallback() {
    int z = getX() + getY();
  int retval = Handler(z);
}

我的 c# 应用现在需要在运行时加载这个 dll。添加到回调函数并调用函数。我不想使用类。我可以加载类并使用

 Type[] types = moduleAssembly.GetTypes();

但这太过了!而且c++不是托管的。

我的意思是它太小了!(是的,这只是一个例子,但“真实”的大小和这个例子一样大)。

我该怎么做呢?

感谢您的帮助!

补充:

  • 我不喜欢框架(比如pinvoke / assembly)

  • 函数名/类型是固定的,永远不会改变(想想一个driver.dll读写)

  • 这个dll是由客户编写的,所以应该尽可能简单!


需求相当荒谬。没有人会要求用最困难的方式去做。而且你真的需要利用pinvoke。让客户编写此代码的COM版本,以便您不必费力地去做,这可能会带来不好的结果。 - Hans Passant
5个回答

1

你也可以通过p/invoke来实现,例如:

[DllImport("unmanaged.dll", CharSet = CharSet.Ansi)]
private extern static int yourFunction(int var1, int var2);

1

您可以使用P/Invoke直接调用dll,或者为其创建一个C++/CLI包装器。

以下是使用P/Invoke的方法:
将编译后的dll添加到您的C#项目中,将其“复制到输出目录”属性设置为“始终复制”,并像这样调用它:

class Program
{
    [DllImport("cppdll.dll")]
    private static extern void addX(int x);

    [DllImport("cppdll.dll")]
    private static extern void addY(int y);

    static void Main(string[] args)
    {
        addX(5);
        addY(7);
    }
}

1
我不喜欢框架(如P/Invoke / Assembly)。
但是,我仍然建议使用P/Invoke。我认为没有任何合理的替代方案。也许可以在托管C++中编写托管包装器,但真的吗?
当您使用P/Invoke时,.NET运行时将动态加载您指定的DLL(这是在您第一次调用函数时完成的)。没有理由先使用P/Invoke调用LoadLibrary。

这个问题是因为一场激烈的讨论而被提出来的,讨论的焦点是是否可以在没有任何框架的情况下调用 .net 库函数。 - Thomas

0

实现这个的一种方法是通过 API。以下是您上面示例中 setX 和 getX 方法的示例。

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    internal static extern IntPtr LoadLibraryEx(string libraryPath, IntPtr fileHandle, int actionFlag);

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    internal static extern bool FreeLibrary(IntPtr libraryHandle);

    [DllImport("kernel32.dll", CallingConvention = CallingConvention.StdCall)]
    internal static extern IntPtr GetProcAddress(IntPtr dllPointer, string functionName);

    IntPtr ptr = IntPtr.Zero;
    private delegate void setX (int i);
    private delegate int getX();

    private setX setXDel;
    private getX getXDel;

    public Constructor()
    {
        loadLib();
    }

    public void YourMethod()
    {
        setXDel(100);
        int y = getXDel();
        Console.WriteLine(y.ToString());
    }

    private void loadLib()
    {
        string path = "your dll path";
        ptr = LoadLibraryEx(path, IntPtr.Zero, 0);

        if (ptr == IntPtr.Zero)
            throw new Exception("Cannot load dll.");

        IntPtr addressPtr = GetProcAddress(ptr, "setX");
        setXDel = (setX)Marshal.GetDelegateForFunctionPointer(addressPtr, typeof(setX));

        addressPtr = GetProcAddress(unrarPtr, "getX");
        getXDel = (getX)Marshal.GetDelegateForFunctionPointer(addressPtr, typeof(getX));

    }

    public void Dispose()
    {
        if (ptr != IntPtr.Zero)
        {
            FreeLibrary(ptr);
        }
    }

2
你正在使用P/Invoke来提供类似于P/Invoke的自己的实现。直接使用P/Invoke即可。 - Martin Liversage

0

我使用了P/Invoke,并发布了自己的原因,因为其他人没有包含回调函数。但是他们给出了很好的答案!C#应用程序使用了GUI

该应用程序的代码如下:(请注意,这只是一个原型,用于回答技术问题)

        public partial class Form1 : Form
        {

        private delegate int Callback(int text);
        private Callback mInstance;   // Ensure it doesn't get garbage collected

        [DllImport(@"C:\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
        private static extern void SetCallback(Callback fn);
        [DllImport(@"C:\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
        private static extern void addX(int x);
        [DllImport(@"C:\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]
        private static extern void addY(int y);
        [DllImport(@"C:\\Visual Studio 2010\Projects\kalkulatorius\Debug\WHUU1.dll")]

        private static extern void TestCallback(); 

        private int Handler(int text) 
        {
          textBox3.Text = text.ToString();
          return 42;                       
        }


        private void button1_Click(object sender, System.EventArgs e)
        {
          mInstance = new Callback(Handler); // to set the callback in lib 
          SetCallback(mInstance);            // could also be withhin constructor!

          addX(int.Parse(textBox1.Text)); // other people in this thread posted this correct answer
          addY(int.Parse(textBox2.Text));
          TestCallback();
        }

        public Form1()
        {
          InitializeComponent();
        }

如果您想使用带有函数和回调的C++库(如所发布的问题),则可以使用此方法。

但是如何更改库呢?

  • 库的路径是硬编码的(请参见[dllImport ......])。但是您可以在文件系统中交换库。这可以在构建此应用程序之后发生。
  • 如果给定路径上没有库,则会抛出异常。因此,如果您的程序想要使用此库,请首先检查文件系统上是否存在该库。(未包含在此简单原型中)
  • 因此,要切换功能(交换驱动程序库等),请将具有相同名称的不同库复制到给定路径中。(复制粘贴-可能更改名称)

此解决方案的开销最小,并且非常适合在构建应用程序后从不更改dll接口!


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接