您的位置:首页 > 其它

公共语言运行库(CLR)开发系列课程(2):Pinvoke 进阶 学习笔记

2016-11-24 16:03 886 查看

上一章地址

API版本

具有字符串参数的API通常有两种版本

GetWindowText

GetWindowTextA

GetWindowTextW

缺省情况下CLR会自动寻找合适的匹配

CharSet=Ansi时寻找A版本

CharSet=Unicode寻找W版本

CharSet=Auto和平台相关

DLLImportAttribute(ExactSpelling=true)会关掉这种行为

Ref/Out参数 (C#关键字)

out=[Out]&

C#:out int a

IL:[Out] int &a

C++:int *pa;

ref=[In,Out]&

C#:ref int a

IL:[In,Out] int &a

C++:int *pa;

个人感觉 使用Ref/Out 参数P/Invoke CRL会对根据对应关键字添加对应IL特性 System.Runtime.InteropServices下面的InAttribute、OutAttribute   

引用类型和值类型

非托管C/C++ 代码:struct A{int a;} 

托管代码:struct A{int a;}  class A{int a;}

非托管C/C++代码AA*A**
A为值类型Aref AIntPtr a
A为引用类型N/AAref A

自定义参数转换

托管类型和非托管类型之间是一对多关系

String

char */wchar_t *  

char
/wchar_t
固定长度的字符串

BSTR, Ansi BSTR

MarshalAsAttribute(UnmanagedType) 

UnmanagedType 枚举指定对应非托管类型

[MarshalAsAttribute(UnmanagedType.LPWSTR)] string A

非托管函数:void Func(wchar_t *)

托管函数:Public static extern void Func([MarshalAs(UnmanagdType.LPWSTR)] string);

内存管理

在数据转换中需要进行内存分配

举例:

C#调用Func(string) Marshal.StringToHGlobalAnsi("balabala")

C#调用Func(ref string)

C#调用string Func()

CoTaskMemAlloc / CoTaskMemFree (分配/释放 内存) 内存交换才使用 

实例分析CreateProcess

API原型

BOOL WINAPI CreateProcess(
_In_opt_    LPCTSTR               lpApplicationName,
_Inout_opt_ LPTSTR                lpCommandLine,
_In_opt_    LPSECURITY_ATTRIBUTES lpProcessAttributes,
_In_opt_    LPSECURITY_ATTRIBUTES lpThreadAttributes,
_In_        BOOL                  bInheritHandles,
_In_        DWORD                 dwCreationFlags,
_In_opt_    LPVOID                lpEnvironment,
_In_opt_    LPCTSTR               lpCurrentDirectory,
_In_        LPSTARTUPINFO         lpStartupInfo,
_Out_       LPPROCESS_INFORMATION lpProcessInformation
);


   (opt 可选的输入参数)

C#  

Public static extern bool CreateProcess(
[In] string applicationName,
[In,Out] StringBuilder commandName,
[In] ref SECURITY_ATTRIBUTES processAttributes,
[In] ref SECURITY_ATTRIBUTES threadAttributes,
[In] bool inheritHandles,
[In] uint creationFlags,
[In] IntPtr lpEnvironment,
[In] string currentDirectory,
[In] ref STARTUP_INFO startupInfo,
[Out] out PROCESS_INFORMATION processInformation
); 

DLL生命周期

Dll在第一次调用的时候自动加载,不会自动被释放,加载不到或者找不到函数就会抛出异常

Dll不会自动被卸载,建议做法生命一个外部调用类 DLL会伴随外部调用类一起被清理卸载

如果需要手段管理DLL生命周期,只能手动调用LoadLibrary/GetProcAddress

逆向P/Invoke

非托管代码到托管代码这种叫逆向P/Invoke

部分API接收函数指针作为参数

EnumWindows(WNDENUMPROC IpEnumFunc,LPARAM lPararm) 

.NET 程序可以通过P/Invoke调用 EnumWindows API

CLR能够将Delegate 转换为函数指针,EnumWindows 来回调这个函数指针

[UnmanagedFunctionPointer(CallingConvention.Winapi)]
delegate bool EnumWindowProc(IntPtr hwnd, IntPtr lParam);
EnumWindow(new EnumWindowProc(MyEnumWindow), IntPtr.Zero);


DEMO

class Program
{
/// Return Type: BOOL->int
///hwnd: HWND->HWND__*
///lParam: LPARAM->LONG_PTR->int
[UnmanagedFunctionPointerAttribute(CallingConvention.Winapi)]
public delegate bool WNDENUMPROC(IntPtr hwnd, IntPtr lParam);

class NativeMethods
{
[DllImport("User32.dll",CallingConvention=CallingConvention.Winapi)]
public static extern int GetWindowText(IntPtr hwnd, StringBuilder text, int count);

/// Return Type: BOOL->int
///lpEnumFunc: WNDENUMPROC
///lParam: LPARAM->LONG_PTR->int
[DllImportAttribute("user32.dll", EntryPoint = "EnumWindows")]
[return: MarshalAsAttribute(UnmanagedType.Bool)]
public static extern bool EnumWindows(WNDENUMPROC lpEnumFunc, int lParam);
}

private static bool WNDENUMPROC_Callback(IntPtr hwnd, IntPtr lParam)
{
StringBuilder text = new StringBuilder(255);
NativeMethods.GetWindowText(hwnd, text, 255);
Console.WriteLine("窗口名:" + text + ",id=" + hwnd.ToInt32() + ",lParam=" + lParam);
return true;
}
static void Main(string[] args)
{
WNDENUMPROC myWNDENUMPROC = new WNDENUMPROC(WNDENUMPROC_Callback);
NativeMethods.EnumWindows(myWNDENUMPROC,22);
Console.Read();
}

}


常见问题

抛出DllNotFoundException

找不到DLL一般他会到当前目录去找 或者系统目录去找 DLL如果实在无法差错可以使用全路径+dll名不推荐最好是放相对路径

抛出AccessViolationException

访问违规,一般是内存不可以读不可以写或者一起读一起写造成的,通常原因参数转换错误,类型不对应,长度问题。

抛出EntryPointNotFoundException

API方法入口找不到一般都是方法名写错或者调用协定不对

MDA报错StackImbalanceMDA

VS提示报错,参数错位调用协定不对

返回数据错误

参数是否正确,MarshalAs转换是否成功,开启非托管调试查看。

进阶DEMO

c++

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <Windows.h>
/*自定义*/
typedef struct Mytype
{
int i;
char *s;
double d;
struct Mytype *p;
}Mytype;

int TestInt(int a,int b)
{
return a + b;
}
void TestIntPtr(int *i)
{
*i = *i * -1;
}
char * TestCharPtr(char *a,char *b)
{
return strcat(a, b);
}
void TestStructPtr(Mytype *p)
{
p->i++;
p->d++;
}
void TestPtrPtr(int **a,int length)
{
*a = (int*)malloc(sizeof(int)*length);
memset(*a, 0, sizeof(int)* length);
}
void TestArray(int *a, int length)
{
for (int i = 0; i < length; i++)
{
a[i]++;
}
}

void TestCallback(void(*pf)())
{
pf();
}
int TestCharWidth(char *s)
{
return strlen(s);
}

文件名 Source.cpp


LIBRARY "PInvoke"
EXPORTS
TestInt
TestIntPtr
TestCharPtr
TestStructPtr
TestPtrPtr
TestArray
TestCallback
TestCharWidth
文件名 Source.def


C#

class Program
{

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct Mytype
{

/// int
public int i;

/// char*
[MarshalAsAttribute(UnmanagedType.LPStr)]
public string s;

/// double
public double d;

/// Mytype*
public System.IntPtr p;
}

/// Return Type: void
public delegate void Callback();

static void CallbackFunction()
{
Console.WriteLine("callback invoked");
}
/// Return Type: int
///a: int
///b: int
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestInt", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestInt(int a, int b);

/// Return Type: void
///i: int*
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestIntPtr", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestIntPtr(ref int i);

/// Return Type: char*
///a: char*
///b: char*
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestCharPtr", CallingConvention = CallingConvention.Cdecl)]
public static extern System.IntPtr TestCharPtr(System.IntPtr a, System.IntPtr b);

/// Return Type: void
///p: Mytype*
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestStructPtr", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestStructPtr(ref Mytype p);

/// Return Type: void
///a: int**
///length: int
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestPtrPtr", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestPtrPtr(ref System.IntPtr a, int length);

/// Return Type: void
///a: int*
///length: int
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestArray", CallingConvention = CallingConvention.Cdecl)]
public static extern void TestArray(int[] a, int length);

/// Return Type: void
///pf: Anonymous_5afb5371_1680_4be9_99a9_ab5bd7ded029
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestCallback", CallingConvention = CallingConvention.Cdecl )]
public static extern void TestCallback(Callback pf);

/// Return Type: int
///s: char*
[DllImportAttribute("Pinvoke.dll", EntryPoint = "TestCharWidth", CallingConvention = CallingConvention.Cdecl)]
public static extern int TestCharWidth( IntPtr s);

static void Main(string[] args)
{

Console.WriteLine("TestInt:" + TestInt(11, 2));

// TestIntPtr
int i = 22;
TestIntPtr(ref i);
Console.WriteLine("TestIntPtr:" + i);

// TestCharPtr
IntPtr helloPtr = Marshal.StringToHGlobalAnsi("啊阿斯达斯的阿萨德+");
IntPtr worldPtr = Marshal.StringToHGlobalAnsi("+阿萨德阿萨德");
IntPtr helloWorldPtr = TestCharPtr(helloPtr, worldPtr);
string helloWorld = Marshal.PtrToStringAnsi(helloWorldPtr);
Console.WriteLine("TestCharPtr:" + helloWorld);

//helloPtr = Marshal.StringToHGlobalUni("啊阿斯蒂芬a");
//worldPtr = Marshal.StringToHGlobalUni("阿萨德阿萨德");
//helloWorldPtr = TestCharPtr(helloPtr, worldPtr);
//helloWorld = Marshal.PtrToStringUni(helloWorldPtr);
//Console.WriteLine("TestCharPtr01:" + helloWorld);
//  Marshal.FreeCoTaskMem()
Marshal.FreeHGlobal(helloPtr);
Marshal.FreeHGlobal(worldPtr);

// Marshal.FreeHGlobal(helloWorldPtr);  // 因为helloWorldPtr和helloPtr指向的是同一地址,所以再次释放会报错

// TestCharWidth
string a = "a的";
IntPtr aPtr = Marshal.StringToHGlobalAnsi(a); // Ansi
int len = TestCharWidth(aPtr);
Console.WriteLine("TestCharWidth:" + len);
a = Marshal.PtrToStringAnsi(aPtr);
Marshal.FreeHGlobal(aPtr);

aPtr = Marshal.StringToHGlobalUni(a); // Unicode
len = TestCharWidth(aPtr); // 值是1,strlen没有正确处理unicode,所以不要使用strlen测量unicode字符串的长度
Console.WriteLine("TestCharWidth:" + len);
a = Marshal.PtrToStringUni(aPtr);
Marshal.FreeHGlobal(aPtr);

// TestStructPtr
Mytype myType = new Mytype { i = 0, d = 1.1, s = a, p = IntPtr.Zero };
TestStructPtr(ref myType);
Console.WriteLine("Mytype->i:" + myType.i);
Console.WriteLine("Mytype->d:" + myType.d);

// TestArray
int[] array = new int[] { 1, 2, 3 };
TestArray(array, array.Length);

for (int z = 0; z < array.Length; z++)
{
Console.WriteLine("array[{0}]" + array[z], z);
}

// TestCallback
TestCallback(CallbackFunction);

Console.Read();
}

}


    

      
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