20170904-20170910
托管代码与非托管代码
托管代码[managed code]:Code executing under the control of the CLR is called managed code. For example, any code written in C# or Visual Basic .NET is managed code;
非托管代码[unmanaged code]:Code that runs outside the CLR is referred to as “unmanaged code.” COM components, ActiveX components, and Win32 API functions are examples of unmanaged code.
托管代码与非托管代码之间具有互操作性,CLR拖过COM组件,COM + 服务,win32 API和替他类型的非托管代码提供与托管代码的交互。数据类型,错误句柄机制,规则的创建和销毁以及设计参考在托管代码与非托管代码之间有很大不同,为了简化它们之间互操作复杂程度,CLR互操作层屏蔽掉了在服务器和客户端之间对象模式的不同。
三种互补技巧完成托管于非托管之间的交互:
1、Platform Invoke——p/Invoke
2、COM interop
3、C++ interop——指It Just Works(IJW)
目前来讲,熟悉Platform Invoke就可以,剩下两种仅做了解,具体可以参考微软一篇文章:[An Overview of Managed/Unmanaged Code Interoperability](An Overview of Managed/Unmanaged Code Interoperability)
我们在C#中调用win32API就是比较典型的这类操作,众所周知,Windows是用C语言写的,win32API当然也是,跨语言通信在软件开发中是必不可少的,避免重复造轮子的过程,针对具体的情况选择合适的语言开发对于提高开发效率很重要。Win32 API函数是Windows的核心,比如我们看到的窗体、按钮、对话框什么的,都是依靠Win32函数“画”在屏幕上的,由于这些控件(有时也称组件)都用于用户与Windows进行交互,所以控制这些控件的Win32 API函数称为“用户界面”函数(User Interface Win32 API),简称UI函数;还有一些函数,并不用于交互,比如管理当前系统正在运行的进程、硬件系统状态的监视等等……这些函数只有一套,但是可以被所有的Windows程序调用(只要这个程序的权限足够高),简而言之,API是为程序所共享的。为了达到所有程序能共享一套API的目的,Windows采用了“动态链接库”的办法。之所以叫“动态链接库”,是因为这样的函数库的调用方式是“随用随取”而不是像静态链接库那样“用不用都要带上”。(引用自《从.NET平台调用Win32 API》,刘铁锰)
Win32 API函数是放在Windows系统的核心库文件中的,这些库在硬盘里的存储形式是.dll文件。我们常用到的dll文件是user32.dll和kernel32.dll两个文件。
如果我们写的是C语言程序,想要调用win32API可以说是很简单了,我们虽然看不到dll里边的东西,但微软在每次发布新的版本的时候会发布相应的SDK(Software development kit),可以明确可以使用的接口。
那么对于.Net平台下的其他语言,要想使用windows公开的接口,要怎么办?
调用Win32 API——以C#为例
1、把要用的dll通过DllImport
的attribute导进来,比如,执行: [DllImport(“User32.dll”)];
这里需要再次说明的是,attribute
是类,但是这种类很特殊,不用new,而是通过[特性类(参数列表)]这种形式;attribute
不能独立存在,一定要用作修饰其它目标上(本例是修饰后面的一个函数),不同的特性可以用来修饰类、函数、变量等等;attribute
实例在被编译的时候也不产生可执行代码,而是被放进metadata里以备检索。
2、C#中调用时的注意事项:
- 名字要与Win32 API的完全一样;
- 函数除了要有相应的
DllImport
类修饰外,还要声明成public static extern类型的; - 函数的返回值和参数类型要与Win32 API完全一致——即一一对应起来。
第 3 条比较重要,常常会由于接受类型不匹配导致调用失败,多加注意。
多说一句,.Net
中包含很多已经封装好的win32API,不到万不得已的时候不需要调用系统函数,不安全。
自定义C/C++_dll
自己写一个C/C++下的dll,用C#调用,以下是例子:
//C#调用
using System;
using System.Runtime.InteropServices;
namespace TestApp
{
class Program
{
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct Name
{
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string FirstName;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public string LastName;
//[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 100)]
public IntPtr Array;
};
[DllImport(@"C:\Users\M0015\source\repos\StructureD\Debug\StructureD.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void GetName(ref Name name);
[DllImport(@"C:\Users\M0015\source\repos\StructureD\Debug\StructureD.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void Hello();
static void Main(string[] args)
{
//Console.ReadLine();
Hello();
var name = new Name();
GetName(ref name);
Console.WriteLine(name.FirstName);
//foreach (var s in name.Array)
// Console.WriteLine(s);
}
}
}
// StructureD.cpp: 定义控制台应用程序的入口点。
//C版dll
#include "stdafx.h"
#include <stdio.h>
#include <Windows.h>
#ifdef _MQ
extern "C"
{
#endif
struct Name
{
char FirstName[100];
char LastName[100];
char *Array;//用指针接收,如果需要转换为字符串,则取指针地址中的值,使用取地址符&即可
};
__declspec(dllexport) void __cdecl GetName(struct Name *name)
{
strncpy_s(name->FirstName, 20,"FirstName", sizeof("FirstName"));
strncpy_s(name->LastName, 20,"LastName", sizeof("LastName"));
printf("array is %x", name->Array);
//strncpy_s(name->Array, 20, "ABC", sizeof("ABC"));
//name->Array = "ABC";
name->Array = Marshal.StringToHGlobalAnsi(textBox1.Text);
}
__declspec(dllexport) void __cdecl Hello()
{
printf("Hello\n");
}
#ifdef _MQ
}
#endif
有一个需要注意的问题:生成dll时,要选择建立windows console程序,之后在项目属性设置配置类型为动态库(.dll),MFC的使用选择在静态库中使用 MFC,这样在其他机器上也可以跑。相当于把dll
打包,走在哪里都可以使用。
控制CPU波形图正弦波显示
这里主要要区分的概念就是:
CPU占用率——一段时间内CPU真正的执行指令的时间占这段时间的比率;
CPU空转和空闲是不一样的,while(true);会消耗CPU的利用率,即执行一些没用的,而空闲指CPU没有执行什么指令,比如Sleep(1000);一般是由操作系统来控制的。
编译和链接
编译和链接合并到一起的过程称为构建。那么
编译和链接到底指什么?
一个可执行文件生成的过程包括:预处理、编译、汇编、链接四个步骤,以下将这4个过程分解:
预编译
预编译过程主要处理那些源代码文件中的以“#”开始的预编译指令,比如,#include,#define等,处理规则主要是:
- 将所有#define删除,并且展开所有的宏定义;
- 处理所有的条件预编译指令,比如#if #ifdef 等;
- 递归处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。
- 删除所有的注释;
- 添加行号和文件名标识;
- 保留所有的#pragma编译器指令,因为编译器需要使用它们,最终生成
.i
文件。
关于#pragma
它的作用是设定编译器的状态或者是指示编译器完成一些特定的动作。#pragma指令对每个编译器给出了一个方法,在保持与C和C++语言完全兼容的情况下,给出主机或操作系统专有的特征。依据定义,编译指示是机器或操作系统专有的,且对于每个编译器都是不同的。[百度百科]
编译
编译过程就是把预处理完的文件进行一系列词法分析、语法分析、语义分析以及优化后生产相应的汇编代码文件,这个过程是整个程序构建的核心部分,也是最复杂的部分之一,涉及到编译原理的内容。
汇编
编译产生的文件即是汇编代码,我们要在这一步之后将汇编代码转换为机器码。其中用到的就是汇编器。汇编器是将汇编代码转变成机器可以执行的指令,每一个汇编语句几乎都对应一条机器指令。
链接
汇编器输出目标文件而非可执行文件,需要链接才可以执行。实际上,编译中有一步会生成目标代码,而目标代码中有变量定义在其他模块,事实上,定义其他模块的全局变量和函数在最终运行时的绝对地址都要在最终链接的时候才能确定。所以现代的编译器可以将一个源代码文件编译成一个未链接的目标文件,然后由链接器最终将这些目标文件链接起来形成可执行文件。链接又可分为静态链接和动态链接,按照不同的方式将单独编译的源代码模块组装起来。
另:一篇关于C#调用Win32 C++动态链接库的文章写的不错,特别是如果要将C++的类传给C#,可以借鉴。