Flex

世界一簡単にANE(iOSのStaticLibrary)を作る方法

えー、なんか、ipaパッケージにする時に「undefined symbols objc_storestrong」とか 「Symbol not found: _objc_retain」とか言われてたんですが、どうもXcodeのLLVMにバグ(?)があったらしいです。

今のXcode5なら、普通にARCでStatic Libraryとしてビルドしても問題なくANE作成と(Flash Builder/Flash Proで)ipaパッケージ作れます。


要はiOSのStatic LibraryをARCで作りたいんです。
MRC/MRRはもうイヤなんです。

で、作り方。

Xcodeのプロジェクトファイルを開いて、プロジェクトを選択。
Build Settings -> Objective-C Automatic Reference Counting = NO


次にビルドするTargetsを選択して、Build Phasesに移動。
Compile SourcesにあるソースファイルのCompiler Flagsに -fno-objc-arcを付ける。

で、あとはフツーにARCで書くだけ。

ANEを作る際、platform.xmlに書いていた

<option>-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc</option>
<option>-larclite_iphoneos</option>

も要らなくなります。

簡単です。

先日 iBeaconのANEを作る機会があったので、その内容をつらつらと書いてみようと思います。
いや、Nativeで一通りやったのをANE化しただけなんですけどね。


ane-labにサンプルを上げてありますので、そっちも参考にして下さい。

iOS_iBeaconFirst

iOS_BLE

ANEの作り方は、Native Alertのトコ(1?3まであります)を順番に見といて下さい。
今回はNativeのコードと、platform.xmlの内容を貼っておきますね。言うまでもなくNatvieはARCです。



まずはiBeaconのObjective-C部分から。




[ iBeacon.h ]
---------------------------------------
//
//  iBeacon.h
//  iBeacon
//
//  Created by hoehoe on 2013/12/02.
//  Copyright (c) 2013年 hoehoe. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <Adobe AIR/Adobe AIR.h>
#import "BeaconController.h"

@interface iBeacon : NSObject
{
    BeaconController* _controller;
}

@property (nonatomic, strong) BeaconController* controller;

@end

[ EOF ]
---------------------------------------



[ iBeacon.m ]
---------------------------------------
//
//  iBeacon.m
//  iBeacon
//
//  Created by hoehoe on 2013/12/02.
//  Copyright (c) 2013年 hoehoe. All rights reserved.
//

#import "iBeacon.h"

@implementation iBeacon

iBeacon* me;
FREContext* context;

-(id)init
{
    if(self == nil){
        self = [super init];
    }
    self.controller = [[BeaconController alloc] init];
    self.controller.context = context;
    NSLog(@"init");
    return self;
}

-(void)startBeacon
{
    [self.controller start];
    NSLog(@"start");
}
-(void)stopBeacon
{
    [self.controller stop];
    NSLog(@"stop");
}

FREObject start(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]) {
    FREObject resultObj = NULL;
    [me startBeacon];
    return resultObj;
}


FREObject stop(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]) {
    FREObject resultObj = NULL;
    [me stopBeacon];
    return resultObj;
}

void contextFinalizer(FREContext ctx)
{
    return;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
    if(me == nil){
        context = ctx;
        me = [[iBeacon alloc] init];
    }
   
    *numFunctions = 2;
    FRENamedFunction*  func= (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));
   
    func[0].name = (const uint8_t*) "start";
    func[0].functionData = NULL;
    func[0].function = &start;

    func[1].name = (const uint8_t*) "stop";
    func[1].functionData = NULL;
    func[1].function = &stop;

    *functions = func;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData)
{
   
}

@end

[ EOF ]
---------------------------------------


特筆する部分は特にないですね。
FREContextと自身のインスタンスを広域で定義している点に注意してください。
FREContextは、iBeaconControllerクラスでも必要になるので、iBeaconControllerをインスタンス化したあとにセッ トしてます。
ここの処理内容は、スタートボタンを押した時とストップボタンを押したときの処理が書いてあるだけです。


で、こっちがiBeaconの本処理ですね。

[ BeaconController.h ]
---------------------------------------
//
//  BeaconController.h
//  iBeacon
//
//  Created by hoehoe on 2013/12/02.
//  Copyright (c) 2013年 hoehoe. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import <Adobe AIR/Adobe AIR.h>

@interface BeaconController : NSObject<CBPeripheralManagerDelegate, CLLocationManagerDelegate>
{
    FREContext* _context;
}
@property (strong, nonatomic) CLBeaconRegion *beaconRegion;
@property (strong, nonatomic) NSDictionary *beaconPeripheralData;
@property (strong, nonatomic) CBPeripheralManager *peripheralManager;
@property (strong, nonatomic) CLLocationManager *locationManager;
@property FREContext* context;

-(void)start;
-(void)stop;

@end
[ EOF ]
---------------------------------------



[ BeaconController.m ]
---------------------------------------
//
//  BeaconController.m
//  iBeacon
//
//  Created by hoehoe on 2013/12/02.
//  Copyright (c) 2013年 hoehoe. All rights reserved.
//

#import "BeaconController.h"

@implementation BeaconController

@synthesize context = _context;

-(id)init
{
    if(self == nil){
        self = [super init];
    }
   
    [self initBeacon];
   
    self.beaconPeripheralData = [self.beaconRegion peripheralDataWithMeasuredPower:nil];
    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self
                                                                     queue:nil
                                                                   options:nil];
   
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
   
    return self;
}

- (void)initBeacon {
    //UUIDはターミナル使ってuuidgenで生成してね
    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:@"501797C5-1AEB-4CA3-8C3C-F7AAF5C0D396"];
    self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
                                                                major:1
                                                                minor:1
                                                           identifier:@"com.chocbanana.myRegion"];
}


-(void)start
{
    [self.locationManager startMonitoringForRegion:self.beaconRegion];
}

-(void)stop{
    [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
}

-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        NSLog(@"Powered On");
        [self.peripheralManager startAdvertising:self.beaconPeripheralData];
    } else if (peripheral.state == CBPeripheralManagerStatePoweredOff) {
        NSLog(@"Powered Off");
        [self.peripheralManager stopAdvertising];
    }
}

- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region
{
    [self.locationManager requestStateForRegion:self.beaconRegion];
}
- (void)locationManager:(CLLocationManager *)manager didDetermineState:(CLRegionState)state forRegion:(CLRegion *)region
{
    switch (state) {
        case CLRegionStateInside:
            if ([region isMemberOfClass:[CLBeaconRegion class]] && [CLLocationManager isRangingAvailable]) {
                [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
            }
        case CLRegionStateOutside:
        case CLRegionStateUnknown:
            break;
            break;
           
        default:
            break;
    }
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}

-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    [self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
}

-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
    CLBeacon *beacon = [[CLBeacon alloc] init];
    beacon = [beacons lastObject];
   
    NSString* range = nil;
   
    if (beacon.proximity == CLProximityUnknown) {
        //NSLog(@"Unknown Proximity");
        range = @"おいぃ、どこだよ...。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    } else if (beacon.proximity == CLProximityImmediate) {
        //NSLog(@"Immediate");
        range = @"近い!近い!近い!近い!近い!";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    } else if (beacon.proximity == CLProximityNear) {
        //NSLog(@"Near");
        range = @"近いよー。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    } else if (beacon.proximity == CLProximityFar) {
        //NSLog(@"Far");
        range = @"遠いよー。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    }
}

@end

[ EOF ]
---------------------------------------

Beaconを受信したら、FREDispatchStatusEventAsyncで戻しています。
AIR/SWC側で addEventListenerしておけば、Beaconの受信値を画面に表示出来ます。

で、iBeaconの問題点は、バックグラウンドでBeaconを送信出来ない...という仕様です。
まぁ、iOSデバイス的には受信がメインなのでしょうね。

あ、swc作る時のplatform.xmlはこちらになります。

[ platfrom.xml ]
---------------------------------------
<platform xmlns="http://ns.adobe.com/air/extension/4.0">
    <sdkVersion>7.0</sdkVersion>
    <linkerOptions>
        <option>-w</option>
        <option>-ios_version_min 7.0</option>
        <option>-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc</option>
        <option>-larclite_iphoneos</option>
        <option>-framework UIKit</option>
        <option>-framework CoreLocation</option>
        <option>-framework CoreBluetooth</option>
    </linkerOptions>

</platform>

[ EOF ]
---------------------------------------

これで、Flash Builderでリリースビルドすると、iBeaconを受信するANEを組み込んだAIR for iOSのアプリケーションが出来上がりです。




続いてこのiBeacon受信アプリをBLE対応にしてみましょう。


Native部分をまた貼っておきます。


[ BeaconController.h ]
---------------------------------------
//
//  BeaconController.h
//  iBeaconfTest
//
//  Created by hoehoe on 2013/12/01.
//  Copyright (c) 2013年 Gigaworks. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreLocation/CoreLocation.h>
#import <CoreBluetooth/CoreBluetooth.h>
#import <Adobe AIR/Adobe AIR.h>

@interface BeaconController : NSObject<CBPeripheralManagerDelegate, CLLocationManagerDelegate, CBCentralManagerDelegate>
{
    NSString* kServiceUUID;
    NSString* kCharacteristicUUID;
    FREContext* context;
}
@property (strong, nonatomic) CLBeaconRegion *beaconRegion;
@property (strong, nonatomic) NSDictionary *beaconPeripheralData;
@property (strong, nonatomic) CBPeripheral *peripheral;
@property (strong, nonatomic) CBCentralManager *centralManager;
@property (strong, nonatomic) CLLocationManager *locationManager;

@property FREContext* context;

-(void)start;
-(void)stop;

@end

[ EOF ]
---------------------------------------

[ BeaconController.m ]
---------------------------------------
//
//  BeaconController.m
//  iBeaconfTest
//
//  Created by hoehoe on 2013/12/01.
//  Copyright (c) 2013年 Gigaworks. All rights reserved.
//

#import "BeaconController.h"

@implementation BeaconController

@synthesize context = _context;

-(id)init
{
    if(self == nil){
        self = [super init];
    }
   
    //uuidgenで生成して下さい。
    kServiceUUID = @"501797C5-1AEB-4CA3-8C3C-F7AAF5C0D396";
    kCharacteristicUUID = @"45077DB4-3DF6-40FC-ACF6-8212B8579C4B";
   
    [self initBeacon];
   
    self.beaconPeripheralData = [self.beaconRegion peripheralDataWithMeasuredPower:nil];
    //self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self
    //                                                                 queue:nil
    //                                                               options:nil];
   
    self.locationManager = [[CLLocationManager alloc] init];
    self.locationManager.delegate = self;
    [self initRegion];
   
   
    //self.sendPeripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:nil];
    //[self initSendPeripheral];
   
    self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:nil options:@{CBAdvertisementDataServiceUUIDsKey: [CBUUID UUIDWithString:kServiceUUID]}];
   
    return self;
}

- (void)initBeacon {
   
    NSUUID *uuid = [[NSUUID alloc] initWithUUIDString:kServiceUUID];
    self.beaconRegion = [[CLBeaconRegion alloc] initWithProximityUUID:uuid
                                                                major:1
                                                                minor:1
                                                           identifier:@"com.devfright.myRegion"];
}

- (void)initRegion {
    [self.locationManager startMonitoringForRegion:self.beaconRegion];
}

-(void)initSendPeripheral
{
    //NSLog(@"%@", NSStringFromSelector(_cmd));
   
    // Creates the characteristic UUID
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
    CBCharacteristic* characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:nil permissions:CBAttributePermissionsReadable];
   
   
    // Creates the service UUID
    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
   
    // Creates the service and adds the characteristic to it
    CBMutableService* service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
   
    // Sets the characteristics for this service
    [service setCharacteristics:@[characteristic]];
   
    // Publishes the service
    //[self.sendPeripheralManager addService:service];

}


-(void)start
{
    [self locationManager:self.locationManager didStartMonitoringForRegion:self.beaconRegion];
}

-(void)stop
{
    [self.locationManager stopMonitoringForRegion:self.beaconRegion];
}


-(void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral {
    if (peripheral.state == CBPeripheralManagerStatePoweredOn) {
        NSLog(@"Powered On");
        //[self.peripheralManager startAdvertising:self.beaconPeripheralData];
        //[self.sendPeripheralManager startAdvertising:[self.beaconRegion peripheralDataWithMeasuredPower:nil]];
    } else if (peripheral.state == CBPeripheralManagerStatePoweredOff) {
        NSLog(@"Powered Off");
        //[self.peripheralManager stopAdvertising];
    }
}

- (void)locationManager:(CLLocationManager *)manager didStartMonitoringForRegion:(CLRegion *)region {
    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}

- (void)locationManager:(CLLocationManager *)manager didEnterRegion:(CLRegion *)region {
    [self.locationManager startRangingBeaconsInRegion:self.beaconRegion];
}

-(void)locationManager:(CLLocationManager *)manager didExitRegion:(CLRegion *)region {
    //[self.locationManager stopRangingBeaconsInRegion:self.beaconRegion];
}

-(void)locationManager:(CLLocationManager *)manager didRangeBeacons:(NSArray *)beacons inRegion:(CLBeaconRegion *)region {
    NSLog(@"Catch Beacon...");
    CLBeacon *beacon = [[CLBeacon alloc] init];
    beacon = [beacons lastObject];
       
    NSString* range;
   
    if (beacon.proximity == CLProximityUnknown) {
        //NSLog(@"Unknown Proximity");
        range = @"どこですかー?";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    } else if (beacon.proximity == CLProximityImmediate) {
        //[self.bluetooth sendData];
        //NSLog(@"Immediate");
        range = @"近いっすよ?";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    } else if (beacon.proximity == CLProximityNear) {
        //[self.bluetooth sendData];
        //NSLog(@"Near");
        range = @"あ、いたいた";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    } else if (beacon.proximity == CLProximityFar) {
        NSLog(@"Far");
        range = @"あ、遠くにいるわ。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    }
   
    //NSMutableDictionary* userInfo = [NSMutableDictionary dictionary];
    //[userInfo setValue:range forKey:@"range"];
    //NSNotification* n = [NSNotification notificationWithName:@"ToNotifyTheRange" object:self userInfo:userInfo];
    //[[NSNotificationCenter defaultCenter] postNotification:n];
}


// デバイス発見時
// centralManager:didDiscoverPeripheral:advertisementData:RSSI:
// Invoked when the central manager discovers a peripheral while scanning.
- (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData RSSI:(NSNumber *)RSSI
{
   
    NSLog(@"RSSI: %d", [RSSI intValue]);
    int proximity = [RSSI intValue];
    NSString* range;
   
    if (proximity < -70){
        range = @"遠いよ。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    }else if (proximity < -55){
        range = @"近いね。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    }else if (proximity < 0){
        range = @"だから、近いってば!";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    }else{
        range = @"居ませんね。";
        FREDispatchStatusEventAsync (self.context, (uint8_t*)[range UTF8String], (uint8_t*)[@"OK" UTF8String]);
    }
   
    //NSMutableDictionary* userInfo = [NSMutableDictionary dictionary];
    //[userInfo setValue:range forKey:@"range"];
    //NSNotification* n = [NSNotification notificationWithName:@"ToNotifyTheRange" object:self userInfo:userInfo];
    //[[NSNotificationCenter defaultCenter] postNotification:n];

    if(self.peripheral != peripheral){
        self.peripheral = peripheral;
        NSLog(@"Connecting to pripheral %@", peripheral);
        NSLog(@"PeripheralName: %@", peripheral.name);
        // 発見されたデバイスに接続
        [self.centralManager connectPeripheral:peripheral options:nil];
    }

}

// ------------------------------
// CBCentralManagerDelegate
// ------------------------------

// Monitoring Connections with Peripherals

// centralManager:didConnectPeripheral:
// Invoked when a connection is successfully created with a peripheral.
- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    NSLog(@"%@", peripheral.description);
   
    // Clears the data that we may already have
    //[self.data setLength:0];
    // Sets the peripheral delegate
    //[self.peripheral setDelegate:self];
    // Asks the peripheral to discover the service
    //[self.peripheral discoverServices:@[ [CBUUID UUIDWithString:kServiceUUID] ]];
}

// centralManager:didDisconnectPeripheral:error:
// Invoked when an existing connection with a peripheral is torn down.
- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
        NSLog(@"[error] %@", [error localizedFailureReason]);
        NSLog(@"[error] %@", [error localizedRecoverySuggestion]);
    }else{
        NSLog(@"disconnect");
        //[self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}];
        //cancelPeripheralConnection
        //[self.centralManager];
    }
}

