Quantcast
Channel: 老谭 »开发示例
Viewing all 34 articles
Browse latest View live

通过IOKit读取系统信息

$
0
0

关于系统的很多信息,例如序列号、磁盘信息、电池的信息、内存信息等等,我们都可以通过系统自带的“系统信息”这个工具可以查看到,如图:

iokit

但我们如何通过程序来获取这些信息呢,那就需要引入iokit这个framework,比如我们需要获取关于磁盘的一些信息,可以使用如下的代码来获取:

- (void)iotest
{
    io_iterator_t iterator;
    kern_return_t kr;
    io_object_t   driver;

    CFMutableDictionaryRef matchDictionary = IOServiceMatching("AppleAHCIDiskDriver");
    kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &iterator);
    if (kr != kIOReturnSuccess)
    {
        return;
    }

    while ((driver = IOIteratorNext(iterator)) != 0)
    {
        CFMutableDictionaryRef properties = NULL;
        kr = IORegistryEntryCreateCFProperties(driver,
                                               &properties,
                                               kCFAllocatorDefault,
                                               kNilOptions);
        if (kr != kIOReturnSuccess || properties == NULL)
        {
            continue;
        }

        NSLog(@"%@",(__bridge NSDictionary*)properties);
    }
}

但你可能会问,AppleAHCIDiskDriver这个字符串是如何取得的呢?在iokit中很多头文件中都定义了相应的宏来标识这个键值,当然如果你需要更详细的信息,你或许就需要使用苹果官方的IORegistryExplorer这个工具了,它可以详细的为你展示每一种硬件对应的这个键值和对应的属性列表,如图:

IORegistryExplorer

如果你需要获得其它硬件的信息,将代码中的AppleAHCIDiskDriver替换为对应的键值就OK了,比如要获取电池信息就用AppleSmartBattery。


关于计时的那些纠结

$
0
0

写程序就避免不了与时间打交道,在C和Objective-C里面有很多与时间有关的函数和类,那我们如果选用合理的计时方式呢?下面就分别对几种最常用的计时方式做出对比:

用time()函数计时

在time.h里面有一个经常使用的时间函数叫做time(),它的原型为time_t time(time_t *),返回从1700年1月1日0点0分0秒到目前的秒数,所以它仅能够精确到秒,使用方式如下:

- (void)timeTest1
{
    time_t time_start = time(NULL);
    sleep(5);
    time_t time_end = time(NULL);
    NSLog(@"%ld",time_end-time_start);
}

用clock()函数计时

该函数定义在time.h之中,原型为clock_t clock(void),这个函数返回从“开启这个程序进程”到“程序中调用clock()函数”时之间的CPU时钟计时单元(clock tick)数,将该数值除以CLOCKS_PER_SEC便可以换算出相应的时间(故它的精度与系统有关系,在OSX系统中CLOCKS_PER_SEC为1000,也就是可以精确到1微秒),所以我们常用它来计算一段代码执行需要多少时间,如:

- (void)timeTest2
{
    clock_t clock_start = clock();
    for(int i = 0; i < 2147483647; i++);
    clock_t clock_end = clock();
    NSLog(@"%f",(clock_end-clock_start)/(double)CLOCKS_PER_SEC);
}

不过需要注意的是,在类OSX系统中,sleep函数是不会消耗处理器时间,所以以下的代码打印的结果接近于0,而不是接近5:

- (void)timeTest2
{
    clock_t clock_start = clock();
    sleep(5);
    clock_t clock_end = clock();
    NSLog(@"%f",(clock_end-clock_start)/(double)CLOCKS_PER_SEC);
}

用gettimeofday()函数计时

该函数定义在time.h之中,原型为gettimeofday(struct timeval *, struct timezone *),参数1是保存获取时间结果的结构体,参数2用于保存时区结果,timeval的结构是:

struct timeval 
{
    long int tv_sec; // 秒数
    long int tv_usec; // 微秒数
}

所以通过gettimeofday()可以获得精确到微秒数的时间,如下:

- (void)timeTest3
{
    struct timeval daytime_start;
    struct timezone timezone_start;
    gettimeofday(&daytime_start, &timezone_start);
    sleep(5);
    struct timeval daytime_end;
    struct timezone timezone_end;
    gettimeofday(&daytime_end, &timezone_end);
    
    NSLog(@"%f",(daytime_end.tv_sec-daytime_start.tv_sec)+(daytime_end.tv_usec-daytime_start.tv_usec)/(double)pow(10, 6));
}

通过NSDate来获取时间

NSDate是Objective-C中用来描述时间和时期的对象,可以利用它更加方便的记录时间,它可以把时间精确到微秒,使用它来计时的示例代码:

- (void)timeTest4
{
    NSDate *date = [NSDate date];
    sleep(5);
    NSLog(@"%f",-[date timeIntervalSinceNow]);
}

在Mac OSX中注册自定义的快捷键

$
0
0

diannao_64b

在Mac OSX系统的开发中,我们除了可以让按钮、菜单拥有特定的快捷键之外,我们还可以注册全局的快捷键,即当前程序不处于活跃状态中也能收到的快捷键事件响应。下面帖上代码:

定义注册快捷键需要保存的变量和回调方法

//用于保存快捷键事件回调的引用,以便于可以注销
static EventHandlerRef g_EventHandlerRef = NULL;

//用于保存快捷键注册的引用,便于可以注销该快捷键
static EventHotKeyRef a_HotKeyRef = NULL;
static EventHotKeyRef b_HotKeyRef = NULL;

//快捷键注册使用的信息,用在回调中判断是哪个快捷键被触发
static EventHotKeyID a_HotKeyID = {'keyA',1};
static EventHotKeyID b_HotKeyID = {'keyB',2};

//快捷键的回调方法
OSStatus myHotKeyHandler(EventHandlerCallRef inHandlerCallRef, EventRef inEvent, void *inUserData)
{
    //判定事件的类型是否与所注册的一致
    if (GetEventClass(inEvent) == kEventClassKeyboard && GetEventKind(inEvent) == kEventHotKeyPressed)
    {
        //获取快捷键信息,以判定是哪个快捷键被触发
        EventHotKeyID keyID;
        GetEventParameter(inEvent,
                          kEventParamDirectObject,
                          typeEventHotKeyID,
                          NULL,
                          sizeof(keyID),
                          NULL,
                          &keyID);
        if (keyID.id == a_HotKeyID.id) {
            NSLog(@"pressed:shift+command+A");
        }
        if (keyID.id == b_HotKeyID.id) {
            NSLog(@"pressed:option+B");
        }
    }

    return noErr;
}

