有些场景需要用到C#调用C++编译的接口,这就要使用[DllImport]直接调用,该[DllImport]所在的名字空间为using System.Runtime.InteropServices;当我们得到一个C++的DLL接口时,我们可以使用[DllImport]调用该DLL就得知道DLL的接口,传入的参数、传出的参数、入口函数。(包括DLL的路径)
如:
[DllImport(@"\..\..\Platform\DLL\TEST.dll", EntryPoint = "uds_calc_key", CharSet = CharSet.Ansi)]
private static extern void uds_calc_key(string seed, StringBuilder key);
上面定义了私有的静态方法包含两个参数,可以看到TEST.dll接口的入口点是uds_calc_key,该方法第一个传入参数类型是string类型,第二个是StringBuilder类型。
但是还要在下面再定义了一个方法去调用上面的DLL的入口函数也就是私有的静态方法
public ActionResult returnKey(string seed)
{
StringBuilder stringBuilder = new StringBuilder();
uds_calc_key(seed, stringBuilder);
string strBuilder = stringBuilder.ToString();
return Json(strBuilder, JsonRequestBehavior.AllowGet);
}
现在知道了怎么去调用C++编译的DLL了,如果有一个需求可以动态更新DLL,那又怎么去调用DLL呢?这就要利用C#的反射的知识了。也就是Assembly反射,所谓 Assembly定义和加载程序集,就是加载在程序集清单中列出模块,以及从程序集中查找类型并创建该类型的。在控制台输出做了一个demo实例。这里需要建立两个项目。
第一个项目:生成反射后的dll接口,里面传递任意参数都可以。
static void Main(string[] args)
{
CompilerResults resualt = DebugRun(@"D:\"); //生成dll的路径 目标mydll.dll
if (resualt.Errors.HasErrors)
{
Console.WriteLine("编译错误:");
foreach (CompilerError err in resualt.Errors)
{
Console.WriteLine(err.ErrorText);
}
}
Console.ReadLine();
}
public static CompilerResults DebugRun(string newPath)
{
ICodeCompiler complier = new CSharpCodeProvider().CreateCompiler();
//设置编译参数
CompilerParameters paras = new CompilerParameters();
//引入第三方dll
paras.ReferencedAssemblies.Add("System.dll");
//引入自定义dll
//paras.ReferencedAssemblies.Add(@"D:\自定义方法\自定义方法\bin\LogHelper.dll");
//是否内存中生成输出
paras.GenerateInMemory = false;
//是否生成可执行文件
paras.GenerateExecutable = false;
paras.OutputAssembly = newPath + "mydll.dll";
//编译代码
CompilerResults result = complier.CompileAssemblyFromSource(paras, GenerateCode());
return result;
}
static string GenerateCode()
{
//这里写mydll.cs 的内容 就是一个cs 模板 部分内容需要根据变量替换
StringBuilder sb = new StringBuilder();
sb.Append("using System;");
sb.Append(Environment.NewLine);
sb.Append("using System.Text;");
sb.Append(Environment.NewLine);
sb.Append("using System.Runtime.InteropServices;");
sb.Append(Environment.NewLine);
sb.Append("namespace mydll"); //可以用变量替换
sb.Append(Environment.NewLine);
sb.Append("{");
sb.Append(Environment.NewLine);
sb.Append(" public class Class1");//可以用变量替换
sb.Append(Environment.NewLine);
sb.Append(" {");
sb.Append(Environment.NewLine);可以用变量替换
sb.Append(" [DllImport(@\"E:\\SGMWVin2PinCN200S.dll\", EntryPoint = \"VinToPin\", CharSet = CharSet.Ansi)]");
sb.Append(Environment.NewLine);
sb.Append(" private static extern void VinToPin(string Vin, StringBuilder Pin);"); //可以用变量替换
sb.Append(Environment.NewLine);
sb.Append(" public static string returnKey(string seed)");//可以用变量替换
sb.Append(Environment.NewLine);
sb.Append(" {");
sb.Append(Environment.NewLine);
sb.Append(" StringBuilder stringBuilder = new StringBuilder();");
sb.Append(Environment.NewLine);
sb.Append(" VinToPin(seed, stringBuilder);");
sb.Append(Environment.NewLine);
sb.Append(" string strBuilder = stringBuilder.ToString();");
sb.Append(Environment.NewLine);
sb.Append(" return strBuilder;");
sb.Append(Environment.NewLine);
sb.Append(" }");
sb.Append(Environment.NewLine);
sb.Append(" }");
sb.Append(Environment.NewLine);
sb.Append("}");
string code = sb.ToString();
//Console.WriteLine(code);
//Console.WriteLine();
return code;
}
第二个项目:我们就会想既然已经动态生成反射dll了,那么怎么去调用反射后dll接口呢。
在Main写以下代码就行了
static void Main(string[] args)
{
//string path = @"D:\SGMWVin2ESK_CN200S.dll";
string path = @"D:\mydll.dll"; //调用生成的dll
Assembly assem = Assembly.LoadFile(path);
Type[] tys = assem.GetTypes();//只好得到所有的类型名,然后遍历,通过类型名字来区别了
foreach (Type ty in tys)//huoquleiming
{
//Console.WriteLine("class name is " + ty.Name);
MethodInfo[] listMethodInfo = ty.GetMethods();
foreach (MethodInfo methodInfo in listMethodInfo)
{
if (methodInfo.Name == "returnKey") {//要调用的mydll的函数
object magicClassObject = Activator.CreateInstance(ty);
MethodInfo mi = ty.GetMethod("returnKey"); //要调用的mydll的函数
object[] obj = new object[1];
obj[0] = "123456789"; //要传入的参数
object aa = mi.Invoke(magicClassObject, obj); //返回值
Console.WriteLine(aa.ToString()); //打印
}
Console.WriteLine("Method name is " + methodInfo.Name);
}
//ConstructorInfo magicConstructor = ty.GetConstructor(Type.EmptyTypes);//获取不带参数的构造函数
//object magicClassObject = magicConstructor.Invoke(new object[] { });//这里是获取一个类似于类的实例的东东
//object magicClassObject = Activator.CreateInstance(ty);//获取无参数的构造实例还可以通过这样
//MethodInfo mi = ty.GetMethod("sayhello");
//object aa = mi.Invoke(magicClassObject, null);
// Console.WriteLine(aa.ToString());//这儿是执行类class1的sayhello方法
}
Console.ReadLine();
}
现在两个项目成功完成,那么就可以C#动态调用C++接口了。这里面好多需要普及的知识点,为了更加深入的了解,就不得不需要花费更多的时间和精力。这里有个缺点就是每当反射一个DLL文件时,文件夹就多了一个DLL文件,或者生成不了,直接报错因为已经生成了DLL文件了,当我们肯定会想如果不需要时,就要删除之前的DLL文件,由于反射后的DLL仍处于锁定中,使用LoadFile读取DLL文件是释放不了的,要每次反射完DLL去释放的话,就要用到Load去读取DLL文件。