// centralManager:didFailToConnectPeripheral:error:
// Invoked when the central manager fails to create a connection with a peripheral.
- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Discovering and Retrieving Peripherals


// centralManager:didRetrieveConnectedPeripherals:
// Invoked when the central manager retrieves a list of peripherals currently connected to the system.
- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// centralManager:didRetrievePeripherals:
// Invoked when the central manager retrieves a list of known peripherals.
- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Monitoring Changes to the Central Manager's State

// CBCentralManager が初期化されたり状態が変化した際に呼ばれるデリゲートメソッド
// centralManagerDidUpdateState:
// Invoked when the central manager's state is updated. (required)
- (void)centralManagerDidUpdateState:(CBCentralManager *)central
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    switch (central.state) {
        case CBCentralManagerStatePoweredOn:
            NSLog(@"%d, CBCentralManagerStatePoweredOn", (int)central.state);
           
            [self.centralManager scanForPeripheralsWithServices:@[[CBUUID UUIDWithString:kServiceUUID]] options:@{CBCentralManagerScanOptionAllowDuplicatesKey:@YES}]; // NO だとダメ?
            break;
        case CBCentralManagerStatePoweredOff:
            NSLog(@"%d, CBCentralManagerStatePoweredOff", (int)central.state);
            break;
        case CBCentralManagerStateResetting:
            NSLog(@"%d, CBCentralManagerStateResetting", (int)central.state);
            break;
        case CBCentralManagerStateUnauthorized:
            NSLog(@"%d, CBCentralManagerStateUnauthorized", (int)central.state);
            break;
        case CBCentralManagerStateUnsupported:
            NSLog(@"%d, CBCentralManagerStateUnsupported", (int)central.state);
            break;
        case CBCentralManagerStateUnknown:
            NSLog(@"%d, CBCentralManagerStateUnknown", (int)central.state);
            break;
        default:
            break;
    }
}

// centralManager:willRestoreState:
// Invoked when the central manager is about to be restored by the system.
- (void)centralManager:(CBCentralManager *)central willRestoreState:(NSDictionary *)dict
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

@end

[ EOF ]
---------------------------------------


前のと違うのはCBCentralManagerDelegateを追加実装している点です。
CBCentralManagerDelegateでBLEを受信出来るようにしました。

で、もう1つ、BLEの送信を出来るようにしましょう。

[ MyBLESend.h ]
---------------------------------------
//
//  MyBLESend.h
//  CoreBluetoothTest
//
//  Created by hoehoe on 2013/11/30.
//  Copyright (c) 2013年 Gigaworks. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <CoreBluetooth/CoreBluetooth.h>

@interface MyBLESend : NSObject<CBPeripheralManagerDelegate>
{
    NSString *kServiceUUID;
    NSString *kCharacteristicUUID;
}

@property (nonatomic, strong) CBPeripheralManager *peripheralManager;
@property (nonatomic, strong) CBCharacteristic *characteristic;
@property (nonatomic, strong) CBMutableService *service;


-(void)setupService;
-(void)stopService;


// ------------------------------
// CBPeripheralManagerDelegate
// ------------------------------

// Monitoring Changes to the Peripheral Manager's State

// peripheralManagerDidUpdateState:
// Invoked when the peripheral manager's state is updated. (required)
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral;

// peripheralManager:willRestoreState:
// Invoked when the peripheral manager is about to be restored by the system.
- (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict;

// Adding Services

// peripheralManager:didAddService:error:
// Invoked when you publish a service, and any of its associated characteristics and characteristic descriptors, to the local Generic Attribute Profile (GATT) database.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error;

// Advertising Peripheral Data

// peripheralManagerDidStartAdvertising:error:
// Invoked when you start advertising the local peripheral device's data.
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error;

// Monitoring Subscriptions to Characteristic Values

// peripheralManager:central:didSubscribeToCharacteristic:
// Invoked when a remote central device subscribes to a characteristic's value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic;

// peripheralManager:central:didUnsubscribeFromCharacteristic:
// Invoked when a remote central device unsubscribes from a characteristic's value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic;

// peripheralManagerIsReadyToUpdateSubscribers:
// Invoked when a local peripheral device is again ready to send characteristic value updates. (required)
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral;

// Receiving Read and Write Requests

// peripheralManager:didReceiveReadRequest:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) read request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request;

// peripheralManager:didReceiveWriteRequests:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) write request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests;

@end

[ EOF ]
---------------------------------------

[ MyBLESend.m ]
---------------------------------------
//
//  MyBLESend.m
//  CoreBluetoothTest
//
//  Created by hoehoe on 2013/11/30.
//  Copyright (c) 2013年 Gigaworks. All rights reserved.
//

#import "MyBLESend.h"

@implementation MyBLESend

- (id)init
{
    // Do any additional setup after loading the view, typically from a nib.
    if(self == nil){
        self = [super init];
    }
   
    //uuidgenで生成したものを使用します。
    kServiceUUID = @"501797C5-1AEB-4CA3-8C3C-F7AAF5C0D396";
    kCharacteristicUUID = @"45077DB4-3DF6-40FC-ACF6-8212B8579C4B";
   
    self.peripheralManager = [[CBPeripheralManager alloc] initWithDelegate:self queue:nil options:@{CBAdvertisementDataServiceUUIDsKey:[CBUUID UUIDWithString:kServiceUUID]}];
    return self;
}


- (void)setupService
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
   
    // Creates the characteristic UUID
    CBUUID *characteristicUUID = [CBUUID UUIDWithString:kCharacteristicUUID];
    NSLog(@"[characteristicUUID.description] %@", characteristicUUID.description);
   
   
    uint battery_level = 39;
    NSData *dataBatteryLevel = [NSData dataWithBytes:&battery_level length:sizeof(battery_level)];
    NSData* value = [NSData dataWithBytes:&dataBatteryLevel length:sizeof(dataBatteryLevel)];
    self.characteristic = [[CBMutableCharacteristic alloc] initWithType:characteristicUUID properties:CBCharacteristicPropertyRead value:value permissions:CBAttributePermissionsReadable];
   
   
    // Creates the service UUID
    CBUUID *serviceUUID = [CBUUID UUIDWithString:kServiceUUID];
   
    // Creates the service and adds the characteristic to it
    self.service = [[CBMutableService alloc] initWithType:serviceUUID primary:YES];
   
    // Sets the characteristics for this service
    [self.service setCharacteristics:@[self.characteristic]];
   
    // Publishes the service
    [self.peripheralManager addService:self.service];
}
-(void)stopService
{
    [self.peripheralManager stopAdvertising];
}

// ------------------------------
// CBPeripheralManagerDelegate
// ------------------------------

// Monitoring Changes to the Peripheral Manager's State

// CBPeripheralManager が初期化されたり状態が変化した際に呼ばれるデリゲートメソッド
// peripheralManagerDidUpdateState:
// Invoked when the peripheral manager's state is updated. (required)
- (void)peripheralManagerDidUpdateState:(CBPeripheralManager *)peripheral
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    switch (peripheral.state) {
        case CBPeripheralManagerStatePoweredOn:
            NSLog(@"%d, CBPeripheralManagerStatePoweredOn", (int)peripheral.state);
            // PowerOn なら,デバイスのセッティングを開始する.
            [self setupService];
            break;
        case CBPeripheralManagerStatePoweredOff:
            NSLog(@"%d, CBPeripheralManagerStatePoweredOff", (int)peripheral.state);
            [self.peripheralManager stopAdvertising];
            break;
        case CBPeripheralManagerStateResetting:
            NSLog(@"%d, CBPeripheralManagerStatePoweredOn", (int)peripheral.state);
            break;
        case CBPeripheralManagerStateUnauthorized:
            NSLog(@"%d, CBPeripheralManagerStatePoweredOn", (int)peripheral.state);
            break;
        case CBPeripheralManagerStateUnsupported:
            NSLog(@"%d, CBPeripheralManagerStatePoweredOn", (int)peripheral.state);
            break;
        case CBPeripheralManagerStateUnknown:
            NSLog(@"%d, CBPeripheralManagerStatePoweredOn", (int)peripheral.state);
            break;
        default:
            break;
    }
}

// peripheralManager:willRestoreState:
// Invoked when the peripheral manager is about to be restored by the system.
- (void)peripheralManager:(CBPeripheralManager *)peripheral willRestoreState:(NSDictionary *)dict
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Adding Services

// peripheralManager:didAddService:error:
// Invoked when you publish a service, and any of its associated characteristics and characteristic descriptors, to the local Generic Attribute Profile (GATT) database.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didAddService:(CBService *)service error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
   
    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }else{
        // Starts advertising the service
        [self.peripheralManager startAdvertising:@{CBAdvertisementDataLocalNameKey : @"mokyu", CBAdvertisementDataServiceUUIDsKey : @[[CBUUID UUIDWithString:kServiceUUID]] }];
        NSLog(@"start advertising");
    }
}

// Advertising Peripheral Data

// peripheralManagerDidStartAdvertising:error:
// Invoked when you start advertising the local peripheral device's data.
- (void)peripheralManagerDidStartAdvertising:(CBPeripheralManager *)peripheral error:(NSError *)error
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
    if(error){
        NSLog(@"[error] %@", [error localizedDescription]);
    }else{
        NSLog(@"no error");
    }
}

// Monitoring Subscriptions to Characteristic Values

// peripheralManager:central:didSubscribeToCharacteristic:
// Invoked when a remote central device subscribes to a characteristic's value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didSubscribeToCharacteristic:(CBCharacteristic *)characteristic
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// peripheralManager:central:didUnsubscribeFromCharacteristic:
// Invoked when a remote central device unsubscribes from a characteristic's value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral central:(CBCentral *)central didUnsubscribeFromCharacteristic:(CBCharacteristic *)characteristic
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// peripheralManagerIsReadyToUpdateSubscribers:
// Invoked when a local peripheral device is again ready to send characteristic value updates. (required)
- (void)peripheralManagerIsReadyToUpdateSubscribers:(CBPeripheralManager *)peripheral
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// Receiving Read and Write Requests

// peripheralManager:didReceiveReadRequest:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) read request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveReadRequest:(CBATTRequest *)request
{
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

// peripheralManager:didReceiveWriteRequests:
// Invoked when a local peripheral device receives an Attribute Protocol (ATT) write request for a characteristic that has a dynamic value.
- (void)peripheralManager:(CBPeripheralManager *)peripheral didReceiveWriteRequests:(NSArray *)requests
{
    NSLog(@"Receive data...");
    NSLog(@"%@", NSStringFromSelector(_cmd));
}

@end

[ EOF ]
---------------------------------------


これで、BLEの送信部分を実装出来ました。

今回はiBeaconとBLE(の受信)を混ぜましたが、iBeacon部分を除外すれば、BLEのみの送受信ANEも出来ます。
色々やってみると面白いです。Androidは受信のみ対応らしいので、iOSから送信してあげて、Androidで受信ということも出来ると思います。

お疲れさまでした。


Air Native Extension - iOS Native Alert

うぃ。
NativeのAlertを呼んでみます。これだけでもネイティブっぽくなりますね。
ARCで出来るので、捗りますねー。

最後です。

AIRからネイティブのUIAlertViewを呼んでみます。
Flexライブラリプロジェクトで作成したSWCとANEをアプリケーション・プロジェクトのlibsフォルダに放り込んで、プロジェクトのプロパティを適切に設定しておいて下さい。

[ NativeAlert.mxml ]
-------
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="160" creationComplete="init();">
    
    <fx:Script>
        <![CDATA[
            import com.chocbanana.iphone.nativeAlertANE;
            private var ane:nativeAlertANE;
            
            public function init():void
            {
                ane = new nativeAlertANE();
            }
            
            protected function btn_nativeAlert_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                var title:String = "てすとあらーと!";
                var message:String = "てすとなめっせーじなう。";
                var cancelbuttonTitle:String = "きゃんせるぴょん";
                var otherbuttonTitle:String = "ふふっ、おっけー。";
                
                ane.showNativeAlert(title, message, cancelbuttonTitle, otherbuttonTitle);
                
            }
        ]]>
    </fx:Script>
    
    <fx:Declarations>
        <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
    </fx:Declarations>
    <s:Button id="btn_nativeAlert" x="132" y="349" label="Alert"
              click="btn_nativeAlert_clickHandler(event)"/>
    
</s:Application>
-------


リビルドして完成でーす。

Air Native Extension - iOS Native Alert

うぃ。
NativeのAlertを呼んでみます。これだけでもネイティブっぽくなりますね。
ARCで出来るので、捗りますねー。

続きです。

ANEを作りますー。

Flexライブラリプロジェクトを選択して、SWCを作成後、adtコマンドでANEを作りましょー。

以下ソース。

[ nativeAlertANE.as ]
----------
package com.chocbanana.iphone{
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;
    
    public class nativeAlertANE extends EventDispatcher{
        
        private var _ExtensionContext:ExtensionContext;
        
        public function nativeAlertANE(target:IEventDispatcher=null){
            //TODO: implement function
            super(target);
            _ExtensionContext = ExtensionContext.createExtensionContext("com.chocbanana.iphone.nativeAlertANE", null);
        }
        
        public function dispose():void{
            _ExtensionContext.dispose();
        }
        
        public function showNativeAlert(title:String, message:String, cancelButtonTitle:String, otherButtonTitle:String):void {
            try{
                _ExtensionContext.call("showNativeAlert", title, message, cancelButtonTitle, otherButtonTitle);
            }catch(ex:Error){
                trace(ex.message);
            }
        }
    }
}
----------


[ descriptor.xml ]
----------
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
    <id>com.chocbanana.iphone.nativeAlertANE</id>
    <versionNumber>1.0</versionNumber>
    <platforms>
        <platform name="iPhone-ARM">
            <applicationDeployment>
                <nativeLibrary>libNativeAlert.a</nativeLibrary>
                <initializer>initializer</initializer>
                <finalizer>finalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>
----------

これだけ書いて、コンパイルしてSWCを作成しましょう。
SWCをZIPに変えて(コピーしてね)、library.swfを取り出し、descriptor.xmlをbinフォルダに放り込んで、platform.xmlを書きます。



ANE作成時の platform.xml と adtコマンドのオプションはこちら。

今回は、コンパイラーに怒られたので、以下の内容になりますた。

[ platform.xml ]
-------
<platform xmlns="http://ns.adobe.com/air/extension/3.1">
    <sdkVersion>6.0</sdkVersion>
    <linkerOptions>
        <option>-w</option>
        <option>-ios_version_min 5.0</option>
        <option>-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc</option>
        <option>-larclite_iphoneos</option>
    </linkerOptions>
</platform>
-------

で、adtコマンド。の、オプション部分。

-package -target ane nativeAlertANE.ane descriptor.xml -swc nativeAlertANE.swc -platform iPhone-ARM -C . library.swf -platformoptions platform.xml libNativeAlert.a


で、ANE完成。姉ですよ姉!

続いてはANEを組み込んで完成でーす。

Air Native Extension - iOS Native Alert

うぃ。
NativeのAlertを呼んでみます。これだけでもネイティブっぽくなりますね。
ARCで出来るので、捗りますねー。

ではコードです。Xcodeから。
Static Libraryを作ります。
Use Automatic Reference Countingのチェックを忘れずに入れましょう。

Adobe AIR.framework
UIKit.framework
Foundation.framewok

を使います。