在程序启动后注册快捷键

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    //先注册快捷键的事件回调
    EventTypeSpec eventSpecs[] = {{kEventClassKeyboard,kEventHotKeyPressed}};
    InstallApplicationEventHandler(NewEventHandlerUPP(myHotKeyHandler),
                                   GetEventTypeCount(eventSpecs),
                                   eventSpecs,
                                   NULL,
                                   &g_EventHandlerRef);

    //注册快捷键:shift+command+A
    RegisterEventHotKey(kVK_ANSI_A,
                        cmdKey|shiftKey,
                        a_HotKeyID,
                        GetApplicationEventTarget(),
                        0,
                        &a_HotKeyRef);

    //注册快捷键:option+B
    RegisterEventHotKey(kVK_ANSI_B,
                        optionKey,
                        b_HotKeyID,
                        GetApplicationEventTarget(),
                        0,
                        &b_HotKeyRef);
}

在程序结束的时候注销快捷键

- (void)applicationWillTerminate:(NSNotification *)notification
{
    //注销快捷键
    if (a_HotKeyRef)
    {
        UnregisterEventHotKey(a_HotKeyRef);
        a_HotKeyRef = NULL;
    }
    if (b_HotKeyRef)
    {
        UnregisterEventHotKey(b_HotKeyRef);
        b_HotKeyRef = NULL;
    }
    //注销快捷键的事件回调
    if (g_EventHandlerRef)
    {
        RemoveEventHandler(g_EventHandlerRef);
        g_EventHandlerRef = NULL;
    }
}

运行程序,当按下shift+command+A或option+B的快捷键后,你便会在控制台看到相应的打印输出了!

修改Mac OSX文件默认打开方式

$
0
0

在Mac OSX中,修改文件的默认打开方式,可以在文件简介面板中设置,那如何用代码的方式来实现呢?下面的给出一个简单的示例,通过文件的扩展名修改该类型文件的打开方式为指定BundleID的应用程序。

    NSString *fileExtension = @"txt";
    NSArray *UTIs = (__bridge NSArray *)UTTypeCreateAllIdentifiersForTag(kUTTagClassFilenameExtension,
                                                                (__bridge CFStringRef)fileExtension,
                                                                nil);
    for (NSString *UTIType in UTIs)
    {
        LSSetDefaultRoleHandlerForContentType((__bridge CFStringRef)UTIType,
                                              kLSRolesEditor,
                                              (__bridge CFStringRef)@"com.apple.Safari");
    }

这段代码实现了将TXT文档的默认打开方式修改为Safari。

让ROOT权限一次获取,终生受用

$
0
0

当我们开发的一款软件或一个或执行工具需要获取ROOT权限时,我们会在运行的时候获取用户的授权,但如果每次运行都需要用户授权就显得很不友好了,所以我便尝试做一个工具/可执行文件,一次授权之后,以后通过它就能永久的保留ROOT的权限,并且可以通过它来提升其它任意我想通过ROOT权限才能执行的命令。

该工具一共只有几十行代码,对于Linux、Unix开发的老鸟就可以无视了,下面是所有的代码:

#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, const char * argv[])
{
    if (getuid() == 0)
    {
        chown(argv[0], getuid(), getgid());
        chmod(argv[0], S_ISUID|S_IRUSR|S_IWUSR|S_IXUSR|S_IXGRP|S_IXOTH);
    }
    
    if (geteuid() != 0 || argc == 1)
    {
        return 0;
    }
    
    int len = 0;
    for (int i=1; i<argc; i++)
    {
        len += strlen(argv[i])+1;
    }
    
    char exe_str[len];
    char *strD=exe_str;
    for (int i=1; i<argc; i++)
    {
        sprintf(strD, "%s ",argv[i]);
        strD+=strlen(argv[i])+1;
    }
    
    if (strcmp(argv[1], "sudo") == 0)
    {
        system(exe_str);
    }else
    {
        char sudo_exe_str[len+5];
        sprintf(sudo_exe_str, "sudo %s",exe_str);
        system(sudo_exe_str);
    }
    
    return 0;
}

在Mac OSX系统测试是完全没有问题的,编译后的可执行文件第一次使用的时候用sudo获取到root权限,以后直接利用它就可以使用ROOT权限去执行命令了(只需要把命令和它的参数一起作为跟在该可执行文件后面即可)。

Build Settings中的变量@rpath,@loader_path,@executable_path

$
0
0

@executable_path 这个变量表示可执行程序所在的目录. 比如 /path/QQ.app/Contents/MacOS/

@loader_path 这个变量表示每一个被加载的 binary (包括App, dylib, framework,plugin等) 所在的目录.

在一个程序中, 对于每一个模块, @loader_path 会解析成不用的路径, 而 @executable_path 总是被解析为同一个路径(可执行程序所在目录). 比如一个会被多个程序调用的 plugin, 位于 /path/Flash Player.plugin/Contents/MacOS/Flash Player, 依赖 /path/Flash Player.plugin/Contents/Frameworks/XPSSO.dylib. 那么 XPSSO.dylib 的 INSTALL_PATH 可以设置为 @loader_path/../Frameworks, 这样设置的话, 不论 Flash Player.plugin 目录放到什么位置, XPSSO.dylib 都能正确的被加载.

@rpath 和前面两个不同, 它只是一个保存着一个或多个路径的变量. 比如 XPSSO.dylib 被两个 .app 使用, 且被包含的路径不同。比如:
softA.app/Contents/MacOS/dylib/XPSSO.dylib
softB.app/Contents/MacOS/Frameworks/XPSSO.dylib
将 XPSSO.dylib 的 INSTALL_PATH 设置成 @loader_path/../dylib 或 @loader_path/../Frameworks 都只能满足其中一个 .app 的需求. 要解决这个问题, 就可以用 @rpath. 将 XPSSO.dylib 的 INSTALL_PATH 设置成 @rpath, 然后在编译 softA.app, softB.app 时分别指定 @rpath 为 @loader_path/../dylib, @loader_path/../Frameworks, 问题得到了解决. @rpath 的另一个优点是可以设置多个路径. 如果 softA.app 还需要使用另一个 .plugin (假设它的 INSTALL_PATH 也设置成了 @rpath), 位于 @loader_path/../plugin, 把这个路径加到 @rpath 即可.

XPSSO.dylib的Build Settings中设置Installation Directory

buildsettings1

在 softA.app或softB.app 中设置 Runpath Search Paths(对应了@rpath)

buildsettings2

获取Mac OSX系统内置的各种图标

$
0
0

我们在开发Mac OSX软件的时候,有很多图像资源都是可以直接利用系统内置的图片的,比如我们常用这样的方法来获得文件夹的图标:

NSImage *img = [NSImage imageNamed:NSImageNameFolder];

