Monday, April 27, 2015

PHP extension 로딩 확인

PHP 프로그래밍을 할때 특정 extension이 올라와 있는지 확인해야 될 때가 있다. 이럴땐 다음과 같이 하면 정확하게 알 수가 있다.

그러나 유의해야 할 점이 php.ini의 패스 우선순위대로 가장 높은 우선순위의 php.ini를 기준으로 한다는 점이다. 여러군데에 php.ini가 있으면, 현재 패스의 php.ini가 가장 높은 우선순위를 갖는다. 그래서 테스트 하기전에 php.ini를 현재 패스로 복사한 후에 테스트 하면 가장 정확하다.

php -m

PHP 소스파일 터미널에서 테스트

PHP 단일 파일을 웹브라우저를 띄우지 않고 터미널에서 간단하게 테스트 할수있다.
다음과 같이 하면 test.php의 결과 내용이 터미널에 출력된다.

php -f test.php

PHP version 확인

PHP는 extension dll이나 so를 개발할 때나 여러가지 이유로 PHP version을 확인해야할 필요가 있다.
다음과 같이 입력하면 간단하게 확인할 수가 있다.

php -v

Android LG U+ IAP(인앱결제) 라이브러리 예제

LG U+ 인앱 결제 라이브러리 예제이다. LG U+의 공식 예제에는 액티비티를 상속받게 되어 있는데 상속 안받는 버전으로 변경해 보았다.

package com.example.lgtsample;

import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;

import com.feelingk.lguiab.LguIAPLib;
import com.feelingk.lguiab.LguIAPLib.OnClientListener;
import com.feelingk.lguiab.vo.IAPLibSetting;
import com.feelingk.lguiab.vo.ProductItemInfo;
import com.feelingk.lguiab.vo.UseItemInfo;

//public class MainActivity extends LguIAPLib {
public class MainActivity extends Activity {

/** IAP Library Init */
private LguIAPLib mIAPLib = null;

private IAPLibSetting setting = null;
private final String lgt_aid = "Q0201083****";

private String mTestAppID = "Q0201083****"; // AppID (개발)
private String mTestPID = "Q02D1083****"; // PID

private String mTestBpIP = null; // BPIP
private String mTestBpPort = "0"; // BPPort
private String mTestBpUri = null; // BPUri
private String mTestBpData = null; // BPData
private boolean serverType = false; // false: 개발기, true: 상용

@Override
protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

mIAPLib = new LguIAPLib();
initLGTStore();

Button payment = (Button) findViewById(R.id.payment);

payment.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// lguIABpopPurchaseDlg(MainActivity.this, lgt_aid,
// "Q02D10830139", null, null, setting);

mIAPLib.lguIABpopPurchaseDlg(MainActivity.this, mTestAppID, mTestPID,
mTestBpUri, mTestBpData, setting);

}
});
}

private OnClientListener mClientListener = new OnClientListener() {
private static final int LGT_BUY_STATE_OK = 0;
private static final int LGT_BUY_STATE_CANCEL = 1;
private static final int LGT_BUY_STATE_ERROR = 2;
private int mState = LGT_BUY_STATE_CANCEL;

@Override
public void lguIABonBuyVerifyComplete(String arg0, String arg1) {
// TODO Auto-generated method stub

}

@Override
public void lguIABonDlgClick() {
// TODO Auto-generated method stub
// OK, ERROR
switch (mState) {
case LGT_BUY_STATE_OK:
// CletActivity.handleSendBuyEvent(M_E_SUCCESS, 0);
break;
case LGT_BUY_STATE_ERROR:
// CletActivity.handleSendBuyEvent(M_E_ERROR, 0);
break;
}
}

@Override
public void lguIABonDlgPurchaseCancel() {
// TODO Auto-generated method stub
mState = LGT_BUY_STATE_CANCEL;
// CletActivity
// .handleSendBuyEvent(M_E_PAYMENT_CANCELED, 0);
}

@Override
public void lguIABonError(int arg0, int arg1) {
// TODO Auto-generated method stub
mState = LGT_BUY_STATE_ERROR;
// 다음에 DlgClick이 불림
}

@Override
public void lguIABonItemAuthInfo(ProductItemInfo arg0) {
// TODO Auto-generated method stub

}

@Override
public void lguIABonItemPurchaseComplete(String arg0, String arg1) {
// TODO Auto-generated method stub
mState = LGT_BUY_STATE_OK;
}

@Override
public boolean lguIABonItemQueryComplete() {
// TODO Auto-generated method stub

// 이게 true여야 함
return true;
}

@Override
public void lguIABonItemUseQuery(UseItemInfo arg0) {
// TODO Auto-generated method stub

}

@Override
public void lguIABonWholeQuery(List<ProductItemInfo> arg0) {
// TODO Auto-generated method stub

}

};

private void initLGTStore() {
// 라이브러리 설정
setting = new IAPLibSetting(this, mTestAppID, mTestBpIP, mTestBpPort,
serverType, mClientListener);
mIAPLib.LguIAPLibInit(setting);
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

}




Saturday, April 25, 2015

FlashBuilder 프로젝트 rebuild 하기

FlashBuilder 프로젝트에서 가장 확실하게 rebuild하는 방법은 다음과 같다.
특히 소스가 변경되지 않고 assets등의 리소스 파일이 변경되었을때 쓰는 방법이다.

1. 일단 리소스 파일이 프로젝트파일 리스트에 추가가 되었을 경우에 FlashBuilder에서 일단 파일 목록을 Refresh 해준다.
2. Project를 Clean한다.
3. Project를 Clean한 직후에 Rebuild하겠다고 체크를 하면 자동으로 Rebuild한다. 이 Rebuild과정에서 추가된 리소스를 복사한다.

Flex AIR Starling 예제

Flex환경에서 Starling을 사용할 경우에 처음에는 화면에 이미지가 도시되지 않는다. 이 문제의 해결 방법은 반드시 application에 backgroundAlpha="0"를 넣어주는 것이다.
<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"
  addedToStage="application1_addedToStageHandler(event)"   backgroundAlpha="0">

그리고 stats클래스의 경우에는 UIComponent에 실어서 application에 addElement하면 되고 Startling클래스의 경우는 바로 application.stage를 넘겨주면 된다.
protected function windowedapplication1_addedToStageHandler(event:Event):void
{
// TODO Auto-generated method stub
stats = new Stats();
var ui:UIComponent = new UIComponent();

ui.addChild(stats);
this.addElement(ui);

myStarling = new Starling(Game, stage);
myStarling.antiAliasing = 1;
myStarling.start();
}



Flex AIR Starling - This application is not correctly embedded (wrong wmode value)

Flex AIR에서 Starling 프로그래밍시 This application is not correctly embedded (wrong wmode value) 에러메세지가 발생할 경우에는 xml파일을 열어서 다음과 같이 수정해 준다.
<renderMode>direct</renderMode>

웹모드일 경우에는 wmode라는 parameter를 수정하는데 AIR에서는 xml이 direct값 설정을 담당한다.
만약 웹모드일 경우에는 html-template/index.template.html에서 다음과 같은 문장을 추가한다.
params.wmode = "direct";

Flash HTML을 탐색기에서 열었을 경우 보안문제

탐색기에서 웹용 Flash SWF가 포함된 HTML 파일을 열었을때 보안문제로 잘 안보이는 경우가 있다. 원인은 인증된 주소를 플래시에서 추가를 안해줬기 때문이다. 

아래와 같이 웹에서 해주거나 제어판 플래시에서 원하는 로컬 폴더를 추가해 준다.
http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manage






Flex SDK 3.6에서 4.6으로 마이그레이션

Flex SDK 3.6에서 작성된 app.xml파일을 4.6으로 빌드하려고 하면 다음과 같은 에러메세지가 발생한다.

invalid application descriptor versionnumber must have a non-empty value

1. 일단 아래의 SDK버전 숫자를 1.5.3 -> 3.1로 변경한다.
<application xmlns="http://ns.adobe.com/air/application/3.1">

2. 버전 정보를 변경한다.
Flex SDK 3.6: <version>v1</version>
Flex SDK 4.6: <versionNumber>0.0.0</versionNumber>

FlashBuilder out of memory 해결

An out of memory error has occurred. Consult the "running eclipse" section of the read me file for information on prevention this kind of error in the future.
you are recommended to exit the workbench.
Subsequent errors may happen and may terminate the workbench without warning.
See the .log file for more details.