[ NativeAlert.h ]
--------
//
//  NativeAlert.h
//  NativeAlert
//
//  Created by hoehoe on 2012/10/06.
//  Copyright (c) 2012年 hoehoe. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <Adobe AIR/Adobe AIR.h>

@interface NativeAlert : NSObject<UIAlertViewDelegate>

@end
--------


[ NativeAlert.m ]
--------
//
//  NativeAlert.m
//  NativeAlert
//
//  Created by hoehoe on 2012/10/06.
//  Copyright (c) 2012年 hoehoe. All rights reserved.
//

#import "NativeAlert.h"

@implementation NativeAlert

NativeAlert* me;

-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
   
}

-(void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex{

}

-(void)alertViewCancel:(UIAlertView *)alertView{

}

FREObject showNativeAlert(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]) {
    FREObject resultObj = NULL;
   
    NSString* title;
    NSString* message;
    NSString* cancel;
    NSString* other;

    //int count = 4;//sizeof(argv);
   
    for(int i=0; i<4; i++){
        uint32_t argv0Len;
        const uint8_t *argv0;
        FREGetObjectAsUTF8(argv[i], &argv0Len, &argv0);

        switch (i) {
            case 0:
                title = [NSString stringWithUTF8String:(char*)argv0];
                break;
            case 1:
                message = [NSString stringWithUTF8String:(char*)argv0];
                break;
            case 2:
                cancel = [NSString stringWithUTF8String:(char*)argv0];
                break;
            case 3:
                other  = [NSString stringWithUTF8String:(char*)argv0];
                break;
            default:
                break;
        }
    }
    // Convert C strings to Obj-C strings
   
    UIAlertView* alert = [[UIAlertView alloc] initWithTitle:title message:message delegate:me cancelButtonTitle:cancel otherButtonTitles:other, nil];
   
    [alert show];
   
    /*
    UIWindow *keyWindow= [[UIApplication sharedApplication] keyWindow];
    UIViewController *mainController = [keyWindow rootViewController];
   
    UISwitch* switchCtrl =[[UISwitch alloc] init];
    switchCtrl.center = CGPointMake(100, 100);
   
    [mainController.view addSubview:switchCtrl];
    */
    return resultObj;
}


void contextFinalizer(FREContext ctx)
{
    return;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
   
    if(me == nil){
        me = [[NativeAlert alloc] init];
    }
   
    *numFunctions = 1;
    FRENamedFunction*  func= (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));
   
    func[0].name = (const uint8_t*) "showNativeAlert";
    func[0].functionData = NULL;
    func[0].function = &showNativeAlert;
   
    *functions = func;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData)
{
   
}
@end
--------

その3です。

FlashBuilderを使用して、Flexモバイルアプリケーションを作ります。
今回は空白のテンプレートを選択して、Apple iOSのみにチェックを入れておきます。

プロジェクトにSWCとANEを追加して、プロジェクトの設定から、FlexビルドパスおよびFlexビルドのパッケージを適切に設定しておいて下さい。
プロビジョニングファイルなども必要になります。

以下コードです。


[ ARC_TEST02.mxml ]
--------
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="160" creationComplete="init();">
    <fx:Script>
        <![CDATA[
            import com.chocbanana.iphone.UITestANE;
            public var ane:UITestANE;
            
            public function init():void{
                if(ane == null){
                    ane = new UITestANE();
                }
                ane.getUIControl();
            }
            
        ]]>
    </fx:Script>
    <fx:Declarations>
        <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
    </fx:Declarations>
</s:Application>
[ EOF ]
--------

リリースビルドして、出来上がりです。

ARCでObjective-Cを書けると、 かなりネイティブ部分が楽になりますねー。
その1の続きです。

Static Library が出来たので、今度はFlashBuilderでSWCとANEを作ります。

基本的に前 のものと変わりません。


まずはSWCから。

[ UITestANE.as ]
------------------------
package com.chocbanana.iphone{
    
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;
    
    public class UITestANE extends EventDispatcher{
        
        private var _ExtensionContext:ExtensionContext;
        
        public function UITestANE(target:IEventDispatcher=null){
            //TODO: implement function
            super(target);
            _ExtensionContext = ExtensionContext.createExtensionContext("com.chocbanana.iphone.UITestANE", null);
            _ExtensionContext.addEventListener(StatusEvent.STATUS, onStatusEventHandler);
        }
        
        public function dispose():void{
            _ExtensionContext.dispose();
        }
        
        public function getUIControl():void {
            try{
                _ExtensionContext.call("getUIControl");
            }catch(ex:Error){
                trace(ex.message);
            }
        }
        public function onStatusEventHandler(event:StatusEvent):void{
            //trace("Dispatch...");
            dispatchEvent(event);
        }
    }
}
[ EOF ]
------------------------


続いてdescriptor.xml

[ descriptor.xml ]
------------------------
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
    <id>com.chocbanana.iphone.UITestANE</id>
    <versionNumber>1.0</versionNumber>
    <platforms>
        <platform name="iPhone-ARM">
            <applicationDeployment>
                <nativeLibrary>libTestARCLibrary.a</nativeLibrary>
                <initializer>initializer</initializer>
                <finalizer>finalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>
[ EOF ]
------------------------

で、コンパイルして、SWCを作成します。


続いて ANE。
SWCをコピーして、コピーしたものの拡張子をzipに変えます。
zipファイルになったら、ダブルクリックして解凍後、library.swfを取り出します。

そこまで済んだら、今回はiOS用のplatform.xmlを書きます。
以下コピペ。

[ platform.xml ]
------------------------
<platform xmlns="http://ns.adobe.com/air/extension/3.1">
    <sdkVersion>5.0</sdkVersion>
    <linkerOptions>
        <option>-L/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc</option>
        <option>-larclite_iphoneos</option>
    </linkerOptions>
</platform>
------------------------



XCodeで作成したStatic Library, SWCファイル、descriptor.xml, library.swf, platform.xmlがbinフォルダにあることを確認して、adtコマンドを使用してANEファイルを作成します。

XCodeのバージョンによって、 /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/arc が変わってくると思います。
注意して下さい。

adtコマンドのオプションはこちら。

-package -target ane ARCANE.ane descriptor.xml -swc ARCANE.swc -platform iPhone-ARM -platformoptions platform.xml -C . library.swf  libTestARCLibrary.a

オプションの内容のみ掲載しておきます。

adtコマンド終了後、ANEが作られていることを確認してください。
次回はアプリケーションにANEを組み込んで終了です。

はい!

AIR Native Extensionです。iOSです。
今回はARCを使用します!

こ こを参考にさせて頂きました。

ARCを使うと retain/release/autoreleaseを使わなくても良くなるので、かなり捗ると思います。

XCodeを使用してCocoa Touch Static Libraryを作成します。
Use Automatic Reference Countingのチェックを入れておいて下さい。

サンプルをANE Labさんに コミットしてありますので、参考にして下さい。


使用するFrameworkは
UIKit.framework
Adobe AIR.framework
Foundation.framework
です。


以下 Native部分のコードです。

TestARCLibrary.h
-------------
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import "FlashRuntimeExtensions.h"

@interface TestARCLibrary : NSObject

@end
[ EOF ]
-------------

TestARCLibrary.m
-------------
//
//  TestARCLibrary.m
//  TestARCLibrary
//
//  Created by hoehoe on 2012/09/27.
//  Copyright (c) 2012年 hoehoe. All rights reserved.
//

#import "TestARCLibrary.h"

@implementation TestARCLibrary

FREObject getUIControl(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]) {
    FREObject resultObj = NULL;
    
    UIWindow *keyWindow= [[UIApplication sharedApplication] keyWindow];
    UIViewController *mainController = [keyWindow rootViewController];
    
    UISwitch* switchCtrl =[[UISwitch alloc] init];
    switchCtrl.center = CGPointMake(100, 100);
    
    [mainController.view addSubview:switchCtrl];
    
    return resultObj;
}


void contextFinalizer(FREContext ctx)
{
    return;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
    
    *numFunctions = 1;
    FRENamedFunction*  func= (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));
    
    func[0].name = (const uint8_t*) "getUIControl";
    func[0].functionData = NULL;
    func[0].function = &getUIControl;
        
    *functions = func;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData)
{
    
}

@end
[ EOF ]
-------------

これをコンパイルすると画面にUISwitchを1個だけ表示するStatic Libraryが作成されます。
とりあえず、ネイティブ部分はここまで。

次回はSWCを作ります。

Native extensions for Adobe AIR with iOS



ProcessList.jpg


iPhoneでのNative extensionも試してみました。
Adobe AIRとObjective-C/C/C++で開発が出来ます。

Windows(C#)と比べると、かなり開発はし易いですが、まぁネイティブ側を知っておかないとダメですね。

ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting に作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


開発環境
 Xcode4
 Adobe FlashBuilder4.6

サンプルは今現在起動しているアプリケーションの一覧を表示するだけのアプリです。


作るのは3つ。

Cocoa touch static library
Flexライブラリプロジェクト(SWC)
Flexモバイルプロジェクト(iOS AIRアプリ)


FlashBuilderでの作業は基本的にWindows(C#)と変わりませんので、そちらを参照して下さい。
一応コードは貼っておきます。

FlashBuilderを使用して、Flexライブラリプロジェクト(SWC)とFlexモバイルプロジェクト(iOS AIRアプリ)を作成します。

まずはSWCから。


swc.jpg


[  ProcessExtension.as ]
------------------------
package com.chocbanana.iphone{
   
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;
   
    public class ProcessExtension extends EventDispatcher{
       
        private var _ExtensionContext:ExtensionContext;
       
        public function ProcessExtension(target:IEventDispatcher=null){
            //TODO: implement function
            super(target);
            _ExtensionContext = ExtensionContext.createExtensionContext("com.chocbanana.iphone.ProcessExtension", null);
        }

        public function dispose():void{
            _ExtensionContext.dispose();
        }

        public function getProcessList():Array {
            return _ExtensionContext.call("getProcessList") as Array;
        }
    }
}
[ EOF ]
------------------------


[ descriptor.xml ]
------------------------
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
    <id>com.chocbanana.iphone.ProcessExtension</id>
    <versionNumber>1.0</versionNumber>
    <platforms>
        <platform name="iPhone-ARM">
            <applicationDeployment>
                <nativeLibrary>libProcessListExtension.a</nativeLibrary>
                <initializer>initializer</initializer>
                <finalizer>finalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>
[ EOF ]
------------------------



で、ANEを作成するためのadtコマンドのオプションはこちら。

[ adt-option ]
------------------------
-package -target ane libProcessList_Extension.ane descriptor.xml -swc  libProcessList_Extension.swc -platform iPhone-ARM library.swf  libProcessListExtension.a
------------------------


では、Flexモバイルアプリです。
やり方はWindowsの方とそう大して変わりません。
生成されたaneファイルとswcファイルをプロジェクトに追加しておいて下さい。


flashbuilder.jpg


[ ProcessList.mxml ]
------------------------
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark" applicationDPI="160">
   
    <fx:Script>
        <![CDATA[
            import com.chocbanana.iphone.ProcessExtension;
            import mx.collections.ArrayList;

            [Bindable]
            private var ar:ArrayList = new ArrayList();

            private var ane:ProcessExtension = new ProcessExtension();
           
            protected function btn_getProcess_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                this.ar.removeAll();
                try{
                    var _ar:Array = ane.getProcessList();
                    for(var i:int =0; i<_ar.length; i++){
                        this.ar.addItem(_ar[i]);
                    }
                }catch(e:Error){
                    trace(e.message);
                    trace(e.getStackTrace());
                }
            }
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
    </fx:Declarations>
    <s:VGroup x="0" y="0" width="100%" height="100%" horizontalAlign="center" verticalAlign="bottom">
        <s:List id="lst_processList" width="100%" height="100%" dataProvider="{ar}"/>
        <s:Button id="btn_getProcess" label="ボタン" click="btn_getProcess_clickHandler(event)"/>
        <s:Spacer width="10" height="10"/>
    </s:VGroup>
</s:Application>

[ EOF ]
------------------------

リリースビルドして、実機にて動作する事を確認して下さい。

お疲れさまでした。

# 証明書やプロビジョニングファイルなどの説明は省いています。
# 各自で調べて下さい。

Native extensions for Adobe AIR with iOS



ProcessList.jpg


iPhoneでのNative extensionも試してみました。
Adobe AIRとObjective-C/C/C++で開発が出来ます。

Windows(C#)と比べると、かなり開発はし易いですが、まぁネイティブ側を知っておかないとダメですね。

ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting に作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


開発環境
 Xcode4
 Adobe FlashBuilder4.6

サンプルは今現在起動しているアプリケーションの一覧を表示するだけのアプリです。


作るのは3つ。

Cocoa touch static library
Flexライブラリプロジェクト(SWC)
Flexモバイルプロジェクト(iOS AIRアプリ)


xcode-project.jpg


まずはCocoa touch static libraryから。
Xcode ProjectにAdobe AIR.frameworkを追加しておきましょう。


xcode.jpg


以下参照。

[ ProcessListExtension.h ]
--------------------------------
//
//  ProcessListExtension.h
//  ProcessListExtension
//
//  Created by  on 12/02/01.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#import <Adobe AIR/Adobe AIR.h>
#import <sys/sysctl.h>
#import <pwd.h>

@interface ProcessListExtension : NSObject


FREObject getProcessList(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]);

void contextFinalizer(FREContext ctx);
void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions);
void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer);
void finalizer(void** extData);

@end

[ EOF ]
--------------------------------


[ ProcessListExtension.m ]
--------------------------------
//
//  ProcessListExtension.m
//  ProcessListExtension
//
//  Created by  on 12/02/01.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//

#import "ProcessListExtension.h"
#import "InstalledApplicationPlist.h"

@implementation ProcessListExtension


/*
 * http://d.hatena.ne.jp/terazzo/comment?date=20120131
 */
FREObject getProcessList(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]) {  
   
    FREObject result = NULL;
    FREObject _pNameAndId = NULL;
   
   
   
    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0};
    size_t miblen = 4;
    size_t size;
    int st = sysctl(mib, miblen, NULL, &size, NULL, 0);
   
    struct kinfo_proc * process = NULL;
    struct kinfo_proc * newprocess = NULL;
   
    do {
        size += size / 10;
        newprocess = realloc(process, size);
       
        if (!newprocess){
           
            if (process){
                free(process);
            }
           
            return nil;
        }
        process = newprocess;
        st = sysctl(mib, miblen, process, &size, NULL, 0);
    } while (st == -1 && errno == ENOMEM);
   
    if (st == 0){
       
        if (size % sizeof(struct kinfo_proc) == 0){
            int nprocess = size / sizeof(struct kinfo_proc);
           
            if (nprocess){
               
                FRENewObject((const uint8_t*)"Array", 0, NULL, &result, nil);

                char* name = NULL;
                NSString * processID = nil;
                NSString * processName = nil;
                NSString * pNameAndId = nil;
                NSString * userName = nil;

                InstalledApplicationPlist* plist = [[[InstalledApplicationPlist alloc] init] autorelease];
               
                int j=0;
                for (int i = nprocess - 1; i >= 0; i--){
                    uid_t  p_uid = process[i].kp_eproc.e_pcred.p_ruid;
                    name = user_from_uid(p_uid, 0);
                   
                    processID = [NSString stringWithFormat:@"%d", process[i].kp_proc.p_pid];
                    processName = [NSString stringWithFormat:@"%s", process[i].kp_proc.p_comm];
                    userName = [NSString stringWithFormat:@"%s", name];
                    pNameAndId = [NSString stringWithFormat:@"%@:%@:%@", processID, processName, userName];
                   
                    if([userName isEqualToString:@"mobile"]){
                        if ([plist AppInstalled:processName] == YES){
                                FRENewObjectFromUTF8(strlen([pNameAndId UTF8String])+1, (const uint8_t*)[pNameAndId UTF8String], &_pNameAndId);
                                FRESetArrayElementAt(result, j++, _pNameAndId);
                            }
                    }
                }
                free(process);
            }
        }
    }
    return result;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions) {
   
    *numFunctions = 1;
    FRENamedFunction*  func= (FRENamedFunction*) malloc(sizeof(FRENamedFunction) * (*numFunctions));
   
    func[0].name = (const uint8_t*) "getProcessList";
    func[0].functionData = NULL;
    func[0].function = &getProcessList;
   
    *functions = func;
}
void contextFinalizer(FREContext ctx) {
    return;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer) {
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData) {
   
}

