在C#调用C++编写的COM DLL封装库时会出现两个问题:
1. 数据类型转换问题
2. 指针或地址参数传送问题
首先是数据类型转换问题。因为C#是.NET语言,利用的是.NET的基本数据类型,所以实际上是将C++的数据类型与.NET的基本数据类型进行对应。
例如C++的原有函数是:
int __stdcall FunctionName(unsigned char param1, unsigned short param2)
其中的参数数据类型在C#中,必须转为对应的数据类型。如:
[DllImport(“ COM DLL path/file ”)]
extern static int FunctionName(byte param1, ushort param2)
因为调用的是__stdcall函数,所以使用了P/Invoke的调用方法。其中的方法FunctionName必须声明为静态外部函数,即加上 extern static声明头。我们可以看到,在调用的过程中,unsigned char变为了byte,unsigned short变为了ushort。变换后,参数的数据类型不变,只是声明方式必须改为.NET语言的规范。
我们可以通过下表来进行这种转换:
Win32 Types |
CLR Type |
char, INT8, SBYTE, CHAR |
System.SByte |
short, short int, INT16, SHORT |
System.Int16 |
int, long, long int, INT32, LONG32, BOOL , INT |
System.Int32 |
__int64, INT64, LONGLONG |
System.Int64 |
unsigned char, UINT8, UCHAR , BYTE |
System.Byte |
unsigned short, UINT16, USHORT, WORD, ATOM, WCHAR , __wchar_t |
System.UInt16 |
unsigned, unsigned int, UINT32, ULONG32, DWORD32, ULONG, DWORD, UINT |
System.UInt32 |
unsigned __int64, UINT64, DWORDLONG, ULONGLONG |
System.UInt64 |
float, FLOAT |
System.Single |
double, long double, DOUBLE |
System.Double |
之后再将CLR的数据类型表示方式转换为C#的表示方式。这样一来,函数的参数类型问题就可以解决了。
现在,我们再来考虑下一个问题,如果要调用的函数参数是指针或是地址变量,怎么办?
对于这种情况可以使用C#提供的非安全代码来进行解决,但是,毕竟是非托管代码,垃圾资源处理不好的话对应用程序是很不利的。所以还是使用C#提供的 ref以及out修饰字比较好。
同上面一样,我们也举一个例子:
int __stdcall FunctionName(unsigned char ¶m1, unsigned char *param2)
在C#中对其进行调用的方法是:
[DllImport(“ COM DLL path/file ”)]
extern static int FunctionName(ref byte param1, ref byte param2)
看到这,可能有人会问,&是取地址,*是传送指针,为何都只用ref就可以了呢?一种可能的解释是ref是一个具有重载特性的修饰符,会自动识别 是取地址还是传送指针。
在实际的情况中,我们利用参数传递地址更多还是用在传送数组首地址上。
如:byte[] param1 = new param1(6);
在这里我们声明了一个数组,现在要将其的首地址传送过去,只要将param1数组的第一个元素用ref修饰。具体如下:
[DllImport(“ COM DLL path/file ”)]
extern static int FunctionName(ref byte param1[1], ref byte param2)
C#调用DLL函数方法
http://www.csharpwin.net/ddwstp/net/csharp/5964dr984.shtml
http://www.csharpwin.net/ddwstp/net/csharp/5970dr7788.shtml
首先,理解托管代码与非托管代码的区别:
1.托管代码所申请的资源统一由.Net Framework管理,你不用操心,非托管代码所申请的内存等资源则需要你手动去释放
2.非托管程序运行会很快,是二进制的,托管程序好写,但是速度就差的很多,资源会用的很多
3.“程序"一般都是在对操作系统进行直接或者间接的操作
"托管程序"是需要通过访问公共语言运行时(cls)才能访问操作系统的程序,而“非托管程序”不用通过访问公共语言运行时(cls)可以直接访问操作系 统的程序
4.vb.net,C#等写的程序是托管程序,VC++可以写托管程序,如果用到了内存管理,则只能编译为非托管程序
VC++写托管的是要用.net的库,因为我们没有用.net,所以只用了非托管方式。
(一) C#调用DLL中的非托管函数一般方法
首先,应该在C#语言源程序中声明外部方法,其基本形式是:
[DLLImport(“DLL文件”)]
修饰符 extern 返回变量类型 方法名称 (参数列表)
其中:
DLL文件:包含定义外部方法的库文件。
修饰符: 访问修饰符,除了abstract以外在声明方法时可以使用的修饰符。
返回变量类型:在DLL文件中你需调用方法的返回变量类型。
方法名称:在DLL文件中你需调用方法的名称。
参数列表:在DLL文件中你需调用方法的列表。
注意:需要在程序声明中使用System.Runtime.InteropServices命名空间。
DllImport只能放置在方法声明上。
DLL文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中Path所设置的路径)。
返回变量类型、方法名称、参数列表一定要与DLL文件中的定义相一致。
其它可选的 DllImportAttribute 属性:
CharSet 指示用在入口点中的字符集,如:CharSet=CharSet.Ansi;
SetLastError 指示方法是否保留 Win32"上一错误",如:SetLastError=true;
ExactSpelling 指示 EntryPoint 是否必须与指示的入口点的拼写完全匹配,如:ExactSpelling=false;
PreserveSig指示方法的签名应当被保留还是被转换, 如:PreserveSig=true;
CallingConvention指示入口点的调用约定, 如:CallingConvention=CallingConvention.Winapi;
此外,关于“数据封送处理”及“封送数字和逻辑标量”请参阅其它一些文章。
举例:
New file,选择visual C# Class。在文件中,创建一个public的类。把待测试的函数在这个类中作声明。
- public class ClassName
- {
- [DllImport("xxx.dll", EntryPoint = "xx")]
- public static extern int StartVideo(
- int nDevNum, int SwitchingChans, IntPtr Main, IntPtr hwndPreview);
- }
Xxx为待测试的dll名称,xx为dll中提供的方法函数。若要使用其它函数名,可以使用EntryPoint属性设置。
如何用DllImport调用DLL中的非托管函数,但是这个是全局的函数,假若DLL中的非托管函数有一个静态变量S,每次调用这个函数的时候,静态变 量S就自动加1。结果,当需要重新计数时,就不能得出想要的结果。所以,要注意啊,用DllImport调用DLL中的非托管函数是全局的、静态的函数。
以上介绍的就是C#调用DLL函数方法(上),剩下的内容将在C#调用DLL函数方法(下)中继续给大家讲解。
因为C#中使用DllImport是不能像动态load/unload assembly那样,所以只能借助API函数了。在kernel32.dll中,与动态库调用有关的函数包括[3]:
①LoadLibrary(或MFC 的AfxLoadLibrary),装载动态库。
②GetProcAddress,获取要引入的函数,将符号名或标识号转换为DLL内部地址。
③FreeLibrary(或MFC的AfxFreeLibrary),释放动态链接库。
它们的原型分别是:
HMODULE LoadLibrary(LPCTSTR lpFileName);
FARPROC GetProcAddress(HMODULE hModule, LPCWSTR lpProcName);
BOOL FreeLibrary(HMODULE hModule);
现在,我们可以用IntPtr hModule=LoadLibrary(“Count.dll”);来获得Dll的句柄,用IntPtr farProc=GetProcAddress(hModule,”_count@4”);来获得函数的入口地址。
但是,知道函数的入口地址后,怎样调用这个函数呢?因为在C#中是没有函数指针的,没有像C++那样的函数指针调用方式来调用函数,所以我们得借助其它方 法。经过研究,发现我们可以通过结合使用System.Reflection.Emit及System.Reflection.Assembly里的类和 函数达到我们的目的。为了以后使用方便及实现代码的复用,我们可以编写一个类。
1) dld类的编写:
1.打开项目“Test”,打开类视图,右击“Tzb”,选择“添加”-->“类”,类名设置为“dld”,即dynamic loading dll 的每个单词的开头字母。
2.添加所需的命名空间及声明参数传递方式枚举:
- using System.Runtime.InteropServices;
- using System.Reflection;
- using System.Reflection.Emit;
3. 在namespace test中,“public class dld”的上面,添加如下代码声明参数传递方式枚举:
-
-
-
- public enum ModePass
- {
- ByValue = 0x0001,
- ByRef = 0x0002
- }
4、在public class DLD中,添加如下代码:
- public class DLD
- {
- [DllImport("kernel32.dll")]
- public static extern IntPtr LoadLibrary(string lpFileName);
-
- [DllImport("kernel32.dll")]
- public static extern IntPtr GetProcAddress(
- IntPtr hModule, string lpProceName);
-
-
-
- [DllImport("kernel32", EntryPoint = "FreeLibrary", SetLastError = true)]
- public static extern bool FreeLibrary(IntPtr hModule);
-
-
-
-
-
-
- private IntPtr hModule = IntPtr.Zero;
-
-
-
-
- private IntPtr farProc = IntPtr.Zero;
-
-
-
-
-
- public void LoadDll(string lpFileName)
- {
-
- hModule = LoadLibrary(lpFileName);
-
- if (hModule == IntPtr.Zero)
- throw (new Exception(" 没有找到 :" + lpFileName + "."));
- }
-
-
-
-
-
-
- public void LoadFun(string lpProcName)
- {
- if (hModule == IntPtr.Zero)
- throw (new Exception(
- " 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));
-
- farProc = GetProcAddress(hModule, lpProcName);
-
- if (farProc == IntPtr.Zero)
- throw (new Exception(
- " 没有找到 :" + lpProcName + " 这个函数的入口点 "));
- }
-
-
-
-
-
-
- public void UnLoadDll()
- {
- FreeLibrary(hModule);
- hModule = IntPtr.Zero;
- farProc = IntPtr.Zero;
- }
-
-
-
-
-
-
-
-
-
-
- public object Invoke(
- object[] ObjArray_Parameter, Type[] TypeArray_ParameterType,
- ModePass[] ModePassArray_Parameter, Type Type_Return)
- {
-
- if (hModule == IntPtr.Zero)
- throw (new Exception(
- " 函数库模块的句柄为空 , 请确保已进行 LoadDll 操作 !"));
-
- if (farProc == IntPtr.Zero)
- throw (new Exception(" 函数指针为空 , 请确保已进行 LoadFun 操作 !"));
- if (ObjArray_Parameter.Length != ModePassArray_Parameter.Length)
- throw (new Exception(" 参数个数及其传递方式的个数不匹配 ."));
-
-
- AssemblyName MyAssemblyName = new AssemblyName();
- MyAssemblyName.Name = "InvokeFun";
-
-
- AssemblyBuilder MyAssemblyBuilder =
- AppDomain.CurrentDomain.DefineDynamicAssembly(
- MyAssemblyName, AssemblyBuilderAccess.Run);
- ModuleBuilder MyModuleBuilder = MyAssemblyBuilder.DefineDynamicModule(
- "InvokeDll");
-
-
- //
- MethodBuilder MyMethodBuilder = MyModuleBuilder.DefineGlobalMethod(
- "MyFun", MethodAttributes.Public | MethodAttributes.Static,
- Type_Return, TypeArray_ParameterType);
-
-
- ILGenerator IL = MyMethodBuilder.GetILGenerator();
- int i;
- for (i = 0; i < ObjArray_Parameter.Length; i++)
- {
- switch (ModePassArray_Parameter[i])
- {
- case ModePass.ByValue:
- IL.Emit(OpCodes.Ldarg, i);
- break;
- case ModePass.ByRef:
- IL.Emit(OpCodes.Ldarga, i);
- break;
- default:
- throw (new Exception(
- " 第 " + (i + 1).ToString() + " 个参数没有给定正确的传递方式 ."));
- break;
- }
- }
-
- if (IntPtr.Size == 4)
- {
- IL.Emit(OpCodes.Ldc_I4, farProc.ToInt32());
- }
- else if (IntPtr.Size == 8)
- {
- IL.Emit(OpCodes.Ldc_I8, farProc.ToInt64());
- }
- else
- {
- throw new PlatformNotSupportedException();
- }
-
- IL.EmitCalli(OpCodes.Calli, CallingConvention.StdCall,
- Type_Return, TypeArray_ParameterType);
-
- IL.Emit(OpCodes.Ret);
- MyModuleBuilder.CreateGlobalFunctions();
-
-
- MethodInfo MyMethodInfo = MyModuleBuilder.GetMethod("MyFun");
-
- return MyMethodInfo.Invoke(null, ObjArray_Parameter);
- }
- }
2) dld类的使用:
1.打开项目“Test”,向“Form1”窗体中添加一个按钮,和一个TestBox,Name改为txRet。视图中双击按钮,在 “button1_Click”方法体上面添加代码,创建一个dld类实例,并进行测试。具体如下:
- private void button1_Click(object sender, EventArgs e)
- {
- int ret = 0;
- dld myDLD = new dld();
- myDLD.LoadDll("xxx.dll");
- myDLD.LoadFun("InitSDK");
- object[] Parameters = new object[] { };
- Type[] ParameterTypes = new Type[] { };
- ModePass[] themode = new ModePass[] { };
-
- Type Type_Return = typeof(int);
- ret = (int)myDLD.Invoke(
- Parameters, ParameterTypes, themode, Type_Return);
-
- txRet.Text = ret.ToString();
- if (ret != 1)
- {
- MessageBox.Show("InitSDK failed !");
- }
-
- if (ret == 1)
- {
- MessageBox.Show("InitSDK Sucessed !");
- }
- }
其中,xxx为要测试的dll名称,InitSDK为dll中的要测试的函数。
至此,C#调用DLL函数方法就介绍完了,希望对大家有所帮助。