Do you want to exit the workbench?

그럴때는 FlashBuilder.ini를 열어서 다음과 같이 수정하여 메모리 할당량을 늘려준다.
-Xms512m
-Xmx1024m
-XX:MaxPermSize=512m
-XX:PermSize=256m

Flash AIR for Android default orientation 고정하기

app.xml
<aspectRatio>landscape</aspectRatio>
<autoOrients>false</autoOrients>

private function onAddedToStage( event:Event ):void {
    stage.addEventListener( Event.RESIZE, onStageResize );
    NativeApplication.nativeApplication.addEventListener( Event.ACTIVATE, onNativeApplicationActivate );
}

private function onStageResize( event:Event ):void {
    checkForOrientationChange();
}

private function onNativeApplicationActivate( event:Event ):void {
    checkForOrientationChange();
}

private function checkForOrientationChange():void {
    if ( height > width ) {
        if ( stage ) {
            stage.setOrientation( StageOrientation.ROTATED_RIGHT );
        } else {
            // The first ACTIVATE event occurs before the Application has been added to the stage
            callLater( checkForOrientationChange );
        }
    }
}

Flash timestamp 얻기

아래는 퍼포먼스 측정을 하기위해서 timestamp을 얻는 소스이다.
time중에서 시,분,초 말고 1970년부터 누적 millisecond를 얻을수 있다.
물론 date.hours, date.minutes...를 하면 개별 시간을 얻을수 있다.

package
{
    import flash.display.Sprite;
    import flash.events.Event;
   
    [SWF(frameRate=30,width=800,height=600)]
    public class Test1 extends Sprite
    {
        public function Test1()
        {
            this.addEventListener(Event.ENTER_FRAME, enterFrame);
        }
        private function enterFrame(evt:Event):void
        {
            var date:Date = new Date();
            trace(date.time);  
        }
    }
}

OpenGL 정보를 한눈에 - GPU Caps Viewer

DirectX에서는 DDSCapsViewer라는 것이 있어서 설치된 VGA에서 텍스처 크기는 얼마이고 셰이더는 몇 버전까지 지원되며 등등의 정보를 표시해 준다. Direct3D Device 차원에서도 DDSCaps가 따로 존재한다.

하지만 OpenGL은 Extension은 glGetString으로 해야하고, Cg, CUDA, OpenCL의 각각의 Caps를 얻는 방법이 다르므로 방법을 통합하기란 쉽지 않다. 그리고 그것을 외부 툴로 확인하는 것은 각각 따로 만들어야 하는데 소개하는 GPU Caps Viewer를 사용하면 그러한 것을 한방에 해결할 수가 있다. 흡사 CPU-Z(구. CPU-ID)와 유사하다.

지원하는 기능은 다음과 같다.
1. OpenGL Extension(with GLSL) capabilities
2. CUDA capabilities
3. OpenCL capabilities

다운로드는 여기가서 받을수 있다.
http://www.geeks3d.com/gpucapsviewer/?from-gpu-caps-viewer

다운로드 받아서 설치후 GpuCapsViewer.exe를 실행시키면 다음과 같은 창이 뜬다.
완전 CPU-Z와 똑같다!

1. GPU/CPU탭은 일반적인 드라이버 정보를 보여준다.

2. OpenGL탭은 OpenGL에 특화된 정보를 보여준다. 버전, 텍스터 정보, 지오메트리 정보, GLSL 셰이더 정보 등등...

3. OpenGL은 Extension이라는 특수한 정보가 있으므로 Extensions의 Show All버튼을 누르면 Extension List창이 뜬다. 현재 드라이버에서 지원가능한 드라이버만 표시할수도 있다.

4. CUDA정보탭은 CUDA데이터와 메모리 관련정보를 보여준다. CUDA를 지원하지 않으면 물론 정보가 없다.

5. OpenCL탭은 역시 OpenCL정보를 보여준다. 이 스크린샷에서는 OpenCL을 NVIDIA CUDA를 사용하여 구현된다라고 보여주고 있다.

6. 보너스: OpenGL(GLSL) 및 OpenCL(CUDA) 샘플을 볼수있다. 탭 아래에 보면 OpenGL and OpenCL Demos에 있는 데모를 선택해서 Start를 누르면 프로그램이 실행된다.


이상으로 GPU Caps Viewer에 대한 툴소개를 마친다.

OpenGL NVIDIA GPU 메모리 체크 예제 다운로드

NVIDIA GPU에서만 사용가능한 메모리 체크 함수이다.
GLUT와 GL_NVX_GPU_memory_info 확장을 사용한다.

#include "GL\glew.h"
#include "GL\wglew.h"
#include "GL\glut.h"
#include <Windows.h>

#pragma comment(lib, "opengl32.lib")
#pragma comment(lib, "glut32.lib")

#define GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX 0x9047
#define GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX 0x9048
#define GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX 0x9049
#define GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX 0x904A
#define GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX 0x904B

int _tmain(int argc, _TCHAR* argv[])
{
 glutCreateWindow("NVXGpuMemoryInfo");

 int total, current;
 glGetIntegerv(GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, &total);
 glGetIntegerv(GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, &current);

 printf("total: %d\n", total);
 printf("current: %d\n", current);

 return 0;
}

다운로드

WebGL 강좌 - Renderer 초기화

인터넷에 돌아다니다 보면 WebGL 강좌가 여러개가 있는데 이것은 Nehe OpenGL Tutorials를 기반으로 하고 있다.

그런데 Nehe 강좌는 예전에는 순수 OpenGL이었으나 WebGL로 포팅한 저자가 전부 GLSL기반으로 바꾸어서 강좌 초기부터 GLSL이 나와있어서 초보자들을 황당하게 만들수가 있다.

그래서 가장 심플하게 아무것도 그리지 않는 Renderer 초기화 코드부터 추려내 보았다. 아래가 전체의 코드이다. WebGL을 초기화 하고 tick을 돌려서 화면을 검게 만드는 것밖에 하지 않는다.

<canvas id="lesson01-canvas" ...>는 canvas를 만든다.
<body onload="webGLStart();">는 HTML의 body가 로딩될때 수행할 callback 함수이다. 여기서 WebGL이 초기화 된다.
webGLStart()에서 var canvas = document.getElementById("lesson01-canvas");는 HTML document에서 canvas객체를 가져오는 역할을 한다.

glClearColor와 glEnable과 같은데 Clear시 색깔과 DepthTest를 활성화 시킨다.
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.enable(gl.DEPTH_TEST);

tick()안에 requestAnimFrame(tick)이 tick을 호출하여 필요한 그림을 계속 그려나간다.
requestAnimFrame() : webgl-utils.js에서 제공하는 함수로, 비동기성으로 webGL에 의한 도형을 그린다.(활성화된 브라우져 텝에서만 작동함.)
지금은 Google에서 제공하는 webgl-utils.js 를 이용해서 requestAnimFrame 를 호출해야 하지만, 결국 모든 브라우져에서 이 기능을 requestAnimFrame로 명명하게 될것이다. 그때까지는 요걸 써야 한다.

<현재 브라우져에서 프레임을 움직이는 기능을 하는 함수명들...>
Firefox : mozRequestAnimationFrame
Chrome / Safari : webkitRequestAnimationFrame
requestAnimFrame 특징 : 기존에 setInterval 함수를 이용하여 수동으로 3D효과를 낼때는 브라우져 Tab이 활성화 되어있지 않아도 작동하고있다는 단점이 있었다. 그로인해 브라우져 Tab이 활성화되어있을때만 작동하는 이 함수가 등장하게 되었다.

다음은 drawScene에서 매 frame마다 viewport를 canvas전체로 하고 clear를 한다. 주의할 점은 swapBuffer가 없다는 점이다.
gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);

<html>

<head>
<title>OpenGL Korea WebGL Lesson 0</title>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1">
<script type="text/javascript" src="http://learningwebgl.com/lessons/lesson03/webgl-utils.js"></script>