@end

[ EOF ]
--------------------------------

[ InstalledApplicationPlist.h ]
--------------------------------
//
//  InstalledApplicationPlist.h
//  ProcessListExtension
//
//  Created by  on 12/02/09.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//

#import <Foundation/Foundation.h>

@interface InstalledApplicationPlist : NSObject

 - (NSDictionary*) getCacheDict;
 //- (NSArray*) installedAppsSystem;
 //- (NSArray*) installedAppsUser;

 // Bundle identifier (eg. com.apple.mobilesafari) used to track apps
 - (BOOL) AppInstalled:(NSString *) bundleIdentifier;

@end

[ EOF ]
--------------------------------

[ InstalledApplicationPlist.m ]
--------------------------------
//
//  InstalledApplicationPlist.m
//  ProcessListExtension
//
//  Created by  on 12/02/09.
//  Copyright (c) 2012年 __MyCompanyName__. All rights reserved.
//

#import "InstalledApplicationPlist.h"

@implementation InstalledApplicationPlist

/*
 * http://www.iphonedevsdk.com/forum/iphone-sdk-development/22289-possible-retrieve-these-information.html
 */
 - (NSDictionary *) getCacheDict{
     NSDictionary *cacheDict = nil;// code herect;
     static NSString *const cacheFileName = @"com.apple.mobile.installation.plist";
     NSString *relativeCachePath = [[@"Library" stringByAppendingPathComponent: @"Caches"] stringByAppendingPathComponent: cacheFileName];

     //[[NSHomeDirectory() stringByAppendingPathComponent: @"../.."] stringByAppendingPathComponent: relativeCachePath];
     NSString *path = nil;
   
     for (short i = 0; 1; i++){
         switch (i) {
             case 0: // Jailbroken apps will find the cache here; their home directory is /var/mobile
                 path = [NSHomeDirectory() stringByAppendingPathComponent: relativeCachePath];
                 break;
             case 1: // App Store apps and Simulator will find the cache here; home (/var/mobile/) is 2 directories above sandbox folder
                 path = [[NSHomeDirectory() stringByAppendingPathComponent: @"../.."] stringByAppendingPathComponent: relativeCachePath];
                 break;
             case 2: // If the app is anywhere else, default to hardcoded /var/mobile/
                 path = [@"/var/mobile" stringByAppendingPathComponent: relativeCachePath];
                 break;
             default: // Cache not found (loop not broken)
                 break;
         }
        
         // Ensure that file exists
         BOOL isDir = NO;
         if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir) {
             cacheDict    = [NSDictionary dictionaryWithContentsOfFile: path];
             return cacheDict;
         }
         /*
         if ([[NSFileManager defaultManager] fileExistsAtPath: path isDirectory: &isDir] && !isDir) // Ensure that file exists
             cacheDict = [NSDictionary dictionaryWithContentsOfFile: path];
        
         if (cacheDict) // If cache is loaded, then break the loop. If the loop is not "broken," it will return NO later (default: case)
             break;
          */
     }

    return nil;
}
/*
 - (NSArray *) installedAppsSystem {
    // Then all the user (App Store /var/mobile/Applications) apps
    NSDictionary* system = [[self getCacheDict] objectForKey: @"System"];
    NSLog(@"Installed Applications = %@",[system allKeys]);
    return [system allKeys];
    //return nil;
}

 - (NSArray *) installedAppsUser {
    // Then all the user (App Store /var/mobile/Applications) apps
    NSDictionary* user = [[self getCacheDict] objectForKey: @"User"];
    NSLog(@"Installed Applications = %@",[user allKeys]);
    return [user allKeys];
    //return nil;
}
*/

- (BOOL) AppInstalled:(NSString *) processName {
    NSString* const bundleIdentifier = @"CFBundleExecutable";

    NSDictionary* systems = [[self getCacheDict] objectForKey: @"System"];
    for(NSDictionary* _system in systems){
        NSDictionary* sysDict = [systems objectForKey:_system];
        NSString* CFBundleExecutable = [sysDict objectForKey:bundleIdentifier];
        if([processName isEqualToString:CFBundleExecutable]){
            return YES;
        }
    }
   
    NSDictionary* users = [[self getCacheDict] objectForKey: @"User"];
    for(NSDictionary* _user in users){
        NSDictionary* userDict = [users objectForKey:_user];
        NSString* CFBundleExecutable = [userDict objectForKey:bundleIdentifier];
        if([processName isEqualToString:CFBundleExecutable]){
            return YES;
        }
    }

    // If nothing returned YES already, we'll return NO now
    return NO;
}

@end

[ EOF ]
--------------------------------

Xcodeはここまで。
続いて、FlashBuilderを使用してSWCとAIRモバイルアプリケーションを作成していきます。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回で終わりです。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


4.FlashBuilderでAIRアプリを作るよ!最後だよ!


FlashBuilder4.6を使用してFlexデスクトップアプリケーション(AIR)を仕上げましょう。
先回作成したANEファイルから、ネイティブな処理を呼び出せるようになります。


Flexプロジェクトにライブラリプロジェクトを参照させ、ANEファイルをlibsディレクトリにコピーします。


air-swc-append.png


air-ane-append.png


ANEファイルをコピーしたら、Flexプロジェクトのビルドパスにある「ネイティブエクステンション」にANEファイルを追加します。



air-ane-append-project.png




ANEファイルを追加すると、ApplicationList-app.xmlファイルに

<extensions>
        <extensionID>com.chocbanana.win.ApplicationListExtension</extensionID>
    </extensions>
</application>

という記述が追加されます。

ここまで終ったらActionScriptのコーディングです。
以下参照。


[ ApplicationList.mxml ]
-------------------------------
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
                       xmlns:s="library://ns.adobe.com/flex/spark"
                       xmlns:mx="library://ns.adobe.com/flex/mx">
   
    <fx:Script>
        <![CDATA[
            import com.chocbanana.win.ApplicationListExtension;
           
            import mx.collections.ArrayCollection;
           
           
            private var ane:ApplicationListExtension = new ApplicationListExtension();
           
            [Bindable]
            private var ar:ArrayCollection = new ArrayCollection();
           
            protected function btn_applicationList_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                var _ar:Array = ane.getApplicationList();
                ar.removeAll();
               
                for(var i:int=0; i<_ar.length; i++){
                    var appName:String = _ar[i] as String;
                    ar.addItem(appName);
                }
            }
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- 非ビジュアルエレメント (サービス、値オブジェクトなど) をここに配置 -->
    </fx:Declarations>
    <s:Panel x="0" y="0" width="100%" height="100%" fontWeight="bold"
             title="Show Installed Applications">
        <s:VGroup x="0" y="0" width="100%" height="100%" horizontalAlign="center"
                  verticalAlign="middle">
            <s:List id="lst_ApplicationList" width="80%" height="80%" dataProvider="{ar}"
                    fontWeight="normal"/>
            <s:Button id="btn_applicationList" label="アプリケーション一覧の取得"
                      click="btn_applicationList_clickHandler(event)" fontWeight="bold"/>
        </s:VGroup>
    </s:Panel>
</s:WindowedApplication>
[ EOF ]
-------------------------------

Flexプロジェクトをビルドして下さい。
証明書は最初に作成したと思うので、もう既にありますね?


ではリリースビルドです。
リリースビルドのエクスポートをクリックして、[ 署名済みネイティブインストーラー ] を選択して下さい。
証明書のパスワードを入力して、[ 終了 ] をクリックすると、ネイティブなインストーラー(EXEファイル)が生成されます。



air-releasebuild.png


air-releasebuild02.png


インストーラーが作成されたら、それをダブルクリックして、インストールして下さい。

1番最初 に作成したC#のDLLはこのインストーラーの中や、ANEファイルの中には含まれていませんの で、アプリケーションを実行すると、異常終了します。

インストールされたアプリケーションは、C:\ProgramFiles\ApplicationListにありますので、そのディレクトリを開いて、 C#のDLLのみを手動でコピーします。


air-dll-copy.png


再度、AIRアプリケーションを実行してみましょう。
無事に実行が終ると、インストールされているアプリケーションの一覧が表示されると思います。


air-native-application.png




お疲れ様でした。


Appendix - インストーラーについて -

ANEを利用したAIRアプリケーションでは、今現在FlashBuilderにてC#のDLLを含めてインストーラー(配布形式)にまとめることが出来 ません。
で、これをどうしたら良いか…という点なのですが、AIRアプリケーションにはサイレントインストールさせるためのインストールオ プションがあると思います。

で、このインストールオプションと、Windowsのもう1つのインストール形式である、MSIパッケージを組み合わせれば、全てを同梱した形で配布出来 るようになると思います。

これを行うには、SharpDevelopとWixを利用すると、msiパッケージを作成出来るので、そちらで(XMLを書いて)msiパッケージとして 1まとめにし、配布する…というのが妥当だと思います。

あ、高価なインストーラー作成アプリを使うなら、この項目は必要ないです。
参考程度に留めておいて下さい。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその3ページ目、あと一息です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。



3.FlashBuilder でSWCとAIRアプリを作るよ!


FlashBuilder4.6使用して、Flexライブラリ(SWC)とFlexデスクトップアプリケーション(AIR)を作成していきます。
2つ同時に作業した方が分かりやすいかもしれません。

これで、SWCからadtコマンドでANEファイルを生成し、ネイティブな処理を呼び出せるようになります。


ではまずAIRアプリのレイアウト及び、証明書の作成から。
先に証明書を作成しておかないと、SWCを作るときにメンドイのです。

画面レイアウトと、証明書が出来たらSWC(Flexライブラリ)を作ります。


air-app-layout.png




air-app-syomeisyo.png




Flexライブラリプロジェクトを作りましょう。

プロジェクトのプロパティ設定で、[ Flexライブラリコンパイラー ] -> [ Aodbe AIRライブラリを含める ] のチェックがONになっていることを確認して下さい。

binディレクトリに VC++で作成したDLLと、先程作ったFlexアプリの証明書を入れておきます。

次に、srcディレクトリに ASのパッケージと、assetsディレクトリ。assetsディレクトリ内にdescriptor.xmlを作成します。

内容は以下の通りで。

[ assets/descriptor.xml ]
------------------------
<extension xmlns="http://ns.adobe.com/air/extension/3.1">
    <id>com.chocbanana.win.ApplicationListExtension</id>
    <versionNumber>1.0</versionNumber>
    <platforms>
        <platform name="Windows-x86">
            <applicationDeployment>
                <nativeLibrary>libApplicationList.dll</nativeLibrary>
                <initializer>initializer</initializer>
                <finalizer>finalizer</finalizer>
            </applicationDeployment>
        </platform>
    </platforms>
</extension>
[ EOF ]
------------------------

このXMLファイルが書けたら、binディレクトリにコピーしておいて下さい。



[ com.chocbanana.win.ApplicationListExtension.as ]
------------------------
package com.chocbanana.win{
   
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.events.IEventDispatcher;
    import flash.events.StatusEvent;
    import flash.external.ExtensionContext;
   
    public class ApplicationListExtension extends EventDispatcher {

        private var _ExtensionContext:ExtensionContext;
       
        public function ApplicationListExtension(target:IEventDispatcher=null) {
            //TODO: implement function
            super(target);
            _ExtensionContext = ExtensionContext.createExtensionContext("com.chocbanana.win.ApplicationListExtension", null);
        }

        public function dispose():void{
            _ExtensionContext.dispose();
        }
        public function getApplicationList():Array {
            return _ExtensionContext.call("GetApplicationList") as Array;
        }
    }
}
[ EOF ]
------------------------

ASファイル内で、FREのContextを作成し、callメソッドで、DLLの関数を呼びます。
戻り値はFREObectで指定した戻り値の型ですね。

で、これのビルドを行うとSWCファイルが出来ると思います。

SWCファイルが出来たら、それをそのまま選択して、右クリック、コピーして貼り付けして下さい。
名前の拡張子をzipにします。

そのzipファイルをダブルクリックして、解凍すると、中にlibrary.swfファイルがあるので、それもbinディレクトリに含めます。
解凍したzipファイルと、フォルダは要らないので、削除しておいて下さい。



air-swc-package.png




ここから、ADTコマンドを使って、ANEファイルを作ります。
FlashBuilderの [ 実行 ] -> [ 外部ツール ] -> [ 外部ツールの構成 ] を選択して、以下の様に。


air-adt-command.png





[ ADTコマンドの引数(改行は挟まず、スペースで) ]
------------------------
-package -storetype pkcs12 -keystore test.p12 -target ane libApplicationList_Extension.ane descriptor.xml -swc  libApplicationList_Extension.swc -platform Windows-x86 library.swf  libApplicationList.dll
------------------------

実行すると証明書のパスワードを聞いてくるので、入力してEnter。
libApplicationList_Extension.aneファイルがbinディレクトリに生成されます。

生成されたANEファイルをコピーして、AIRアプリのlibsディレクトリにコピーして下さい。

ひとまずはココまで。
次回はAIRアプリの仕上げです。

C# で作る AIR Native Extension


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその2ページ目です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。



2.VC ++でDLL作るよ!


Visual Studio 2010 Express Edition VC++ を使用して、AIRライブラリ(SWC)からコールするためのDLLを作成していきます。
VC++でCLRライブラリプロジェクトを選択してください。



vc-project.png




先回作成したC#のDLLとtlbファイル、及びAIR SDKに同梱されているヘッダーファイルとlibファイルをプロジェクトに追加します。



cs-dll.png




air-lib.png




project-files.png




で、ヘッダーファイル2つとメインのCPPファイルを書きます。


[ Stdafx.h ]
------------------------------
// stdafx.h : 標準のシステム インクルード ファイルのインクルード ファイル、または
// 参照回数が多く、かつあまり変更されない、プロジェクト専用のインクルード ファイル
// を記述します。

#pragma once
#include "FlashRuntimeExtensions.h";

[ EOF ]
------------------------------


[ libApplicationList.h ]
------------------------------
// libApplicationList.h
#pragma once
#include <stdlib.h>
#include <wchar.h>
#include <windows.h>

#using <mscorlib.dll>
#using "ApplicationList_CS.dll"

using namespace System;
using namespace System::Runtime::InteropServices;
using namespace System::Collections;
using namespace ApplicationList_CS;

namespace libApplicationList {

    public ref class Class1
    {
        // TODO: このクラスの、ユーザーのメソッドをここに追加してください。
    };
}
[ EOF ]
------------------------------

[ libApplicationList.cpp ]
------------------------------
// これは メイン DLL ファイルです。

#include "stdafx.h"
#include "libApplicationList.h"


extern "C" __declspec(dllexport) void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer);
extern "C" __declspec(dllexport) void finalizer(void** extData);

void ConvertToUni(String^ _str, char* szBuf, const int bufLen)
{
    IntPtr ptr = Marshal::StringToHGlobalUni(_str);
    const char* str = static_cast<const char*>(ptr.ToPointer());
    WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)str, -1, szBuf, bufLen, NULL, NULL);
    Marshal::FreeHGlobal(ptr);
}

FREObject GetApplicationList(FREContext ctx, void* funcData, uint32_t argc, FREObject argv[]){
    FREObject resultObj;
    FREObject elementObj;

    ApplicationList_CS::AppList_CS^ cs = gcnew ApplicationList_CS::AppList_CS();
    ArrayList^ list = gcnew ArrayList();
    list = cs->getApplicationNames();

    // Application Name buffer
    const int bufLen = 1024;
    char szBuf[bufLen];

    const int count = list->Count;

    FRENewObject((const uint8_t*)"Array", 0, NULL, &resultObj, NULL);
    FRESetArrayLength(resultObj, count);

    for(int i=0; i<count; i++){
        String^ str = (String^)list[i];
        ConvertToUni(str, szBuf, bufLen);
        FRENewObjectFromUTF8(strlen(szBuf)+1, (const uint8_t *)szBuf, &elementObj);
        FRESetArrayElementAt(resultObj, i, elementObj);
    }

    return resultObj;
}
void contextFinalizer(FREContext ctx)
{
    return;
}