类似NSImageNameFolder还有NSImageNameComputer,NSImageNameApplicationIcon等等,具体可以查询NSImage.h头文件中相关的声明。
另外我们也可以通过以下的方式来获取不同类型文件的图标:

NSImage *img = [[NSWorkspace sharedWorkspace] iconForFileType:@"pkg"];

当然上面的那个方法还可以通过HFSTypeCode来获得图标:

NSImage *img = [[NSWorkspace sharedWorkspace] iconForFileType:NSFileTypeForHFSTypeCode(kFinderIcon)];

kFinderIcon是一个枚举类型的值,类似的值有一百多个,所以为了能够方便的找到自己想到的图标,我写了一个小程序来枚举了这些图标,小程序的运行结果如下图:
icondemo

该小程序的源代码下载:IconDemo.zip

用代码实现截屏并保存为图片

$
0
0

如果需要写一个抓屏软件,我们知道截屏是可以按command+shift+3就可以实现的,对应到程序中就是可以通过NSTask调用screencapture命令来完成,但有没有更好的方法呢,比如通过原生的接口来实现?

其实在Carbon中就有很方便的接口可以实现这样的功能:

- (NSImage *)screenShot
{
    CFArrayRef windowsRef = CGWindowListCreate(kCGWindowListOptionOnScreenOnly, kCGNullWindowID);
    NSRect rect = [[NSScreen mainScreen] frame];
    CGImageRef imgRef = CGWindowListCreateImageFromArray(rect, windowsRef, kCGWindowImageDefault);
    CFRelease(windowsRef);
    NSImage *img = [[NSImage alloc] initWithCGImage:imgRef size:NSZeroSize];
    CGImageRelease(imgRef);
    return img;
}

以上这个方法就可以截取屏幕内容并保存成图片,非常方便吧!


纯代码绘制的滑动开关按钮

$
0
0

之前看到@连栩的Miao中写了一个滑动开关,最近自己写的一个小软件中也想要一个用来表示两种状态的开关按钮,在OSX的控件库里面只有NSSegmentedControl比较满足这样的需求,但我又觉得用NSSegmentedControl又太不美观了(本来我的小软件已经非常不美观了),于是就想自己也做一个滑动的开关按钮,连栩兄写的那个滑动开关是用图片实现的,所以我就想在山寨的时候来点“微创新”,于是改为全用绘制的方式给画出来NSGradient+NSShadow。

虽然只是一个小小的按钮,但里面的阴影、高亮等效果用程序来绘制还着实让我微调了很久,最终成果个人评估90%的相似度吧。

下面是效果截图一张:

THSlideButton

 

再附上Demo的源码吧:THSlideButtonDemo.zip

轻量级的KV数据库LevelDB在Objective-C上的应用

$
0
0

Leveldb是一个google实现的非常高效的kv数据库,目前能够支持billion级别的数据量了。 在这个数量级别下还有着非常高的性能,主要归功于它的良好的设计。Leveldb是Jeff Dean和Sanjay Ghemawat两位大神级别的工程师发起的开源项目。其它更多更关Leveldb的介绍,可以google详细了解。
Leveldb的项目托管在https://code.google.com/p/leveldb/

其实很早之前就已经有人为Leveldb写了Objective-C的接口,今天抽时间写了一个类似NSUserDefaults的接口,以后我就可以在自己的项目中用它来代替SQLite了。

如果需要使用Leveldb,首先需要去Clone托管在google code上的代码,然后将代码编译成静态库:
如果需要编译成Mac OSX系统的静态库:

cd leveldb
CXXFLAGS=-stdlib=libc++ make

如果需要编译成iOS系统的表态库:

cd leveldb
CXXFLAGS=-stdlib=libc++ make PLATFORM=IOS

然后将include头拖到自己的工程中,到此就可以使用Leveldb提供的接口了,由于Leveldb是用C++开发的,所以在使用的时候记得将.m修改为.mm。

这是我为OC写的接口:

@interface THLevelDB : NSObject

+ (THLevelDB *)levelDBWithPath:(NSString *)path;
- (id)initWithPath:(NSString *)path;

//Getting Default Values
- (BOOL)boolForKey:(NSString *)aKey;
- (double)floatForKey:(NSString *)aKey;
- (NSInteger)intForKey:(NSString *)aKey;
- (NSString *)stringForKey:(NSString *)aKey;
- (NSData *)dataForKey:(NSString *)aKey;
- (id)objectForKey:(NSString *)aKey;

//Setting Default Values
- (BOOL)setBool:(BOOL)value forKey:(NSString *)aKey;
- (BOOL)setInt:(NSInteger)value forKey:(NSString *)aKey;
- (BOOL)setFloat:(double)value forKey:(NSString *)aKey;
- (BOOL)setString:(NSString *)value forKey:(NSString *)aKey;
- (BOOL)setData:(NSData *)value forKey:(NSString *)aKey;
- (BOOL)setObject:(id)value forKey:(NSString *)aKey;

- (BOOL)removeValueForKey:(NSString *)aKey;
- (NSArray *)allKeys;

- (void)enumerateKeys:(void (^)(NSString *key, BOOL *stop))block;

- (BOOL)clear;
- (BOOL)deleteDB;

@end

下面是我写的一个Mac的测试工程,如果需要在iOS中使用,记得重新编译ios版本的leveldb.a文件,如果觉得我写的接口用得着的话,请点击下载吧:

THLevelDBDemo.zip

实现将NSImage保持指定像素的拉伸

$
0
0

在UIKit中的UIImage有以下两个方法可以实现对图片的部分拉伸:

- (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight
- (UIImage *)resizableImageWithCapInsets:(UIEdgeInsets)capInsets

但在AppKit中的NSImage却没有类似的方法,于是手动写了类似这两个功能的函数:

//保持四周一定区域像素不拉伸,将图像扩散到一定的大小
- (NSImage *)stretchableImageWithSize:(NSSize)size edgeInsets:(NSEdgeInsets)insets;
//保持leftWidth,rightWidth这左右一定区域不拉伸,将图片宽度拉伸到(leftWidth+middleWidth+rightWidth)
- (NSImage *)stretchableImageWithLeftCapWidth:(float)leftWidth middleWidth:(float)middleWidth rightCapWidth:(float)rightWidth;

如图所示:
StretchableDemo

Demo下载:StretchableDemo

检测Mac是否在使用耳机

$
0
0

前两天有朋友在我博客留言问如何检测Mac是否在使用耳机,今天抽时间去查了一下API,检测方法应该不只一种,我暂且贴出这一段代码吧:

#import "AppDelegate.h"
#import <CoreAudio/CoreAudio.h>

@interface AppDelegate ()
{
    AudioDeviceID defaultDevice;
}
@end

@implementation AppDelegate

- (void)checkAudioHardware:(const AudioObjectPropertyAddress *)sourceAddr
{
    UInt32 dataSourceId = 0;
    UInt32 dataSourceIdSize = sizeof(UInt32);
    AudioObjectGetPropertyData(defaultDevice, sourceAddr, 0, NULL, &dataSourceIdSize, &dataSourceId);
    
    if (dataSourceId == 'ispk') {
        NSLog(@"没用耳机");
    } else if (dataSourceId == 'hdpn') {
        NSLog(@"使用耳机");
    }
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    //获得内置输出设备
    UInt32 defaultSize = sizeof(AudioDeviceID);
    const AudioObjectPropertyAddress defaultAddr =
    {
        kAudioHardwarePropertyDefaultOutputDevice,
        kAudioObjectPropertyScopeGlobal,
        kAudioObjectPropertyElementMaster
    };
    AudioObjectGetPropertyData(kAudioObjectSystemObject, &defaultAddr, 0, NULL, &defaultSize, &defaultDevice);
    
    //注册输出设备改变的通知
    AudioObjectPropertyAddress sourceAddr;
    sourceAddr.mSelector = kAudioDevicePropertyDataSource;
    sourceAddr.mScope = kAudioDevicePropertyScopeOutput;
    sourceAddr.mElement = kAudioObjectPropertyElementMaster;
    AudioObjectAddPropertyListenerBlock(defaultDevice, &sourceAddr, dispatch_get_current_queue(), ^(UInt32 inNumberAddresses, const AudioObjectPropertyAddress *inAddresses) {
        [self checkAudioHardware:inAddresses];
    });
    
    //第一次主动检测
    [self checkAudioHardware:&sourceAddr];
}

@end

NSView的自定义动画更简单

$
0
0

CustomAnimationDemo
之前写过几篇关于Mac动画的系列文章,当今天在工作中写一个动画Demo时才发现那一系列的文章应该少了一篇才是,最近工作变动之后很久都没有来更新过博客了,所以今天来发一篇小小的技术文章,其实也就几句代码能搞定的事儿,只是可能被你忽略了而已。
之前那系列关于动画的文章中一篇分享了如何为CALayer自定义动画,虽然代码也很简单,但毕竟还是要去实现两三个方法,其实用NSView去实现一个自定义的动画就显得更简单了,几乎可以算是只需要去实现一个方法,以下就是完整的代码:

此处忽略了头文件,头文件只是定义了一个属性progress而已

#import "THAnimationView.h"
#import <QuartzCore/QuartzCore.h>

@implementation THAnimationView
@synthesize progress;

- (double)progress
{
    return progress;
}

//此处必需去刷新界面,否则动画无效
- (void)setProgress:(double)value
{
    progress = value;
    [self setNeedsDisplay:YES];
}

//画一道弧线,用来展示动画
- (void)drawRect:(NSRect)dirtyRect
{
    NSBezierPath *path = [NSBezierPath bezierPath];
    NSPoint center = NSMakePoint(NSMidX(self.bounds), NSMidY(self.bounds));
    [path appendBezierPathWithArcWithCenter:center radius:50 startAngle:0 endAngle:360*progress clockwise:NO];
    [path setLineWidth:10];
    [[NSColor redColor] set];
    [path stroke];
}

//这就是你需要去实现的一个方法,根据属性返回一个动画对象
+ (id)defaultAnimationForKey:(NSString *)key
{
    if ([key isEqualToString:@"progress"])
    {
        return [CABasicAnimation animation];
    }else
    {
        return [super defaultAnimationForKey:key];
    }
}

@end

当你需要展示动画时,只需要这样使用即可:

[[animationView animator] setProgress:1.0];

如果需要设置指定的动画时间,你还可以这样使用:

[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:1.0];
[[animationView animator] setProgress:1.0];
[NSAnimationContext endGrouping];

以下Demo的完整源代码下载:CustomAnimationDemo.zip

软件导致MacBook Pro切换为独立显卡的问题

$
0
0

之前我在工作中开发的软件也收到过用户的反馈,内容就是当使用我们软件的时候,MacBook Pro自动就切换回独立显卡模式了,当时我还不是特别在意,觉得这个应该是系统自动根据使用环境而智能的切换的。

直到这几天看到连栩他们在讨论这个问题,才发现原来通过一个Info.plist的一个参数可以很好的解决这个问题,通过查询文档我也发现了一个叫NSSupportsAutomaticGraphicsSwitching的参数,但千万别被这个Key的名称给欺骗了,文档上面已经介绍得非常清楚,默认情况,当你的软件使用了OpenGL的动画,比如使用了Core Animation的接口,MacBook Pro就会自动切换为独立显卡直到程序退出,在10.6之前的环境下你是没办法去干预的,直到10.7以后引入了这个参数,当你把这个参数置为true,就是允许OpenGL动画仍然使用集成显卡,记住只是设置为true才是你所想要的,默认情况就是false.

初探Mac OSX内核开发(一)——创建内核扩展

$
0
0

从事Mac的应用程序开发已经有很长一段时间了,但对于Mac下的内核层编程却一直处于空白状态,每次看到那些写内核扩展的大牛,我都无比崇拜,所以最近下决心来学习学习这方面的知识,同时计划一边学习一边在博客上写心得,加深记忆的同时也希望与大家一同交流,如遇大神路过请一定记得点拨一二~~~
好了,废话不多说了,直接打开Xcode准备进入写代码的节奏,Xcode本身就已经提供了内核扩展和内核驱动的模板,如图:
QQ20131026-1

第一个框中的模板就是内核扩展,第二个框中的模板是驱动模板,他们的区别就是内核扩展会随系统运行而加载,而驱动会因为特定的硬件才会被加载,我们先创建一个内核扩展的工程,然后工程名为TestExtension,然后可以看到工程中会自动创建一个TestExtension.C的文件,并且已经生成了两个方法,如下:

#include <mach/mach_types.h>
#include <libkern/libkern.h>

kern_return_t TestExtension_start(kmod_info_t * ki, void *d);
kern_return_t TestExtension_stop(kmod_info_t *ki, void *d);

kern_return_t TestExtension_start(kmod_info_t * ki, void *d)
{
    printf("TestExtension Start!\n");
    return KERN_SUCCESS;
}

kern_return_t TestExtension_stop(kmod_info_t *ki, void *d)
{
    printf("TestExtension Stop!\n");
    return KERN_SUCCESS;
}

这两个方法分别会在扩展加载时和卸载时调用,你可能会觉得好奇系统怎么会知道这两个方法的名字呢?其实秘诀就是在工程的配置文件中有两个属性,所以也可以手动去修改这两个方法名并与这两个属性相对应即可,如图:
QQ20131026-3

并注意Info.plist文件中OSBundleLibraries项下面的com.apple.kpi.libkern属性,它对应的是系统内核的版本,你可以通过在命令行中输入uname -r来查看,比如10.9的内核版本为13.0.0。
至此,一个最最简单的内核扩展就已经完成了,编译工程,到build目录下找到编译的后的TestExtension.kext文件,下一步就是怎样加载它了,内核扩展要求文件的所有者是root,用户组是wheel,所以你可以在命令行下通过以下的命令修改文件所有者:

sudo chown -R root:wheel TestExtension.kext

然后通过以下的命令加载内核扩展:

sudo kextload TestExtension.kext

此时你可以通过控制台程序Console查看到“TestExtension Start!”的输出,说明我们编写的该扩展已经能够正常工作了,在Mac下内核扩展通常都放在/System/Library/Extensions目录下面,所以也建议加载前先将TestExtension.kext移动到该目录。
如果需要卸载该内核扩展,使用以下的命令即可:

sudo kextunload TestExtension.kext

同时在控制台也就能看到“TestExtension Stop!”的输出了。

文章相关的Demo示例下载:TestExtension.zip


初探Mac OSX内核开发(二)——创建驱动程序

$
0
0

接上文,我们已经通过Xcode自带的模板完成了简单的内核创建到部署的过程,然后我们准备来尝试创建一个驱动程序然后完成布署,上文上已经提到了内核扩展与驱动的差别在于加载的时机不一样,另外他们还有一个很明显的差别,内核扩展是用C来实现的,而Mac的驱动却是用C++去完成的,Mac OSX系统的驱动开发有一套基于C++的IO Kit框架,这也是OSX内核中非常重要的一个部分,在内核开发中使用C++其实只是它的一个子集,嵌入式C++,它不可以使用C++的异常、多继承、模板、运行时等特性,但IO Kit框架为了开发的方便而去实现了类似Cocoa编程中的引用计数、运行时、容器等特性。
关于驱动程序的加载过程,我用下面的图来表示:
driver_01

当硬件插入电脑时,系统会根据硬件的类型创建一个Provider(提供者)的对象,并且这个Provider会在初使化的过程中去尝试匹配合适的驱动程序,如上图,我们开发一款适合于PCI声卡或USB音频设备的驱动程序,首先硬件载入后会去查找驱动程序Info.plist中的IOKitPersonalities的信息,如果IOProviderClass中定义了IOPCIDriver就表示可以为PCI做匹配,如果同时也有IOUSBDriver项就表示USB的设备也可以做匹配,同时如果有多个支持PCI或USB的驱动出现时,就会去调用驱动程序的probe方法,最终找到匹配度最高的驱动程序进行加载(实际的原理会更复杂),然后该驱动程序就可以通过不同的Provider与硬件通信,并且通过标准的IOAudioDriver接口为系统提供音频的服务,从而用户程序就可以通过系统的标准方法最终让音频设备工作。

好了,直接开始编码,打开Xcode,选择IOKit Driver模板,取名工程名IOKitTest,然后可以在工程中看到会自动创建两个文件,分别是IOKitTest.h和IOKitTest.cpp,但非常遗憾的是,模板并没有为我们在这两个文件中生成任何的内容。我们手动在IOKitTest.h中输入以下内容:

#include <IOKit/IOService.h>

class com_osxkernel_driver_IOKitTest : public IOService {
    
    //一个宏定义,会自动生成该类的构造方法、析构方法和运行时
    OSDeclareDefaultStructors(com_osxkernel_driver_IOKitTest)
    
public:
    //该方法与Cocoa中的init和C++中的构造方法类似
    virtual bool init(OSDictionary* dictionary = NULL);
    //该方法与Cocoa中的dealloc和C++中的析构方法类似
    virtual void free(void);
    
    //进行驱动匹配时调用
    virtual IOService* probe(IOService* provider, SInt32* score);
    //当匹配成功后加载驱动
    virtual bool start(IOService *provider);
    //当硬件移除时或卸载驱动
    virtual void stop(IOService *provider);
};

在IOKitTest.cpp中输入以下的代码:

#include "IOKitTest.h"
#include <IOKit/IOLib.h>

//让你拥有Cocoa中的super关键字一样的体验
#define super IOService

//和头文件中的宏定义类似,自动生成一些特定代码
OSDefineMetaClassAndStructors(com_osxkernel_driver_IOKitTest, IOService)

//该方法打印出dict的内容
bool com_osxkernel_driver_IOKitTest::init(OSDictionary *dict)
{
    bool res = super::init(dict);
    IOLog("IOKitTest::init abcd\n");
    
    OSCollectionIterator *iter = OSCollectionIterator::withCollection(dict);
    if (iter)
    {
        OSObject *object = NULL;
        while ((object = iter->getNextObject()))
        {
            OSSymbol *key = OSDynamicCast(OSSymbol, object);
            IOLog("key:%s  ",key->getCStringNoCopy());
            OSString *value = OSDynamicCast(OSString, dict->getObject(key));
            if (value != NULL)
            {
                IOLog("value:%s\n",value->getCStringNoCopy());
            }
        }
    }
    
    return res;
}

void com_osxkernel_driver_IOKitTest::free(void)
{
    IOLog("IOKitTest::free\n");
    super::free();
}

IOService* com_osxkernel_driver_IOKitTest::probe(IOService* provider, SInt32* score)
{
    IOService *res = super::probe(provider, score);
    IOLog("IOKitTest::probe\n");
    return res;
}

bool com_osxkernel_driver_IOKitTest::start(IOService *provider)
{
    bool res = super::start(provider);
    IOLog("IOKitTest::start\n");
    //只有调用了此方法,应用层才可以连接该驱动
    registerService();
    return res;
}

void com_osxkernel_driver_IOKitTest::stop(IOService *provider)
{
    IOLog("IOKitTest::stop\n");
    super::stop(provider);
}

至此,程序部分就完成了,但如果需要让驱动程序正常的被加载,我们需要在Info.plist文件中加入必要的项目,如下图:
QQ20131026-5

其中OSBundleLibraries中的两个值对应的是内核中iokit和libkern的版本,此处我们设置内核的版本即可,而IOKitPersonalities就是上文也有提到的,用于匹配驱动程序用,IOProviderClass是需要对应特定类型的,如IOPCIDriver、IOUSBDriver等,但我们这是一个测试程序,不希望去驱动某个特定的硬件,所以指定IOResources就是无需硬件设备。

好了,我们的驱动程序就已经开发完成了,编译后在Build目录中找到IOKitTest.kext文件,如第一篇文章一样,用以下的命令完成文件所有者修改及驱动的加载:

sudo chown -R root:wheel IOKitTest.kext
sudo kextload IOKitTest.kext

然后我们在控制台Console中就可以看到依次执行了init,probe,start这三个方法,并且发现在init中打印的dict字典正是Info.plist文件中IOKitPersonalities项所填写的项目。
当在命令行下执行以下操作:

sudo kextunload IOKitTest.kext

你会在控制台上收到stop,free的的日志信息。

本文也只是记录了如何创建一个驱动程序的示例,但具体如何通过驱动程序与特定的硬件通信,还未涉及到相关的业务,文章中的示例代码下载:IOKitTest.zip

初探Mac OSX内核开发(三)——通过属性与应用层交互

$
0
0

前面两篇文章已经介绍了如何创建内核扩展和驱动程序,这都属于内核态编程,内核态与用户态编程是两个被操作系统所隔离的区间,两者运行的环境、权限都不相同,所以也不能直接进行通信(关于OSX系统的编程环境介绍可以点击我之前的一篇文章《OSX系统编程环境的介绍》),所以我们就来一起探讨一下用户态的应用层程序如何与驱动程序进行交互。
在前一篇文章的文章中我们已经看到,工程中是引用了Kernel.framework框架的,它是一个面向于内核编程的框架,而如果应用层需要与内核通信呢就需要引用另一个框架IOKit.framework,这个框架定义了一些与公共的接口,可以让用户层通过这些接口与一些标准的驱动程序进行交互,在之前的工作中,我也站在应用层的角度使用过这些接口来获取硬件和系统的状态信息,相关的示例可以点击这篇文章《通过IOKit读取系统信息》,并且本文中也要使用到这篇文章中的代码块。

好了,打开Xcode,创建一个普通的应用程序,由于不涉及到UI部分,我们就选择命令行程序的模板就行了,然后取名为AppTest,然后为工程添加IOKit.framework的引用,并修改main.m文件为以下的内容:

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>

int main(int argc, const char * argv[])
{
    io_iterator_t iterator;
    kern_return_t kr;
    io_object_t   driver;
    
    CFMutableDictionaryRef matchDictionary = IOServiceMatching("com_osxkernel_driver_IOKitTest");
    kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &iterator);
    if (kr != kIOReturnSuccess)
    {
        return 0;
    }
    
    while ((driver = IOIteratorNext(iterator)) != 0)
    {
        CFMutableDictionaryRef properties = NULL;
        kr = IORegistryEntryCreateCFProperties(driver,
                                               &properties,
                                               kCFAllocatorDefault,
                                               kNilOptions);
        if (kr != kIOReturnSuccess || properties == NULL)
        {
            continue;
        }
        
        NSLog(@"%@",(__bridge NSDictionary*)properties);
    }
    
    return 0;
}