<script type="text/javascript">

    var gl;
    function initGL(canvas) {
        try {
   gl = canvas.getContext("experimental-webgl");
            gl.viewportWidth = canvas.width;
            gl.viewportHeight = canvas.height;
        } catch (e) {
        }
        if (!gl) {
            alert("Could not initialise WebGL, sorry :-(");
        }
    }
    function tick() {
        requestAnimFrame(tick);
        drawScene();

    }

    function drawScene() {
        gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    }
    function webGLStart() {
        var canvas = document.getElementById("lesson01-canvas");
        initGL(canvas);

        gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.enable(gl.DEPTH_TEST);

        tick();
    }

</script>
</head>

<body onload="webGLStart();">
    <canvas id="lesson01-canvas" style="border: none;" width="500" height="500"></canvas>
    <br/>
</body>



vi ^m 없애기

다음과 같이 문자 치환 명령으로 이 문자를 없애도록 합니다.
:1,$s/^M//g

물론 이것의 입력은 다음과 같이 하도록 합니다.
:1,$s/[Control]+v+m을 누른다.//g

vi 찾기와 바꾸기

Vi: Search and Replace

Change to normal mode with <ESC>.

Search (Wrapped around at end of file):

  Search STRING forward:   / STRING.
  Search STRING backward:   ? STRING.

  Repeat search:   n
  Repeat search in opposite direction:  N  (SHIFT-n)

Replace: Same as with sed, Replace OLD with NEW:

  First occurrence on current line:         :s/OLD/NEW
  Globally (all) on current line:           :s/OLD/NEW/g
  Between two lines #,#:                    :#,#s/OLD/NEW/g
  Every occurrence in file:                 :%s/OLD/NEW/g

Thursday, April 23, 2015

Android Admob 예제 다운로드

 <uses-permission android:name="android.permission.INTERNET"/>
 <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

package com.example.admobsample;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
import android.widget.LinearLayout;

import com.google.ads.AdRequest;
import com.google.ads.AdSize;
import com.google.ads.AdView;

public class MainActivity extends Activity {
 private AdView adView;
 private final String MY_AD_UNIT_ID = "a151b9ce1aabeef";

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);

  // adView 만들기
  adView = new AdView(this, AdSize.BANNER, MY_AD_UNIT_ID);
  LinearLayout layout = (LinearLayout) findViewById(R.id.mainLayout);

  // 찾은 LinearLayout에 adView를 추가
  layout.addView(adView);

  // 기본 요청을 시작하여 광고와 함께 요청을 로드
  adView.loadAd(new AdRequest());
 }

 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  // Inflate the menu; this adds items to the action bar if it is present.
  getMenuInflater().inflate(R.menu.main, menu);
  return true;
 }
}

다운로드

Android AudioTrack 사인파 사운드 예제 다운로드

이 예제는 시작하면 비프음(삐-) 소리가 나므로 주의하길 바란다.

public class MainActivity extends Activity {
 PlayAudioTask task = new PlayAudioTask();
 class PlayAudioTask extends AsyncTask<Void, Integer, Void> {
  final static String TAG = "PlayAudioTask";
  int streamType = AudioManager.STREAM_MUSIC;
 
  int sampleRateInHz = 44100;
  //int sampleRateInHz = 11025;
  //int sampleRateInHz = 22050;
 
  int channelConfig = CHANNEL_OUT_MONO;
  int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
  int mode = AudioTrack.MODE_STATIC;
  // 몇초간 플레이 할것인지 정의함
  // 1초에는 short bytes * 44100이 필요함
  // 1초를 그렇게 많이 쪼갠다는 뜻인가?
  int seconds = 2;
  short[] samples = new short[sampleRateInHz * seconds];
 
  // 1초간 4000번 상한과 하한을 진동함
  int freqOfTone = 4000;
  int bufferSizeInBytes = sampleRateInHz * seconds * 2;
  @Override
  protected Void doInBackground(Void... params) {
   // init audio
   //int streamType, int sampleRateInHz, int channelConfig, int audioFormat, int bufferSizeInBytes, int mode
   final AudioTrack track = new AudioTrack(streamType, sampleRateInHz, channelConfig, audioFormat, bufferSizeInBytes, mode);
   double angle = 0;
   double increment = (2 * Math.PI * freqOfTone / sampleRateInHz); // angular increment
   for (int i = 0; i < samples.length; i++) {
    // 사인파(Sine wave)
    samples[i] = (short) (Math.sin(angle) * Short.MAX_VALUE);
//    Log.d(TAG, "sample i=" + i + " value=" + samples[i]);
    // 파일 내용 저장하자.
    angle += increment;
   }
   track.write(samples, 0, samples.length);
   track.play();
   // TODO Auto-generated method stub
   return null;
  }
 }

다운로드

Wednesday, April 22, 2015

Java JRE Mac Elipse에 설치하기

아래에서 JDK 1.7을 다운 받는다.
http://www.oracle.com/technetwork/java/javase/downloads/index.html 

그러면 /Library/Java/JavaVirtualMachines/jdk1.7.0_<something>.jdk/Contents/Home 폴더에 생성이 되는데 이 패스를 Eclipse의 Preferences -> Java -> Installed JREs에서 추가한다.

아래의 화면에서 Standard VM으로 선택하여 패스를 추가한다.


Tuesday, April 21, 2015

Java 서버 프로그램 nohup 백그라운드 실행 및 종료

자바로 만든 서버 파일이 server.jar일때 이 jar를 실행하는 방법은 다음과 같다.
java -jar server.jar

그러나 이렇게 하면 백그라운드 실행이 안되므로 다음과 같이 start/stop.sh를 만들었다.
[start.sh]
nohup java -jar server.jar >/dev/null 2>&1 &

[stop.sh]
pid=`ps -ef | grep server.jar | grep -v 'grep' | awk '{print $2}'`
kill -9 $pid

Android 바탕화면 바로가기 만들기

<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT" />

private void addShortcut(Context context) {
   Intent shortcutIntent = new Intent();
   shortcutIntent.setAction(Intent.ACTION_MAIN);
   shortcutIntent.addCategory(Intent.CATEGORY_LAUNCHER);
   shortcutIntent.setClassName(context, getClass().getName());
   shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TOP);
 
   Parcelable iconResource = Intent.ShortcutIconResource.fromContext( this,  R.drawable.ic_launcher);
 
   Intent intent = new Intent();
   intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent);
   intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, getResources().getString(R.string.app_name));
   intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE,iconResource);
   intent.putExtra("duplicate", false);
   intent.setAction("com.android.launcher.action.INSTALL_SHORTCUT");      
   sendBroadcast(intent);  
 }

Sunday, April 19, 2015

Android 앱 액티비티 중복 실행 방지

AndroidManifest.xml
<application ...>

android:launchMode="singleInstance"
android:excludeFromRecents="true"
android:taskAffinity=""

아래는 노티피케이션이 왔을때 중복 실행 방지
@Override
public void onNewIntent(Intent i) {

}

C# 쓰레드 생성시 폼이 종료되지 않는 문제

만약에 C# Form에서 쓰레드를 생성한 후, Form Close가 되었는데 프로그램이 종료되지 않는다면, 다음과 같이 IsBackgound를 true로 설정하면 된다.

Thread t = new Thread(myAction);
t.IsBackground = true;
t.Start();

Wednesday, April 15, 2015

Android 소프트키 풀스크린 처리하기

아래는 풀스크린을 처리할때 소프트키를 영구히 안보이게 제거하는 방법이다.

소프트키를 원래대로 돌리려면 아래와 같이 호출한다.
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_VISIBLE);

소프트키 영구히 안보이게 제거
private int currentApiVersion;

@Override
@SuppressLint("NewApi")
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);

    currentApiVersion = android.os.Build.VERSION.SDK_INT;

    final int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
        | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
        | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
        | View.SYSTEM_UI_FLAG_FULLSCREEN
        | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;

    // This work only for android 4.4+
    if(currentApiVersion >= Build.VERSION_CODES.KITKAT)
    {

        getWindow().getDecorView().setSystemUiVisibility(flags);

        // Code below is to handle presses of Volume up or Volume down.
        // Without this, after pressing volume buttons, the navigation bar will
        // show up and won't hide
        final View decorView = getWindow().getDecorView();
        decorView
            .setOnSystemUiVisibilityChangeListener(new View.OnSystemUiVisibilityChangeListener()
            {

                @Override
                public void onSystemUiVisibilityChange(int visibility)
                {
                    if((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0)
                    {
                        decorView.setSystemUiVisibility(flags);
                    }
                }
            });
    }

}