void contextInitializer(void* extData, const uint8_t* ctxType, FREContext ctx, uint32_t* numFunctions, const FRENamedFunction** functions)
{
    *numFunctions = 1;
    FRENamedFunction*  func= (FRENamedFunction*)malloc(sizeof(FRENamedFunction) * (*numFunctions));

    func[0].name = (const uint8_t*) "GetApplicationList";
    func[0].functionData = NULL;
    func[0].function = &GetApplicationList;

    *functions = func;
}

void initializer(void** extData, FREContextInitializer* ctxInitializer, FREContextFinalizer* ctxFinalizer)
{
    *ctxInitializer = &contextInitializer;
    *ctxFinalizer = &contextFinalizer;
}

void finalizer(void** extData)
{
   
}
[ EOF ]
------------------------------

ポイントは

contextInitializerのFRENamedFunctionでActionScriptからCallされる名前を定義しているのと、ネイ ティブの処理として

Marshal::StringToHGlobalUni(_str);
WideCharToMultiByte(CP_UTF8, 0, (wchar_t*)str, -1, szBuf, bufLen, NULL, NULL);

を使用している点でしょう。

WindowsではOS内部の値はUnicodeで格納されているため、UnicodeからUTF8に変換しないと、AIRに持っていったときに文字化け して表示されます。
ですので、WMIやレジストリなどから”マルチバイト文字”を引っ張ってくるときは要注意です。

あとは、ArrayList^ listに格納された文字列を1つ1つ分解して、FREObjectの構造体に突っ込む作業です。
全部突っ込んだらreturnで戻して終わりです。

AIR側に渡すために各パラメータを細かく分解してから、それぞれ格納し直す作業がメンドイですね…。


では、次回はFlashBuilderでFlexライブラリプロジェクト(SWC)とFlexプロジェクト(デスクトップ AIRアプリ)を作ります。

C# で作る AIR Native Extension



ApplicationList.png


Adobe AIR3 から ANE という ネイティブ拡張機能が実装されたのでWindowsで試してみました。

全部で4ページにまとめます。

今回はその1ページ目です。


