C#引入非托管dll的一些转换技巧

C#引入C/C++非托管dll

原文链接:https://blog.csdn.net/weixin_37537723/article/details/106399524

基础格式转换

数据类型转换

最简单的,在C#和C/C++可以直接转换的数据格式,即数据名称上的变化。

C/C++C#
HANDLE(void *)IntPtr
Byte(unsigned char)Byte
SHORT(short)Int16
WORD(unsigned short)UInt16
INT(int)Int16/Int32
UINT(unsigned int)UInt16/UInt32
LONG(long)Int32
ULONG(unsigned long)UInt32
DWORD(unsigned long)UInt32
DECIMALDecimal
BOOL(long)Boolean
CHAR(char)Char
LPSTR(char *)String
LPWSTR(wchar_t *)String
LPCSTR(const char *)String
LPCWSTR(const wchar_t *)String
PCAHR(char *)String
BSTRString
FLOAT(float)Single
DOUBLE(double)Double
VARIANTObject
PBYTE(byte *)Byte[]
BSTRStringBuilder
LPCTSTRStringBuilder
LPCTSTRstring
LPTSTR[MarshalAs(UnmanagedType.LPTStr)] string
LPTSTR 输出变量名StringBuilder 输出变量名
LPCWSTRIntPtr
BOOLbool
HMODULEIntPtr
HINSTANCEIntPtr
结构体public struct 结构体{}
结构体 **变量名out 结构体 变量名
结构体 &变量名ref 结构体 变量名
WORDushort
DWORDuint/int
UCHARint/byte
UCHAR*string/IntPtr
GUIDGuid
HandleIntPtr
HWNDIntPtr
COLORREFuint
unsigned charbyte
unsigned char *ref byte/ [MarshalAs(UnmanagedType.LPArray)] byte[]/ [MarshalAs(UnmanagedType.LPArray)] Intptr
unsigned char &ref byte
unsigned char 变量名byte 变量名
unsigned short 变量名ushort 变量名
unsigned int 变量名uint 变量名
unsigned long 变量名ulong 变量名
char 变量名byte 变量名
char 数组名[数组大小]MarshalAs(UnmanagedType.ByValTStr, SizeConst = 数组大小)] public string 数组名
char *string(传入)/StringBuilder(传出)
char *变量名ref string 变量名
char *输入变量名string 输入变量名
char *输出变量名[MarshalAs(UnmanagedType.LPStr)] StringBuilder 输出变量名
char **string
char **变量名ref string 变量名
const char *string
char[]string
char 变量名[数组大小][MarshalAs(UnmanagedType.ByValTStr,SizeConst = 数组大小)] public string 变量名
struct 结构体名 *变量名ref 结构体名 变量名
委托 变量名委托变量名
intint/ref int
int &ref int
int *ref int
*intIntPtr
int32 PIPTR *int32[]
float PIPTR *float[]
double** 数组名ref double 数组名
double*[] 数组名ref double 数组名
longint
ulongint
UINT8 *ref byte
void * 对象名称[MarshalAs(UnmanagedType.AsAny)] Object 对象名称
char/INT8/SBYTE/CHARSByte
short/short int/INT16/SHORTInt16
int/long/long int/INT32/LONG32/BOOL/INTInt32
__int64/INT64/LONGLONGInt64
unsigned char/UINT8/UCHAR/BYTEByte
unsigned short/UINT16/USHORT/WORD/ATOM/WCHAR/ __wchar_tUInt16
unsigned/unsigned int/UINT32/ULONG32/DWORD32/ULONG/ DWORD/UINTUInt32
unsigned __int64/UINT64/DWORDLONG/ULONGLONGUInt64
float/FLOATSingle
double, long double, DOUBLEDouble
Win32 TypesCLR Type

结构体转换

C#转换的结构体需要用LayoutKind.Sequential这个属性修饰,因为在C/C++的结构体内存是顺序布局的,所以需要C#转换的结构体内存也按照顺序布局,这样传递指针时dll内部不会出错。
例如:
C的结构体声明为

struct demobuf

C#中的结构体声明为

[StructLayout(LayoutKind.Sequential)]
public struct DemoBuf

C#转换的结构体成员需要用public修饰符,如果不添加public修饰符,C#成员变量默认是保护的,在其它方法内定义这个结构体就不能随便访问修改其成员变量。并且在C的结构体中对其内部成员变量的访问权限只能是public,C++中允许public/protected/private。
C的结构体为

struct demobuf
{
	int a;
	int b;
	bool c;
}

C#的结构体为

[StructLayout(LayoutKind.Sequential)]
public struct DemoBuf
{
	public int a;
	public int b;
	public bool c;
}

当转换的结构体成员中包含数组时,需要获取转换数组的大小,用到MarshalAs属性。
C的结构体为