@SuppressLint("NewApi")
@Override
public void onWindowFocusChanged(boolean hasFocus)
{
    super.onWindowFocusChanged(hasFocus);
    if(currentApiVersion >= Build.VERSION_CODES.KITKAT && hasFocus)
    {
        getWindow().getDecorView().setSystemUiVisibility(
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE
                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_FULLSCREEN
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
    }
}

Tuesday, April 14, 2015

Android 파일 탐색기 예제

public class MainActivity extends Activity {

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// 리스트뷰 및 패스 얻기
ListView listView1 = (ListView) findViewById(R.id.listView1);
String path = Environment.getExternalStorageDirectory().getAbsolutePath();

// 폴더 리스트 읽기
File file = new File(path);
File[] files = file.listFiles();

ArrayList<String> fileList = new ArrayList<String>();

for (int i = 0; i < files.length; i++) {
File subFile = files[i];
//String absPath = subFile.getAbsolutePath();
String absPath = subFile.getName();
fileList.add(absPath);
}

ArrayAdapter<String> fileAdapter = new ArrayAdapter<String>(this, R.layout.item, R.id.listItem1, fileList);
listView1.setAdapter(fileAdapter);
}
}

activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.explorer.MainActivity" >

<ListView android:id="@+id/listView1"
   android:layout_width="fill_parent"
   android:layout_height="fill_parent"
   >
   
</ListView>
</RelativeLayout>

item.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >
    
<TextView
   android:id="@+id/listItem1"
   android:layout_width="fill_parent"
   android:layout_height="40dp"
   android:textSize="20dp">
       
</TextView>
</RelativeLayout>


Monday, April 13, 2015

Android 전체화면 사용하기

Activity.onCreate()에서
// 풀스크린 지원
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);

Friday, April 10, 2015

Visual Studio 2010 IDE 최적화(2/3)

지난글에 이어서 Visual Studio 2010 IDE 최적화(프로그래밍시 사용하는 UI) 2번째이다.
이번에는 좀더 심화적으로 속도업을 해보았고, 이후에는 언어별 최적화를 할 예정이다.

1. 일단 OS가 윈도우XP라면 다음과 같이 윈도우XP 스타일로 만들자. 클래식 스타일보다는 원래 윈도우XP 스타일이 GUI렌더링시 더 빠르다.(의외의 결과임)

2. 다음과 같이 바탕화면 등록정보에서 ClearType을 끄자. 이는 글자를 부드럽게 만들어 주는 옵션인데 소스 편집시 텍스트 렌더링에 많은 리소스를 차지 한다.

3. Visual Studio 2010 IDE를 열어서 [Tools]-[Options]로 간다.
그리고 다음과 같이 Rich Client Visual Experience를 끈다. 이것은 윈도우7의 Aero인터페이스와 비슷하다.

4. Macro를 전부 끈다. 특별히 사용하는 매크로가 있으면 패스하자.

5. 자동 복구정보를 5분에 한번씩 저장하는 기능을 끄자.

6. 현재 윈도우 재사용 기능(저장되었다면), 외부에서 편집된 파일을 로딩하는 기능, 저장할때 리드온리 워닝기능 등을 끄자.

7. IDE 로딩시 빈 페이지를 로딩하게 하자.

8. 비쥬얼 스튜디오 익스텐션 갤러리에 접속하는 걸 막자.

9. 편집하고 계속하기를 끄자.

중간중간에 안해도 되는 부분이 있지만 이렇게 하면 공통 UI부분은 최적화가 된다. 다음 순서는 언어별 옵션 설명과 최적화이다.

Visual Studio 2010 IDE 최적화(1/3)

Visual Studio 2010으로 소스편집을 하다가 너무 느려져서 예전의 2005/2008과 같은 최적화 옵션이 있는지 알아봤다. Visual Studio 2005/2008에는 UI 애니메이션을 끄는 것과 소스 편집시의 몇가지 옵션이 있었다. 그런데 Visual Studio 2010에서는 UI쪽이 약간 변경이 되었다.

간단하게 UI 애니메이션과, 소스 편집시 delimeter highlighting만 껐다.

1)UI 애니메이션 끄기

2)소스 편집시 delimeter highlighting, drag and drop, utf-8 자동 인식 끄기


이렇게 하면 쓸데없는 UI애니메이션과 delimeter highlighting 등을 안하게 된다. 소스편집시에 멤버변수나오거나 하는데에는 전혀 문제가 없다. 그리고 속도도 무척 만족할만하게 빨라졌다.

OpenGL BMP 텍스처 저장

OpenGL에서 현재 texture를 눈으로 확인하며 디버깅하고 싶을 때 texture를 bmp파일로 저장하여 디버깅 할때 아래와 같이 한다.

이때 중요한 함수가 glGetTexLevelParameteriv인데 이것은 현재 texture 현재 mipmap level예제에서는 level가 1개 이므로 0이다.)의 parameter를 얻는 함수이다. 저장할 때는 32비트 bmp로 저장한다. glGetTexImage는 현재 texture의 pixel을 얻어온다.

이때 32비트 bmp는 GL_BGRA이므로 format을 GL_RGBA로 하면 색깔이 뒤집힌다.

#define GL_BGRA 0x80E1 // Use this for 32bit bmp

GLint cx, cy, format, bpp;

// 저장할 texture를 bind
glBindTexture(GL_TEXTURE_2D, id);

// cx,cy는 현재 texture의 width, height이다.
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &cx);
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &cy);

// 현재 texture의 internal format을 얻는다. 여기서는 GL_RGBA만 대상으로 한다.
glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_INTERNAL_FORMAT, &format);

if (format == GL_RGBA)
{
size_t data_size = 4 * cx*cy;

// 32비트 bmp텍스처의 픽셀버퍼 메모리할당
unsigned char *data = new unsigned char[4 * cx*cy];

// 현재 texture의 픽셀을 얻어온다.
glGetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, data);

// bmp구조를 만든다음 저장한다.
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
bfh.bfType = 0x4d42;

// Entire file size(40+14+data_size)
bfh.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + data_size;
bfh.bfReserved1 = 0;
bfh.bfReserved2 = 0;
bfh.bfOffBits = 54;// offset(14+40)

// major
bih.biSize = sizeof(BITMAPINFOHEADER);//40
bih.biWidth = cx;
bih.biHeight = cy;
bih.biPlanes = 1;
bih.biBitCount = 32;

// minor
bih.biCompression = BI_RGB;
bih.biSizeImage = data_size;
bih.biXPelsPerMeter = 0;
bih.biYPelsPerMeter = 0;
bih.biClrUsed = 0;
bih.biClrImportant = 0;

FILE *fp = fopen("out.bmp", "wb");
if (fp) {
fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);
fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);
fwrite(data, 1, data_size, fp);
fclose(fp);
}
delete[] data;
}

// texture를 unbind
glBindTexture(GL_TEXTURE_2D, 0);

OpenGL BMP 텍스처 로딩

OpenGL에서 bmp파일을 로딩하여 texture를 생성하는 코드이다. bmp는 24/32비트만을 지원한다. 15,16비트와 256칼라는 지원하지 않는다. glTexImage2D로 생성하기 때문에 다음과 같이 필터가 셋팅되어 있어야 한다.

#define GL_BGRA 0x80E1 // Use this for 32bit bmp
#define GL_BGR_EXT 0x80E0

void LoadBmp()
{
glBindTexture(GL_TEXTURE_2D, tex);
FILE *fp = fopen("c:\\cap2.bmp", "rb");
if (!fp)
return;
BITMAPFILEHEADER bfh;
BITMAPINFOHEADER bih;
fread(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);
fread(&bih, 1, sizeof(BITMAPINFOHEADER), fp);
fseek(fp, bfh.bfOffBits, SEEK_SET);// 중간에 더미값이 있을수도 있으니 정확하게 이렇게해서 데이타 시작 바이트를 찾아가자
int internalFormat = GL_BGR_EXT;
if (bih.biBitCount == 32)
{
internalFormat = GL_BGRA;
}
else if (bih.biBitCount == 24)
{
internalFormat = GL_BGR_EXT;
}
else
{
// 지원안되는 포맷(24,32만 지원)
fclose(fp);
glBindTexture(GL_TEXTURE_2D, 0);
return;
}
unsigned char *data2 = new unsigned char[bih.biSizeImage];
fread(data2, 1, bih.biSizeImage, fp);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, bih.biWidth, bih.biHeight, 0, GL_BGRA, GL_UNSIGNED_BYTE, data2);// GL_BGRA
fclose(fp);
delete[] data2;
glBindTexture(GL_TEXTURE_2D, 0);
}

