MSDN官网资料:
- Blittable and Non-Blittable Types
- Copying and Pinning
- CLR Inside Out: Marshaling between Managed and Unmanaged Code
- .NET Column: P/Invoke Revisited
- An Overview of Managed/Unmanaged Code Interoperability
.dll
.lib
库包括两种:
动态链接库:.lib文件是不包含程序的相关代码,只需要.dll文件
静态链接库:(不需要.dll文件的),只需要.lib文件(既包含符号、函数名等相关信息,又包含程序的相关代码)
COM : 是一种跨应用和语言共享二进制代码的方法,可以是DLL或exe; Windows使用DLLs在二进制级共享代码,
COM的限制:不支持Genrics(泛型)、方法重载 和 带参数的构造函数;
所以一般的应用或库需要结合ComVisible这个Attribute来使用( 默认缺省的是[ComVisible(true)] );
ComVisible特性应用于程序集、接口、类、结构、委托、枚举、字段、方法或属性。
DLL文件: 是一种PE格式的文件.
动态链接库dll的作用:程序的模块化,方便升级,只需要替换dll文件即可,不需要把整个程序都替换一遍。
extern "C" : 由于C++支持函数重载,所以C++编译器会对函数名修改的;所以强制使用C语言的方式导出此函数,这样函数名就不会被改变。
_declspec(dllexport) :导出dll需要用到此关键字来导出
导出:
Dll(动态链接库)的调用:DLL的调用分为 load-time and run-time dynamic linking
1.静态调用: 需要用到.dll和.lib文件; 程序一启动时就加载dll文件
extern "C" _declspec(dllimport) int add(int a, int b);
引用lib文件:有两种方式
//2.1
//#pragma comment(lib, "Dll.lib")
//2.2 项目设置: VC++目录->库目录, 编辑并添加刚才lib的目录
// 链接器->附加依赖项, 把Dll.lib这个名字添加进去
int main()
{
std::cout<< add(2,4);
return 0;
}
2.动态调用:run-time dynamic linking,需要用到的时候才加载; 只需要.dll文件(不需要.lib文件) ; 需要使用到windows提供的API,步骤:
//1.LoadLibrary
//2.GetProcAddress
//3.FreeeLibrary
#include <Windows.h>
HINSTANCE hDLL = LoadLibrary(L"DLL.dll");
typedef int(*p_add)(int a, int b);
p_add addFunction = (p_add)GetProcAddress(hDLL, "add");
std::cout << addFunction(2,4);
BOOL fFreeResult = FreeLibrary(hDLL);
//样例详见
根据MSDN LoadLibrary的文档, 程序中需要使用DLL时搜索路径的先后顺序 :
- The directory from which the application loaded.
- The current directory
- The system directory. Use the GetSystemDirectory() function to get the path of this directory.
- The 16-bit system directory.
- The Windows directory. Use the GetWindowsDirectory() function to get the path of this directory.
- The directories that are listed in the
PATH
environment variable.
//当然实际上不同的windows系统,有的目录会不一样的.
上面提到的都是C++上的操作.
3. C#中的DllImport是静态调用; 需要使用动态调用,直接使用上面C++的动态调用的方法(已在kernel32.dll里包含了),套一层wrapper,给C#这边P/Invoke;
4. __cdecl(默认的,C), __stdcall(C++), __fastcall, __thiscall
规定了dll文件的调用方式 ,入栈方式,清理方式等,https://blog.csdn.net/hanchengxi/article/details/8491650
5.Marshalling native type to .Net type:
Managed类型中分为两大类:simple type和compound type:
simple type又分3类: numeric, textual and handles.
数字类型:System.Byte, System.Int32, System.Double等
文本类型:System.Char //注: System.String是复合类型,因为它实际上是字符数组
句柄类型:System.IntPtr
compound type有:structure, class, string
Blittable type 和 non-blittable type
Blittable Type : 大部分简单类型在数据封送时不需要处理,因为在非托管中有对应的副本(对应的类型),这些类型叫Blittable类型,因为它 们在托管和非托管之间传参时不需要转换。
Non-blittable type : 需要特殊处理,且它是由其它类型组合起来的。
简单类型中是Blittable的有:
System.Byte 、System.SByte 、System.Int16 、 System.UInt16 、System.Int32 、System.UInt32 、System.Int64 、
System.UInt64 、System.IntPtr 、System.UIntPtr 、System.Single 、System.Double
复合类型满足以下条件之一的,也是blittable:
blittable类型的一维数组
Formatted value types that contain only blittable types (and classes if they are marshalled as formatted types).
剩下的其它类型就是non-blittable类型,需要做特殊处理
结构体:
一般原生和托管的结构体在内存中的表示是不一样的,所以memory layout需要考虑。
windows(原生)使用地址来访问结构体的成员,而CLR使用名字来访问结构体的成员。
且托管中的结构体出于性能考虑可reorder,而非托管中的结构体是按照声明时的顺序连续存放的。
托管中通过StructLayoutAttribute声明,有两类:LayoutKind.Sequential 和 LayoutKind.Explicit;
第一类是与非托管的是对应的,第二类用于用户自定义的内存布局,且需要在每一个成员上使用FieldOffsetAttribute,来标注偏移结构体的开始位置多少个字节 :
[StructLayout(LayoutKind.Sequential)]
public struct SystemTime
{
public short wYear;
public short wMonth;
}
[StructLayout(LayoutKind.Explicit)]
public struct SystemTime
{
[FieldOffset(0)]
public short wYear;
[FieldOffset(2)]
public short wMonth;
}
封送处理:
MarshalAs:指示如何在托管代码和非托管代码之间封送数据
//UnmanagedType:指定如何将参数或字段封送到非托管内存块
[MarshalAs(UnmanagedType.ByValArray, SizeConst = define.MAX_LENGTH_OF_IDENTICARDID)]
public byte[] identicardid;
/*********scriptlet 1************/
extern "C"
{
EXPORT_API const char* PrintHello(){
return "Hello";
}
EXPORT_API int PrintANumber(){
return 5;
}
}
//Lets make our calls from the Plugin
[DllImport("ASimplePlugin", CallingConvention = CallingConvention.Cdecl)]
private static extern int PrintANumber();
[DllImport("ASimplePlugin", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr PrintHello();
void Start()
{
Debug.Log(PrintANumber());
Debug.Log(Marshal.PtrToStringAnsi(PrintHello()));
}
/*********scriptlet 2************/
float[] _managed_data =... // this is the c# managed data
GCHandle unmanaged_data_handle = GCHandle.Alloc(_managed_data, GCHandleType.Pinned); //这里将标记_managed_data暂时不能被gc回收,并且固定对象的地址
func(unmanaged_data_handle.AddrOfPinnedObject(),_managed_data.Length);//这里将拿到非托管内存的固定地址,传给c++
unmanaged_data_handle.Free();//使用完毕后,将其handle free,这样c#可以正常gc这块内存