struct demobuf
{
	int a;
	int b;
	bool c;
	int arr[9];
	char ch[9];
}

 
 

    需要注意的是,在一个类型中,引用类型和值类型地址的重叠是不合法的。在一个类型中,虽然允许多个引用类型在同一个起始偏移位置相互重叠,但无法验证。但是多个值类型互相重叠是合法的。为了使这样的类型能够验证,所有重叠的字段都必须能够通过公共字段访问。
    比如,C#转换的联合中是无法使用数组的,这时需要使用unsafe和fixed属性,让数组和普通值类型能够共存,这里讲一个最简单的方法:利用固定大小的缓冲区(fixed)实现数组和值的类型的共存。
    C的联合体为

    typeof union
    {
    	int nValue;
    	float fValue;
    	char chValue[32];
    }DemoInfo;
    

    C#的结构体为

    [StructLayout(LayoutKind.Explicit,Pack=1)]
    public unsafe struct DemoInfo
    {
    	[FieldOffset(0)]
    	public int nValue;
    	[FieldOffset(0)]
    	public float fValue;
    	[FieldOffset(0)]
    	public fixed byte chValue[32];
    }
    

    指针内存对齐

    笔者经验学识有限,查阅资料只发现C#提供了结构体字节对齐的属性,并没有找到实现指针内存字节对齐的系统函数。
    但是在项目中遇到转义C代码的算法函数的问题,其中有很多句柄需要按给定字节要求进行对齐,无奈只能根据C的扩展系统函数_aligned_malloc做了自我的实现
    C#的方法

    public unsafe IntPtr AlignedPointer(int size,int align)
    {
    	void* raw_malloc_ptr;
        void* aligned_ptr;
        IntPtr ptr = Marshal.AllocHGlobal((int)(size + align));
        raw_malloc_ptr = ptr.ToPointer();
        aligned_ptr = (void*)(((UInt64)raw_malloc_ptr + align) & ~(align - 1));
    	((void**)aligned_ptr)[-1] = raw_malloc_ptr;
    	return (IntPtr)aligned_ptr;
    }
    

    dll方法转换

    调用dll方法

    要声明一个方法使其具有来自 dll 导出的实现:

    • 使用 C# 关键字 static 和 extern 声明方法
    • 将 DllImport 属性附加到该方法。DllImport 属性允许指定包含该方法的 dll 的名称。通常的做法是用与导出的方法相同的名称命名 C# 方法,但也可以对 C# 方法使用不同的名称
    [DllImport("dll文件")]
    static extern 返回变量类型 方法名(参数列表)
    

    dll文件:包含定义外部方法的库文件。
    返回变量类型:在dll文件中需调用方法的返回变量类型。
    方法名:在dll文件中调用方法的名称。
    参数列表:在dll文件中调用方法的列表。
    dll文件必须位于程序当前目录或系统定义的查询路径中(即:系统环境变量中Path所设置的路径)。
    返回变量类型、方法名称、参数列表一定要与dll文件中的定义一致。
    如果需要重新定义调用的方法名,需要使用EntryPoint属性,将该属性设置为调用的方法名。

    [DllImport("dll文件", EntryPoint="方法名")]
    static extern 返回变量类型 自定义方法名(参数列表)
    
    • 调用含指针的方法
      C#为了安全起见,隐形的避开了指针,采用了引用的方式来部分实现指针的功能,引用的好处就是可以和指针一样操作参数原地址内的数据,并且返回修改的数据,但是引用无法使用++或者–来移动指针。
      使用引用代替指针

    C的方法

    int fnAdd(int *p1,int* p2);
    

    C#的方法

    [DllImport("dll文件", CallingConvention=CallingConvention.Cdecl)]
    public static extern int fnAdd(ref int p1,ref int p2);
    

    一般我不会这样使用,上面讲到了使用引用的一些弊端,还有一种方式是直接使用C#的指针变量,会涉及到指针与结构体之间的转换。
    C的方法

    int SetParamValue(void* handle,DemoBuf* paramValue);
    

    C#的方法

    //申请结构体内存大小的指针
    IntPtr paramValue=Marshal.AllocHGlobal(Marshal.Sizeof<DemoBuf>());
    [DllImport("dll文件",CallingConvention.Cdecl)]
    public static extern int SetParamValue(IntPtr handle,IntPtr paramValue);
    

    方法返回的指针paramValue,我们再转换成结构体

    //将指针转换为结构体
    DemoBuf params=(DemoBuf)Marshal.PtrToStructure(paramValue,typeof(DemoBuf));
    

    当然,可以看到引用非常的简单易用,所以根据实际情形还是优先考虑使用引用,上面只是为了介绍另一个方法。

    • 调用复杂指针的方法
      双指针的情形

    C的方法

    int CreateHandle(void** handle);
    

    C#的方法

    [DllImport("dll文件", CallingConvention=CallingConvention.Cdecl)]
    public static extern int CreateHandle(out IntPtr handle);
    

    结构体数组的情形
    C的方法

    int GetMemSize(DemoInfo *ability,DemoBuf buf[2]);
    

    C#的方法

    [DllImport("dll文件", CallingConvention=CallingConvention.Cdecl)]
    public static extern int GetMemSize(DemoInfo ability,[Out]DemoBuf[] buf);
    

    实际使用该C#方式时需要实例化ability和buf,buf的数组大小为2。

    • 调用带回调函数的方法

    C的方法

    //回调函数定义
    void ResultCallBack(unsigned int Type,void *Result,void *User);
    //回调方法
    int RegisterCallBack(void *handle,ResultCallBack callback,void *User);
    

    C#的方法

    [DllImport("dll文件", CallingConvention=CallingConvention.Cdecl)]
    public static extern int RegisterCallBack(IntPtr handle,ResultCallbackDelegate callback,IntPtr User);
    public delegate void ResultCallbackDelegate(uint Type,IntPtr Result,IntPtr User);
    
    • 34
      点赞
    • 1
      收藏
      觉得还不错? 一键收藏
    • 0
      评论

    “相关推荐”对你有帮助么?

    • 非常没帮助
    • 没帮助
    • 一般
    • 有帮助
    • 非常有帮助
    提交
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值