Visual Studio에서 Visual C++ 화면 레이아웃 및 키보드로 재설정하기

일반적으로 Visual C++개발자가 Visual Studio .NET(2002이상, Visual C++ 7.0이상)으로 넘어 왔을 경우에 화면의 레이아웃(배치)라던가 키보드 단축키 설정이 마음에 들지 않는다. 왜냐면 Visual Studio 6.0은 Visual C++이 그 자체로 Visual Studio를 대표하고 있었고, Visual Studio .NET은 VB개발자를 통합하여 만든 것이기 때문에 VB의 화면 레이아웃도 가져온 경향이 있다.

아무튼, 전통적인 Visual C++개발자에겐 6.0의 레이아웃과 단축키가 가장 익숙하다. F5로 디버그 실행, Ctrl+F5로 실행, F7로 빌드 등... 그런데 Visual Studio .NET으로 넘어올 경우에 설치후 최초 실행시 세팅을 정하라는 화면에서 설정하지 않으면 다시 찾기란 쉽지 않다. 또한, Visual Studio 버전별로 설정 메뉴의 위치가 상이하므로 최신버전인 Visual Studio 2010에서 Visual C++ 설정을 하는 방법을 알아 보겠다.

Visual Studio C++ 설정을 하지 않은 화면

먼저 [Tools] - [Import and Export Settings...]를 선택

다음과 같은 Import and Export Settings Wizard에서 Reset all settings를 선택

Save Current Settings에 No라고 선택

다음 설정에서 Visual C++ Development Settings를 선택

Reset 완료

짜잔! 다음과 같이 Visual Studio 2010의 레이아웃이 바뀌면서 키보드(단축키) 셋팅도 바뀐다.

Visual Studio 2010 - Visual Studio 2008(v90)으로 컴파일 하기

Managed C++ 프로젝트의 .NET Framework 버전 선택

Visual Studio 2010을 발표하면서 가장 큰 공약으로 내 건 것이, .NET Framework 뿐만 아니라 네이티브인 Visual C++도 이전 버전의 컴파일러(ToolSet)을 사용하게 해준다고 한 것이다. 그러나 공약만 거창했을 뿐, 실제로는 Visual Studio 2008만 하위버전 컴파일러를 지원한다. 그리고 실제 Visual Studio 2008에 대한 라이센스와, Visual Studio 2008이 설치가 되어 있어야 한다.

다음과 같이 [General] - [Platform ToolSet]을 v90으로 맞춘다. v100은 10.0이라는 뜻으로 Visual Studio 2010이며, v90은 2008, v80은 2005, v71은 2003, v70은 2002라는 것은 Visual Studio 사용자라면 익히 일수 있는 버전번호이다.

그런데 이것은 컴파일러만 v90을 선택하기로 한것이지, 나머지 include(STL)이나 lib는 v100을 사용하겠다고 암묵적으로 동의한 것이다. 따라서 완벽한 호환성을 보장하기 위해서는 아래의 include, lib를 Visual Studio 2008의 것으로 다음과 같이 설정을 추가해주어야 한다.

[C/C++] - [General] - [Additional Include Directories]
- "C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\include" (64비트)
또는
- "C:\Program Files\Microsoft Visual Studio 9.0\VC\include" (32비트)


[Linker] - [General] - [Additional Library Directories]
(64비트 머신에서)
- C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib\amd64 (64비트 컴파일)
- C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib (32비트 컴파일)
(32비트 머신에서)
- C:\Program Files (x86)\Microsoft Visual Studio 9.0\VC\lib


이렇게 설정하면 include와 lib가 Visual Studio 2008과 동일하게 설정되어 컴파일 결과도 같아진다.

Managed C++ 프로젝트의 .NET Framework 버전 선택

.NET Framework 버전 선택은 C#등 .NET Framework 프로젝트에서는 프로젝트 속성창에서 Target Framework 버전을 선택할 수 있었다.

그런데 Visual Studio 이전버전의 Managed C++ 프로젝트를  변환한 경우에는 Target Framework 버전을 프로젝스 속성창에서 명시적으로 선택할 수가 없다.(Managed C++의 경우 프로젝트 속성창이 .NET Framework 프로젝트와 상이하기 때문이다.) 기본적으로 Visual Studio 2010의 경우에는 .NET Framework 버전이 4.0이다. 그대로 컴파일 할 경우에는 많은 문제가 발생한다.

따라서, Visual Studio 2008 이전버전의 Managed C++ 프로젝트를 Visual Studio 2010으로 변환한 경우에는 다음과 같이 *.vcxproj(Visual Studio 2010에서 vcproj가 vcxproj로 변환됨)에 아래 문장을 삽입 함으로서 이전 버전의 Visual Studio 에서 지원하는 Framework으로 명시적으로 기술할 수가 있다.

<Project ...>
  <PropertyGroup>
    <TargetFrameworkVersion>v2.0</TargetFrameworkVersion>
  </PropertyGroup>
</Project>

위 문장으로 Visual Studio 2010의 Managed C++프로젝트는 .NET Framework 2.0을 사용하여 컴파일 한다.

Managed C++에서 System.BadImageFormatException 에러

An unhandled exception of type 'System.BadImageFormatException' occurred in mscorlib.dll
Additional information: Could not load file or assembly 'CLRMC8, Version=1.0.3820.17987, Culture=neutral, PublicKeyToken=null' or one of its dependencies. An attempt was made to load a program with an incorrect format.

위와 같은 에러메세지가 Managed C++.NET에서 DLL로 만든 다음에 C# 프로젝트에서 참조로 넣어서 로딩할때 발생하였다. (개발환경은 Windows XP 64Bit/Visual Studio 2005/2008/2010)

C#으로 Class Library를 만들어서 C# 프로젝트에서 참조로 넣었을때는 되는데 왜 MC++로 만들때는 저렇게 에러가 나는가... 고민끝에 나온 결론은 C++은 x86/x64가 엄격하게 분리가 되며, C#은 Any CPU라는 것이 있어서 알아서 적용이 되는 것 때문에 그랬던 것이다.

따라서 위와 같은 문제를 해결하려면, C++을 x86 -> C#도 x86으로 명시적으로 Configuration Manager에서 설정하고, C++을 x64일 경우에는 C#도 x64로 명시해 주어야 한다.


Cg vs GLSL 비교

현재 GLSL이 나온 상태에서 Cg의 장점은 무엇인가?

장점
1)Cg자체가 OpenGL, Direct3D 멀티 플랫폼 API이다. Direct3D도 지원하기 때문에 Direct3D에서 HLSL대신에 Cg를 사용해도 된다.

2)Cg는 문법이 HLSL과 동일하므로 MS의 예제에서도 많은 것을 공유할 수 있으다. 특히 멀티 플랫폼(OpenGL, Direct3D)시에 코드를 공유할 수 있다. 게다가 Effect파일 포맷인 fx도 CgFX, HLSL Effect끼리 호환된다는 것은 엄청난 장점이다. 1)번 장점에서 연속 되는 장점이다.

3)Cg는 어셈블리 셰이더로 컴파일 해서 그 결과를 어셈블리로 떨어뜨려 주기 때문에 어셈블리 셰이더를 한번 더 최적화가 가능하다.

4)GLSL이 지원이 안되는 구형 VGA에서는 Cg만 지원이 되는 경우가 있다. 이는 Cg는 OpenGL 익스텐션에서 Cg를 지원해야 되는 것이 아니라 어셈블리 셰이더만 지원이 되면 Cg 라이브러리에서 하이레벨 언어를 처리하므로 가능한 일이다. 3)번 장점에서 연속되는 장점이다.
e.g.)GLSL이 지원되려면
GL_ARB_vertex_shader, GL_ARG_fragment_shader이 필요하다.

4)NVIDIA인 경우 OpenGL에서 NVIDIA 특화 익스텐션을 사용함으로서 NVIDIA 부가기능을 사용할 수 있다. Profile 선택시 NVIDIA의 최신 버전 익스텐션 프로파일을 선택하면 가능하다.
e.g.)NVIDIA 최신셰이더의 예
GL_NV_vertex_program*

5)GLSL은 CgFX(Effect 포맷)가 없다.

단점
1)현재는 GLSL이 OpenGL 표준이다.
두말할 필요없이 새로 배우는 사람들은, 특히 OpenGL only유저들은 GLSL을 배우고 있다.

2)GLSL은 부가 API가 필요가 없지만, Cg는 DLL을 가지고 다녀야 한다.(부가적인 라이브러리가 필요하다)
GLSL은 이미 표준이므로 OpenGL 익스텐션의 일부이므로 Cg Toolkit과 같은 부가적인 라이브러리가 필요가 없다.

3)OpenGL ES지원이 미약하다.
Cg를 임베디드 버전인 OpenGL ES에서 사용하려면 수고가 뒤따른다.

Visual C++ MD5 암호화 예제

MD5 암호화를 MIT에서 공개한 라이브러리의 예제파일이다.

다운로드

C# WPF XAML 로딩 에러

Visual Studio 2010에서 원격지에 있는 WPF소스로 작업을 하다 보면 분명히 소스가 컴파일은 되는데 XAML 창에서는 컴파일 에러라고 나오는 경우가 있다.

이 경우는 Visual Studio 2010에서 보안 문제로 인하여 WPF원격지 컨트롤들을 기본적으로 로딩하지 않아서 생기는 문제이다. 다음과 같이 수정하면 해결이 가능하다.

1.다음파일을 연다.
C:\Program Files\Microsoft Visual Studio 10.0\Common7\IDE\devenv.exe.config

2.다음과 같이 XML 엘리먼트를 추가한다.
<?xml version ="1.0"?>
<configuration>
    <runtime>
 <loadFromRemoteSources enabled="true"/>
    </runtime>
</configuration>

3.Visual Studio 2010을 새로연다.

Tuesday, April 7, 2015

Android Studio 라이브러리 프로젝트에 NDK 설정하기

인터넷에 있는 Android Studio NDK 설정 내용들은 전부다 메인 프로젝트에 NDK 소스파일 들이 있는 내용이고 라이브러리 프로젝트에 있는 내용은 없다. 그래서 라이브러리 프로젝트에서 NDK 빌드를 하는 설정을 해보았다.

그런데 이클립스의 라이브러리 프로젝트와 다르게, so파일이 생성은 되지만 자동으로 메인 프로젝트에 복사가 되지 않는다. 다음 글에서는 gradle로 빌드된 so파일을 복사를 해보겠다.

apply plugin: 'com.android.library' // 라이브러리 프로젝트

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.2"

    defaultConfig {
        minSdkVersion 18
        targetSdkVersion 21

        ndk {
            moduleName "duongame"
            ldLibs "EGL", "log", "GLESv2", "android"// 추가 라이브러리 설정
        }


    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
        }
    }

    // 여기서부터 ndk-build를 위한 추가 설정
    sourceSets.main{
        jni.srcDirs = []
        jniLibs.srcDir 'src/main/libs'
    }
    tasks.withType(JavaCompile) {
        compileTask -> compileTask.dependsOn ndkLibsToJar
    }

    task ndkLibsToJar(type: Zip, dependsOn: 'ndkBuild', description: 'Create a JAR of the native libs') {
        destinationDir new File(buildDir, 'libs')
        baseName 'ndk-libs'
        extension 'jar'
        from(new File(buildDir, 'libs')) { include '**/*.so' }
        into 'lib/'
    }

    task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
// 이 부분이 메인 프로젝트에서 설정하는 것과 다른 부분이다.
// com.android.application과 다르기 library를 사용하며
// sdkHandler를 추가하여야 한다.
// 플러그인의 인터페이스가 다르다.
        def ndkDir = project.plugins.findPlugin('com.android.library').sdkHandler.getNdkFolder()
        println(ndkDir)
        commandLine "$ndkDir/ndk-build",
                'NDK_PROJECT_PATH=build',
                'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
                'NDK_APPLICATION_MK=src/main/jni/Application.mk'
    }

}

dependencies {
    compile 'com.android.support:support-v4:21.0.3'
}

Monday, April 6, 2015

MSBuild 시작하기

MSBuild란 무엇인가? MSBuild란 오프라인, 그러니까 콘솔에서 Visual Studio IDE에서 수행했던 컴파일과 링크, 빌드를 수행해 주게 하는 프로그램이다. 유닉스에서 GNU Make, 자바의 Ant와 비슷하다. (금융권에 근무하던 시절 Makefile을 수천번 수정하고, 자바의 Ant로 자동빌드를 수행하던 기억이 난다.)

사실 처음에 MSBuild를 접하고 공부하였을 때는 Visual Studio는 JDK와 Eclipse처럼 IDE와 SDK가 분리되지 않는다고 생각했었다. 그러나 이것도 예전에 어렴풋이 알고 있던 cl.exe, al.exe, ln.exe와 같이 빌드시스템도 마찬가지로 분리되어 있었다.

Visual Studio에서 무언가를 컴파일 할려면 일반적으로 IDE(즉, Visual Studio GUI 프로그램)을 열어서 컴파일을 해야한다. 그런데 솔루션에 포함된 프로젝트가 정말 많고, 일부만 변해도 전체를 다 읽어서 컴파일을 할 것인가? 이것은 대규모 프로젝트에서는 IDE를 로딩하고 솔루션을 로딩하는데에 상당한 시간을 할애하게 되어, 누적되면 엄청난 시간의 손실을 가져온다.

또한, 그래피컬하게 프로젝트의 프로퍼티 페이지를 수정하는 것의 이점도 있지만, 직접 MSBuild 스크립트를 수정하여 사용하게 되면 조건에 따른 컴파일, 프로젝트 설정간의 상속도 가능하며, 중앙집중식 관리도 가능하다. MSBuild 스크립트는 csproj(C#), vbproj(VB.NET)와 같이 .NET 언어의 프로젝트 파일을 말하는데, 열어보면 알겠지만 XML이다.

암튼, 그래서 배우게 된 MSBuild. 이름도 많이 들어보고 인터넷 상에 자료도 많고 MSDN에 자료도 많지만 정작 가장 simple한 example이 없어서 직접 만들어 보았다. 언어는 C#이며, csproj에 .NET Framework 2.0의 MSBuild를사용한다.

MSBuild.exe는 C:\Windows\Microsoft.NET\Framework\v2.0.50727에 있다.
그리고 C#컴파일러인 csc.exe도 같은 폴더에 존재한다.

다음은 간단한 예제 C#소스이다. 사실 아무것도 없는 Hello World이다.
consolehwcs1.cs:
using System;
/// <summary>
/// Summary description for Class1
/// </summary>
public class ConsoleHWCS1
{
    public static void Main()
 {
  //
  // TODO: Add constructor logic here
  //
        Console.WriteLine("ConsoleHWCS1");
 }
}

다음은 우리가 살펴볼 예제 msbuild스크립트이다.(확장자가 csproj이다.)
MSBuildTest.csproj:
// 디폴트 타겟은 "Compile"이다.
<Project DefaultTargets = "Compile"
    xmlns="http://schemas.microsoft.com/developer/msbuild/2003" >
// 프로퍼티 그룹, 여기에 정의된 <tag>들은 $(tag)이렇게 사용할 수 있다. 상수와 같다.
    <PropertyGroup>
// 어플리케이션 이름 
        <!-- Set the application name as a property -->
        <appname>HelloWorldCS</appname><!-- exe output filename -->
// 출력 폴더 이름
        <!-- Set the output folder as a property -->
        <builtdir>BuiltApp</builtdir>
    </PropertyGroup>

    <ItemGroup>
// 여기는 컴파일될 소스들의 리스트를 적는다.
        <!-- Specify the inputs by type and file name -->
        <CSFile Include = "consolehwcs1.cs"/>
    </ItemGroup>

// 여기는 컴파일 타겟을 정의 한다.
    <Target Name = "Compile">
        <!-- Check whether an output folder exists and create
        one if necessary -->
// 빌드디렉토리가 없을 경우(컨디션 적용), 디렉토리를 만든다. 이런것도 가능하다. 원래 Visual Studio의 기능이니
        <MakeDir Directories = "$(builtdir)" 
            Condition = "!Exists('$(builtdir)')" />

// 컴파일러를 호출해서 컴파일을 수행한다.
// 출력파일(어셈블리)이름도 정의해 준다.
// 그리고 소스들은 멀티로 적용한다. @(CSFIle)
        <!-- Run the Visual C# compiler -->
        <CSC Sources = "@(CSFile)" 
            OutputAssembly = "$(BuiltDir)\$(appname).exe">
            <Output TaskParameter = "OutputAssembly"
                ItemName = "EXEFile" />
        </CSC>

// 빌드가 끝나고 화면에 메세지를 뿌려준다. 위에서 정의한 OutputAssembly를 EXEFile에 보존했다가 화면에 뿌려줌
        <!-- Log the file name of the output file -->
        <Message Text="The output file is @(EXEFile)"/>
    </Target>
// 유닉스에서 Make clean과 동일하다. 전체 지우고 리빌드 할 경우에 사용

    <Target Name = "Clean">
        <RemoveDir Directories="$(builtdir)" />
    </Target>
</Project>


다음과 같이 입력후에 수행을 하면 빌드가 된다.
C:\Windows\Microsoft.NET\Framework\v2.0.50727\msbuild msbuildtest.csproj /t:Compile

/t:Compile은 컴파일 타겟이다. 유닉스에서 Make TargetName을 하는 것과 유사하다.
그러면 다음과 같이 메세지가 나오면서 빌드이 될 것이고, 결과 실행파일인 HelloWorldCS.exe는 .\BuildApp에 들어가게 된다.


이상으로 첫 MSBuild 프로그램이 완성 되었다. 끝으로, csproj파일을 Visual Studio에서 로딩을해 보면 로딩이 될 것이다.  

VB6 마우스 휠 패치

VB6 에디터에서 마우스 휠을 사용할 수 있게 하는 패치이다.(MS 공식 버전)
압축을 풀고 VBA Mouse Wheel Fix.reg를 통해 등록한다.

다운로드

Visual C++ MFC MDI 프로그램 시작시 뜨는 도큐먼트 없애기

자동으로 열리는 빈 윈도우를 없애는 방법은 의외로 간단하다. 클래스 위저드로 생성한 소스중 xxxApp.CPP라는 파일에 CWinApp에서 상속받은 클래스가 들어 있는데, 이 멤버 함수중 InitInstance() 안에 다음 코드가 존재한다.
ParseCommandLine(comInfo);
if( ProcessShellCommand(...))..

이 두 줄 사이에 다음 코드를 추가하면 프로그램이 처음 실행될 때 쉘명령(ShellCommand)으로 새로운 파일을 만들라는 'FileNew' 명령행이 인자로 들어오게 됩니다(도큐먼트를 지정하지 않았을 때). 이 경우에만 FileNothing으로 바꿔주면 빈 도큐먼트를 만들지 않으며 당연히 뷰/프레임도 보이지 않는다.

if(cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew )
  cmdInfo.m_nShellCommand = CCommandLineInfo::FileNothing;

Visual C++ MFC 실시간 윈도우 스타일 변경하기

void CMainFrame::OnStyleNosysmenu()
{
        // 기존의 스타일 정보를 얻는다
        long style = GetWindowLong(m_hWnd, GWL_STYLE);
        // 시스템 메뉴 속성을 없앤다
        style &= ~WS_SYSMENU;
        // 변경된 스타일 반영
        SetWindowLong(m_hWnd, GWL_STYLE, style);
        // 화면이 실제로 변경된 것을 반영하도록 한다
        SendMessage(WM_NCPAINT, (WPARAM)1);
}

void CMainFrame::OnStyleSysmenu()
{
        long style = GetWindowLong(m_hWnd, GWL_STYLE);
        style |= WS_SYSMENU;
        SetWindowLong(m_hWnd, GWL_STYLE, style);
        SendMessage(WM_NCPAINT, (WPARAM)1);
}

C# PasrseHTML로 HTML 파싱하기

ParseHTML이라는 외부 라이브러리로 HTML을 파싱해보자.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using HTML;
using System.IO;

namespace ParseHTMLTester
{
    class Program
    {
        static void Main(string[] args)
        {
            StreamReader reader = new StreamReader("1.txt");// html을 1.txt로 저장해 놓음
            HTML.ParseHTML parse = new HTML.ParseHTML();

            // 스트림 리터에서 파일 전체를 string으로 변경
            string data = reader.ReadToEnd();
            parse.Source = data;

            while (!parse.Eof())
            {
                char ch = parse.Parse();
                if (ch == 0)
                {
                    AttributeList tag = parse.GetTag();
                    if (tag.Name == "th")
                    {
                        // th일 경우 class의 값을 프린트
                        if (tag["class"] != null)
                        {
                            Console.WriteLine("th class=" + tag["class"].Value);
                        }
                    }
                    else
                    {
                        // 일반 태그는 그냥 프린트
                        Console.WriteLine(tag.Name);
                    }
                }
            }
        }
    }
}

다운로드

Java Triple DES 예제

Triple DES Security Java 소스이다.
다른 소스와 다른 점은 NoSuchAlgorithm이 날 경우에 SunJCE를 설치하여 Exception을 없앤 소스라는 것...


/*
 * Copyright (c) 2000 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 2nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book (recommended),
 * visit http://www.davidflanagan.com/javaexamples2.
 */

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.Provider;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.DESedeKeySpec;

/**
 * This class defines methods for encrypting and decrypting using the Triple DES
 * algorithm and for generating, reading and writing Triple DES keys. It also
 * defines a main() method that allows these methods to be used from the command
 * line.
 */
public class TripleDES {
  /**
   * The program. The first argument must be -e, -d, or -g to encrypt,
   * decrypt, or generate a key. The second argument is the name of a file
   * from which the key is read or to which it is written for -g. The -e and
   * -d arguments cause the program to read from standard input and encrypt or
   * decrypt to standard output.
   */
  public static void main(String[] args) {
    try {
      // Check to see whether there is a provider that can do TripleDES
      // encryption. If not, explicitly install the SunJCE provider.
      try {
        Cipher c = Cipher.getInstance("DESede");
      } catch (Exception e) {
        // An exception here probably means the JCE provider hasn't
        // been permanently installed on this system by listing it
        // in the $JAVA_HOME/jre/lib/security/java.security file.
        // Therefore, we have to install the JCE provider explicitly.
        System.err.println("Installing SunJCE provider.");
        Provider sunjce = new com.sun.crypto.provider.SunJCE();
        Security.addProvider(sunjce);
      }

      // This is where we'll read the key from or write it to
      File keyfile = new File(args[1]);

      // Now check the first arg to see what we're going to do
      if (args[0].equals("-g")) { // Generate a key
        System.out.print("Generating key. This may take some time...");
        System.out.flush();
        SecretKey key = generateKey();
        writeKey(key, keyfile);
        System.out.println("done.");
        System.out.println("Secret key written to " + args[1]
            + ". Protect that file carefully!");
      } else if (args[0].equals("-e")) { // Encrypt stdin to stdout
        SecretKey key = readKey(keyfile);
        encrypt(key, System.in, System.out);
      } else if (args[0].equals("-d")) { // Decrypt stdin to stdout
        SecretKey key = readKey(keyfile);
        decrypt(key, System.in, System.out);
      }
    } catch (Exception e) {
      System.err.println(e);
      System.err.println("Usage: java " + TripleDES.class.getName()
          + " -d|-e|-g <keyfile>");
    }
  }

  /** Generate a secret TripleDES encryption/decryption key */
  public static SecretKey generateKey() throws NoSuchAlgorithmException {
    // Get a key generator for Triple DES (a.k.a DESede)
    KeyGenerator keygen = KeyGenerator.getInstance("DESede");
    // Use it to generate a key
    return keygen.generateKey();
  }

  /** Save the specified TripleDES SecretKey to the specified file */
  public static void writeKey(SecretKey key, File f) throws IOException,
      NoSuchAlgorithmException, InvalidKeySpecException {
    // Convert the secret key to an array of bytes like this
    SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("DESede");
    DESedeKeySpec keyspec = (DESedeKeySpec) keyfactory.getKeySpec(key,
        DESedeKeySpec.class);
    byte[] rawkey = keyspec.getKey();

    // Write the raw key to the file
    FileOutputStream out = new FileOutputStream(f);
    out.write(rawkey);
    out.close();
  }

  /** Read a TripleDES secret key from the specified file */
  public static SecretKey readKey(File f) throws IOException,
      NoSuchAlgorithmException, InvalidKeyException,
      InvalidKeySpecException {
    // Read the raw bytes from the keyfile
    DataInputStream in = new DataInputStream(new FileInputStream(f));
    byte[] rawkey = new byte[(int) f.length()];
    in.readFully(rawkey);
    in.close();

    // Convert the raw bytes to a secret key like this
    DESedeKeySpec keyspec = new DESedeKeySpec(rawkey);
    SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("DESede");
    SecretKey key = keyfactory.generateSecret(keyspec);
    return key;
  }

  /**
   * Use the specified TripleDES key to encrypt bytes from the input stream
   * and write them to the output stream. This method uses CipherOutputStream
   * to perform the encryption and write bytes at the same time.
   */
  public static void encrypt(SecretKey key, InputStream in, OutputStream out)
      throws NoSuchAlgorithmException, InvalidKeyException,
      NoSuchPaddingException, IOException {
    // Create and initialize the encryption engine
    Cipher cipher = Cipher.getInstance("DESede");
    cipher.init(Cipher.ENCRYPT_MODE, key);

    // Create a special output stream to do the work for us
    CipherOutputStream cos = new CipherOutputStream(out, cipher);

    // Read from the input and write to the encrypting output stream
    byte[] buffer = new byte[2048];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
      cos.write(buffer, 0, bytesRead);
    }
    cos.close();

    // For extra security, don't leave any plaintext hanging around memory.
    java.util.Arrays.fill(buffer, (byte) 0);
  }

  /**
   * Use the specified TripleDES key to decrypt bytes ready from the input
   * stream and write them to the output stream. This method uses uses Cipher
   * directly to show how it can be done without CipherInputStream and
   * CipherOutputStream.
   */
  public static void decrypt(SecretKey key, InputStream in, OutputStream out)
      throws NoSuchAlgorithmException, InvalidKeyException, IOException,
      IllegalBlockSizeException, NoSuchPaddingException,
      BadPaddingException {
    // Create and initialize the decryption engine
    Cipher cipher = Cipher.getInstance("DESede");
    cipher.init(Cipher.DECRYPT_MODE, key);

    // Read bytes, decrypt, and write them out.
    byte[] buffer = new byte[2048];
    int bytesRead;
    while ((bytesRead = in.read(buffer)) != -1) {
      out.write(cipher.update(buffer, 0, bytesRead));
    }

    // Write out the final bunch of decrypted bytes
    out.write(cipher.doFinal());
    out.flush();
  }
}