開発環境
 Windows XP SP3
 Visual Studio 2010 Express Edition( C# / VC++ )
 FlashBuilder4.6( FlexSDK4.6 )


まぁ、アレです、各プラットフォーム固有の機能が使えたりするというヤツです。
Windowsならね、わざわざAIRでやらなくてもね、C#で全部出来るからね…、あんまり意味無いかもしれませんけどね。 orz
フロントエンドをAIRでまとめて、各プラットフォームごとにANEで固有機能を実装して…という具合にアプリを作るなら、ANE はアリです。

が、結構かなりシビアにキツイです。キツイっス…。orz

レジストリにアクセスしたり、WMI(Windows Management Instrumentation)を使ってハードウェア・ソフト ウェアの情報を取得したり、C#のAPIに丸投げしたり出来ま す。はい。

ane-lab(AIR Native Extension is Lab codes) - Google Project Hosting にC#で作ったサンプルをコミットさせて頂きましたので、参考にして下さい。
間違ってるかもしれないので、決して鵜呑みにしないように。


で、作り方。


作るのは4つ。

C#クラスライブラリ(C#のDLL)
VC++のCLRのクラスライブラリ(VC++ CLR/DLL)
Flexライブラリプロジェクト(SWC)
Flexプロジェクト(デスクトップ AIRアプリ)

です。



1.C#でDLL作るよ!

template.png

最初にキモを抑えて起きましょう。
C#で作ったDLLをVC++から参照して、VC++で作るDLLの中でFlashRuntimeExtensionsを組み込むことにより、AIRアプ リに通知することが出来るようになります。

えー???ですよね。C#からVC++のDLLをImportする…のではなく、その逆です。VC++からC#のDLLを参照しま す。
イヤーな予感がします。はい。


キモはコレ!
VC++から参照出来るように、「アセンブリをCOM参照可能にする」のチェックと、「COM相互運用機能の登録」のチェックをONにしておきます。
これでVC++からCOM経由でC#のクラスやそのメンバを参照出来るようになります。

CS_Setting01.png
CS_Setting02.png

ここで1つ注意。

COM 参照可能にする、とかCOM相互運用機能の登録とありますが、これはWindowsのCOMという機能を利用/経由(.NET/C#がCOMにラップされ る)して、VC++から.NET Objectを操作出来るようにする(マネージドからアンマネージにアクセスする)…というものです。

詳しくは「COM ・ RCW ・ 参照カウント 」などのキーワードで調べてください。

C#(.NET)にはGC(ガベージコレクション)という機能があります。が、COMはMSのドキュメントによると、「COM 自身は、オブジェクトが不要になったと判断しても、メモリから自動的にオブジェクトを削除するわけではありません。不要なオブジェクトの削除は、オブジェ クトのプログラマが行います。プログラマは、参照カウントに基づいて、オブジェクトを削除できるかどうかを判断します。」とあります。(http://msdn.microsoft.com/ja-jp/library/4947zb56%28v=vs.100%29.aspx)


完全にGCと相反していますね…。C#でどうやんのよ、それ…。

えー、通常C#で開発をする場合、変数にスコープがあり、このスコープから抜けるとGCの対象になる(実際にGCが何時行われるかはプログラマからは分か りませんが)…のですが、VC++から参照させるために、C#のライブラリをCOM参照させる必要があるため、C#側のインスタン スを明示的に解放してやる必要があります。

MSのドキュメントにも「すべてのア ウトポ インタパ ラメータを明 示的にNULLに設定する必要があります。」 と記載されています。(http://msdn.microsoft.com/ja-jp/library/ms686638.aspx)& rdquo;すばらしいGoogle翻訳”。

えー、ですので、C#側でも不必要な変数にはNULLを入れてやって下さい。
そうしないと、AIRアプリのプロセスが正常に終了しない場合があります。

あと、string[] とかは使わない方が良いです。使うなら、List<string>とかCollection.Genericを使いましょ う。



では、サンプルです。
OS内にインストールされているアプリケーション一覧を表示します。
これで、えろげとかもバッチリだね!(ぉ

プロジェクト名は ” ApplicationList_CS ” です。


[ App.cs ]
----------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Management;
using Microsoft.Win32;

namespace ApplicationList_CS {
    class App {
        //HotFix情報
        private const string root = "root\\CIMV2";
        private const string query = "SELECT * FROM Win32_QuickFixEngineering WHERE ServicePackInEffect <> '' AND InstalledBy <> ''";

        public ArrayList getApplicationNames() {
            List<string> hotfix = new List<string>();
            List<string> app = new List<string>();

            ManagementObjectSearcher searcher = new ManagementObjectSearcher(root, query);

            try {
                foreach (ManagementObject queryObj in searcher.Get()) {
                    hotfix.Add((string)queryObj["HotFixID"]);
                }
            } catch (Exception e) {
                Console.WriteLine(e.StackTrace);
            } finally {
                searcher = null;
            }
            RegistryKey regKey = null;
            RegistryKey _subRegKey = null;
            try {
                //アプリケーション情報
                string reg = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
                string regValue = "DisplayName";
                regKey = Registry.LocalMachine.OpenSubKey(reg);
                string[] subkey = regKey.GetSubKeyNames();

                foreach (string keyName in subkey) {

                    _subRegKey = Registry.LocalMachine.OpenSubKey(reg + keyName);
                    string value = (string)_subRegKey.GetValue(regValue);

                    if (value != null) {
                        for (int j = 0; j < hotfix.Count; j++) {
                            if (value.Contains(hotfix[j].ToString())) {
                                ;
                            } else if (value.Contains("KB")) {
                                ;
                            } else {
                                app.Add(value);
                                break;
                            }
                        }
                    }
                    //value = null;
                }
                app.TrimExcess();

                reg = null;
                regValue = null;
                subkey = null;
            } catch (NullReferenceException e) {
                Console.WriteLine(e.StackTrace);
            } finally {
                regKey.Close();
                _subRegKey.Close();
            }

            app.Sort();

            for (int i = 0; i < (app.Count - 1); i++) {
                try {
                    if (app[i].Equals(app[i + 1])) {
                        app.RemoveAt(i);
                    }
                } catch (Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            }
            app.TrimExcess();
            hotfix = null;
            return new ArrayList(app);
        }
    }
}
[ EOF ]
----------------------------------------



[ AppList_CS.cs ]
----------------------------------------
using System;
using System.Collections;

namespace ApplicationList_CS {
    public class AppList_CS {

        private App app = new App();
       
        public ArrayList getApplicationNames() {
            return app.getApplicationNames();
        }
    }
}
[ EOF ]
----------------------------------------


やっちゃダメなダメ・コードも記載しておきます。絶対やっちゃダメだよw
配列多すぎ。orz


[ アプリケーション名・バージョン・HotFix情報を取得する変なC#コード ]
----------------------------------------
private void setApplicationReg() {
            string[] val1 = null;
            string[] val2 = null;
            string[] val3 = null;

            ArrayList application;
            ArrayList applicationVersion;
            ArrayList hotfix;

            application = new ArrayList(); // application
            applicationVersion = new ArrayList(); // applicationVersion
            hotfix = new ArrayList(); // HotFix

            try {
                //HotFix情報
                string root = "root\\CIMV2";
                string query = "SELECT * FROM Win32_QuickFixEngineering WHERE ServicePackInEffect <> '' AND InstalledBy <> ''";
                ManagementObjectSearcher searcher = new ManagementObjectSearcher(root, query);
                int cnt = 0;
                foreach (ManagementObject queryObj in searcher.Get()) { cnt++; }
                val1 = new string[cnt];
                int i=0;
                foreach (ManagementObject queryObj in searcher.Get()) {
                    val1[i] = (string)queryObj["HotFixID"];
                    i++;
                }
            }catch(Exception e){
                string error = e.StackTrace;
                //Console.WriteLine(e.StackTrace);
            }
            try {
                //アプリケーション情報
                string reg = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\";
                string regValue = "DisplayName";
                RegistryKey regKey = Registry.LocalMachine.OpenSubKey(reg);
                string[] subkey = regKey.GetSubKeyNames();
                int max = subkey.Length;
                regKey.Close();

                //アプリケーションの総数
                val2 = new string[max];
                val3 = new string[max];
                RegistryKey _subRegKey = null;
                int i = 0;
                foreach (string keyName in subkey) {
                    _subRegKey = Registry.LocalMachine.OpenSubKey(reg + keyName);
                    if ((string)_subRegKey.GetValue(regValue) != null) {
                        val2[i] = (string)_subRegKey.GetValue("DisplayName");
                        val3[i] = (string)_subRegKey.GetValue("DisplayVersion");
                        i++;
                    }
                    _subRegKey.Close();
                }
            } catch (NullReferenceException e) {
                string error = e.StackTrace;
                //Console.WriteLine(e.StackTrace);
            }
            try {
                for (int i = 0; i < val2.Length; i++) {
                    if(val2[i] != null){
                        for(int j = 0; j < val1.Length; j++){
                            if (val2[i].Contains(val1[j])) {
                                hotfix.Add(val2[i]);
                                val2[i] = null;
                                val3[i] = null;
                                break;
                            }else if(val2[i].Contains("KB")){
                                hotfix.Add(val2[i]);
                                val2[i] = null;
                                val3[i] = null;
                                break;
                            }
                        }
                    }
                }
                foreach(string _val in val2){
                    if( _val == null){
                        ;
                    } else {
                        application.Add(_val);
                    }
                }
                foreach (string _val in val3) {
                    if (_val == null) {
                        ;
                    } else {
                        applicationVersion.Add(_val);
                    }
                }
                application.TrimToSize();
                applicationVersion.TrimToSize();
                hotfix.TrimToSize();

                application.Sort();
                for (int i = 0; i < application.Count; i++ ) {
                    try {
                        if (application[i].Equals(application[i + 1])) {
                            application.RemoveAt(i);
                        }
                    }catch(Exception){
                   
                    }
                }
                application.TrimToSize();

                this.application = new string[application.Count];
                this.applicationVersion = new string[applicationVersion.Count];
                this.hotfix = new string[hotfix.Count];

                for (int i = 0; i < application.Count; i++) { this.application[i] = (string)application[i]; }
                for (int i = 0; i < applicationVersion.Count; i++) { this.applicationVersion[i] = (string)applicationVersion[i]; }
                for (int i = 0; i < hotfix.Count; i++) { this.hotfix[i] = (string)hotfix[i]; }
            } catch (NullReferenceException ne) {
                Console.WriteLine(ne.StackTrace);
            }
        }
----------------------------------------



もうちょっとだけ続くんじゃ。


ダメじゃない方のコードをコンパイルすると、DLLとtlbファイルが出来ますので、その2つをVC++のプロジェクトで使用します。

で、結局このDLLってどうなるのっ?ってことなのですが、「Visual Studioコマンドプロンプト」というのがあります。
このDOS窓で、oleviewと入力すると、COMを使用しているアプリケーションやコンポーネントが見れたりします。

oleview.png


えー、2ページ目に続きますが、今度はVC++を使って、AIRアプリとを繋ぐDLLを作成していきます。

【 BlazeDS + SpringFramework + MyBatis + HSQLDBの連携 】



Spring Framework3.0.5 - DI(Dependency Injection)コンテナ
MyBatis3.0.6 - O/R Mapper
HSQLDB2.2.5 - Java製RDB



Spring2.0.x + iBatis2.x + HSQLDB1.8.xを Spring3.0.5 + MyBatis3.0.6 + HSQLDB2.2.5で置き換えてみました。
こちらはAnnotationベースでの実装となります。

DBのScirpt, FlexのMXML・ASは変わりませんので、XMLベースで実装したドキュメントを参考にして下さい。
MyBatisを使用して、Annotationベースの実装を2種類(Annotation指定のものと、 SelectBuilder/SqlBuilderを使用したもの)試してみました。
XML書かなくていいので、こちらの方が便利ではあるけれど、ソース追いかけるのがヤヤコシイかな?



[ web.xml ]
---
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Spring BlazeDS</display-name>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext.xml</param-value>
    </context-param>


    <!-- Filter config with GZIP -->
    <filter>
        <filter-name>CompressingFilter</filter-name>
        <filter-class>
            com.planetj.servlet.filter.compression.CompressingFilter
        </filter-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>statsEnabled</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CompressingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>HttpContextUtilServlet</servlet-name>
        <servlet-class>com.chocbanana.http.HttpContextUtilServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
</web-app>
[ EOF ]
---

[ HttpContextUtilServlet.java ]
---
package com.chocbanana.http;

import java.io.Serializable;

import javax.servlet.ServletException;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;


public class HttpContextUtilServlet extends javax.servlet.http.HttpServlet implements Serializable {

    private static WebApplicationContext ctx;
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        try{
            //dao.shutdown();
        }catch(Exception e){
            e.printStackTrace();
        }
        super.destroy();
    }

    @Override
    public synchronized void init() throws ServletException {
        // TODO Auto-generated method stub
        super.init();
        try{
            if(ctx == null) ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public synchronized static WebApplicationContext getContext(){
        return ctx;
    }
}
[ EOF ]
---



[ applicationContext.xml ]
---
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
   http://www.springframework.org/schema/context
   http://www.springframework.org/schema/context/spring-context-3.0.xsd">

    <!-- jdbc.propertiesを認識させる propertyConfigurer -->
    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>
                    classpath:/com/chocbanana/dao/database.properties
                </value>
            </list>
        </property>
    </bean>

    <!-- jdbc.propertiesの設定値をdataSourceにインジェクション -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${db.driver}" />
        <property name="url" value="${db.url}" />
        <property name="username" value="${db.user}" />
        <property name="password" value="${db.pass}" />
    </bean>

    <!-- Dao Mapperのサービスクラス・パッケージをコンポーネントスキャンだぜ! -->
    <context:component-scan base-package="com.chocbanana.dao.service"/>
     
    <!-- enable autowire -->
    <context:annotation-config />
   
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
    </bean>
   
    <!-- トランザクション制御のインターセプターを構成 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
   
    <!-- 実装クラスのTransactionalアノテーションでジョイントポイントの認識をさせる  -->
    <!-- @transaction-managerのデフォルトは"transactionManager"だが念のため指定  -->
    <!-- @proxy-target-classをtureにしておく。CGLIBでプロキシーを作る方が多少速いらしい -->
    <!-- -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
   

    <!-- アノテーションベースのMapper -->
    <!--
     - コンポーネントスキャンでスキャンだぜ。Injectionされるだぜー。
     - MyBatisのDAO Mapperです。
    -->
     <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.chocbanana.dao.mapper" />
    </bean>
</beans>
[ EOF ]
---


[ database.properties ]
---
#Wed Apr 01 22:43:23 JST 2009
db.pass=
#db.url=jdbc\:hsqldb\:file:/Users/hoehoe/Documents/workspace/WEB-INF/resource/db
db.url=jdbc\:hsqldb\:file:C:/Documents and Settings/hoehoe/My Documents/EclipseProject/SpringBlazeDS/WEB-INF/resource/db
db.driver=org.hsqldb.jdbc.JDBCDriver
db.user=sa
[ EOF ]
---


[ DaoMapper.java ]
---
package com.chocbanana.dao.mapper;

import java.util.ArrayList;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.InsertProvider;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.SelectProvider;
import org.springframework.dao.DataAccessException;

import com.chocbanana.bean.PostMessage;

public interface DaoMapper {

    /* Annotation base */
    @Select("SELECT TOP 1000 * FROM MESSAGE ORDER BY ID DESC")
    ArrayList<PostMessage> getMessages() throws DataAccessException;

    @Insert("INSERT INTO MESSAGE (NAME, MAIL, MESSAGE, DAY) VALUES (#{name}, #{mail}, #{message}, #{day})")
    int putMessages(PostMessage Message) throws DataAccessException;
   
    /* SelectBuilder and SqlBuilder base  */
    @SelectProvider(type=com.chocbanana.dao.provider.SelectProvider.class, method="selectMessage")
    ArrayList<PostMessage> selectMessages() throws DataAccessException;
   
    @SelectProvider(type=com.chocbanana.dao.provider.SelectProvider.class, method="selectOne")
    PostMessage selectOne(PostMessage message) throws DataAccessException;

    @InsertProvider(type=com.chocbanana.dao.provider.SqlProvider.class, method="insertMessage")
    int insertMessages(PostMessage messge) throws DataAccessException;
   
}
[ EOF ]
---

[ SelectProvider.java ]
---
package com.chocbanana.dao.provider;

import static org.apache.ibatis.jdbc.SelectBuilder.BEGIN;
import static org.apache.ibatis.jdbc.SelectBuilder.FROM;
import static org.apache.ibatis.jdbc.SelectBuilder.SELECT;
import static org.apache.ibatis.jdbc.SelectBuilder.SQL;
import static org.apache.ibatis.jdbc.SelectBuilder.ORDER_BY;
import static org.apache.ibatis.jdbc.SelectBuilder.WHERE;

import com.chocbanana.bean.PostMessage;

public class SelectProvider {
     public static String selectMessage() {
         BEGIN();
         SELECT("TOP 1000 * ");
         FROM("MESSAGE");
         ORDER_BY("ID DESC");
         return SQL();
     }

     public static String selectOne(PostMessage message) {
         BEGIN();
         SELECT("*");
         FROM("MESSAGE");
         WHERE("ID like #{id}");
         return SQL();
     }
}
[ EOF ]
---


[ SqlProvider.java ]
---
package com.chocbanana.dao.provider;

import static org.apache.ibatis.jdbc.SelectBuilder.BEGIN;
import static org.apache.ibatis.jdbc.SqlBuilder.INSERT_INTO;
import static org.apache.ibatis.jdbc.SqlBuilder.VALUES;
import static org.apache.ibatis.jdbc.SqlBuilder.SQL;

import com.chocbanana.bean.PostMessage;

public class SqlProvider {
     public static String insertMessage(PostMessage message){
         BEGIN();
         INSERT_INTO("MESSAGE");
         VALUES("NAME, MAIL, MESSAGE, DAY", "#{name}, #{mail}, #{message}, #{day}");
         return SQL();
     }
}
[ EOF ]
---


[ FooService.java ]
---
package com.chocbanana.dao.service;

import java.util.ArrayList;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Transactional;

import com.chocbanana.bean.PostMessage;

@Transactional
public interface FooService {
    public ArrayList<PostMessage> getMessages() throws DataAccessException;
    public int putMessages(PostMessage Message) throws DataAccessException;
    public ArrayList<PostMessage> selectMessages() throws DataAccessException;
    public PostMessage selectOne(PostMessage message) throws DataAccessException;
    public int insertMessages(PostMessage messge) throws DataAccessException;
}

[ EOF ]
---

[ FooServiceImpl.java ]
---
package com.chocbanana.dao.service;

import java.util.ArrayList;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.stereotype.Service;

import com.chocbanana.bean.PostMessage;
import com.chocbanana.dao.mapper.DaoMapper;

@Service("fooService")
public class FooServiceImpl implements FooService {
   
    @Autowired
    private DaoMapper daoMapper;
   
    public void setDaoMapper(DaoMapper daoMapper){
        this.daoMapper = daoMapper;
    }
   
    public ArrayList<PostMessage> getMessages() throws DataAccessException{
        return this.daoMapper.getMessages();
    }
    public int putMessages(PostMessage Message) throws DataAccessException{
        return this.daoMapper.putMessages(Message);       
    }
    public ArrayList<PostMessage> selectMessages() throws DataAccessException{
        return this.daoMapper.selectMessages();
    }
    public PostMessage selectOne(PostMessage message) throws DataAccessException{
        return this.daoMapper.selectOne(message);
    }
    public int insertMessages(PostMessage messge) throws DataAccessException{
        return this.daoMapper.insertMessages(messge);
    }
}
[ EOF ]
---



[ FlexService.java ]
---
package com.chocbanana.ws;

import java.text.SimpleDateFormat;
import java.util.ArrayList;

import javax.annotation.Resource;

import com.chocbanana.bean.*;
import com.chocbanana.dao.Db2DaoInterface;
import com.chocbanana.dao.service.FooService;
import com.chocbanana.http.HttpContextUtilServlet;

@SuppressWarnings("serial")
public class FlexService implements java.io.Serializable{

    @Resource
    private static FooService service;

    private static final String BEAN_TYPE="fooService";

    public FlexService(){
        service = (FooService) HttpContextUtilServlet.getContext().getBean(BEAN_TYPE);
    }
    public ArrayList<ResultMessage> getMessages(){
       
        ArrayList<ResultMessage> result = new ArrayList<ResultMessage>();
        try{
            ArrayList<PostMessage> list;
            list = service.selectMessages(); /* service.getMessages(); */

            java.util.Iterator<PostMessage> it = list.iterator();
            ResultMessage resultMsg = null;

            while(it.hasNext()){
                PostMessage _msg = it.next();
                resultMsg = new ResultMessage();
                resultMsg.setMessage(
                        _msg.getId() +  " " +
                        _msg.getName() + " " +
                        _msg.getMail() + " " +
                        _msg.getDay() + " " + "\n\n" +
                        _msg.getMessage());
                result.add(resultMsg);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
       
    }
    public int putMessages(PostMessage message) {
        int ret=0;
        String day = "";
        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
        day = format.format(new java.util.Date());
        message.setDay(day);
       
        if("".equals(message.getMessage())){
            return 0;
        }
       
        try{
            ret = service.insertMessages(message); /* service.putMessages(message); */
        }catch(Exception e){
            e.printStackTrace();
        }
            return ret;
    }
}
[ EOF ]
---

【 BlazeDS + SpringFramework + MyBatis + HSQLDBの連携 】



Spring Framework3.0.5 - DI(Dependency Injection)コンテナ
MyBatis3.0.6 - O/R Mapper
HSQLDB2.2.5 - Java製RDB



Spring2.0.x + iBatis2.x + HSQLDB1.8.xを Spring3.0.5 + MyBatis3.0.6 + HSQLDB2.2.5で置き換えてみました。
こちらはXMLベースでの実装となります。

packageの階層に注意してください。



[ db.script -抜粋- ]
---
CREATE CACHED TABLE MESSAGE(
    ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) NOT NULL PRIMARY KEY,
    NAME VARCHAR(255) NOT NULL,
    MAIL VARCHAR(255) NOT NULL,
    MESSAGE VARCHAR(65535) NOT NULL,
    DAY VARCHAR(255) NOT NULL
    );
[ EOF ]
---



[ web.xml ]
---
<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
    "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
    <display-name>Spring BlazeDS</display-name>

    <!-- Contextに渡すXML -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/applicationContext-xml.xml</param-value>
    </context-param>


    <!-- Filter config with GZIP -->
    <filter>
        <filter-name>CompressingFilter</filter-name>
        <filter-class>
            com.planetj.servlet.filter.compression.CompressingFilter
        </filter-class>
        <init-param>
            <param-name>debug</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>statsEnabled</param-name>
            <param-value>false</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>CompressingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
   
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
   
    <servlet>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <servlet-class>flex.messaging.MessageBrokerServlet</servlet-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet>
        <servlet-name>HttpContextUtilServlet</servlet-name>
        <servlet-class>com.chocbanana.http.HttpContextUtilServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
    </servlet>


    <servlet-mapping>
        <servlet-name>MessageBrokerServlet</servlet-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </servlet-mapping>
</web-app>
[EOF]
---


[ HttpContextUtilServlet.java ]
---
package com.chocbanana.http;

import java.io.Serializable;

import javax.servlet.ServletException;

import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;


public class HttpContextUtilServlet extends javax.servlet.http.HttpServlet implements Serializable {

    private static WebApplicationContext ctx;
    /**
     *
     */
    private static final long serialVersionUID = 1L;

    @Override
    public void destroy() {
        // TODO Auto-generated method stub
        try{
            //dao.shutdown();
        }catch(Exception e){
            e.printStackTrace();
        }
        super.destroy();
    }

    @Override
    public synchronized void init() throws ServletException {
        // TODO Auto-generated method stub
        super.init();
        try{
            if(ctx == null) ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext());
        }catch(Exception e){
            e.printStackTrace();
        }
    }
    public synchronized static WebApplicationContext getContext(){
        return ctx;
    }
}
[EOF]
---


[ applicationContext.xml ]
---
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
   http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/tx
   http://www.springframework.org/schema/tx/spring-tx-3.0.xsd">

    <!-- jdbc.propertiesを認識させる propertyConfigurer -->
    <bean id="propertyConfigurer"
        class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="locations">
            <list>
                <value>
                    classpath:/com/chocbanana/dao/database.properties
                </value>
            </list>
        </property>
    </bean>

    <!-- jdbc.propertiesの設定値をdataSourceにインジェクション -->
    <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${db.driver}" />
        <property name="url" value="${db.url}" />
        <property name="username" value="${db.user}" />
        <property name="password" value="${db.pass}" />
    </bean>

    <!-- MyBatisのフロントになるsqmMapClientを構成。プロパティ値にMapper.xmlのパスを指定するだけ -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="mapperLocations" value="classpath:/com/chocbanana/dao/Mapper.xml" />
    </bean>
   
    <!-- トランザクション制御のインターセプターを構成 -->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
   
    <!-- 実装クラスのTransactionalアノテーションでジョイントポイントの認識をさせる  -->
    <!-- @transaction-managerのデフォルトは"transactionManager"だが念のため指定  -->
    <!-- @proxy-target-classをtureにしておく。CGLIBでプロキシーを作る方が多少速いらしい -->
    <!-- -->
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
   
    <!-- XMLベースのDAO -->
    <bean id="daoXml" class="com.chocbanana.dao.Db2Dao">
        <property name="sqlSessionFactory" ref="sqlSessionFactory" />
    </bean>
</beans>
[EOF]
---

[ database.properties ]
---
#Wed Apr 01 22:43:23 JST 2009
db.pass=
#db.url=jdbc\:hsqldb\:file:/Users/hoehoe/Documents/workspace/WEB-INF/resource/db
db.url=jdbc\:hsqldb\:file:C:/Documents and Settings/hoehoe/My Documents/EclipseProject/SpringBlazeDS/WEB-INF/resource/db
db.driver=org.hsqldb.jdbc.JDBCDriver
db.user=sa
[EOF]
---




[ Db2DaoInterface.java ]
---
/*
 * Created on 2005/06/19
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.chocbanana.dao;

import java.util.ArrayList;

import org.springframework.dao.DataAccessException;
import com.chocbanana.bean.PostMessage;
/**
 * @author hoehoe
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public interface Db2DaoInterface {
   
    ArrayList<PostMessage> getMessages() throws DataAccessException;
    int putMessages(PostMessage Message) throws DataAccessException;
   
    void shutdown() throws DataAccessException;
   
}
[EOF]
---

[ Db2Dao.java ]
---
/*
 * Created on 2005/06/18
 *
 * TODO To change the template for this generated file go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
package com.chocbanana.dao;

import java.sql.SQLException;
import java.util.ArrayList;

import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.chocbanana.bean.PostMessage;

/**
 * @author hoehoe
 *
 * TODO To change the template for this generated type comment go to
 * Window - Preferences - Java - Code Style - Code Templates
 */
public class Db2Dao extends SqlSessionDaoSupport implements Db2DaoInterface{
    @SuppressWarnings("unchecked")
    @Transactional(readOnly=true, propagation=Propagation.REQUIRED)
    public ArrayList<PostMessage> getMessages() throws DataAccessException{
        return (ArrayList<PostMessage>) getSqlSession().selectList("com.chocbanana.dao.Mapper.getMessage");
    }
   
    @Transactional(readOnly=false, rollbackFor=SQLException.class)
    public int putMessages(PostMessage message) throws DataAccessException{
        return getSqlSession().insert("com.chocbanana.dao.Mapper.putMessage", message);
    }

    public void shutdown() throws DataAccessException{
        getSqlSession().selectOne("shutdown");
    }
}
[EOF]
---


[ /com/chocbanana/dao/Mapper.xml ]
---
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--
    Copyright 2010-2011 The myBatis Team

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->

<!--
    version: $Id: UserMapper.xml 2444 2010-09-15 07:38:37Z simone.tripodi $
-->
<mapper namespace="com.chocbanana.dao.Mapper">
    <select id="getMessage"  resultType="com.chocbanana.bean.PostMessage">
        SELECT TOP 1000 * FROM MESSAGE ORDER BY ID DESC
    </select>

    <insert id="putMessage" parameterType="com.chocbanana.bean.PostMessage" >
        INSERT INTO MESSAGE (NAME, MAIL, MESSAGE, DAY) VALUES (#{name}, #{mail}, #{message}, #{day})
    </insert>

    <!-- database shutdown -->
    <sql id="shutdown">shutdown</sql>
</mapper>
[EOF]
---



[ FlexService.java ]
---
package com.chocbanana.ws;

import java.text.SimpleDateFormat;
import java.util.ArrayList;

import com.chocbanana.bean.*;
import com.chocbanana.dao.Db2DaoInterface;
import com.chocbanana.http.HttpContextUtilServlet;

@SuppressWarnings("serial")
public class FlexService implements java.io.Serializable{
   
    private static Db2DaoInterface dao;
    private static final String BEAN_TYPE = "daoXml";

    public FlexService(){
        dao = (Db2DaoInterface) HttpContextUtilServlet.getContext().getBean(BEAN_TYPE);
    }
    public ArrayList<ResultMessage> getMessages(){
       
        ArrayList<ResultMessage> result = new ArrayList<ResultMessage>();
        try{
            ArrayList<PostMessage> list = dao.getMessages();
            java.util.Iterator<PostMessage> it = list.iterator();
            ResultMessage resultMsg = null;

            while(it.hasNext()){
                PostMessage _msg = it.next();
                resultMsg = new ResultMessage();
                resultMsg.setMessage(
                        _msg.getId() +  " " +
                        _msg.getName() + " " +
                        _msg.getMail() + " " +
                        _msg.getDay() + " " + "\n\n" +
                        _msg.getMessage());
                result.add(resultMsg);
            }
        }catch(Exception e){
            e.printStackTrace();
        }
        return result;
       
    }
    public int putMessages(PostMessage message) {
        int ret=0;
        String day = "";
        SimpleDateFormat format = new SimpleDateFormat("yyyy/MM/dd hh:mm:ss");
        day = format.format(new java.util.Date());
        message.setDay(day);
       
        if("".equals(message.getMessage())){
            return 0;
        }
       
        try{
            ret = dao.putMessages(message);
        }catch(Exception e){
            e.printStackTrace();
        }
            return ret;
    }
}
---


- Flexのソースコード一覧 -

[ SpringBlazeDS.mxml ]
---
<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               minWidth="950" minHeight="500" viewSourceURL="srcview/index.html">

    <fx:Script source="SpringBlazeDS.as"/>
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
        <s:RemoteObject id="srv" endpoint="{'http://{server.name}:{server.port}/SpringBlazeDS/messagebroker/amf'}" destination="flexService" showBusyCursor="true">
            <s:method name="getMessages" result="resultMessage(event)" fault="faultMessage(event)"/>
        </s:RemoteObject>
    </fx:Declarations>

    <s:Panel x="0" y="0" width="100%" height="100%" title="SpringBlazeDS">
        <s:HGroup x="10" y="10" width="100%" verticalAlign="middle">
            <s:Spacer width="20" height="10"/>
            <s:Button id="btn_post" label="Post" click="btn_post_clickHandler(event)"
                      fontWeight="bold"/>
            <s:Spacer width="15" height="10"/>
            <s:Button id="btn_reload" label="Reload" click="btn_reload_clickHandler(event)"
                      fontWeight="bold"/>
        </s:HGroup>
        <s:Spacer x="10" y="39" width="20" height="10"/>
        <s:HGroup x="10" y="56" width="100%" height="85%">
            <s:DataGrid width="100%" height="100%" dataProvider="{srv.getMessages.lastResult}" creationComplete="d1_creationCompleteHandler(event)">
                <s:columns>
                    <s:ArrayList>
                        <s:GridColumn dataField="message" headerText="" />
                    </s:ArrayList>
                </s:columns>
            </s:DataGrid>
            <s:Spacer width="10" height="10"/>
        </s:HGroup>
       
    </s:Panel>
</s:Application>
[ EOF ]
---


[ SpringBlazeDS.as ]
---

import flash.events.MouseEvent;

import mx.controls.Alert;
import mx.events.FlexEvent;
import mx.managers.PopUpManager;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

protected function btn_post_clickHandler(event:MouseEvent):void
{
    // TODO Auto-generated method stub
    var post:Post = Post(PopUpManager.createPopUp(this, Post, true));
    post.addEventListener(FlexEvent.REMOVE, closePopUp);
    PopUpManager.centerPopUp(post);
}
private function closePopUp(e:FlexEvent):void{
    srv.getMessages();
}

public function resultUser(event:ResultEvent):void{

}
protected function btn_reload_clickHandler(event:MouseEvent):void
{
    // TODO Auto-generated method stub
    srv.getMessages();
}

protected function d1_creationCompleteHandler(event:FlexEvent):void
{
    // TODO Auto-generated method stub
    srv.getMessages();
}

public function resultMessage(event:ResultEvent):void{

}

public function faultMessage(event:FaultEvent):void{
    Alert.show(event.toString());
}
[ EOF ]
---


[ Post.mxml ]
---
<?xml version="1.0" encoding="utf-8"?>
<s:TitleWindow xmlns:fx="http://ns.adobe.com/mxml/2009"
               xmlns:s="library://ns.adobe.com/flex/spark"
               xmlns:mx="library://ns.adobe.com/flex/mx"
               width="800" height="400" creationComplete="t1_creationCompleteHandler(event)" close="t1_closeHandler(event)" title="PostMessage">
   
    <fx:Script>
        <![CDATA[
            import com.chocbanana.bean.PostMessge;
           
            import mx.controls.Alert;
            import mx.events.CloseEvent;
            import mx.events.FlexEvent;
            import mx.managers.PopUpManager;
            import mx.rpc.events.FaultEvent;
            import mx.rpc.events.ResultEvent;
           
            protected function btn_cancel_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                txt_message.text = "";
            }
           
            protected function btn_post_clickHandler(event:MouseEvent):void
            {
                // TODO Auto-generated method stub
                var post:PostMessge = new PostMessge();
                post.name = txt_name.text;
                if(post.name == ""){
                    post.name = "以下、名無しに変わりましてfxug-nagoyaがお送りします";
                }
                post.mail = "sage";
                post.message = txt_message.text;
                if(post.message == ""){
                    Alert.show("本文が空です。");
                    return;
                }
                srv.putMessages(post);
            }
           
            protected function t1_closeHandler(event:CloseEvent):void
            {
                // TODO Auto-generated method stub
                PopUpManager.removePopUp(this);
            }
           
            protected function t1_creationCompleteHandler(event:FlexEvent):void
            {
                // TODO Auto-generated method stub
                txt_message.setFocus();
            }
           
            protected function method1_resultHandler(event:ResultEvent):void
            {
                // TODO Auto-generated method stub
                PopUpManager.removePopUp(this);
               
            }
           
            protected function method1_faultHandler(event:FaultEvent):void
            {
                // TODO Auto-generated method stub
                Alert.show(event.message.toString());
            }
           
        ]]>
    </fx:Script>
   
    <fx:Declarations>
        <!-- Place non-visual elements (e.g., services, value objects) here -->
        <s:RemoteObject id="srv" endpoint="{'http://{server.name}:{server.port}/SpringBlazeDS/messagebroker/amf'}" destination="flexService" showBusyCursor="true">
            <s:method name="putMessages" result="method1_resultHandler(event)" fault="method1_faultHandler(event)" />
        </s:RemoteObject>
    </fx:Declarations>
    <s:VGroup x="10" y="10" width="780" height="346">
        <s:HGroup verticalAlign="middle">
            <s:Label text="Name: "/>
            <s:TextInput id="txt_name" width="320" text="以下、名無しに変わりましてfxug-nagoyaがお送りします"/>
        </s:HGroup>
        <s:VGroup width="770" verticalAlign="bottom">
            <s:Spacer height="5" width="5" />
            <s:Label text="Message: "/>
            <s:TextArea id="txt_message" width="100%" height="200"/>
        </s:VGroup>
        <s:Spacer width="20" height="10"/>
        <s:HGroup width="100%" horizontalAlign="right" verticalAlign="middle">
            <s:Button id="btn_cancel" label="Cancel" click="btn_cancel_clickHandler(event)"/>
            <s:Spacer width="20" height="10"/>
            <s:Button id="btn_post" label="Post" click="btn_post_clickHandler(event)"
                      fontWeight="bold"/>
            <s:Spacer width="20" height="10"/>
        </s:HGroup>
    </s:VGroup>

</s:TitleWindow>

[ EOF ]
---



社内から帳票作成とその情報を一括管理出来るように…という要望を頂いたので、AdobeAIRで実装してみました。


サンプルアプリのソースFxUG名古屋勉強会資料_20110709.pdf を置いておきます。


必要となるJavaのライブラリ一式。
----------------------
aopalliance-1.0.jar
asm-2.2.3.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-beanutils.jar
commons-collections-3.2.1.jar
commons-dbcp-1.4.jar
commons-digester.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-logging-1.1.1.jar
commons-pool-1.5.4.jar
cxf-2.2.12.jar
FastInfoset-1.2.7.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-remoting.jar
freemarker-2.3.16.jar
geronimo-annotation_1.0_spec-1.1.1.jar
geronimo-servlet_2.5_spec-1.2.jar
geronimo-stax-api_1.0_spec-1.0.1.jar
geronimo-ws-metadata_2.0_spec-1.1.2.jar
ibatis-2.3.4.726.jar
iText-2.1.7.jar
iTextAsian.jar
jasperreports-4.0.1.jar
jasperreports-extensions-3.5.3.jar
jasperreports-fonts-4.0.1.jar
javassist.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.13.jar
jcommon-1.0.16.jar
jdt-compiler-3.1.1.jar
log4j-1.2.16.jar
log4j.dtd
neethi-2.0.4.jar
ognl-3.0.jar
opensaml-1.1.jar
pjl-comp-filter-1.7.jar
poi-3.7-20101029.jar
postgresql-8.3-603.jdbc4.jar
saaj-api-1.3.jar
saaj-impl-1.3.2.jar
serializer-2.7.1.jar
spring-aop.jar
spring-beans.jar
spring-context-support.jar
spring-context.jar
spring-core.jar
spring-jdbc.jar
spring-jms.jar
spring-orm.jar
spring-test.jar
spring-tx.jar
spring-web.jar
spring-webmvc-struts.jar
spring.jar
sql-map-2.dtd
sql-map-config-2.dtd
struts2-convention-plugin-2.2.1.jar
struts2-core-2.2.1.jar
wsdl4j-1.6.2.jar
wss4j-1.5.8.jar
wstx-asl-3.2.9.jar
xalan-2.7.1.jar
xml-resolver-1.2.jar
XmlSchema-1.4.5.jar
xmlsec-1.4.3.jar
xwork-core-2.2.1.jar
----------------------



PDFとして作成された帳票を表示。
pict0003.jpg


iReportでテンプレートを作成し、データを流し込んで、PDFを作成します。

pict0005.jpg


※データは架空のものです。
ご参考まで。

ApacheとResinとStruts2とFlex(BlazeDS)の連携

メモしときます。

Project名はHello。
HelloWorld ! を表示します。


[ resin.conf ]
----------
# web-appのRoot設定
<web-app id="/" root-directory='C:\Documents and Settings\hoehoe\My Documents\EclipseProject\Hello'/>
----------


[ WEB-INF/lib/ ]
----------
aopalliance-1.0.jar
asm-2.2.3.jar
backport-util-concurrent.jar
cglib-nodep-2.1_3.jar
commons-beanutils.jar
commons-dbcp-1.4.jar
commons-digester.jar
commons-fileupload-1.2.1.jar
commons-io-1.3.2.jar
commons-logging-1.1.1.jar
commons-pool-1.5.4.jar
cxf-2.2.9.jar
dir.txt
FastInfoset-1.2.7.jar
flex-messaging-common.jar
flex-messaging-core.jar
flex-messaging-remoting.jar
freemarker-2.3.16.jar
geronimo-annotation_1.0_spec-1.1.1.jar
geronimo-servlet_2.5_spec-1.2.jar
geronimo-stax-api_1.0_spec-1.0.1.jar
geronimo-ws-metadata_2.0_spec-1.1.2.jar
ibatis-2.3.4.726.jar
javassist.jar
jaxb-api-2.1.jar
jaxb-impl-2.1.13.jar
jcommon-1.0.16.jar
log4j-1.2.16.jar
log4j.dtd
neethi-2.0.4.jar
ognl-3.0.jar
opensaml-1.1.jar
pjl-comp-filter-1.7.jar
postgresql-8.3-603.jdbc4.jar
saaj-api-1.3.jar
saaj-impl-1.3.2.jar
serializer-2.7.1.jar
spring-aop.jar
spring-beans.jar
spring-context-support.jar
spring-context.jar
spring-core.jar
spring-jdbc.jar
spring-jms.jar
spring-orm.jar
spring-test.jar
spring-tx.jar
spring-web.jar
sql-map-2.dtd
sql-map-config-2.dtd
struts2-convention-plugin-2.2.1.jar
struts2-core-2.2.1.jar
wsdl4j-1.6.2.jar
wss4j-1.5.8.jar
wstx-asl-3.2.9.jar
xalan-2.7.1.jar
xml-resolver-1.2.jar
XmlSchema-1.4.5.jar
xmlsec-1.4.3.jar
xwork-core-2.2.1.jar
----------




[ web.xml ]
----------
<?xml version="1.0"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
                      http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- Flex(BlazeDSもFilter経由。先にBlazeDSのFilterを記述しておくこと) -->
    <filter>
        <filter-name>flex</filter-name>
        <filter-class>hello.filter.FlexFilter</filter-class>
        <init-param>
            <param-name>services.configuration.file</param-name>
            <param-value>/WEB-INF/flex/services-config.xml</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>flex</filter-name>
        <url-pattern>/messagebroker/*</url-pattern>
    </filter-mapping>


    <filter>
        <filter-name>struts2</filter-name>
        <filter-class>
            org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
        </filter-class>
        <init-param>
            <param-name>actionPackages</param-name>
            <param-value>hello.action</param-value>
        </init-param>
    </filter>

    <filter-mapping>
        <filter-name>struts2</filter-name>
        <url-pattern>/struts2/*</url-pattern>
    </filter-mapping>
</web-app>
[EOF]
----------


Struts2の配置 アノテーションを使用するのでstruts.xmlなどは無いです。

[ hello.action.HelloAction ]
----------
package hello.action;

import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.opensymphony.xwork2.ActionSupport;

/**
 * 相変わらず標準出力に欧米の挨拶をかますだけのアクション。
 */
@Namespace("/struts2")
@Results({
    @Result(name="success", location="hello.jsp"),
    @Result(name="failure", location="error.jsp")
})
public class HelloAction extends ActionSupport {
//public class SayHelloAction {
    private static final long serialVersionUID = 1L;

    /**
     * アクション実行メソッド。
     */
    public String execute() {
        // アクション実行の結果を文字列で返す。
        return ActionSupport.SUCCESS;
    }
    public String getMessage(){
        return "Hello World!!";
    }
}
[ EOF ]
----------


[ /WEB-INF/content/struts2/hello.jsp ]
----------
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="s" uri="/struts-tags" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<s:property value="message"/>
</body>
</html>
[ EOF ]
----------


[ hello.filter.FlexFilter ]
----------
package hello.filter;

import java.io.IOException;
import java.util.Enumeration;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.mock.web.MockServletConfig;

import flex.messaging.MessageBrokerServlet;

/**
 * The MessageBrokerServlet has a lower priority than Filters, regardless of the
 * url-pattern we use. For that reason I created a filter that directly extends
 * the MessageBrokerServlet but behaves as a filter. This might be quite dodgy
 * but that's the only way I found to integrate struts 2 and Adobe Flex/BlazeDS.
 * Anyone who knows a simpler way please email me at info at spltech.co.uk
 *
 * @author Armindo Cachada
 *
 */
public class FlexFilter extends MessageBrokerServlet implements Filter {

    /**
     *
     */
    private static final long serialVersionUID = 1L;

    public void destroy() {
        super.destroy();
    }

    /**
     * If this method is called the parent service method of the
     * MessageBrokerServlet is called, which does whatever BlazeDS needs to do
     * to communicate with the flex client. Note Here that any subsequent filter
     * will not be called because I am not invoking filterChain.doFilter/ That
     * is on purpose, because if it does, the normal struts 2 action mapping
     * mechanism will be called.
     *
     */
    public void doFilter(ServletRequest servletrequest,
            ServletResponse servletresponse, FilterChain filterchain)
            throws IOException, ServletException {
        this.service((HttpServletRequest) servletrequest,
                (HttpServletResponse) servletresponse);
    }

    /**
     * Note the use here of MockServletConfig. This utility class is available
     * in the spring framework. It is meant to be used for testing but I am
     * actually giving it a real purpose :)
     */
    public void init(FilterConfig filterconfig) throws ServletException {
        System.out.println("filter called");
        MockServletConfig servletConfig = new MockServletConfig(filterconfig
                .getServletContext());
        Enumeration filterParameters = filterconfig.getInitParameterNames();

        while (filterParameters.hasMoreElements()) {
            String filterParameter = (String) filterParameters.nextElement();
            System.out.println("Found parameter: " + filterParameter);
            String value = filterconfig.getInitParameter(filterParameter);
            servletConfig.addInitParameter(filterParameter, value);

        }
        super.init(servletConfig);
    }

}
[ EOF ]
----------


[ /WEB-INF/flex/services-config.xml ]
----------
<?xml version="1.0" encoding="UTF-8"?>
<services-config>

    <services>
        <!-- service要素には何でもいいからIDが要る模様 -->
        <service id="remoting-service"
            class="flex.messaging.services.RemotingService">
            <adapters>
                <adapter-definition id="java-object"
                    class="flex.messaging.services.remoting.adapters.JavaAdapter"
                    default="true" />
            </adapters>
            <default-channels>
                <channel ref="my-amf" />
                <channel ref="my-secure-amf"/>
            </default-channels>

            <!-- destination要素のIDが mx:RemoteObjectの destination プロパティに対応する -->
            <!-- リモート呼び出しを可能にしたい Beanの数だけ destination要素を記述する -->
            <destination id="fxService">
                <properties>
                    <!-- リモート呼び出ししたいJava Beansのクラス名 -->
                    <source>hello.ws.FlexService</source>
                    <!-- 注:この Beanはステートレスである -->
                </properties>
            </destination>
        </service>
    </services>

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>

        <!-- SSL用チャンネルの定義 -->
        <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
            <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>
    </channels>
</services-config>
[ EOF ]
----------


[ hello.ws.FlexService ]
----------
package hello.ws;

@SuppressWarnings("serial")
public class FlexService implements java.io.Serializable{
        
    public FlexService(){
    }
    public String sayHello(){
        return "Hello World !";
    }
}
[ EOF ]
----------



Apache
[ httpd.conf -Apache- ]
----------
#
# mod_caucho Resin Configuration
#
#Include conf/extra/httpd-proxy.conf
LoadModule caucho_module /usr/local/apache/modules/mod_caucho.so

ResinConfigServer 127.0.0.1 6800
CauchoConfigCacheDirectory /tmp
CauchoStatus yes
<Location /struts2/*>
  SetHandler caucho-request
</Location>
[ EOF ]
----------


Flex
[ Hello.mxml ]
----------
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" backgroundColor="white" creationComplete="init();">
<mx:Script source="Hello.as"/>
    <mx:RemoteObject id="srv" endpoint="{lbl_endPoint.text}" destination="fxService" showBusyCursor="true">
        <mx:method name="sayHello" result="resultHello(event);" fault="faultHello(event);"/>
    </mx:RemoteObject>
    <mx:Button x="65" y="59" label="click" id="btn_message" click="btn_message_click();"/>
    <mx:Text x="38" y="33" id="txt_message" text="{srv.sayHello.lastResult}"/>
    <mx:Label x="84" y="114" id="lbl_endPoint"/>
</mx:Application>
[ EOF ]
----------

[ Hello.as ]
----------
// ActionScript file
import hello.EndPoint;

import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;

private function init():void{
    lbl_endPoint.text = EndPoint.getEndPointRevers();
}
private function btn_message_click():void{
    srv.sayHello();
}

private function resultHello(event:ResultEvent):void{
    Alert.show(event.message.toString());
}
private function faultHello(event:FaultEvent):void{
    Alert.show(event.message.toString());
}
[ EOF ]
----------


[ hello.EndPoint.as ]
----------
package hello {

    import flash.external.ExternalInterface;
   
    import mx.messaging.ChannelSet;
    import mx.messaging.channels.AMFChannel;
    import mx.rpc.remoting.mxml.RemoteObject;

    public class EndPoint {
       
        private static var remote:RemoteObject;
       
        public function EndPoint() {
            //TODO: implement function
        }
        public static function getEndPointRevers():String{
            var endPointURL:String = ExternalInterface.call("getFQDN");
            endPointURL = endPointURL + "messagebroker/amf";
            return endPointURL;
        }
        public static function getRemoteObject():RemoteObject{
            //var remote:RemoteObject = new RemoteObject();
            if(remote == null){
                remote = new RemoteObject();
                var cs:ChannelSet = new ChannelSet();
                var ac:AMFChannel = new AMFChannel("my-amf", EndPoint.getEndPointRevers());
                cs.addChannel(ac);
                remote.destination = "flexService";
                remote.channelSet = cs;
            }
            return remote;
        }
    }
}
[ EOF ]
----------

[ index.template.html(JavaScript) ]
----------
<script language="JavaScript" type="text/javascript">
<!--
//------------------------------------------------------------------------------
// MyScript
function setTitle(){
    if(location.href.indexOf("#",0) == -1){
        document.title = "IntraMaster";
    }else{
        document.title = "IntraMaster";
    }
}
function getURL(){
    return (document.URL);
}
function getPort(){
    var protocol = location.protocol;
    var port = location.port;
   
    if(protocol == "http:"){
        if(port == ""){
            port = 80;
        }else{
       
        }
    }else if(protocol == "https:"){
        if(port == ""){
            port = 443;
        }else{
       
        }       
    }
   
    return port;
}
function getFQDN(){
    var fqdn = "";
    var hostname = location.hostname;
    var protocol = location.protocol;
    var port = location.port;
    var href= location.href;
   
    var a;
    var b;
   
    var lastIndex = href.lastIndexOf("/");
    href = href.substring(0, lastIndex);
   
    if(href.indexOf("~") == -1){
        if(port == ""){
            fqdn = protocol + "//" + hostname + "/"
        }else{
            fqdn = protocol + "//" + hostname + ":" + port + "/"
        }
    }else{
        a = href.split("//")[1];
        b = a.split("/")[1];
        if(port == ""){
            fqdn = protocol + "//" + hostname + "/" + b;
        }else{
            fqdn = protocol + "//" + hostname + ":" + port + "/" + b;
        }
    }
    //fqdn = "http://127.0.0.1:8081/messagebroker/amf";
    return fqdn;
}

function getUserAgent(){
    var ua = navigator.userAgent;
    var strOS;
   
    if(ua.indexOf("Win") >= 0){ strOS = "Win"; }
    else if(ua.indexOf("Mac") >= 0){ strOS = "Mac"; }
    else if(ua.indexOf("Linux") >= 0){ strOS = "Linux"; }
    else{ strOS = ""; }

    return strOS;
}

//------------------------------------------------------------------------------
//-->
</script>
[ EOF ]
----------
 
Tomcatと連携する場合はApacheのコンパイル時に ./configure --enable-proxy --enable-proxy-ajp を有効にし、
/usr/local/apache2/conf/extra/httpd-proxy.conf を新規に作成後、以下を記述保存します。

以下の例では、/struts2/* ( /struts2以下のすべてのリクエスト ) に対して ProxyPass を設定しています。

<Location /struts2/>
ProxyPass ajp://localhost:8009/
</Location>

Flexアプリを利用する場合、IEのバージョンによって指定するオプションが違ったのでmemoしておきます。

えー、これくらい統一して下さいな、M$さん...。


[ IE6 の場合 ]
SSL2.0/3.0を使用するのcheckをONに。
TLS 1.0を使用するのcheckをONに。
暗号化されたページをディスクに保存しないのcheckをOFFに。

ie6-internetoption.jpg

[ IE8 の場合 ]
SSL2.0/3.0を使用するのcheckをONに。
TLS 1.0を使用するのcheckをONに。
暗号化されたページをディスクに保存しないのcheckをONに。

ie8-internetoption.jpg

あと、無効なサイトの証明書について警告するのcheckをON/OFFにすると、証明書の確認ダイアログを抑制できたりします(IE6)。

IE8の場合は証明書のアドレスが不一致について警告するのcheckになります。
以上。
IE6 + mod_deflate(GZip圧縮) + Flex/FlashでFlashPlayerの無応答(もしくは動作不良)に対する解決策です。

以下、http://www.fxug.net/modules/xhnewbb/viewtopic.php?viewmode=flat&topic_id=3510&forum=16 に投稿した内容と重複します。

(■追記 - 2010/02/27 - この内容を編集しました。FxUGに投稿した内容と多少前後します。)


GZip compression + Flash Player + IE6 = Nightmare


先に参考にさせて頂いたURLをペタっと。

http://www.google.com/search?hl=ja&safe=off&rlz=1B3GGGL_jaJP336JP336&num=100&q=add-no-cache-headers&btnG=%E6%A4%9C%E7%B4%A2&lr=lang_ja&aq=&oq=
http://www.riaservice.com/?p=526
http://help.adobe.com/ja_JP/LiveCycleDataServicesES/3.0/Developing/WSc3ff6d0ea77859461172e0811f00f6e876-7fd6.html
http://www.adobe.com/support/documentation/jp/flex/2/releasenotes_flex2_fds.html



[ エラー内容 ]

デバッグ版FlashPlayer ver.10.0.32.18, IE6-SP1, Windows2000 Professionalでキャプチャしたエラー画面です。

flexError.jpg



[ 解決策 ]

結果としてはHTTPS + IEでの解決策と同様の手順になりました。
BlazeDS側にある WEB-INF\flex\services-config.xml を編集します。


    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectのendpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
        </channel-definition>

の中に

<properties>
    <add-no-cache-headers>false</add-no-cache-headers>
</properties>

を記述するだけです。これで、mod_deflateやpjl-comp-filterなどによるGZip圧縮を有効にしても、ちゃんと動作するようにな ると思います。
下記の様に(endpoint以下に)properties部分を加えてWebコンテナを再起動して下さい。

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>

IEオプションのHTTP1.1を有効にして、URLにアクセスした時に動作すればOKかと。

結局パケットをキャプチャしてもダメだったので、デバッグ版FlashPlayerをIE6にインストールしてエラーメッセージを読みました。
エラーメッセージの内容は「NetConnection.Call.Failed: HTTP: Status 200」でしたので、それをキーワードにググったところIE + HTTPSの解決策と同じであることが判明致しましたので、ご報告とさせて頂きます。


以上。ご参考までに。

[ services-config.xml ]
-------
<?xml version="1.0" encoding="UTF-8"?>
<services-config>

    <services>
        <!-- service要素には何でもいいからIDが要る模様 -->
        <service id="remoting-service"
            class="flex.messaging.services.RemotingService">
            <adapters>
                <adapter-definition id="java-object"
                    class="flex.messaging.services.remoting.adapters.JavaAdapter"
                    default="true" />
            </adapters>
            <default-channels>
                <channel ref="my-amf" />
                <channel ref="my-secure-amf"/>
            </default-channels>

            <!-- destination要素のIDが mx:RemoteObjectの destination プロパティに対応する -->
            <!-- リモート呼び出しを可能にしたい Beanの数だけ destination要素を記述する -->
            <destination id="flexService">
                <properties>
                    <!-- リモート呼び出ししたいJava Beansのクラス名 -->
                    <source>com.chocbanana.ws.FlexService</source>
                    <!-- 注:この Beanはステートレスである -->
                </properties>
            </destination>
        </service>
    </services>

    <channels>
        <channel-definition id="my-amf"
            class="mx.messaging.channels.AMFChannel">
            <!--
                ここで構成されたエンドポイントURLを mx:RemoteObjectの
                endpoint プロパティにセットする
                {}内は実行時に自動で置き換えられるのでこの記述のままで良い。
            -->
            <endpoint
                url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf"
                class="flex.messaging.endpoints.AMFEndpoint" />
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>

        <!-- SSL用チャンネルの定義 -->
        <channel-definition id="my-secure-amf" class="mx.messaging.channels.SecureAMFChannel">
            <endpoint url="https://{server.name}:{server.port}/{context.root}/messagebroker/amfsecure" class="flex.messaging.endpoints.SecureAMFEndpoint"/>
            <properties>
                <add-no-cache-headers>false</add-no-cache-headers>
            </properties>
        </channel-definition>
    </channels>
</services-config>
[ EOF ]
-------



それでもエラーが出る場合は、↓の画像を参考にIEオプションも見直して下さい。

ieOption.jpg

このチェックが付いていると、上手く動かないんだと思います。
「暗号化されたページをディスクに保存しない」のチェックをOFFにして、再度確かめてください。


以下のURLが公式の回答ですね。
http://support.microsoft.com/kb/312496/ja
http://support.microsoft.com/kb/871205/ja


M$的には、
「HTTP 圧縮を使用している Web サーバーから返送されたデータの最初の 2,048 バイトが Internet Explorer で失われる」
「この問題は、コンピュータで Apache HTTP Server を実行していると発生する可能性が高くなります」
「Apache HTTP Server は、任意の種類のファイルでチャンク エンコードを使用できるため」
「IIS は、デフォルトではチャンク エンコードを使用しません」

だそうで、勘弁して下さい、M$さん。ホント。仕様だと回避策探すのが大変です。


MSのドキュメントでは

----------------------
この問題は、WININET と URLMON の間で通知が失われた場合に発生します。
通知が失われると、圧縮解除に必要なエンコードされたチャンクが失われます。
エンコードされたチャンクが失われると、gzip 圧縮解除プログラムが失敗します。

注)
WININET (Wininet.dll) は、HTTP データを受信するコンポーネントです。
URLMON (urlmon.dll) は、gzip 圧縮解除プログラムを実装するコンポーネントです。
----------------------

とあります。
で、IEでのGZipの解凍は urlmon.dll がやっているらしいので、バージョンを比較してみました。

urlmon_dll_img.jpg

エラーが出たのは 6.0.2800 のDLLを持ったWindows2000だけでした。
6.0.2900以上のバージョンのIE6ではエラーメッセージが出ませんでしたので、おそらくは多分、
urlmon.dllのバージョンによる不具合だと思われます。



■ 2010/02/27 - Windows2000 IE6-SP1のDLLをデバッグしてみました。 -

デバッグ対象であるDLLは WININET.DLLです。
URLMON.DLLはインストールされているDLLを使用しました。

んで、これのログを採ってみると...。

-----------------
21:59:49.054 0000024c:<app> 001 HttpQueryInfoA(0xcc000c, HTTP_QUERY_CONTENT_LENGTH (0x5), 0x12d774 [""], 0x12e9e8 [4168], 0x0 [0])
21:59:49.054 0000024c:<app> 001   *lpdwBufferLength = 3
21:59:49.054 0000024c:<app> 001   Query data:
21:59:49.054 0000024c:<app> 001   3 (0x3) bytes @ 0x12d774
21:59:49.054 0000024c:<app> 001   0012d774  36 37 35                                          675
21:59:49.054 0000024c:<app> 001 HttpQueryInfoA() returning TRUE

21:59:49.054 0000024c:<app> 001 HttpQueryInfoA(0xcc000c, HTTP_QUERY_CONTENT_DISPOSITION (0x2f), 0x12d774 [""], 0x12e9e8 [4168], 0x0 [0])
21:59:49.054 0000024c:<app> 001   HttpQueryInfoA() returning 12150 [ERROR_HTTP_HEADER_NOT_FOUND]
21:59:49.054 0000024c:<app> 001 HttpQueryInfoA() returning FALSE
-----------------

というエラーが延々と...。出ます...。orz
ここで、HttpQueryInfoA() returning 12150 [ERROR_HTTP_HEADER_NOT_FOUND] と HttpQueryInfoA() returning FALSE が出ているのが確認できます。

# 本来のログはもっともっと長いです。テキストで5MB近くになりました。追っかけるだけで死ねる...。orz

えー、つまりHTTPヘッダがぶっ千切られてますね。

これにブチ当たった場合、Flexのサーバー側を設定し、IEオプションを見直し、DLLのバージョンを確認して、出来ればDLLのデバッグを行って確認 して下さい。

...まぁIE6だけ...でしょうから。今後は良いと思うんですけどね...。
debug_log.txt

このページの先頭へ