把上一篇文章中的驱动文件加载好,然后编译运行AppTest,如果一切顺利的话,你就能看到控制台会输出与IOKitTest驱动相关的属性信息,至此,从应用层单向的获取驱动的属性信息就成功,由于该属性列表由驱动程序去维护管理,所以驱动程序通过给属性中添加不同的信息都能传递到应用层了。

上面已经实现了从应用层到内核层的单向通信,下面我们继续修改代码来尝试从应用层向内核传递信息,从上面的代码我们已经看到可以获得驱动程序的属性列表,那我们如何在应用层向该属性表中写入数据而达到向内核层传输数据呢,修改上面的代码:

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>

int main(int argc, const char * argv[])
{
    io_iterator_t iterator;
    kern_return_t kr;
    io_object_t   driver;
    
    CFMutableDictionaryRef matchDictionary = IOServiceMatching("com_osxkernel_driver_IOKitTest");
    kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &iterator);
    if (kr != kIOReturnSuccess)
    {
        return 0;
    }
    
    while ((driver = IOIteratorNext(iterator)) != 0)
    {
        //读取数据
        CFMutableDictionaryRef properties = NULL;
        kr = IORegistryEntryCreateCFProperties(driver,
                                               &properties,
                                               kCFAllocatorDefault,
                                               kNilOptions);
        if (kr == kIOReturnSuccess)
        {
            NSLog(@"%@",(__bridge NSDictionary*)properties);
        }
        
        //写入数据
        kr = IORegistryEntrySetCFProperty(driver,
                                          CFSTR("message"),
                                          CFSTR("Hello,Kernel!"));
        if (kr == kIOReturnSuccess)
        {
            NSLog(@"已经向内核发送消息!");
        }
    }
    return 0;
}

修改之后,我们的应用层代码便已经有向内核传递信息的功能,但如何让内核收到消息呢?当然我们是需要去修改驱动程序的代码的,应用层中我们使用了IORegistryEntrySetCFProperty方法去设置属性,驱动中对应需要去实现setProperties方法来接收数据并处理,打开驱动程序的工程,加入以下的代码:

//记得去头文件中声明方法
IOReturn com_osxkernel_driver_IOKitTest::setProperties(OSObject *properties)
{
    IOLog("IOKitTest::setProperties\n");
    OSDictionary *dict = OSDynamicCast(OSDictionary, properties);
    if (dict)
    {
        OSObject *value = dict->getObject("message");
        OSString *message = OSDynamicCast(OSString, value);
        if (message)
        {
            setProperty("message", message);
            IOLog("message:%s\n",message->getCStringNoCopy());
            return kIOReturnSuccess;
        }
    }
    return kIOReturnUnsupported;
}

然后重新编译并加载驱动程序,此时再运行应用程序,你便在控制台看到各种输出信息了,至此,驱动程序与应用程序之间的双向通信就已经都实现了,但基于属性的的交互只适合于数据量很小的情境,如果需要更灵活的交互,后面我们会继续探讨其它的方式!

本文中的驱动程序与应用程序的Demo下载:IOKitTest+AppTest.zip

初探Mac OSX内核开发(四)——通过连接与应用层交互

$
0
0

又过了一个星期了,终于抽出时间继续来更新我的博客,接上一篇文章,我们已经讨论了如何通过属性来让应用层与内核产生交互,但试想一下,针对一个功能更加复杂的驱动程序,仍然使用属性的值来定义一套交互的过程,会显得既笨拙又低效,今天就来继续学习一下如何用更好的方式来完成这个交互,我用了两篇文章来讨论应用层与内核之间的交互过程,一方面因为我能力有限短时间内还不能深入到具体的驱动程序的开发,但一方面我觉得既然驱动程序最终也是为应用层提供服务,所以两者之间的交互是一个必需的也非常重要的过程。

在上一篇文章中使用IOKit相关接口的时,或许你不经意间已经留意到了类似IOServiceOpen的方法,没错,这篇文章正是讨论怎样打开一个枚举到的驱动程序对象而建立连接,但IOKit框架并不会让会应用程序直接与驱动程序连接,而是需要引用一个IOUserClient作为媒介,其原因是因为应用程序与驱动程序并非一对一的关系,驱动程序可能需要同时为多个应用提供服务,而应用程序也可以同时与多个驱动对象交互,所以每一个连接就需要一个对象来进行维护,而这个工作就是由IOUserClient来完成的(大多情况下你会根据需求编写子类),这与面向对象编程的低耦合思想是相符的,为了加深理解这个机制,我们通过下面的关系图可以更加直观的理清它们之间的关系:
IOUserClient

理论就介绍到这儿了,下面就直接开始编码了,我们本篇文章的Demo让驱动程序实现一个四则运算,然后用户程序通过与驱动建立连接,调用接口并得到计算结果。
IOUserClient虽然是应用程序与驱动程序的枢纽,但由于它也属于驱动程序的一个部分,所以应用程序也不能直接通过函数调用的方法使用到IOUserClient,而是通过IOKit框架提供的接口IOConnectCallStructMethod方法,应用程序需要调用什么什么方法,通过指定一个uint64的值传递到内核的IOUserClient,然后选择对应的方法,方法需要的参数和返回值都是通过结构体保存和传递,所以我们需要定义表示不同方法的uint64的值和不同的结构体,而这些定义在应用程序和驱动程序中都需要使用到,所以我们定义在一个公共的头文件Common.h中:

//四则运算
enum CalculateSelectorCode
{
    kCalculatePlus,
    kCalculateMinus,
    kCalculateMultiply,
    kCalculateDivided
};

//运算的参数
typedef struct CalculateArguments
{
    double va_a;
    double va_b;
}CalculateArguments;

//运算的结果
typedef struct CalculateResult
{
    double va;
}CalculateResult;

然后我们编写应用程序中的代码:

#import <Foundation/Foundation.h>
#import <IOKit/IOKitLib.h>
#include "Common.h"

int main(int argc, const char * argv[])
{
    io_iterator_t iterator;
    kern_return_t kr;
    io_object_t   driver;
    
    CFMutableDictionaryRef matchDictionary = IOServiceMatching("com_osxkernel_driver_IOKitTest");
    kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDictionary, &iterator);
    if (kr != kIOReturnSuccess)
    {
        return 0;
    }
    
    while ((driver = IOIteratorNext(iterator)) != 0)
    {
        //service表示需要连接的驱动程序
        io_service_t service = driver;
        //task表示当前应用程序
        task_port_t task = mach_task_self();
        //type的含义由开发者选定方式定义(忽略)
        uint32_t type = 0;
        //connect用于保存这个连接
        io_connect_t connect = 0;
        
        //打开连接
        IOServiceOpen(service, task, type, &connect);
        
        //设置参数
        size_t inputCount = sizeof(CalculateArguments);
        CalculateArguments *inputStruct = (CalculateArguments *)calloc(inputCount, 1);
        inputStruct->va_a = 4.0;
        inputStruct->va_b = 2.0;
        size_t outputCount = sizeof(CalculateResult);
        CalculateResult *outputStruct = (CalculateResult *)calloc(outputCount, 1);
        
        //加法
        IOConnectCallStructMethod(connect, kCalculatePlus, inputStruct, inputCount, &outputStruct, &outputCount);
        NSLog(@"%.2f",outputStruct->va);
        
        //减法
        IOConnectCallStructMethod(connect, kCalculateMinus, inputStruct, inputCount, &outputStruct, &outputCount);
        NSLog(@"%.2f",outputStruct->va);
        
        //乘法
        IOConnectCallStructMethod(connect, kCalculateMultiply, inputStruct, inputCount, &outputStruct, &outputCount);
        NSLog(@"%.2f",outputStruct->va);
        
        //除法
        IOConnectCallStructMethod(connect, kCalculateDivided, inputStruct, inputCount, &outputStruct, &outputCount);
        NSLog(@"%.2f",outputStruct->va);
        
        //关闭连接
        IOServiceClose(service);
    }
    return 0;
}

应用程序的代码就已经完成了,下面就需要修改驱动程序的代码了,修改之前编写的驱动程序代码,添加四个方法:

double com_osxkernel_driver_IOKitTest::calculatePlus(double valueA,double valueB)
{
    return valueA+valueB;
}
double com_osxkernel_driver_IOKitTest::calculateMinus(double valueA,double valueB)
{
    return valueA-valueB;
}
double com_osxkernel_driver_IOKitTest::calculateMultiply(double valueA,double valueB)
{
    return valueA*valueB;
}
double com_osxkernel_driver_IOKitTest::calculateDivided(double valueA,double valueB)
{
    return valueA/valueB;
}

然后下面就要编写一个自定义的IOUserClient的子类com_osxkernel_driver_IOKitTestUserClient,实现应用程序与驱动程序之间的连接,为了让IOKit框架可以自动识别我们的用户连接类我们需要修改Info.plist文件,并在IOKitPersonalities下的IOKitTest字典中添加IOUserClientClass的值为com_osxkernel_driver_IOKitTestUserClient,另外将前面的公共头拖入到工程中,然后就开始编写代码,下面是com_osxkernel_driver_IOKitTestUserClient的头文件:

#include <IOKit/IOUserClient.h>
#include "IOKitTest.h"

class com_osxkernel_driver_IOKitTestUserClient : public IOUserClient
{
    OSDeclareAbstractStructors(com_osxkernel_driver_IOKitTestUserClient)
    
private:
    //保存对应用程序的引用
    task_t m_task;
    //保存对驱动程序的引用
    com_osxkernel_driver_IOKitTest *m_driver;
    
public:
    //由驱动程序调用
    virtual bool start(IOService *provider);
    
    //连接初使化
    virtual bool initWithTask(task_t owningTask, void * securityToken, UInt32 type,
                              OSDictionary * properties);
    //连接断开或应用程序退出
    virtual IOReturn clientClose(void);

    //当应用程序调用方法时
    virtual IOReturn externalMethod( uint32_t selector, IOExternalMethodArguments * arguments,
                                    IOExternalMethodDispatch * dispatch = 0, OSObject * target = 0, void * reference = 0 );
};

由代码可以看到用户连接类与驱动程序的类结构非常相似,其实IOUserClient也是IOService的一个子类,只不过初使化的方法和结束的方法不太一样,另外一个关键的方法就是externalMethod,具体的一些实现细节就直接看实现文件:

#include "MyUserClient.h"
#include <IOKit/IOLib.h>
#include "Common.h"

#define super IOUserClient
OSDefineMetaClassAndStructors(com_osxkernel_driver_IOKitTestUserClient, IOUserClient)

bool com_osxkernel_driver_IOKitTestUserClient::initWithTask(task_t owningTask,
                                                                          void *securityToken,
                                                                          UInt32 type,
                                                                          OSDictionary *properties)
{
    if (!super::initWithTask(owningTask, securityToken, type, properties))
    {
        return false;
    }
    
    IOReturn ret = clientHasPrivilege(securityToken, kIOClientPrivilegeAdministrator);
    if (ret == kIOReturnSuccess)
    {
        //Admin权限
    }
    
    //获得应用程序的引用
    m_task = owningTask;
    return true;
}

bool com_osxkernel_driver_IOKitTestUserClient::start(IOService *provider)
{
    if (!super::start(provider))
    {
        return false;
    }
    
    //获得驱动程序的引用
    m_driver = OSDynamicCast(com_osxkernel_driver_IOKitTest, provider);
    if (!m_driver)
    {
        return false;
    }
    return true;
}

IOReturn com_osxkernel_driver_IOKitTestUserClient::clientClose(void)
{
    //销毁连接对象
    terminate();
    return kIOReturnSuccess;
}

IOReturn com_osxkernel_driver_IOKitTestUserClient::externalMethod(uint32_t selector,
                                                                  IOExternalMethodArguments * arguments,
                                                                  IOExternalMethodDispatch * dispatch,
                                                                  OSObject * target,
                                                                  void * reference)
{
    //从参数的结构体中获得值
    double valueA = ((CalculateArguments*)arguments->structureInput)->va_a;
    double valueB = ((CalculateArguments*)arguments->structureInput)->va_b;
    CalculateResult *result = (CalculateResult *)arguments->structureOutput;
    
    //根据应用程序传递的整型值判断执行的方法
    switch (selector)
    {
        case kCalculatePlus:
            result->va = m_driver->calculatePlus(valueA, valueB);
            break;
        case kCalculateMinus:
            result->va = m_driver->calculateMinus(valueA, valueB);
            break;
        case kCalculateMultiply:
            result->va = m_driver->calculateMultiply(valueA, valueB);
            break;
        case kCalculateDivided:
            result->va = m_driver->calculateDivided(valueA, valueB);
            break;
        default:
            return kIOReturnUnsupported;
            break;
    }
    return kIOReturnSuccess;
}

好了,代码到这儿就完全结束了,现在编译加载驱动,然后运行应用程序相信就能得到相应的结果了,本文章中的Demo源代码下载:IOKitTest+AppTest_2.zip

自定义NSSearchFiled样式

$
0
0

THSearchField

在Mac OSX的软件中经常会使用到NSSearchFiled这个控件,用它来做搜索框还是非常方便的,之前使用它时都是采用的默认造型,但最近的一个项目因为整体风格的原因,它的外观显然不满足使用,最初本打算自己去实现一个类似的NSSearchField,但发现要实现功能和NSSearchField一样的控件,需要的代码量还不少,于是最终还是选择了去派生一个风格不一样的NSSearchFieldCell,简单几行代码便可实现修改背景色、光标颜色、放大镜图标和退出图标,恰好这两天有人写邮件问我如何修改NSSearchField的样式,我就随便贴出这几行代码,其实非常简单:

#import "THSearchFieldCell.h"

@implementation THSearchFieldCell

//通过代码实例化
- (id)init
{
    self = [super init];
    if (self)
    {
        [self setUp];
    }
    return self;
}

//通过xib实例化
- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self)
    {
        [self setUp];
    }
    return self;
}

- (void)setUp
{
    //重写搜索图标
    NSButtonCell *searchButtonCell = [self searchButtonCell];
    NSImage *searchImage = [NSImage imageNamed:NSImageNameHomeTemplate];
    [searchImage setSize:NSMakeSize(16, 16)];
    [searchButtonCell setImage:searchImage];
    [searchButtonCell setAlternateImage:searchImage];
    
    //重写取消图标
    NSButtonCell *cancelButtonCell = [self cancelButtonCell];
    NSImage *cancelImage = [NSImage imageNamed:NSImageNameRevealFreestandingTemplate];
    [cancelImage setSize:NSMakeSize(16, 16)];
    [cancelButtonCell setImage:cancelImage];
    [cancelButtonCell setAlternateImage:cancelImage];
}

//重写该方法实现对背景色的修改
- (void)drawInteriorWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
{
    NSRect rect = controlView.bounds;
    NSBezierPath *bezierPath = [NSBezierPath bezierPathWithRoundedRect:rect
                                                               xRadius:NSHeight(rect)/2
                                                               yRadius:NSHeight(rect)/2];
    [[NSColor orangeColor] set];
    [bezierPath fill];
    [super drawInteriorWithFrame:cellFrame inView:controlView];
}

//重写该方法实现对光标颜色的修改
- (NSText *)setUpFieldEditorAttributes:(NSText *)textObj
{
    NSText *text = [super setUpFieldEditorAttributes:textObj];
    [(NSTextView*)text setInsertionPointColor:[NSColor whiteColor]];
    return text;
}

@end

完整的测试代码下载:THSearchFieldDemo.zip

在Mac OSX中获取网卡MAC地址

$
0
0

今天在CocoaChina上看到有人发帖在询问如何在Mac OSX下获得网卡的物理地址,恰好最近在学习IOKit的一些知识,所以通过IOKit的接口肯定是可以很方便的拿到MAC地址信息的,但不知道还有没有其它更方便的API了,所以我姑且贴出一段通过IOKit去获得网卡MAC地址的代码:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    kern_return_t kr;
    CFMutableDictionaryRef matchDict;
    io_iterator_t iterator;
    io_registry_entry_t entry;
    
    matchDict = IOServiceMatching("IOEthernetInterface");
    kr = IOServiceGetMatchingServices(kIOMasterPortDefault, matchDict, &iterator);
    
    NSDictionary *resultInfo = nil;
    
    while ((entry = IOIteratorNext(iterator)) != 0)
    {
        CFMutableDictionaryRef properties=NULL;
        kr = IORegistryEntryCreateCFProperties(entry,
                                               &properties,
                                               kCFAllocatorDefault,
                                               kNilOptions);
        if (properties)
        {
            resultInfo = (__bridge_transfer NSDictionary *)properties;
            NSString *bsdName = [resultInfo objectForKey:@"BSD Name"];
            NSData *macData = [resultInfo objectForKey:@"IOMACAddress"];
            if (!macData)
            {
                continue;
            }
            
            NSMutableString *macAddress = [[NSMutableString alloc] init];
            const UInt8 *bytes = [macData bytes];
            for (int i=0; i<macData.length; i++)
            {
                [macAddress appendFormat:@"%02x",*(bytes+i)];
            }
            
            //打印Mac地址
            if (bsdName && macAddress)
            {
                NSLog(@"网卡:%@\nMac地址:%@\n",bsdName,macAddress);
            }
        }
    }
    
    IOObjectRelease(iterator);
}
Viewing all 34 articles
Browse latest View live