C# 디버그 메세지 출력하기

C#에서 OutputDebugString을 할려면 다음과 같은 2가지 방법이 있다.

Debug.Write(), Trace.Write()의 차이점은 Debug는 Debug모드 빌드에서만 동작하며, Trace는 Release/Debug 둘다 동작한다는 점이다. 물론 OutputDebugString기반이므로 DebugView에서도 확인할 수 있다.

System.Diagnostics.Debug.Write("Debug1");
System.Diagnostics.Debug.Write("Debug2");
System.Diagnostics.Trace.Write("Trace1");
System.Diagnostics.Trace.Write("Trace2");

DebugView의 특징과 실수하기 쉬운 것들

DebugView는 Win32 개발환경에서 디버그 메세지 확인용 함수인 OutputDebugString의 결과를 따로 모아서 보여주는 유틸리티이다.

그런데 OutputDebugString의 특성 때문에 몇가지 상황을 겪을수 있는데 그 경우의 몇가지 예는 다음과 같다.
  1. Visual Studio를 F5모드(Ctrl+F5가 아닌)로 Debug 시켰을 경우에는 메세지를 보여주지 않는다.
  2. DebugView가 두개이상 띄워졌을 경우에는 한곳에서만 Connect되어 있다. 다시 Connect할려면 [Computer] - [Connect Local]하면된다.
  3. .NET에서는 OutputDebugString이 있지만 System.Diagnostics.Trace.Write()함수를 사용한다. 그런데 System.Diagnostics.Debug.Write()를 사용하면 Release에서는 메세지가 보이지 않는다. MFC에서 TRACE()도 마찬가지이다.

PHP 4.0 SOAP

PHP로 SOAP을 구현하려면 PHP5가 있어야 된다.
PHP5에만 SOAP이 내장되어 있다.

그러나 PHP4에서 구현을 하려면 NuSOAP을 사용하면 된다.
그리고 PHP5도 동일한 방법을 사용하면 되기 때문에 NuSOAP으로 하는것이 편하다.

nusoap-0.7.3.zip
nusoap-docs-0.7.3.zip
Mail_Mime-1.4.0.gz

세개의 파일이 필요한데
Mime은 NuSOAP 0.7.3에서 바이너리를 SOAP으로 보낼때 쓴다.

첨부된 nusoap.zip을 받고
htdocs폴더에다 lib에 풀고

// NuSOAP설정
 include_once('lib/nusoap.php');
 include_once('lib/nusoapmime.php');
 // WSDL설정
 $wsdl = 'http://url/WebService/Service?wsdl';
 // NuSOAP설정
 $client = new nusoap_client($wsdl, 'wsdl');
 $client->setEndpoint("'http://url/WebService/Service");

위와 같이 하면 함수를 호출할 수 있다.
반드시 endPoint를 설정해야 한다.

다운로드

Visual C++ 다른 프로그램의 메세지 박스 자동 Yes 클릭하기

다른 Application을 띄워서 매번 실행해야되는데 시작시 수동으로 매번 MessageBox에서 Yes를 선택해야 되는 경우 자동으로 Yes를 누르는 프로그램이다.

Win32 Application을 만든다. 메인 메세지 루프에서 다음과 같이 타이머를 설정하자.

switch (message)
{
case WM_CREATE:
hwndMain = hWnd;
SetTimer(hWnd, 1, 100, NULL);
...
  case WM_TIMER:
switch(wParam){
case 1:
KillTimer(hWnd, 1);
EnumWindows(FIndMessageBoxProc, lParam);
SetTimer(hWnd, 1, 100, NULL);
...

메세지 박스는 기본적으로 IDYES와 IDNO를 아이디로 가진다. 그리고 메세지 박스의 WindowText를 알아두자. (Spy++을 사용하면 쉽다.)

EnumWindows라는 함수로 Desktop이하의 모든 윈도우를 Enumerate 한 후에 해당 MessageBox를 찾자.
BOOL CALLBACK FIndMessageBoxProc(HWND hWnd, LPARAM lParam)
{
char text[512];
GetWindowTextA(hWnd, text, 512);

if(!strcmp(text, "MessageBoxApplication")) {// 메세지 박스 이름
EnumChildWindows(hWnd, FIndYesBtnProc, lParam);
SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(BN_SETFOCUS, IDYES), (LPARAM)hwndYes);
OutputDebugStringA("ManusHider WM_COMMAND BN_SETFOCUS");

SendMessage(hWnd, WM_COMMAND, MAKEWPARAM(BN_CLICKED, IDYES), (LPARAM)hwndYes);
OutputDebugStringA("ManusHider WM_COMMAND BN_CLICKED");

//KillTimer(hwndMain, 1);
//SetTimer(hwndMain, 2, 100, NULL);
return false;
}
return true;
}

그리고 IDYES라고 아이디를 알고 있더라도 BN_CLICKED를 발생시키려면 그 버튼의 HWND도 알아야 한다.

BOOL CALLBACK FIndYesBtnProc(HWND hWnd, LPARAM lParam)
{
char text[512];
GetWindowTextA(hWnd, text, 512);
if(!strcmp(text,"&Yes")) {
hwndYes = hWnd;
return false;
}
return true;
}

중요한 점은 현재 작성하는 프로그램에 Focus가 와있기 때문에 대상 프로그램에 BN_CLICKED를 SendMessage하기전에 BN_SETFOCUS를 먼저 줘야 한다는 점이다. 그리고 또하나는 Yes가 아니라 &Yes가 Yes버튼의 WindowText이다.

이렇게 하면 원하는 메세지박스에서 항상 Yes/No를 선택해야 되는 프로그램 테스트 실행시 자동으로 Yes를 클릭할 수 있다. 서비스 프로그램으로 만들어도 되고 위에처럼 매번 같이 실행하면서 종료해도 된다.