C++與C#有關對庫(動態庫dll,靜態庫.lib)文件的調用


 

1 動態庫的相互調用

1.1 C#調用C++ dll步驟(只能導出方法):

1. c++建立空項目->源文件文件夾中添加cpp文件和函數
2. c++屬性設置中,配置類型設置為動態庫dll,公共語言運行時支持改為/clr
3. c#引用c++的dll
4. c#聲明c++的方法,並添加 DllImport特性
5. c#工程屬性設置為:目標平台x86
6. 注意方法的類型匹配
7. 引發PInvokeStackImbalance異常:注意:C++的"_declspec"和C#的“CallingConvention=CallingConvention.Cdecl”

另外,可以通過VS的異常窗口,取消掉對 PInvokeStackImbalance異常的檢測

點擊VS的“調試 - 異常”,打開異常窗口,展開選擇“Managed Debugging Assistants\PInvokeStackImbalance”,去掉對應的“引發”可選框。 
 
1.2  c++調用 c# dll的步驟(可直接使用C#類):

1. 創建c++控制台應用程序
2. 拷貝c# dll到c++工程根目錄
3. 工程屬性->配置->常規->公共語言運行時支持->clr
工程屬性->配置->c/c++常規->調試信息格式->zi
工程屬性->配置->c/c++常規->公共語言運行時支持->clr

#using "CSharpDllProject.dll"
using namespace CSharpDllProject;

 
1.3 c#調用c++類步驟(c++/cli,可直接使用C++類)

c++/cli簡介:C++/CLI標准是基於Microsoft提交的標准C++與通用語言基礎結構(Common Language Infrastructure)結合的技術
1.使用c++/cli語法對標准c++類進行包裝(可采用聚合模式,引用標准c++類,實現所有標准c++的方法)
2.c#引用c++ dll后,可直接new出一個 c++/cli創建的托管類對象

 

2 DEMOS

2.1 BASIC

新建一個classlibrary,包含兩個類class1和class2,這兩個類中分別有一個方法,都是返回一個字符串,代碼如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace mydll
{
  public class Class1
  {
    public Class1()
    {

    }
    public string sayhello()
    {
      return "hello,word!";
    }
  }

  public class Class2
  {
    public Class2()
    {

    }

    public string saybeautiful()
    {
      return "beautiful,very good!";
    }
  }

}
View Code

在編譯完成后會生成一個mydll.dll動態鏈接庫,然后新建一個winform項目:

private void button1_Click(object sender, EventArgs e)
    {
      string path = @"D:\123\mydll\mydll\bin\Debug\mydll.dll";


      //Byte[] byte1 = System.IO.File.ReadAllBytes(path);//也是可以的
      //Assembly assem = Assembly.Load(byte1);

      Assembly assem = Assembly.LoadFile(path);


      //string t_class = "mydll.Class1";//理論上已經加載了dll文件,可以通過命名空間加上類名獲取類的類型,這里應該修改為如下:

      //string t_class = "mydll.Class1,mydll";//如果你想要得到的是被本工程內部的類,可以“命名空間.父類……類名”;如果是外部的,需要在后面加上“,鏈接庫名”;

      //再次感謝thy38的幫助。

      //Type ty = Type.GetType(t_class);//這兒在調試的時候ty=null,一直不理解,望有高人可以解惑
 Type[] tys = assem.GetTypes();//只好得到所有的類型名,然后遍歷,通過類型名字來區別了
      foreach (Type ty in tys)//huoquleiming
      {
        if (ty.Name == "Class1")
        {
          ConstructorInfo magicConstructor = ty.GetConstructor(Type.EmptyTypes);//獲取不帶參數的構造函數
          object magicClassObject = magicConstructor.Invoke(new object[] { });//這里是獲取一個類似於類的實例的東東

          //object magicClassObject = Activator.CreateInstance(t);//獲取無參數的構造實例還可以通過這樣
          MethodInfo mi = ty.GetMethod("sayhello");
          object aa=mi.Invoke(magicClassObject, null);
          MessageBox.Show(aa.ToString());//這兒是執行類class1的sayhello方法
        }
        if (ty.Name == "Class2")
        {
          ConstructorInfo magicConstructor = ty.GetConstructor(Type.EmptyTypes);//獲取不帶參數的構造函數,如果有構造函數且沒有不帶參數的構造函數時,這兒就不能這樣子啦
          object magicClassObject = magicConstructor.Invoke(new object[] { }); MethodInfo mi = ty.GetMethod("saybeautiful"); object aa = mi.Invoke(magicClassObject, null);//方法有參數時,需要把null替換為參數的集合
 MessageBox.Show(aa.ToString());
        } 
      }

      //AppDomain pluginDomain = (pluginInstanceContainer[key] as PluginEntity).PluginDomain;
      //if (pluginDomain != null)
      //{
      //  AppDomain.Unload(pluginDomain);
      // } 

    }

 

動態加載、卸載

c#中通過反射可以方便的動態加載dll程序集,但是如果你需要對dll進行更新,卻發現.net類庫沒有提供卸載dll程序集的方法。在.net 中,加入了應用程序域的概念,應用程序域是可以卸載的。也就是說,如果需要對動態加載的dll程序集進行更新,可以通過以下方法解決:

新建一個應用程序域,在該應用程序域中動態加載DLL,然后可以卸載掉該應用程序域。該應用程序域被卸載的時候,相關資源也會被回收。

要想這樣實現,就要讓你程序的currentDomain和新建的newDomain之間進行通信,穿過應用程序域的邊界。從網上找到了某大牛的解決方法,抄下來留給自己看吧:

using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Reflection;
namespace UnloadDll
{
    class Program
    {
        static void Main(string[] args)
        {
            string callingDomainName = AppDomain.CurrentDomain.FriendlyName;//Thread.GetDomain().FriendlyName;
            Console.WriteLine(callingDomainName);
            AppDomain ad = AppDomain.CreateDomain("DLL Unload test");
            ProxyObject obj = (ProxyObject)ad.CreateInstanceFromAndUnwrap(@"UnloadDll.exe", "UnloadDll.ProxyObject");
            obj.LoadAssembly();
            obj.Invoke("TestDll.Class1", "Test", "It's a test");
            AppDomain.Unload(ad);
            obj = null;
            Console.ReadLine();
        }
    }
    class ProxyObject : MarshalByRefObject
    {
        Assembly assembly = null;
        public void LoadAssembly()
        {
            assembly = Assembly.LoadFile(@"TestDLL.dll");           
        }
        public bool Invoke(string fullClassName, string methodName, params Object[] args)
        {
            if(assembly == null)
                return false;
            Type tp = assembly.GetType(fullClassName);
            if (tp == null)
                return false;
            MethodInfo method = tp.GetMethod(methodName);
            if (method == null)
                return false;
            Object obj = Activator.CreateInstance(tp);
            method.Invoke(obj, args);
            return true;           
        }
    }
} 

注意:

1. 要想讓一個對象能夠穿過AppDomain邊界,必須要繼承MarshalByRefObject類,否則無法被其他AppDomain使用。

2. 每個線程都有一個默認的AppDomain,可以通過Thread.GetDomain()來得到

 

 

 

參考文章:

C++與C#互調dll的實現步驟

c# 動態加載dll文件,並實現調用其中的方法(推薦)

c#動態加載卸載DLL的方法


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM