Capturing the Desktop Screen with the Mouse Cursor Image

下面的文章介绍了两种截屏方式及如何“截取”光标的方法。

通常情况下是无法截取光标的,因为光标不属于窗口中的windows元素,而是独立与窗口系统的另一层东西,叫Hot Spot。

申明:本文以下内容归原作者所有!

By 27 Jan 2006

DesktopCaptureWithMouse

Introduction

This article shows how you can capture screen images including the mouse cursor.

Background

Screen capturing is a very useful way of resource sharing as used in applications like Remote Desktop, Virtual Network Computing (VNC), where a user can access, view, and interact with a remote desktop as his own desktop. Also, it is used in non ethical applications like hacking applications, where a hacker can hack a computer using some malicious server application, and the server then frequently takes screenshots of the prey machine and sends them to the clients. You will see a lot of source code resources over the internet discussing how to take screenshots of the desktop or an area of the desktop but none of them discuss how to capture the mouse cursor bitmap with the screenshot. Sometimes it becomes necessary to capture the mouse to see the whole activity of the hacked machine. First, we will discuss here what the actual problem is.

Problem

Most of us think that the mouse cursor image is a part of the desktop display but actually it works on an upper layer over the desktop. Windows always tracks mouse with a “Hot Spot”, the actual position seen by the Windows.

Currently, there are two common ways to capture and manipulate the desktop image:

  1. Copy the desktop bitmap data from Video Memory to the System Memory. Do processing and then again blit it back to the Video Memory. This can be easily done using the BitBlt() or the StretchBlt() APIs provided by Win32.
  2. Another way is to directly manipulate the desktop bitmap in the Video Memory if enough memory is available as provided by DirectDraw.

Both of these don’t provide us the facility to capture the mouse cursor image with the desktop image.

Solution

The solution to the problem of capturing the mouse cursor image with the desktop image is quite simple.

  1. First, get the bitmap of the screen using BitBlt(). I have provided a simple function namedCaptureDesktop() in the CaptureScreen.cs file that captures the screen bitmap as almost all the codes available over the internet do.
  2. Then capture the mouse cursor bitmap as:

    Get Cursor Icon:

    First, get the cursor information using the Win32 GetCursorInfo(). The function fills the CURSORINFOstructure provided as a parameter. Don’t forget to initialize the cbSize member of the structure before passing it as an argument. Then we check whether the cursor is visible or not, by checking for the equality of the flagsmember of the filled structure with the CURSOR_SHOWING constant. If they are equal then we get the handle to the cursor icon using the CopyIcon() function that takes the hCursor member of the above filled structure.

    Get Cursor Position:

    Now, we have to get the Icon information so that we can get the hotspot position. This information is easily retrieved using the GetIconInfo() function. Here is the C# implementation of the mouse capturing function:

    static Bitmap CaptureCursor(ref int x, ref int y)
    {
       Bitmap bmp;
       IntPtr hicon;
       Win32Stuff.CURSORINFO ci = new Win32Stuff.CURSORINFO(); 
       Win32Stuff.ICONINFO icInfo;
       ci.cbSize = Marshal.SizeOf(ci);
       if(Win32Stuff.GetCursorInfo(out ci))
       {
           if (ci.flags == Win32Stuff.CURSOR_SHOWING)
           { 
               hicon = Win32Stuff.CopyIcon(ci.hCursor);
               if(Win32Stuff.GetIconInfo(hicon, out icInfo))
               {
                   x = ci.ptScreenPos.x - ((int)icInfo.xHotspot);
                   y = ci.ptScreenPos.y - ((int)icInfo.yHotspot);
                   Icon ic = Icon.FromHandle(hicon);
                   bmp = ic.ToBitmap(); 
    
                   return bmp;
               }
           }
       }
       return null;
    }
  3. We now have both the bitmaps, i.e., the desktop bitmap and the mouse cursor bitmap with its position on the screen. Now, it’s time to place the mouse cursor bitmap on the desktop bitmap. I have provided the following function that places the mouse cursor image over the desktop bitmap at the proper position:
    public static Bitmap CaptureDesktopWithCursor()
    {     
       int cursorX = 0;
       int cursorY = 0;
       Bitmap desktopBMP;
       Bitmap cursorBMP;
       Bitmap finalBMP;
       Graphics g;
       Rectangle r;
       desktopBMP = CaptureDesktop();
       cursorBMP = CaptureCursor(ref cursorX, ref cursorY);
       if(desktopBMP != null)
       {
           if (cursorBMP != null)
           {
               r = new Rectangle(cursorX, cursorY, 
                       cursorBMP.Width, cursorBMP.Height);
               g = Graphics.FromImage(desktopBMP);
               g.DrawImage(cursorBMP, r);
               g.Flush();
               return desktopBMP;
           }
           else
               return desktopBMP;
       }
       return null;
    }
  4. The bitmap with the cursor is now ready to be rendered over a viewer surface (a PictureBox used here). Since the viewer’s visible area is smaller than the desktop bitmap area, scaling has been used in the function that displays the cooked desktop image.
    // ssWithMouseViewer is the PictureBox control
    private void Display(Bitmap desktop)
    {
        Graphics g;
        Rectangle r;
        if(desktop != null)
        {
            r = new Rectangle(0,0,ssWithMouseViewer.Width, 
                                ssWithMouseViewer.Height);
            g = ssWithMouseViewer.CreateGraphics();
            g.DrawImage(desktop,r);
            g.Flush();
        }
    }

Note

The binary image provided is compiled with Visual Studio .NET 2005 so you have to install .NET Framework 2.0.

Known problems

I have tested the application on my machine with no known problems, so I expect the same behavior on your machine.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

本文转自:http://www.codeproject.com/Articles/12850/Capturing-the-Desktop-Screen-with-the-Mouse-Cursor

十大滤波算法程序大全(Arduino精编无错版)

最近用Arduino做电子秤,为了解决数据的跳变研究了不少滤波算法。网上能找到大把的十大滤波算法帖子,每一篇都不太一样,都号称精编啊,除错啊什么的,可是放到板子里却没一个能正常跑起来的。于是决定自己整理一下这些程序,完美移植到Arduino中。

所以大家看到这个帖子的时候,不要怀疑我重复发帖。我的代码都是经过反复试验,复制到Arduino中就能开跑的成品代码,移植到自己的程序中非常方便。而且都仔细研究了各个算法,把错误都修正了的(别的程序连冒泡算法都是溢出的,不信自己找来细看看),所以也算个小原创吧,在别人基础上的原创。

转载请注明出处:极客工坊  http://www.geek-workshop.com/thread-7694-1-1.html

By shenhaiyu 2013-11-01

 

1、限幅滤波法(又称程序判断滤波法)
2、中位值滤波法
3、算术平均滤波法
4、递推平均滤波法(又称滑动平均滤波法)
5、中位值平均滤波法(又称防脉冲干扰平均滤波法)
6、限幅平均滤波法
7、一阶滞后滤波法
8、加权递推平均滤波法
9、消抖滤波法
10、限幅消抖滤波法
11、新增加 卡尔曼滤波(非扩展卡尔曼),感谢zhangzhe0617分享

程序默认对int类型数据进行滤波,如需要对其他类型进行滤波,只需要把程序中所有int替换成long、float或者double即可。

1、限幅滤波法(又称程序判断滤波法)

ARDUINO 代码
/*
A、名称:限幅滤波法(又称程序判断滤波法)
B、方法:
    根据经验判断,确定两次采样允许的最大偏差值(设为A),
    每次检测到新值时判断:
    如果本次值与上次值之差<=A,则本次值有效,
    如果本次值与上次值之差>A,则本次值无效,放弃本次值,用上次值代替本次值。
C、优点:
    能有效克服因偶然因素引起的脉冲干扰。
D、缺点:
    无法抑制那种周期性的干扰。
    平滑度差。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Value = Filter_Value;          // 最近一次有效采样的值,该变量为全局变量
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅滤波法(又称程序判断滤波法)
#define FILTER_A 1
int Filter() {
  int NewValue;
  NewValue = Get_AD();
  if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
    return Value;
  else
    return NewValue;
}

2、中位值滤波法

ARDUINO 代码
/*
A、名称:中位值滤波法
B、方法:
    连续采样N次(N取奇数),把N次采样值按大小排列,
    取中间值为本次有效值。
C、优点:
    能有效克服因偶然因素引起的波动干扰;
    对温度、液位的变化缓慢的被测参数有良好的滤波效果。
D、缺点:
    对流量、速度等快速变化的参数不宜。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 中位值滤波法
#define FILTER_N 101
int Filter() {
  int filter_buf[FILTER_N];
  int i, j;
  int filter_temp;
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  // 采样值从小到大排列(冒泡法)
  for(j = 0; j < FILTER_N - 1; j++) {
    for(i = 0; i < FILTER_N - 1 - j; i++) {
      if(filter_buf[i] > filter_buf[i + 1]) {
        filter_temp = filter_buf[i];
        filter_buf[i] = filter_buf[i + 1];
        filter_buf[i + 1] = filter_temp;
      }
    }
  }
  return filter_buf[(FILTER_N - 1) / 2];
}

3、算术平均滤波法

ARDUINO 代码
/*
A、名称:算术平均滤波法
B、方法:
    连续取N个采样值进行算术平均运算:
    N值较大时:信号平滑度较高,但灵敏度较低;
    N值较小时:信号平滑度较低,但灵敏度较高;
    N值的选取:一般流量,N=12;压力:N=4。
C、优点:
    适用于对一般具有随机干扰的信号进行滤波;
    这种信号的特点是有一个平均值,信号在某一数值范围附近上下波动。
D、缺点:
    对于测量速度较慢或要求数据计算速度较快的实时控制不适用;
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 算术平均滤波法
#define FILTER_N 12
int Filter() {
  int i;
  int filter_sum = 0;
  for(i = 0; i < FILTER_N; i++) {
    filter_sum += Get_AD();
    delay(1);
  }
  return (int)(filter_sum / FILTER_N);
}

4、递推平均滤波法(又称滑动平均滤波法)

ARDUINO 代码
/*
A、名称:递推平均滤波法(又称滑动平均滤波法)
B、方法:
    把连续取得的N个采样值看成一个队列,队列的长度固定为N,
    每次采样到一个新数据放入队尾,并扔掉原来队首的一次数据(先进先出原则),
    把队列中的N个数据进行算术平均运算,获得新的滤波结果。
    N值的选取:流量,N=12;压力,N=4;液面,N=4-12;温度,N=1-4。
C、优点:
    对周期性干扰有良好的抑制作用,平滑度高;
    适用于高频振荡的系统。
D、缺点:
    灵敏度低,对偶然出现的脉冲性干扰的抑制作用较差;
    不易消除由于脉冲干扰所引起的采样值偏差;
    不适用于脉冲干扰比较严重的场合;
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 递推平均滤波法(又称滑动平均滤波法)
#define FILTER_N 12
int filter_buf[FILTER_N + 1];
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N] = Get_AD();
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
    filter_sum += filter_buf[i];
  }
  return (int)(filter_sum / FILTER_N);
}

5、中位值平均滤波法(又称防脉冲干扰平均滤波法)

ARDUINO 代码
/*
A、名称:中位值平均滤波法(又称防脉冲干扰平均滤波法)
B、方法:
    采一组队列去掉最大值和最小值后取平均值,
    相当于“中位值滤波法”+“算术平均滤波法”。
    连续采样N个数据,去掉一个最大值和一个最小值,
    然后计算N-2个数据的算术平均值。
    N值的选取:3-14。
C、优点:
    融合了“中位值滤波法”+“算术平均滤波法”两种滤波法的优点。
    对于偶然出现的脉冲性干扰,可消除由其所引起的采样值偏差。
    对周期干扰有良好的抑制作用。
    平滑度高,适于高频振荡的系统。
D、缺点:
    计算速度较慢,和算术平均滤波法一样。
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 中位值平均滤波法(又称防脉冲干扰平均滤波法)(算法1)
#define FILTER_N 100
int Filter() {
  int i, j;
  int filter_temp, filter_sum = 0;
  int filter_buf[FILTER_N];
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  // 采样值从小到大排列(冒泡法)
  for(j = 0; j < FILTER_N - 1; j++) {
    for(i = 0; i < FILTER_N - 1 - j; i++) {
      if(filter_buf[i] > filter_buf[i + 1]) {
        filter_temp = filter_buf[i];
        filter_buf[i] = filter_buf[i + 1];
        filter_buf[i + 1] = filter_temp;
      }
    }
  }
  // 去除最大最小极值后求平均
  for(i = 1; i < FILTER_N - 1; i++) filter_sum += filter_buf[i];
  return filter_sum / (FILTER_N - 2);
}
 
 
//  中位值平均滤波法(又称防脉冲干扰平均滤波法)(算法2)
/*
#define FILTER_N 100
int Filter() {
  int i;
  int filter_sum = 0;
  int filter_max, filter_min;
  int filter_buf[FILTER_N];
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = Get_AD();
    delay(1);
  }
  filter_max = filter_buf[0];
  filter_min = filter_buf[0];
  filter_sum = filter_buf[0];
  for(i = FILTER_N - 1; i > 0; i--) {
    if(filter_buf[i] > filter_max)
      filter_max=filter_buf[i];
    else if(filter_buf[i] < filter_min)
      filter_min=filter_buf[i];
    filter_sum = filter_sum + filter_buf[i];
    filter_buf[i] = filter_buf[i - 1];
  }
  i = FILTER_N - 2;
  filter_sum = filter_sum - filter_max - filter_min + i / 2; // +i/2 的目的是为了四舍五入
  filter_sum = filter_sum / i;
  return filter_sum;
}*/

6、限幅平均滤波法

ARDUINO 代码
/*
A、名称:限幅平均滤波法
B、方法:
    相当于“限幅滤波法”+“递推平均滤波法”;
    每次采样到的新数据先进行限幅处理,
    再送入队列进行递推平均滤波处理。
C、优点:
    融合了两种滤波法的优点;
    对于偶然出现的脉冲性干扰,可消除由于脉冲干扰所引起的采样值偏差。
D、缺点:
    比较浪费RAM。
E、整理:shenhaiyu 2013-11-01
*/
 
#define FILTER_N 12
int Filter_Value;
int filter_buf[FILTER_N];
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  filter_buf[FILTER_N - 2] = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅平均滤波法
#define FILTER_A 1
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N - 1] = Get_AD();
  if(((filter_buf[FILTER_N - 1] - filter_buf[FILTER_N - 2]) > FILTER_A) || ((filter_buf[FILTER_N - 2] - filter_buf[FILTER_N - 1]) > FILTER_A))
    filter_buf[FILTER_N - 1] = filter_buf[FILTER_N - 2];
  for(i = 0; i < FILTER_N - 1; i++) {
    filter_buf[i] = filter_buf[i + 1];
    filter_sum += filter_buf[i];
  }
  return (int)filter_sum / (FILTER_N - 1);
}

7、一阶滞后滤波法

ARDUINO 代码
/*
A、名称:一阶滞后滤波法
B、方法:
    取a=0-1,本次滤波结果=(1-a)*本次采样值+a*上次滤波结果。
C、优点:
    对周期性干扰具有良好的抑制作用;
    适用于波动频率较高的场合。
D、缺点:
    相位滞后,灵敏度低;
    滞后程度取决于a值大小;
    不能消除滤波频率高于采样频率1/2的干扰信号。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 一阶滞后滤波法
#define FILTER_A 0.01
int Filter() {
  int NewValue;
  NewValue = Get_AD();
  Value = (int)((float)NewValue * FILTER_A + (1.0 - FILTER_A) * (float)Value);
  return Value;
}

8、加权递推平均滤波法

ARDUINO 代码
/*
A、名称:加权递推平均滤波法
B、方法:
    是对递推平均滤波法的改进,即不同时刻的数据加以不同的权;
    通常是,越接近现时刻的数据,权取得越大。
    给予新采样值的权系数越大,则灵敏度越高,但信号平滑度越低。
C、优点:
    适用于有较大纯滞后时间常数的对象,和采样周期较短的系统。
D、缺点:
    对于纯滞后时间常数较小、采样周期较长、变化缓慢的信号;
    不能迅速反应系统当前所受干扰的严重程度,滤波效果差。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 加权递推平均滤波法
#define FILTER_N 12
int coe[FILTER_N] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12};    // 加权系数表
int sum_coe = 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12; // 加权系数和
int filter_buf[FILTER_N + 1];
int Filter() {
  int i;
  int filter_sum = 0;
  filter_buf[FILTER_N] = Get_AD();
  for(i = 0; i < FILTER_N; i++) {
    filter_buf[i] = filter_buf[i + 1]; // 所有数据左移,低位仍掉
    filter_sum += filter_buf[i] * coe[i];
  }
  filter_sum /= sum_coe;
  return filter_sum;
}

9、消抖滤波法

ARDUINO 代码
/*
A、名称:消抖滤波法
B、方法:
    设置一个滤波计数器,将每次采样值与当前有效值比较:
    如果采样值=当前有效值,则计数器清零;
    如果采样值<>当前有效值,则计数器+1,并判断计数器是否>=上限N(溢出);
    如果计数器溢出,则将本次值替换当前有效值,并清计数器。
C、优点:
    对于变化缓慢的被测参数有较好的滤波效果;
    可避免在临界值附近控制器的反复开/关跳动或显示器上数值抖动。
D、缺点:
    对于快速变化的参数不宜;
    如果在计数器溢出的那一次采样到的值恰好是干扰值,则会将干扰值当作有效值导入系统。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 消抖滤波法
#define FILTER_N 12
int i = 0;
int Filter() {
  int new_value;
  new_value = Get_AD();
  if(Value != new_value) {
    i++;
    if(i > FILTER_N) {
      i = 0;
      Value = new_value;
    }
  }
  else
    i = 0;
  return Value;
}

10、限幅消抖滤波法

ARDUINO 代码
/*
A、名称:限幅消抖滤波法
B、方法:
    相当于“限幅滤波法”+“消抖滤波法”;
    先限幅,后消抖。
C、优点:
    继承了“限幅”和“消抖”的优点;
    改进了“消抖滤波法”中的某些缺陷,避免将干扰值导入系统。
D、缺点:
    对于快速变化的参数不宜。
E、整理:shenhaiyu 2013-11-01
*/
 
int Filter_Value;
int Value;
 
void setup() {
  Serial.begin(9600);       // 初始化串口通信
  randomSeed(analogRead(0)); // 产生随机种子
  Value = 300;
}
 
void loop() {
  Filter_Value = Filter();       // 获得滤波器输出值
  Serial.println(Filter_Value); // 串口输出
  delay(50);
}
 
// 用于随机产生一个300左右的当前值
int Get_AD() {
  return random(295, 305);
}
 
// 限幅消抖滤波法
#define FILTER_A 1
#define FILTER_N 5
int i = 0;
int Filter() {
  int NewValue;
  int new_value;
  NewValue = Get_AD();
  if(((NewValue - Value) > FILTER_A) || ((Value - NewValue) > FILTER_A))
    new_value = Value;
  else
    new_value = NewValue;
  if(Value != new_value) {
    i++;
    if(i > FILTER_N) {
      i = 0;
      Value = new_value;
    }
  }
  else
    i = 0;
  return Value;
}

 

11、卡尔曼滤波(非扩展卡尔曼)

ARDUINO 代码
#include <Wire.h> // I2C library, gyroscope

// Accelerometer ADXL345
#define ACC (0x53)    //ADXL345 ACC address
#define A_TO_READ (6)        //num of bytes we are going to read each time (two bytes for each axis)


// Gyroscope ITG3200 
#define GYRO 0x68 // gyro address, binary = 11101000 when AD0 is connected to Vcc (see schematics of your breakout board)
#define G_SMPLRT_DIV 0x15   
#define G_DLPF_FS 0x16   
#define G_INT_CFG 0x17
#define G_PWR_MGM 0x3E

#define G_TO_READ 8 // 2 bytes for each axis x, y, z


// offsets are chip specific. 
int a_offx = 0;
int a_offy = 0;
int a_offz = 0;

int g_offx = 0;
int g_offy = 0;
int g_offz = 0;
////////////////////////

////////////////////////
char str[512]; 

void initAcc() {
  //Turning on the ADXL345
  writeTo(ACC, 0x2D, 0);      
  writeTo(ACC, 0x2D, 16);
  writeTo(ACC, 0x2D, 8);
  //by default the device is in +-2g range reading
}

void getAccelerometerData(int* result) {
  int regAddress = 0x32;    //first axis-acceleration-data register on the ADXL345
  byte buff[A_TO_READ];
  
  readFrom(ACC, regAddress, A_TO_READ, buff); //read the acceleration data from the ADXL345
  
  //each axis reading comes in 10 bit resolution, ie 2 bytes.  Least Significat Byte first!!
  //thus we are converting both bytes in to one int
  result[0] = (((int)buff[1]) << 8) | buff[0] + a_offx;   
  result[1] = (((int)buff[3]) << 8) | buff[2] + a_offy;
  result[2] = (((int)buff[5]) << 8) | buff[4] + a_offz;
}

//initializes the gyroscope
void initGyro()
{
  /*****************************************
  * ITG 3200
  * power management set to:
  * clock select = internal oscillator
  *     no reset, no sleep mode
  *   no standby mode
  * sample rate to = 125Hz
  * parameter to +/- 2000 degrees/sec
  * low pass filter = 5Hz
  * no interrupt
  ******************************************/
  writeTo(GYRO, G_PWR_MGM, 0x00);
  writeTo(GYRO, G_SMPLRT_DIV, 0x07); // EB, 50, 80, 7F, DE, 23, 20, FF
  writeTo(GYRO, G_DLPF_FS, 0x1E); // +/- 2000 dgrs/sec, 1KHz, 1E, 19
  writeTo(GYRO, G_INT_CFG, 0x00);
}


void getGyroscopeData(int * result)
{
  /**************************************
  Gyro ITG-3200 I2C
  registers:
  temp MSB = 1B, temp LSB = 1C
  x axis MSB = 1D, x axis LSB = 1E
  y axis MSB = 1F, y axis LSB = 20
  z axis MSB = 21, z axis LSB = 22
  *************************************/

  int regAddress = 0x1B;
  int temp, x, y, z;
  byte buff[G_TO_READ];
  
  readFrom(GYRO, regAddress, G_TO_READ, buff); //read the gyro data from the ITG3200
  
  result[0] = ((buff[2] << 8) | buff[3]) + g_offx;
  result[1] = ((buff[4] << 8) | buff[5]) + g_offy;
  result[2] = ((buff[6] << 8) | buff[7]) + g_offz;
  result[3] = (buff[0] << 8) | buff[1]; // temperature
  
}


float xz=0,yx=0,yz=0;
float p_xz=1,p_yx=1,p_yz=1;
float q_xz=0.0025,q_yx=0.0025,q_yz=0.0025;
float k_xz=0,k_yx=0,k_yz=0;
float r_xz=0.25,r_yx=0.25,r_yz=0.25;
  //int acc_temp[3];
  //float acc[3];
  int acc[3];
  int gyro[4];
  float Axz;
  float Ayx;
  float Ayz;
  float t=0.025;
void setup()
{
  Serial.begin(9600);
  Wire.begin();
  initAcc();
  initGyro();
  
}

//unsigned long timer = 0;
//float o;
void loop()
{
  
  getAccelerometerData(acc);
  getGyroscopeData(gyro);
  //timer = millis();
  sprintf(str, "%d,%d,%d,%d,%d,%d", acc[0],acc[1],acc[2],gyro[0],gyro[1],gyro[2]);
  
  //acc[0]=acc[0];
  //acc[2]=acc[2];
  //acc[1]=acc[1];
  //r=sqrt(acc[0]*acc[0]+acc[1]*acc[1]+acc[2]*acc[2]);
  gyro[0]=gyro[0]/ 14.375;
  gyro[1]=gyro[1]/ (-14.375);
  gyro[2]=gyro[2]/ 14.375;
  
   
  Axz=(atan2(acc[0],acc[2]))*180/PI;
  Ayx=(atan2(acc[0],acc[1]))*180/PI;
  /*if((acc[0]!=0)&&(acc[1]!=0))
    {
      Ayx=(atan2(acc[0],acc[1]))*180/PI;
    }
    else
    {
      Ayx=t*gyro[2];
    }*/
  Ayz=(atan2(acc[1],acc[2]))*180/PI;
  
  
//kalman filter
  calculate_xz();
  calculate_yx();
  calculate_yz();
  
  //sprintf(str, "%d,%d,%d", xz_1, xy_1, x_1);
  //Serial.print(xz);Serial.print(",");
  //Serial.print(yx);Serial.print(",");
  //Serial.print(yz);Serial.print(",");
  //sprintf(str, "%d,%d,%d,%d,%d,%d", acc[0],acc[1],acc[2],gyro[0],gyro[1],gyro[2]);
  //sprintf(str, "%d,%d,%d",gyro[0],gyro[1],gyro[2]);
    Serial.print(Axz);Serial.print(",");
    //Serial.print(Ayx);Serial.print(",");
    //Serial.print(Ayz);Serial.print(",");
  //Serial.print(str);
  //o=gyro[2];//w=acc[2];
  //Serial.print(o);Serial.print(",");
  //Serial.print(w);Serial.print(",");
  Serial.print("\n");

  
  //delay(50);
}
void calculate_xz()
{

xz=xz+t*gyro[1];
p_xz=p_xz+q_xz;
k_xz=p_xz/(p_xz+r_xz);
xz=xz+k_xz*(Axz-xz);
p_xz=(1-k_xz)*p_xz;
}
void calculate_yx()
{
  
  yx=yx+t*gyro[2];
  p_yx=p_yx+q_yx;
  k_yx=p_yx/(p_yx+r_yx);
  yx=yx+k_yx*(Ayx-yx);
  p_yx=(1-k_yx)*p_yx;

}
void calculate_yz()
{
  yz=yz+t*gyro[0];
  p_yz=p_yz+q_yz;
  k_yz=p_yz/(p_yz+r_yz);
  yz=yz+k_yz*(Ayz-yz);
  p_yz=(1-k_yz)*p_yz;

}


//---------------- Functions
//Writes val to address register on ACC
void writeTo(int DEVICE, byte address, byte val) {
   Wire.beginTransmission(DEVICE); //start transmission to ACC 
   Wire.write(address);        // send register address
   Wire.write(val);        // send value to write
   Wire.endTransmission(); //end transmission
}


//reads num bytes starting from address register on ACC in to buff array
void readFrom(int DEVICE, byte address, int num, byte buff[]) {
  Wire.beginTransmission(DEVICE); //start transmission to ACC 
  Wire.write(address);        //sends address to read from
  Wire.endTransmission(); //end transmission
  
  Wire.beginTransmission(DEVICE); //start transmission to ACC
  Wire.requestFrom(DEVICE, num);    // request 6 bytes from ACC
  
  int i = 0;
  while(Wire.available())    //ACC may send less than requested (abnormal)
  { 
    buff[i] = Wire.read(); // receive a byte
    i++;
  }
  Wire.endTransmission(); //end transmission
}

 

本文转自:http://www.geek-workshop.com/thread-7694-1-1.html

 

三种截取屏幕的方式

Various methods for capturing the screen

By 19 Sep 2006

Contents

Introduction

Some times, we want to capture the contents of the entire screen programmatically. The following explains how it can be done. Typically, the immediate options we have, among others, are using GDI and/or DirectX. Another option that is worth considering is Windows Media API. Here, we would consider each of them and see how they can be used for our purpose. In each of these approaches, once we get the screenshot into our application defined memory or bitmap, we can use it in generating a movie. Refer to the article Create Movie From HBitmap for more details about creating movies from bitmap sequences programmatically.

Capture it the GDI way

When performance is not an issue and when all that we want is just a snapshot of the desktop, we can consider the GDI option. This mechanism is based on the simple principle that the desktop is also a window – that is it has a window Handle (HWND) and a device context (DC). If we can get the device context of the desktop to be captured, we can just blit those contents to our application defined device context in the normal way. And getting the device context of the desktop is pretty straightforward if we know its window handle – which can be achieved through the function GetDesktopWindow(). Thus, the steps involved are:

  1. Acquire the Desktop window handle using the function GetDesktopWindow();
  2. Get the DC of the desktop window using the function GetDC();
  3. Create a compatible DC for the Desktop DC and a compatible bitmap to select into that compatible DC. These can be done using CreateCompatibleDC() and CreateCompatibleBitmap(); selecting the bitmap into our DC can be done with SelectObject();
  4. Whenever you are ready to capture the screen, just blit the contents of the Desktop DC into the created compatible DC – that’s all – you are done. The compatible bitmap we created now contains the contents of the screen at the moment of the capture.
  5. Do not forget to release the objects when you are done. Memory is precious (for the other applications).

Example

Void CaptureScreen()
{
    int nScreenWidth = GetSystemMetrics(SM_CXSCREEN);
    int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
    HWND hDesktopWnd = GetDesktopWindow();
    HDC hDesktopDC = GetDC(hDesktopWnd);
    HDC hCaptureDC = CreateCompatibleDC(hDesktopDC);
    HBITMAP hCaptureBitmap =CreateCompatibleBitmap(hDesktopDC, 
                            nScreenWidth, nScreenHeight);
    SelectObject(hCaptureDC,hCaptureBitmap); 
    BitBlt(hCaptureDC,0,0,nScreenWidth,nScreenHeight,
           hDesktopDC,0,0,SRCCOPY|CAPTUREBLT); 
    SaveCapturedBitmap(hCaptureBitmap); //Place holder - Put your code
                                //here to save the captured image to disk
    ReleaseDC(hDesktopWnd,hDesktopDC);
    DeleteDC(hCaptureDC);
    DeleteObject(hCaptureBitmap);
}

In the above code snippet, the function GetSystemMetrics() returns the screen width when used withSM_CXSCREEN, and returns the screen height when called with SM_CYSCREEN. Refer to the accompanying source code for details of how to save the captured bitmap to the disk and how to send it to the clipboard. Its pretty straightforward. The source code implements the above technique for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

And the DirectX way of doing it

Capturing the screenshot with DirectX is a pretty easy task. DirectX offers a neat way of doing this.

Every DirectX application contains what we call a buffer, or a surface to hold the contents of the video memory related to that application. This is called the back buffer of the application. Some applications might have more than one back buffer. And there is another buffer that every application can access by default – the front buffer. This one, the front buffer, holds the video memory related to the desktop contents, and so essentially is the screen image.

By accessing the front buffer from our DirectX application, we can capture the contents of the screen at that moment.

Accessing the front buffer from the DirectX application is pretty easy and straightforward. The interfaceIDirect3DDevice9 provides the GetFrontBufferData() method that takes a IDirect3DSurface9 object pointer and copies the contents of the front buffer onto that surface. The IDirect3DSurfce9 object can be generated by using the method IDirect3DDevice8::CreateOffscreenPlainSurface(). Once the screen is captured onto the surface, we can use the function D3DXSaveSurfaceToFile() to save the surface directly to the disk in bitmap format. Thus, the code to capture the screen looks as follows:

extern IDirect3DDevice9* g_pd3dDevice;
Void CaptureScreen()
{
    IDirect3DSurface9* pSurface;
    g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
        D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, &pSurface, NULL);
    g_pd3dDevice->GetFrontBufferData(0, pSurface);
    D3DXSaveSurfaceToFile("Desktop.bmp",D3DXIFF_BMP,pSurface,NULL,NULL);
    pSurface->Release(); 
}

In the above, g_pd3dDevice is an IDirect3DDevice9 object, and has been assumed to be properly initialized. This code snippet saves the captured image onto the disk directly. However, instead of saving to disk, if we just want to operate on the image bits directly – we can do so by using the method IDirect3DSurface9::LockRect(). This gives a pointer to the surface memory – which is essentially a pointer to the bits of the captured image. We can copy the bits to our application defined memory and can operate on them. The following code snippet presents how the surface contents can be copied into our application defined memory:

extern void* pBits;
extern IDirect3DDevice9* g_pd3dDevice;
IDirect3DSurface9* pSurface;
g_pd3dDevice->CreateOffscreenPlainSurface(ScreenWidth, ScreenHeight,
                                          D3DFMT_A8R8G8B8, D3DPOOL_SCRATCH, 
                                          &pSurface, NULL);
g_pd3dDevice->GetFrontBufferData(0, pSurface);
D3DLOCKED_RECT lockedRect;
pSurface->LockRect(&lockedRect,NULL,
                   D3DLOCK_NO_DIRTY_UPDATE|
                   D3DLOCK_NOSYSLOCK|D3DLOCK_READONLY)));
for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy( (BYTE*) pBits + i * ScreenWidth * BITSPERPIXEL / 8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth * BITSPERPIXEL / 8);
}
g_pSurface->UnlockRect();
pSurface->Release();

In the above, pBits is a void*. Make sure that we have allocated enough memory before copying into pBits. A typical value for BITSPERPIXEL is 32 bits per pixel. However, it may vary depending on your current monitor settings. The important point to note here is that the width of the surface is not same as the captured screen image width. Because of the issues involved in the memory alignment (memory aligned to word boundaries are assumed to be accessed faster compared to non aligned memory), the surface might have added additional stuff at the end of each row to make them perfectly aligned to the word boundaries. The lockedRect.Pitch gives us the number of bytes between the starting points of two successive rows. That is, to advance to the correct point on the next row, we should advance by Pitch, not by Width. You can copy the surface bits in reverse, using the following:

for( int i=0 ; i < ScreenHeight ; i++)
{
    memcpy((BYTE*) pBits +( ScreenHeight - i - 1) * 
        ScreenWidth * BITSPERPIXEL/8 , 
        (BYTE*) lockedRect.pBits + i* lockedRect.Pitch , 
        ScreenWidth* BITSPERPIXEL/8);
}

This may come handy when you are converting between top-down and bottom-up bitmaps.

While the above technique of LockRect() is one way of accessing the captured image content onIDirect3DSurface9, we have another more sophisticated method defined for IDirect3DSurface9, the GetDC()method. We can use the IDirect3DSurface9::GetDC() method to get a GDI compatible device context for the DirectX image surface, which makes it possible to directly blit the surface contents to our application defined DC. Interested readers can explore this alternative.

The sample source code provided with this article implements the technique of copying the contents of an off-screen plain surface onto a user created bitmap for capturing the screen contents at regular intervals, and creates a movie out of the captured image sequences.

However, a point worth noting when using this technique for screen capture is the caution mentioned in the documentation: The GetFrontBufferData() is a slow operation by design, and should not be considered for use in performance-critical applications. Thus, the GDI approach is preferable over the DirectX approach in such cases.

Windows Media API for capturing the screen

Windows Media 9.0 supports screen captures using the Windows Media Encoder 9 API. It includes a codec namedWindows Media Video 9 Screen codec that has been specially optimized to operate on the content produced through screen captures. The Windows Media Encoder API provides the interface IWMEncoder2 which can be used to capture the screen content efficiently.

Working with the Windows Media Encoder API for screen captures is pretty straightforward. First, we need to start with the creation of an IWMEncoder2 object by using the CoCreateInstance() function. This can be done as:

IWMEncoder2* g_pEncoder=NULL; 
CoCreateInstance(CLSID_WMEncoder,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncoder2,(void**)&g_pEncoder);

The Encoder object thus created contains all the operations for working with the captured screen data. However, in order to perform its operations properly, the encoder object depends on the settings defined in what is called a profile. A profile is nothing but a file containing all the settings that control the encoding operations. We can also create custom profiles at runtime with various customized options, such as codec options etc., depending on the nature of the captured data. To use a profile with our screen capture application, we create a custom profile based on the Windows Media Video 9 Screen codec. Custom profile objects have been supported with the interfaceIWMEncProfile2. We can create a custom profile object by using the CoCreateInstance() function as:

IWMEncProfile2* g_pProfile=NULL;
CoCreateInstance(CLSID_WMEncProfile2,NULL,CLSCTX_INPROC_SERVER,
        IID_IWMEncProfile2,(void**)&g_pProfile);

We need to specify the target audience for the encoder in the profile. Each profile can hold multiple number of audience configurations, which are objects of the interface IWMEncAudienceObj. Here, we use one audience object for our profile. We create the audience object for our profile by using the methodIWMEncProfile::AddAudience(), which would return a pointer to IWMEncAudienceObj which can then be used for configurations such as video codec settings (IWMEncAudienceObj::put_VideoCodec()), video frame size settings (IWMEncAudienceObj::put_VideoHeight() and IWMEncAudienceObj::put_VideoWidth()) etc. For example, we set the video codec to be Windows Media Video 9 Screen codec as:

extern IWMEncAudienceObj* pAudience;
#define VIDEOCODEC MAKEFOURCC('M','S','S','2') 
    //MSS2 is the fourcc for the screen codec

long lCodecIndex=-1;
g_pProfile->GetCodecIndexFromFourCC(WMENC_VIDEO,VIDEOCODEC,
    &lCodecIndex); //Get the Index of the Codec
pAudience->put_VideoCodec(0,lCodecIndex);

The fourcc is a kind of unique identifier for each codec in the world. The fourcc for the Windows Media Video 9 Screen codec is MSS2. The IWMEncAudienceObj::put_VideoCodec() accepts the profile index as the input to recognize a particular profile – which can be obtained by using the method IWMEncProfile::GetCodecIndexFromFourCC().

Once we have completed configuring the profile object, we can choose that profile into our encoder by using the method IWMEncSourceGroup :: put_Profile() which is defined on the source group objects of the encoder. A source group is a collection of sources where each source might be a video stream or audio stream or HTML stream etc. Each encoder object can work with many source groups from which it get the input data. Since our screen capture application uses only a video stream, our encoder object need to have one source group with a single source, the video source, in it. This single video source needs to configured to use the Screen Device as the input source, which can be done by using the method IWMEncVideoSource2::SetInput(BSTR) as:

extern IWMEncVideoSource2* pSrcVid;
pSrcVid->SetInput(CComBSTR("ScreenCap://ScreenCapture1");

The destination output can be configured to save into a video file (wmv movie) by using the methodIWMEncFile::put_LocalFileName() which requires an IWMEncFile object. This IWMEncFile object can be obtained by using the method IWMEncoder::get_File() as:

IWMEncFile* pOutFile=NULL;
g_pEncoder->get_File(&pOutFile);
pOutFile->put_LocalFileName(CComBSTR(szOutputFileName);

Now, once all the necessary configurations have been done on the encoder object, we can use the methodIWMEncoder::Start() to start capturing the screen. The methods IWMEncoder::Stop() andIWMEncoder::Pause might be used for stopping and pausing the capture.

While this deals with full screen capture, we can alternately select the regions of capture by adjusting the properties of input video source stream. For this, we need to use the IPropertyBag interface of the IWmEnVideoSource2 object as:

#define WMSCRNCAP_WINDOWLEFT CComBSTR("Left")
#define WMSCRNCAP_WINDOWTOP CComBSTR("Top")
#define WMSCRNCAP_WINDOWRIGHT CComBSTR("Right")
#define WMSCRNCAP_WINDOWBOTTOM CComBSTR("Bottom")
#define WMSCRNCAP_FLASHRECT CComBSTR("FlashRect")
#define WMSCRNCAP_ENTIRESCREEN CComBSTR("Screen")
#define WMSCRNCAP_WINDOWTITLE CComBSTR("WindowTitle")
extern IWMEncVideoSource2* pSrcVid;
int nLeft, nRight, nTop, nBottom;
pSrcVid->QueryInterface(IID_IPropertyBag,(void**)&pPropertyBag);
CComVariant varValue = false;
pPropertyBag->Write(WMSCRNCAP_ENTIRESCREEN,&varValue);
varValue = nLeft;
pPropertyBag->Write( WMSCRNCAP_WINDOWLEFT, &varValue );
varValue = nRight;
pPropertyBag->Write( WMSCRNCAP_WINDOWRIGHT, &varValue );
varValue = nTop;
pPropertyBag->Write( WMSCRNCAP_WINDOWTOP, &varValue );
varValue = nBottom;
pPropertyBag->Write( WMSCRNCAP_WINDOWBOTTOM, &varValue );

The accompanied source code implements this technique for capturing the screen. One point that might be interesting, apart from the nice quality of the produced output movie, is that in this, the mouse cursor is also captured. (By default, GDI and DirectX are unlikely to capture the mouse cursor).

Note that your system needs to be installed with Windows Media 9.0 SDK components to create applications using the Window Media 9.0 API.

To run your applications, end users must install the Windows Media Encoder 9 Series. When you distribute applications based on the Windows Media Encoder SDK, you must also include the Windows Media Encoder software, either by redistributing Windows Media Encoder in your setup, or by requiring your users to install Windows Media Encoder themselves.

The Windows Media Encoder 9.0 can be downloaded from:

Conclusion

All the variety of techniques discussed above are aimed at a single goal – capturing the contents of the screen. However, as can be guessed easily, the results vary depending upon the particular technique that is being employed in the program. If all that we want is just a random snapshot occasionally, the GDI approach is a good choice, given its simplicity. However, using Windows Media would be a better option if we want more professional results. One point worth noting is, the quality of the content captured through these mechanisms might depend on the settings of the system. For example, disabling hardware acceleration (Desktop properties | Settings | Advanced | Troubleshoot) might drastically improve the overall quality and performance of the capture application.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)

How can I return a string from a C dll to C#?

// c code:
#include <ole2.h> /* needed for CoTaskMemAlloc */
EXPORT char* getUrlFromBaseURLInList(char *ListFilePath, char *SrchBaseURL, char* strBuffer)
{
    static const char[] sTest =  "Test My String";
    /* you must use CoTaskMemAlloc to allocate the memory, not malloc, new, or anything else */
    char* returnedString = CoTaskMemAlloc(sizeof(sTest));
    strcpy(returnedString, sTest);
    return returnedString;
}

// c# code:
[DllImport("SLSSeoUrlFunc.dll", CharSet=CharSet.Ansi, CallingConvention=CallingConvention.Cdecl)]
public static extern string getUrlFromBaseURLInList(byte[] ListFilePath, byte[] sSrchUrl);

string myTestString = SLSSeoUrlFuncDllWrap.getUrlFromBaseURLInList(null, null);
Console.WriteLine("Eureka - {0}", myTestString);

svn、bugzilla等穿过防火墙访问:如何在 Windows 防火墙中打开端口

如果 Windows 防火墙阻止某一程序,而您希望允许该程序通过防火墙进行通信,通常可以通过在 Windows 防火墙允许的程序列表(也称为“例外列表”)中选中该程序来实现。若要了解如何进行此操作,请参阅允许程序通过 Windows 防火墙进行通信

但是,如果没有列出该程序,则可能需要打开一个端口。例如,当您与朋友联机进行多人游戏时,可能需要为该游戏打开一个端口,这样防火墙才能允许游戏信息到达您的计算机。端口始终保持打开状态,因此请确保关闭不需要打开的端口。

  1. 通过单击「开始」按钮 「开始」按钮的图片,然后单击“控制面板”,打开“Windows 防火墙”。 在搜索框中,键入防火墙,然后单击“Windows 防火墙”

  2. 在左窗格中,单击“高级设置” 需要管理员权限 如果系统提示您输入管理员密码或进行确认,请键入该密码或提供确认。

  3. “高级安全 Windows 防火墙”对话框的左窗格中,单击“入站规则”,然后在右窗格中,单击“新建规则”

  4. 按照新建入站规则向导中的说明进行操作。

如果您无法通过 Windows 防火墙让其他计算机与您的计算机通信,则可以尝试使用“传入连接”疑难解答自动查找并修复一些常见问题。

通过单击“开始”按钮 「开始」按钮的图片,然后单击“控制面板”,打开“传入的连接”疑难解答。在搜索框中,键入疑难解答,然后单击“疑难解答”。单击“查看全部”,然后单击“传入的连接”

解决 error C1083: 无法打开预编译头文件xxx.pch: No such file or directory

问题产生:

通常情况下不会出现这种错误,我出现这样的错误提示是因为,我要发布的是一个静态库,于是把要发布的头文件放到了include目录下,把其他文件(包括stdAfx.h及stdAfx.cpp)放到了src目录下,于是编译时提示:

错误 1 error C1083: 无法打开预编译头文件:“Debug\PlotData.pch”: No such file or directory e:\mcp prj\tmdataplotting\plotdata\src\stdafx.cpp 5 1 PlotData

 

解决办法:

在解决方案资源管理器中:

1、在stdafx.cpp上右键–>属性

2、弹出的stdafx.cpp属性框中,选择配置属性–>C/C++–>预编译头

3、在预编译头中选择“创建”,而不是原来默认的“使用”

4、其他cpp文件默认“使用”就OK了

 

 PS:

预编译头.pch文件是咋回事?

编译器一般都是以文件为单位进行编译,如果修改了工程中的一个文件,那么将导致所有文件都要从新编译,这样的编译将耗费很长时间。
为了提高编译速度,将那些不常被修改,比较稳定,文件单独包含到一个指定的头文件中, 然后生成一个预编译头文件 *.pch 。

VC中默认的头文件为 stdAfx.h, 但光有头文件无法编译,所以还要用到 StdAfx.cpp里只包含一句有效代码,这样编译时,通过编译 stdAfx.cpp就把大部分系统头文件编译进来, Debug目录下便会产生一个 存储了预编译信息的 *.pch 文件。
如果 *.pch文件损坏或者不存在,就会出现,题目所示的无法打开预编译头文件的错。

 

可见,你也可以在项目属性中选择“不使用预编译头”,但是编译大项目时可能会很慢

 

 

【屌丝们,让老夫来拯救你们吧!】MAC OSX 10.9 下通过U盘安装Win8/Win8.1

这顿折腾就不说了,折磨啊,苦逼的想谋害心爱的MBP了,重点是,最后参考了官网的说明,终于定位到了问题所在:10.9后BootCamp更新到了5.1了,这个版本只支持部分操作系统的安装,不是所有的Win7、Win8版本都可以安装!或者说,Win8的有些版本对硬件(尤其是硬盘)的要求,决定了BootCamp只能支持部分系统的安装,而不是所有!!!

 

apple官网Frequently asked questions提到:

Which versions of Windows work with Boot Camp 5.1?
You can use:

  • Microsoft Windows 8.0 and 8.1 (64-bit)
  • Microsoft Windows 8.0 and 8.1 Pro (64-bit)
  • Microsoft Windows 7 Home Premium (64-bit)
  • Microsoft Windows 7 Professional (64-bit)
  • Microsoft Windows 7 Ultimate (64-bit)

Windows XP, Windows Vista, and Enterprise versions of Windows 7 and Windows 8 are not supported.

通常情况下,大部分人按照网上满天飞的MBP安装Win8的“教程”还是都顺利装上了,但是一部分人就没这么幸运了,没被折磨死也差不多半死了,最后还是无功而返,废话就不说了。

MacBookPro上通过BootCamp安装Win8通常要解决两个问题:

1、制作U盘Win8安装盘

通常情况下,带有光驱的MBP是“无法”通过BootCamp用U盘来制作Win8启动盘的,这也许是因为Apple的人认为有了光驱,干嘛还要用U盘安装呢?实际上,各种需求各种可能都有——光驱被替换成固态硬盘?光驱坏了?俺就没有光盘安装盘?U盘满天飞,就想用U盘装?我是技术控,就想玩玩U盘安装的心跳???

这部分人就必须破解BootCamp了,方法如下所术,精彩的是第四步,因为Mac OSX的10.9不像10.8了):

So basically, I have trying to install windows on my mbp using a usb drive. However bootcamp wont allow me to do so since I have a optical drive on the laptop. I have been searching for a long time and eventually came across this solution and I would like to share this so u guys dont have to google all over the place again .

The solutions given before by changing info.plist is correct except that now Bootcamp crashes everytime you change it in OSX 10.9.

 

Full solution:

 

1. Add your Boot Rom Version(from system info) under DARequiredROMVersions.

2. Add Model Identifier(from system info) under PreUSBBootSupportedModels

3. Delete “Pre” from “PreUSBBootSupportedModels”, so you have “USBBootSupportedModels”

 

The first 3 steps are same as before and if its not clear you can easily google solutions with screenshots.

The next step is only for OSX 10.9, as it employs some kind of code signature to prevent you from changing info.plist and cause bootcamp to crash.

 

4. Open your terminal, use the following command

sudo codesign -fs – /Applications/Utilities/Boot\ Camp\ Assistant.app

 

Sudo means using administrator privilege and u need to enter your mac password. And the command resigns the bootcamp application so that it runs with the new info.plist file and not crash.

 

5. Continue on with your installation….

 

Cheers.

 

2、安装

启动盘成功制作完毕(或者已经有了U盘启动盘的同学),接下来就可以进入安装步骤了,悲剧的就在这一步:一开始就用错Win8版本,那么安装到选择安装盘时,总是提示:无法在带有MBR的硬盘上安装系统,要求必须装载PGT硬盘上!!!

这就是为什么,同样的教程,一拨人乐呵呵的装完了,一拨人却倒下了,无论如何也装不上,各种猜测各种尝试均失败,苦逼啊。

10.9后BootCamp更新到了5.1了,这个版本不是所有的Win7、Win8版本都可以安装:

apple官网Frequently asked questions提到:

Which versions of Windows work with Boot Camp 5.1?
You can use:

  • Microsoft Windows 8.0 and 8.1 (64-bit)
  • Microsoft Windows 8.0 and 8.1 Pro (64-bit)
  • Microsoft Windows 7 Home Premium (64-bit)
  • Microsoft Windows 7 Professional (64-bit)
  • Microsoft Windows 7 Ultimate (64-bit)

Windows XP, Windows Vista, and Enterprise versions of Windows 7 and Windows 8 are not supported.

看到了吧????要多人走狗屎运,选对了iso版本,所以过了,儿有些人踩到了狗屎,选什么企业版,悲催的命运从一开始就注定了,而整个过程怀疑各种可能,就没怀疑到这里!

 

Enable Bootcamp to install from usb for OSX 10.9**WORKS**

要不说老外还是牛逼啊:

 

So basically, I have trying to install windows on my mbp using a usb drive. However bootcamp wont allow me to do so since I have a optical drive on the laptop. I have been searching for a long time and eventually came across this solution and I would like to share this so u guys dont have to google all over the place again .

The solutions given before by changing info.plist is correct except that now Bootcamp crashes everytime you change it in OSX 10.9.

 

Full solution:

 

1. Add your Boot Rom Version(from system info) under DARequiredROMVersions.

2. Add Model Identifier(from system info) under PreUSBBootSupportedModels

3. Delete “Pre” from “PreUSBBootSupportedModels”, so you have “USBBootSupportedModels”

 

The first 3 steps are same as before and if its not clear you can easily google solutions with screenshots.

The next step is only for OSX 10.9, as it employs some kind of code signature to prevent you from changing info.plist and cause bootcamp to crash.

 

4. Open your terminal, use the following command

sudo codesign -fs – /Applications/Utilities/Boot\ Camp\ Assistant.app

 

Sudo means using administrator privilege and u need to enter your mac password. And the command resigns the bootcamp application so that it runs with the new info.plist file and not crash.

 

5. Continue on with your installation….

 

Cheers.

 

P.S. back up info.plist before u change anything.

用U盘在Macbook Pro上安装Win8(双系统/3分区)

帮朋友刚买的MBP装Win8,2012年中的13寸MBP,系统OS X 10.8.2,硬盘500G,

最终结果为:OS X系统分区+Win8系统分区+Exfat数据共享分区(不装插件共享数据)。

OS X新版本中BOOTCAMP已无需破解就能支持用U盘安装Windows,简略记录步骤如下:

 工具/原料

  • Win8 ISO & KEY

  • Win7 ISO or 安装盘

  • U盘>4G

 方法/步骤

  1. 1

    先保证Mac整个硬盘只有一个GUID分区,

    提前准备好Win8的ISO和KEY,Win7的ISO,或者先准备好Win7的安装盘。

  2. 2

    插入U盘(>4G吧?)运行BOOTCAMP按说明操作,并分100G给Win8做系统分区,

    BOOTCAMP做好U盘安装盘后会下载驱动然后就开始安装Win8。

  3. 3

    在Win8安装过程中格式化标记为BOOT CAMP的分区并将Win8安装于此分区,

    装好Win8后进Win8安装驱动(执行U盘内BootCamp文件夹里的setup)。

  4. 4

    重启按option回OS X,将剩下的苹果系统的分区分出200多G当数据共享分区,留下100G做苹果的系统分区,打开磁盘工具 – 选择硬盘 – 分区 – 点击装OS X的分区 – 按“+”划分出数据共享分区 – 格式为Exfat – 命名设置大小后点应用。

  5. 5

    分好数据共享分区之后重启按option会发现Win8的启动盘没了,

    想像装Win7那样用安装盘修复启动项也发现Win8的安装盘没有这个功能,

    这时候不要着急不要放弃,行百里者半九十;拿出Win7安装盘或现做一个,

    换Win7的安装盘插入,开机按option启动安装盘并修复启动项即可顺利进入Win8

     

本文转自:http://www.chinamac.com/2013/0912/2234.html

 

在iOS设备上运行python

作者: 张初阳

iOS上其实有很多优秀的跨平台项目,比如RubyMotion,能作出一些简单的UI逻辑了。

最近因为分析数据的缘故需要用python,昨天突然想在iOS上跑python,毕竟有的时候iPad还是挺好的。可是老婆的iPad没有越狱,而且被我升级了iOS6,所以不能通过shell来运行。

网上搜索了下有Python for iOS,应该很不错,可是有些贵,于是想想自己能不能也写一个?

应该可以吧。

其实很久之前就有了在iOS上假设更高级语言的IDE的想法,毕竟iOS就是一个BSD。设备有了Cydia之后拥有更多的权限和更多的API,甚至底层的调用,自由度会很大。至于Cydia的延伸行为我就不发表意见了。

我记得首先在iOS上出现的IDE是关于html5的,这个很好理解,有webkit对吧。

言归正传,我使用的是boost的lib,然后加上python到c的翻译,原理很简单。

这是一个简单的测试:

b_large_xwiw_7cc400000a611262

主要的工作就是编译boost到ios上面,我已经把boost和python-c两个文件价上传到了baidu网盘。

链接地址在下面,只要直接加入工程就好了。网上还有一种方法是做成动态链接库,链接地址在下面,过程老长老长了- -不过是一个一劳永逸的方法,我提供的文件每个工程都需要添加,而且比较大。

boost:http://pan.baidu.com/share/link?shareid=68742&uk=1194307691

python-c:http://pan.baidu.com/share/link?shareid=68743&uk=1194307691

另外给点福利,iOS的越狱入门书,别理解错了,是教你怎么自己动手不用工具越狱ios设备并获得高级权限,也就是ios黑客的入门教材。

连接在下面:

http://pan.baidu.com/share/link?shareid=68809&uk=1194307691

其实开发不难,重在参与。

Xcode 快捷键

0.跳到指定行command + L;

1.配置高亮方案:Fonts & Colors

2.键盘快捷键:Key Bindings

3.代码缩进:Re-indent selection。(快捷键CTRL+I)

4.代码自动完成:Table(代码上屏),Esc(打开自动完成列表)

5.command+[  (代码左移),花+] (代码右移)

6.双击某个“括号匹配”的某个分隔符(如 {  } ( ) [ ] 等),Xcode会选中它以及与它匹配的括号之间的全部代码。

7.快速重命名局部变量名(右键-Edit All In Scope)command+control+E

8.重构(右键-Reflector…)

9.常用光标控制按键:(也适用与TextEdit,Safari URL地址栏等文本域)上 下 左 右 箭头     control-A:移动到行首(同command+左箭头) control-E:移动到行尾(End,同command+右箭头)control-K:删除(Kill)光标所在行中光标后的代码,便于你重写行尾的代码。

10.切换到头文件或源码文件:command + option + 上箭头,可以在头文件与源码文件之间快速切换。菜单为View – Switch Head/Source File。或者单击代码导航条的最后一个控件。

11.书签:添加书签(command – D,跟浏览器里的添加书签快捷键一样),可以在项目Groups&Files下的Bookmarks下看到添加的书签

12.command+shift+E:显示/隐藏代码屏幕上面的浏览器窗格

13.代码折叠:单击左边的灰色竖线即可。(View-Code Folding)

14.代码功能注释:

// MARK:  ***(标签,功能同#pragma mark ***),
// TODO: ***
// FIXME: ***
// !!!: ***
// ???: ***

15.Control-2: 快速浏览当前文件的成员列表

16.帮助:快速帮助(option+单击),文档搜索(option+双击)

17.修改Xcode代码自动完成的左花括号单独成行(if语句的下一行):

运行Terminal,输入

defaults write com.apple.Xcode XCCodeSenseFormattingOptions -dict BlockSeparator “n”
后重启Xcode即应用。

18  command +  /      快速注释或取消注释

[转]iOS中NSUserDefaults的用法(轻量级本地数据存储)

NSUserDefaults适合存储轻量级的本地数据,比如要保存一个登陆界面的数据,用户名、密码之类的,个人觉得使用NSUserDefaults是首选。下次再登陆的时候就可以直接从NSUserDefaults里面读取上次登陆的信息咯。

因为如果使用自己建立的plist文件什么的,还得自己显示创建文件,读取文件,很麻烦,而是用NSUserDefaults则不用管这些东西,就像读字符串一样,直接读取就可以了。

NSUserDefaults支持的数据格式有:NSNumber(Integer、Float、Double),NSString,NSDate,NSArray,NSDictionary,BOOL类型。很实用吧

NSUserDefaults很方便,读取也很容易。下面给出一个示例看看如何使用:(PS:更详细的也可以参考官方文档哈)

ViewController.h文件中主要是放几个控件,用于显示存储的数据:

  1. #import <UIKit/UIKit.h>
  2. @interface ViewController : UIViewController
  3. {
  4.     IBOutlet UILabel *txtInteger;
  5.     IBOutlet UILabel *txtFloat;
  6.     IBOutlet UILabel *txtDouble;
  7.     IBOutlet UILabel *txtNSString;
  8.     IBOutlet UILabel *txtNSDate;
  9.     IBOutlet UILabel *txtNSArray;
  10.     IBOutlet UILabel *txtNSDictionary;
  11. }
  12. @end

ViewController.m文件中最重要的是两个方法:saveNSUserDefaults:用于将各种类型数据保存到NSUserDefaults中

readNSUserDefautls:用于从NSUserDefaults中读取各种类型的数据。在viewDidLoad中调用这两个方法就可以看出结果咯

  1. #import “ViewController.h”
  2. @interface ViewController ()
  3. @end
  4. @implementation ViewController
  5. – (void)viewDidLoad
  6. {
  7.     [super viewDidLoad];
  8.     [self saveNSUserDefaults];  //调用此方法将各种数据存储到NSUserDefautls中,在下面定义
  9.     [self readNSUserDefaults];  //调用此方法从NSUserDefautls中读取各种数据,在下面定义
  10. }
  11. – (void)viewDidUnload
  12. {
  13.     [txtNSString release];
  14.     txtNSString = nil;
  15.     [txtNSDate release];
  16.     txtNSDate = nil;
  17.     [txtNSArray release];
  18.     txtNSArray = nil;
  19.     [txtNSDictionary release];
  20.     txtNSDictionary = nil;
  21.     [txtInteger release];
  22.     txtInteger = nil;
  23.     [txtFloat release];
  24.     txtFloat = nil;
  25.     [txtDouble release];
  26.     txtDouble = nil;
  27.     [super viewDidUnload];
  28.     // Release any retained subviews of the main view.
  29. }
  30. – (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
  31. {
  32.     return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
  33. }
  34. – (void)dealloc {
  35.     [txtNSString release];
  36.     [txtNSDate release];
  37.     [txtNSArray release];
  38.     [txtNSDictionary release];
  39.     [txtInteger release];
  40.     [txtFloat release];
  41.     [txtDouble release];
  42.     [super dealloc];
  43. }
  44. //保存数据到NSUserDefaults
  45. -(void)saveNSUserDefaults
  46. {
  47.     NSString *myString = @”enuola”;
  48.     int myInteger = 100;
  49.     float myFloat = 50.0f;
  50.     double myDouble = 20.0;
  51.     NSDate *myDate = [NSDate date];
  52.     NSArray *myArray = [NSArray arrayWithObjects:@”hello”, @”world”, nil];
  53.     NSDictionary *myDictionary = [NSDictionary dictionaryWithObjects:[NSArray arrayWithObjects:@”enuo”, @”20″, nil] forKeys:[NSArray arrayWithObjects:@”name”, @”age”, nil]];
  54.     //将上述数据全部存储到NSUserDefaults中
  55.     NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
  56.     //存储时,除NSNumber类型使用对应的类型意外,其他的都是使用setObject:forKey:
  57.     [userDefaults setInteger:myInteger forKey:@”myInteger”];
  58.     [userDefaults setFloat:myFloat forKey:@”myFloat”];
  59.     [userDefaults setDouble:myDouble forKey:@”myDouble”];
  60.     [userDefaults setObject:myString forKey:@”myString”];
  61.     [userDefaults setObject:myDate forKey:@”myDate”];
  62.     [userDefaults setObject:myArray forKey:@”myArray”];
  63.     [userDefaults setObject:myDictionary forKey:@”myDictionary”];
  64.     //这里建议同步存储到磁盘中,但是不是必须的
  65.     [userDefaults synchronize];
  66. }
  67. //从NSUserDefaults中读取数据
  68. -(void)readNSUserDefaults
  69. {
  70.     NSUserDefaults *userDefaultes = [NSUserDefaults standardUserDefaults];
  71.     //读取数据到各个label中
  72.     //读取整型int类型的数据
  73.     NSInteger myInteger = [userDefaultes integerForKey:@”myInteger”];
  74.     txtInteger.text = [NSString stringWithFormat:@”%d”,myInteger];
  75.     //读取浮点型float类型的数据
  76.     float myFloat = [userDefaultes floatForKey:@”myFloat”];
  77.     txtFloat.text = [NSString stringWithFormat:@”%f”,myFloat];
  78.     //读取double类型的数据
  79.     double myDouble = [userDefaultes doubleForKey:@”myDouble”];
  80.     txtDouble.text = [NSString stringWithFormat:@”%f”,myDouble];
  81.     //读取NSString类型的数据
  82.     NSString *myString = [userDefaultes stringForKey:@”myString”];
  83.     txtNSString.text = myString;
  84.     //读取NSDate日期类型的数据
  85.     NSDate *myDate = [userDefaultes valueForKey:@”myDate”];
  86.     NSDateFormatter *df = [[NSDateFormatter alloc] init];
  87.     [df setDateFormat:@”yyyy-MM-dd HH:mm:ss”];
  88.     txtNSDate.text = [NSString stringWithFormat:@”%@”,[df stringFromDate:myDate]];
  89.     //读取数组NSArray类型的数据
  90.     NSArray *myArray = [userDefaultes arrayForKey:@”myArray”];
  91.     NSString *myArrayString = [[NSString alloc] init];
  92.     for(NSString *str in myArray)
  93.     {
  94.         NSLog(@”str= %@”,str);
  95.         myArrayString = [NSString stringWithFormat:@”%@  %@”, myArrayString, str];
  96.         [myArrayString stringByAppendingString:str];
  97. //        [myArrayString stringByAppendingFormat:@”%@”,str];
  98.         NSLog(@”myArrayString=%@”,myArrayString);
  99.     }
  100.     txtNSArray.text = myArrayString;
  101.     //读取字典类型NSDictionary类型的数据
  102.     NSDictionary *myDictionary = [userDefaultes dictionaryForKey:@”myDictionary”];
  103.     NSString *myDicString = [NSString stringWithFormat:@”name:%@, age:%d”,[myDictionary valueForKey:@”name”], [[myDictionary valueForKey:@”age”] integerValue]];
  104.     txtNSDictionary.text = myDicString;
  105. }
  106. @end

好了,运行一下,可以看到xib文件中的各种数据已经绑定上了吧?1

再次运行的时候,可以把viewDidLoad中的  [self  saveNSUserDefaults];  这一行注释掉,让程序直接读取而不存储数据,发现以前保存的数据仍然可以读取到界面上。

嘻嘻,很简单吧,就这样就可以是实现数据的存储了。

下面讲一下原理:

你可能会问一个问题:NSUserDefautls将数据存储在什么地方了???我都没有显示的指定路径???很疑惑吧。。。。

用NSUserDefaults存储的数据下次程序运行的时候依然存在,它把数据存储在什么地方了?如何能够清除?

其实它存储在应用程序内置的一个plist文件里,这个可以根据路径看到。
比如说这个是你的程序沙盒位置
/UsersLibrary/Application Support/iPhoneSimulator/4.1/Applicati*****/29788E40-AF47-45A0-8E92-3AC0F501B7F4/,(这个是应用程序对应在mac上的位置)
这个下面有/Library/Prefereces,里面有个plist文件,存储的就是你的userDefaults
想要删掉的话,用removeObjectForKey或者删掉沙盒,也就是你的应用程序然后重新安装。

解决 “this class is not key value coding-compliant for the key view”问题

在iPhone开发中,由于喜欢比较干净的环境,所以放弃了StoreBoard,改用手工创建xib文件和controller,结果删除storeboard、添加xib及其控制后,怎么也无法启动,报错:

Terminating app due to uncaught exception ‘NSUnknownKeyException’, reason: ‘[<UIApplication 0x14568a10> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key view.’

*** First throw call stack:

(0x2ef1ef4b 0x3935f6af 0x2ef1ec61 0x2f85e18b 0x2f86fdd5 0x2ee8ee6f 0x319f0f87 0x319f27eb 0x3190a94b 0x316fc265 0x3169741f 0x31696721 0x316fbb3d 0x33b7a70d 0x33b7a2f7 0x2eee99df 0x2eee997b 0x2eee814f 0x2ee52c27 0x2ee52a0b 0x316fadd9 0x316f6049 0xa4235 0x39867ab7)

libc++abi.dylib: terminating with uncaught exception of type NSException

(lldb)

 

stackoverflow中的解释是File’s Owner没有跟他的view连接导致,实际上我试验过,根本不是,而是因为我的app delegate中的代码也是自己写的,通过手动装载xib文件:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.viewController = [[RootViewController alloc] initWithNibName:@"RootView" bundle:nil];

    UINavigationController *nationController = [[UINavigationController alloc] initWithRootViewController:self.viewController];

    self.window.rootViewController = nationController;
    [self.window makeKeyAndVisible];
    return YES;
}

 

一是想到,既然我手动装载了RootView,那么就没必要在File’s Owner中将RootViewController关联给xib:

1

 

一是马上又想到,既然是我自己在app delegate中装载了根视图,那么在项目设置中也没必要指定启动视图,在Main Interface中留空就行:

2

而我在删除storeboard时想到,既然默认启动是storeboard,现在被删除了,是不是也要指定一个默认装载视图,于是在Main Interface中填入了一个rootview。

就是第二个想法修复了我的问题,原因嘛,只能是猜测:你自己手工装载了一份;由于你又在Main Interface中指定了一个,所以系统编译时会指定要启动的默认视图,结果你手工装载的视图把系统默认装载的视图替换了,导致错误的产生。

 

 

 

 

 

 

vs2010 error lnk2001无法解析的外部符号

一个项目需要引用另一个静态库,而这个静态库中有个全局变量,这样的全局变量只有静态连接性能,编译时不报错,链接时则会报LNK2001错误。

解决办法也很怪异,你需要再次在主程序需要用到的地方申明一个同名全局变量,这样则能消除链接错误!

Settings g_Settings;

 

============================================

这里有一篇文章详细讲解了LNK2001错误:

 

vs2010 error LNK2001: 无法解析的外部符号

        学习VC++时经常会遇到链接错误LNK2001,该错误非常讨厌,因为对于 编程者来说,最好改的错误莫过于编译错误,而一般说来发生连接错误时, 编译都已通过。产生连接错误的原因非常多,尤其LNK2001错误,常常使人不 明其所以然。如果不深入地学习和理解VC++,要想改正连接错误LNK2001非 常困难。
初学者在学习VC++的过程中,遇到的LNK2001错误的错误消息主要为: unresolved   external   symbol   “symbol”(不确定的外部“符号”)。 如果连接程序不能在所有的库和目标文件内找到所引用的函数、变量或 标签,将产生此错误消息。一般来说,发生错误的原因有两个:一是所引用 的函数、变量不存在、拼写不正确或者使用错误;其次可能使用了不同版本 的连接库。
以下是可能产生LNK2001错误的原因:
一.由于编码错误导致的LNK2001。
1.不相匹配的程序代码或模块定义(.DEF)文件能导致LNK2001。例如,   如果在C++   源文件内声明了一变量“var1”,却试图在另一文件内以变量 “VAR1”访问该变量,将发生该错误。
2.如果使用的内联函数 是在.CPP文件内定义的,而不是在头文件 内定义将导致LNK2001错误。
3.调用函数时如果所用的参数类型同函数声明时的类型不符将会产生 LNK2001。
4.试图从基类 的构造函数 或析构函数 中调用虚拟函数时将会导致LNK2001。
5.要注意函数和变量的可公用性,只有全局变量 、函数是可公用的。
静态函数 和静态变量 具有相同的使用范围限制。当试图从文件外部访问 任何没有在该文件内声明的静态变量时将导致编译错误或LNK2001。 函数内声明的变量(局部变量)   只能在该函数的范围内使用。
C++   的全局常量只有静态连接性能。这不同于C,如果试图在C++的 多个文件内使用全局变量也会产生LNK2001错误。一种解决的方法是需要时在 头文件中加入该常量的初始化代码,并在.CPP文件中包含该头文件;另一种 方法是使用时给该变量赋以常数 。
二.由于编译和链接的设置而造成的LNK2001
1.如果编译时使用的是/NOD(/NODEFAULTLIB)选项,程序所需要的运行 库和MFC库在连接时由编译器 写入目标文件模块,   但除非在文件中明确包含 这些库名,否则这些库不会被链接进工程文件。在这种情况下使用/NOD将导 致错误LNK2001。
2.如果没有为wWinMainCRTStartup设定程序入口,在使用Unicode和MFC 时将得到“unresolved   external   on   _WinMain@16”的LNK2001错误信息。
3.使用/MD选项编译时,既然所有的运行库都被保留在动态链接库之内, 源文件中对“func”的引用,在目标文件里即对“__imp__func”   的引用。 如果试图使用静态库LIBC.LIB或LIBCMT.LIB进行连接,将在__imp__func上发 生LNK2001;如果不使用/MD选项编译,在使用MSVCxx.LIB连接时也会发生LNK2001。
4.使用/ML选项编译时,如用LIBCMT.LIB链接会在_errno上发生LNK2001。
5.当编译调试版的应用程序时,如果采用发行版模态库进行连接也会产 生LNK2001;同样,使用调试版模态库连接发行版应用程序时也会产生相同的 问题。
6.不同版本的库和编译器的混合使用也能产生问题,因为新版的库里可 能包含早先的版本没有的符号和说明。
7.在不同的模块使用内联和非内联的编译选项 能够导致LNK2001。如果 创建C++库时打开了函数内联(/Ob1或/Ob2),但是在描述该函数的相应头 文件里却关闭了函数内联(没有inline关键字 ),这时将得到该错误信息。 为避免该问题的发生,应该在相应的头文件中用inline关键字标志内联函数。
8.不正确的/SUBSYSTEM或/ENTRY设置也能导致LNK2001。
其实,产生LNK2001的原因还有很多,以上的原因只是一部分而已,对初 学者来说这些就够理解一阵子了。但是,分析错误原因的目的是为了避免错 误的发生。LNK2001错误虽然比较困难,但是只要注意到了上述问题,还是能 够避免和予以解决的。

CoreTelephony框架

iOS 4.0 的官方 API 里头,多了一个叫做 Core Telephony 的 framework;一直以来 Core Telephony 都是 private API,现在开放出来,但是从文件来看,里头根本没有几行,既没有告诉你应该怎么用,也没有范例,你从 framework 里头寥寥四个 class 的 header 中,也搞不清楚,究竟可以把这个东西用在什么用途上。

目前只知道可以拿来做两件事情:1. 知道目前你这只 iPhone 用的是哪个电信商的服务;2. 知道现在 iPhone 是不是在打电话。

※ 电信商资讯

用 CTTelephonyNetworkInfo 与 CTCarrier 这两个 class,就可以取得电信商资讯,例如:

CTTelephonyNetworkInfo *info = [[CTTelephonyNetworkInfo alloc] init];
CTCarrier *carrier = info.subscriberCellularProvider;
NSLog(@"carrier:%@", [carrier description]);

倒出来的结果像是:

CTCarrier (0x140dc0) {

    Carrier name: [中国移动]
    Mobile Country Code: [466]
    Mobile Network Code:[92]
    ISO Country Code:[tw]
    Allows VOIP? [YES]
}

 

然后,如果你对 CTTelephonyNetworkInfo 喂一个 block 进去,像是:

info.subscriberCellularProviderDidUpdateNotifier = ^(CTCarrier *carrier) {NSLog(@"carrier:%@", [carrier description]);};

 

如 此一来,当你的 iPhone 漫游到了其他网路的时候,就会执行你这段 block,但光是知道手机现在漫游在哪个电信商的网路里头,大概能做的,就是一些跟电信商关系密切的服务之类,你或许可以决定软体里头有哪些功能,一定 要在某个电信商的网路才能用;电信商自己做 iPhone 软体的时候大概会想做这种事情。
※ 通话资料

用 CTCallCenter 与 CTCall 这两个 class,便可以知道目前 iPhone 是否在通话中。CTCallCenter 的用途是用来监控是不是有电话打进来、正在接听、或是已经挂断,而 CTCall 则是将每一则通话事件包装成一个物件。我们先写一小段程式-

CTCallCenter *center = [[CTCallCenter alloc] init];
center.callEventHandler = ^(CTCall *call) {
    NSLog(@"call:%@", [call description]);
};

然后,在实机上执行,接著打通电话到这支 iPhone 上,打通电话进去,然后马上挂断(人好端端的,干嘛为了测试程式跟自己的电话费帐单过不去呢?)就可以看到 iPhone 执行了我们的 block,把 CTCall 物件倒出来:

CTCall (0x143400) {
    callState: [CTCallStateIncoming]
    Call ID: [CE5F9337-1990-4254-8797-1CCEA85B061B]
}
CTCall (0x10bac0) {
    callState: [CTCallStateDisconnected]
    Call ID: [CE5F9337-1990-4254-8797-1CCEA85B061B]
}

 

CTCall 物件只有两个属性,一是通话状态(来电中、通话中…),二是这则通话的 unique id,除此之外没有其他资讯,你没办法知道这通电话是从哪里打来的,只能知道有电话进来而已,也没办法透过这个 API 打电话出去。

大抵上可以想到的用途,就是当你的程式执行到一半的时候,程式流程被电话打断,这时候就可能要中断原本正在做的事情,在通话结束之后恢复。

最后,CTCallCenter 与 CTTelephonyNetworkInfo,在模拟器上是没有办法用的,呼叫 alloc、init 之后回传的结果只会是 nil。

 

 

一种简便获取iPhone IMEI 的方法

 

使用CoreTelephony库,它是一个公开的framework,但很多API没有出现在文档中

iPhone私有API跟电话相关的CoreTelephony 里面提到了很多API, 其中有些可能跟电话录音有关系.

其中 _CTServerConnectionCopyMobileIdentity 就是用来获取IMEI的

#import

struct CTServerConnection
{
int a;
int b;
CFMachPortRef myport;
int c;
int d;
int e;
int f;
int g;
int h;
int i;
};

struct CTResult
{
int flag;
int a;
};

struct CTServerConnection * _CTServerConnectionCreate(CFAllocatorRef, void *, int *);

void _CTServerConnectionCopyMobileIdentity(struct CTResult *, struct CTServerConnection *, NSString **);
保存为 CoreTelephony.h

#import "CoreTelephony.h"

struct CTServerConnection *sc=NULL;
struct CTResult result;

void callback() { }

int main()
{
sc = _CTServerConnectionCreate(kCFAllocatorDefault, callback, NULL);

NSString *imei;
_CTServerConnectionCopyMobileIdentity(&result, sc, &imei);

NSLog (@"zhiwei's IMEI is %@", imei);

return 0;
}

IMSI

// 需要 CoreTelephony framework
// 在文件开头加入
extern NSString* CTSIMSupportCopyMobileSubscriberIdentity();

+ (NSString*) getDeviceIMSI {
    return CTSIMSupportCopyMobileSubscriberIdentity();
}

 

本机电话号码

// 需要 CoreTelephony framework
// 在文件开头加入
extern NSString* CTSettingCopyMyPhoneNumber();

+ (NSString*) getPhoneCodeByCT {
    return CTSettingCopyMyPhoneNumber();
}

 

本文转自:http://blog.csdn.net/kingkong1024/article/details/8363726

获得通讯录中联系人的所有属性

ABAddressBookRef addressBook = ABAddressBookCreate();

    CFArrayRef results = ABAddressBookCopyArrayOfAllPeople(addressBook);

    for(int i = 0; i < CFArrayGetCount(results); i++)
    {
        ABRecordRef person = CFArrayGetValueAtIndex(results, i);
        //读取firstname
        NSString *personName = (NSString*)ABRecordCopyValue(person, kABPersonFirstNameProperty);
        if(personName != nil)
            textView.text = [textView.text stringByAppendingFormat:@"\n姓名:%@\n",personName];
        //读取lastname
        NSString *lastname = (NSString*)ABRecordCopyValue(person, kABPersonLastNameProperty);
        if(lastname != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",lastname];
        //读取middlename
        NSString *middlename = (NSString*)ABRecordCopyValue(person, kABPersonMiddleNameProperty);
        if(middlename != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",middlename];
        //读取prefix前缀
        NSString *prefix = (NSString*)ABRecordCopyValue(person, kABPersonPrefixProperty);
        if(prefix != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",prefix];
        //读取suffix后缀
        NSString *suffix = (NSString*)ABRecordCopyValue(person, kABPersonSuffixProperty);
        if(suffix != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",suffix];
        //读取nickname呢称
        NSString *nickname = (NSString*)ABRecordCopyValue(person, kABPersonNicknameProperty);
        if(nickname != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",nickname];
        //读取firstname拼音音标
        NSString *firstnamePhonetic = (NSString*)ABRecordCopyValue(person, kABPersonFirstNamePhoneticProperty);
        if(firstnamePhonetic != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",firstnamePhonetic];
        //读取lastname拼音音标
        NSString *lastnamePhonetic = (NSString*)ABRecordCopyValue(person, kABPersonLastNamePhoneticProperty);
        if(lastnamePhonetic != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",lastnamePhonetic];
        //读取middlename拼音音标
        NSString *middlenamePhonetic = (NSString*)ABRecordCopyValue(person, kABPersonMiddleNamePhoneticProperty);
        if(middlenamePhonetic != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",middlenamePhonetic];
        //读取organization公司
        NSString *organization = (NSString*)ABRecordCopyValue(person, kABPersonOrganizationProperty);
        if(organization != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",organization];
        //读取jobtitle工作
        NSString *jobtitle = (NSString*)ABRecordCopyValue(person, kABPersonJobTitleProperty);
        if(jobtitle != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",jobtitle];
        //读取department部门
        NSString *department = (NSString*)ABRecordCopyValue(person, kABPersonDepartmentProperty);
        if(department != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",department];
        //读取birthday生日
        NSDate *birthday = (NSDate*)ABRecordCopyValue(person, kABPersonBirthdayProperty);
        if(birthday != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",birthday];
        //读取note备忘录
        NSString *note = (NSString*)ABRecordCopyValue(person, kABPersonNoteProperty);
        if(note != nil)
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",note];
        //第一次添加该条记录的时间
        NSString *firstknow = (NSString*)ABRecordCopyValue(person, kABPersonCreationDateProperty);
        NSLog(@"第一次添加该条记录的时间%@\n",firstknow);
        //最后一次修改該条记录的时间
        NSString *lastknow = (NSString*)ABRecordCopyValue(person, kABPersonModificationDateProperty);
        NSLog(@"最后一次修改該条记录的时间%@\n",lastknow);

        //获取email多值
        ABMultiValueRef email = ABRecordCopyValue(person, kABPersonEmailProperty);
        int emailcount = ABMultiValueGetCount(email);    
        for (int x = 0; x < emailcount; x++)
        {
            //获取email Label
            NSString* emailLabel = (NSString*)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(email, x));
            //获取email值
            NSString* emailContent = (NSString*)ABMultiValueCopyValueAtIndex(email, x);
            textView.text = [textView.text stringByAppendingFormat:@"%@:%@\n",emailLabel,emailContent];
        }
        //读取地址多值
        ABMultiValueRef address = ABRecordCopyValue(person, kABPersonAddressProperty);
        int count = ABMultiValueGetCount(address);    

        for(int j = 0; j < count; j++)
        {
            //获取地址Label
            NSString* addressLabel = (NSString*)ABMultiValueCopyLabelAtIndex(address, j);
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",addressLabel];
            //获取該label下的地址6属性
            NSDictionary* personaddress =(NSDictionary*) ABMultiValueCopyValueAtIndex(address, j);        
            NSString* country = [personaddress valueForKey:(NSString *)kABPersonAddressCountryKey];
            if(country != nil)
                textView.text = [textView.text stringByAppendingFormat:@"国家:%@\n",country];
            NSString* city = [personaddress valueForKey:(NSString *)kABPersonAddressCityKey];
            if(city != nil)
                textView.text = [textView.text stringByAppendingFormat:@"城市:%@\n",city];
            NSString* state = [personaddress valueForKey:(NSString *)kABPersonAddressStateKey];
            if(state != nil)
                textView.text = [textView.text stringByAppendingFormat:@"省:%@\n",state];
            NSString* street = [personaddress valueForKey:(NSString *)kABPersonAddressStreetKey];
            if(street != nil)
                textView.text = [textView.text stringByAppendingFormat:@"街道:%@\n",street];
            NSString* zip = [personaddress valueForKey:(NSString *)kABPersonAddressZIPKey];
            if(zip != nil)
                textView.text = [textView.text stringByAppendingFormat:@"邮编:%@\n",zip];    
            NSString* coutntrycode = [personaddress valueForKey:(NSString *)kABPersonAddressCountryCodeKey];
            if(coutntrycode != nil)
                textView.text = [textView.text stringByAppendingFormat:@"国家编号:%@\n",coutntrycode];    
        }

        //获取dates多值
        ABMultiValueRef dates = ABRecordCopyValue(person, kABPersonDateProperty);
        int datescount = ABMultiValueGetCount(dates);    
        for (int y = 0; y < datescount; y++)
        {
            //获取dates Label
            NSString* datesLabel = (NSString*)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(dates, y));
            //获取dates值
            NSString* datesContent = (NSString*)ABMultiValueCopyValueAtIndex(dates, y);
            textView.text = [textView.text stringByAppendingFormat:@"%@:%@\n",datesLabel,datesContent];
        }
        //获取kind值
        CFNumberRef recordType = ABRecordCopyValue(person, kABPersonKindProperty);
        if (recordType == kABPersonKindOrganization) {
            // it's a company
            NSLog(@"it's a company\n");
        } else {
            // it's a person, resource, or room
            NSLog(@"it's a person, resource, or room\n");
        }

        //获取IM多值
        ABMultiValueRef instantMessage = ABRecordCopyValue(person, kABPersonInstantMessageProperty);
        for (int l = 1; l < ABMultiValueGetCount(instantMessage); l++)
        {
            //获取IM Label
            NSString* instantMessageLabel = (NSString*)ABMultiValueCopyLabelAtIndex(instantMessage, l);
            textView.text = [textView.text stringByAppendingFormat:@"%@\n",instantMessageLabel];
            //获取該label下的2属性
            NSDictionary* instantMessageContent =(NSDictionary*) ABMultiValueCopyValueAtIndex(instantMessage, l);        
            NSString* username = [instantMessageContent valueForKey:(NSString *)kABPersonInstantMessageUsernameKey];
            if(username != nil)
                textView.text = [textView.text stringByAppendingFormat:@"username:%@\n",username];

            NSString* service = [instantMessageContent valueForKey:(NSString *)kABPersonInstantMessageServiceKey];
            if(service != nil)
                textView.text = [textView.text stringByAppendingFormat:@"service:%@\n",service];            
        }

        //读取电话多值
        ABMultiValueRef phone = ABRecordCopyValue(person, kABPersonPhoneProperty);
        for (int k = 0; k<ABMultiValueGetCount(phone); k++)
        {
            //获取电话Label
            NSString * personPhoneLabel = (NSString*)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(phone, k));
            //获取該Label下的电话值
            NSString * personPhone = (NSString*)ABMultiValueCopyValueAtIndex(phone, k);

            textView.text = [textView.text stringByAppendingFormat:@"%@:%@\n",personPhoneLabel,personPhone];
        }

        //获取URL多值
        ABMultiValueRef url = ABRecordCopyValue(person, kABPersonURLProperty);
        for (int m = 0; m < ABMultiValueGetCount(url); m++)
        {
            //获取电话Label
            NSString * urlLabel = (NSString*)ABAddressBookCopyLocalizedLabel(ABMultiValueCopyLabelAtIndex(url, m));
            //获取該Label下的电话值
            NSString * urlContent = (NSString*)ABMultiValueCopyValueAtIndex(url,m);

            textView.text = [textView.text stringByAppendingFormat:@"%@:%@\n",urlLabel,urlContent];
        }

        //读取照片
        NSData *image = (NSData*)ABPersonCopyImageData(person);

        UIImageView *myImage = [[UIImageView alloc] initWithFrame:CGRectMake(200, 0, 50, 50)];
        [myImage setImage:[UIImage imageWithData:image]];
        myImage.opaque = YES;
        [textView addSubview:myImage];

    }

    CFRelease(results);
    CFRelease(addressBook);

 

ios实现通讯录的查询与删除

os提供了对通讯录操作的组建,其中一个是直接操作通讯录,另一个是调用通讯录的UI组建。实现方法如下:

添加AddressBook.framework到工程中。

1

代码实现:

    -(IBAction)onClickbutton:(id)sender
    {
        NSMutableArray* personArray =[[[NSMutableArray alloc] init] autorelease];
        ABAddressBookRef addressBook =ABAddressBookCreate();
        NSString*firstName,*lastName,*fullName;
        personArray =(NSMutableArray*)ABAddressBookCopyArrayOfAllPeople(addressBook);
        if([sender tag]==0){

            for(id *person in personArray)
            {
                firstName =(NSString*)ABRecordCopyValue(person, kABPersonFirstNameProperty);
                firstName =[firstName stringByAppendingFormat:@" "];
                lastName =(NSString*)ABRecordCopyValue(person, kABPersonLastNameProperty);   
                fullName =[firstName stringByAppendingFormat:@"%@",lastName];
                NSLog(@"===%@",fullName);
                ABMultiValueRef phones =(ABMultiValueRef)ABRecordCopyValue(person, kABPersonPhoneProperty);
                for(int i =0;i <ABMultiValueGetCount(phones); i++)
                { 
                    NSString*phone =(NSString*)ABMultiValueCopyValueAtIndex(phones, i);
                    NSLog(@"===%@",phone);
                }
                ABMultiValueRef mails =(ABMultiValueRef)ABRecordCopyValue(person, kABPersonEmailProperty);
                for(int i =0;i <ABMultiValueGetCount(mails); i++)
                { 
                    NSString*mail =(NSString*)ABMultiValueCopyValueAtIndex(mails, i);
                    NSLog(@"==%@",mail);
                }       
            }   
        }else{
            //删除信息
            //返回所有联系人到一个数组中
            CFArrayRef personArray =ABAddressBookCopyArrayOfAllPeople(addressBook);
            CFIndex personCount =ABAddressBookGetPersonCount(addressBook);
              for(int i =0;i<personCount;i++){
                  ABRecordRefref=CFArrayGetValueAtIndex(personArray, i);
                  CFStringRef firstName1 =ABRecordCopyValue(ref, kABPersonFirstNameProperty);
                  CFStringRef lastName1 =ABRecordCopyValue(ref, kABPersonLastNameProperty);
                  NSString*contactFirstLast =[NSString stringWithFormat: @"%@%@",(NSString*)firstName1,(NSString*)lastName1];
                if([contactFirstLast isEqualToString:@"徐梦"]){
                    //删除联系人
                    ABAddressBookRemoveRecord(addressBook,ref,nil);
                }
            }
            //保存电话本
            ABAddressBookSave(addressBook,nil);  
            //释放内存
            //CFRelease(personRef);
    //        CFRelease(addressbookRef); 
        }
    }

 

本文转自:http://blog.csdn.net/kingkong1024/article/details/8364037

ios6上读取通讯录的方法

ios升级到6.0以后的变化之一就是增加了通讯录的隐私设置,防止程序未通过许可就访问用户的通讯录。因此在app开发中,读取通讯录的方法也有了一些变化。上代码:

-(BOOL)isABAddressBookCreateWithOptionsAvailable {
return &ABAddressBookCreateWithOptions != NULL;
}

-(void)loadContacts {
ABAddressBookRef addressBook;
if ([self isABAddressBookCreateWithOptionsAvailable]) {

// ios6以上读取方法
CFErrorRef error = nil;
addressBook = ABAddressBookCreateWithOptions(NULL,&error);
ABAddressBookRequestAccessWithCompletion(addressBook, ^(bool granted, CFErrorRef error) {
// callback can occur in background, address book must be accessed on thread it was created on
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {

} else if (!granted) {

} else {
// access granted
AddressBookUpdated(addressBook, nil, (__bridge void *)(self));
CFRelease(addressBook);
}
});
});
} else {
// iOS 4/5
addressBook = ABAddressBookCreate();
AddressBookUpdated(addressBook, NULL, (__bridge void *)(self));
CFRelease(addressBook);
}
}

void AddressBookUpdated(ABAddressBookRef addressBook, CFDictionaryRef info, void *context) {
ABAddressBookRevert(addressBook);
CFArrayRef people = ABAddressBookCopyArrayOfAllPeople(addressBook);
};

 

本文转自:http://bluevt.org/?p=41

用xcode 5 开发访问IOS 7上面的通讯录问题

NSMutableArray *addressBookTemp = [NSMutableArray array];ABAddressBookRef addressBooks = ABAddressBookCreate();
CFArrayRef allPeople = ABAddressBookCopyArrayOfAllPeople(addressBooks);

CFIndex nPeople = ABAddressBookGetPersonCount(addressBooks);

 

 

这段代码在IOS6是可以读取通讯录的,但在IOS7下面就不行,程序没报错,调试时候发现返回的都是nil,有木有朋友遇到这个问题?

找到原因了,原来IOS7需要隐私验证,调用通讯录里面API需要以下代码作为权限认证:

 __block BOOL accessGranted = NO;
 if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusNotDetermined)
{
  ABAddressBookRequestAccessWithCompletion(addressBooks, ^(bool granted, CFErrorRef error)
 {
   // First time access has been granted, add the contact 
   accessGranted = granted; });
 }
 else if (ABAddressBookGetAuthorizationStatus() == kABAuthorizationStatusAuthorized)
 {
  // The user has previously given access, add the contact 
  accessGranted = YES;
 }
 else
 {
 // The user has previously denied access
 // Send an alert telling user to change privacy setting in settings app
 }

不过IOS6好像没有,希望对其他朋友有帮助 – sfdux 2013-09-24 11:12 回复