Java JNI 코드 분석 : GetObjectClass()

안드로이드/프레임워크 2015. 4. 29. 00:12

 안드로이드 프레임워크 프로그래밍을 진행하시다 보면 JNI 부분이 생각보다 많은 지식이 필요함을 느끼고 있습니다. 오늘 분석하게 되는 코드 또한 안드로이드는 물론이고 Java 에서 JNI를 사용할 때 자주 사용하는 함수들인데요. 오늘부터 각 코드들에 대한 설명을 적어내려가볼까 생각합니다.


jclass    (*env)->GetObjectClass(env, jobject);
jclass    env->GetObjectClass(jobject);

 윗부분운 C로 JNI를 코딩하실 때, 아래부분은 C++로 JNI를 코딩하실 때 사용하는 함수입니다. 이 함수는 jobject 변수로부터 해당 변수의 jclass 값을 얻기 위해 사용합니다.

 이게 무슨 말이냐 하면, jobject는 java 클래스 객체 자체라고 이해하시면 되겠습니다. 그리고 jclass는 해당 java 클래스의 경로를 저장한다는 역할을 하고 있다는 식으로 이해하시면 감이 오실 것이라 생각합니다. 위의 함수의 결과값인 return은 아래의 함수를 실행할 때에도 같은 값을 나타냅니다.


jclass    (*env)->FindClass(env, "Java 클래스의 경로명");


 위와 같이 해당 Java 클래스의 경로명을 직접 적어주는 방식으로로도 jclass 값을 return 받으실 수 있습니다.


아래는 위의 소스코드를 적용한 예제입니다. 해당 예제에 대한 자세한 사항은 아래 포스팅을 참조해 주시기 바랍니다.


http://elecs.tistory.com/71


hello.c

1
2
3
4
5
6
7
8
9
10
11
#include <string.h>
#include <jni.h>
#include <stdio.h>
 
void Java_com_example_calljava_MainActivity_callJava(JNIEnv* env, jobject thiz){
    //jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
    jclass jCallJava = (*env)->GetObjectClass(env, thiz);
 
    jmethodID testToast = (*env)->GetStaticMethodID(env, jCallJava, "testToast""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, testToast);
}
cs


 결론적으로 말해서


jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
jclass jCallJava = (*env)->GetObjectClass(env, thiz);


이 두 함수의 return 값이 같다는 의미로 기억해 주시면 되겠습니다!!


300x250

안드로이드 프레임워크 프로그래밍(20) [JNI를 통해 Native에서 JAVA 변수 및 클래스 객체 다루기]

안드로이드/프레임워크 2015. 4. 28. 13:19

 안드로이드 Native 단계의 Framework에서는 종종 Java 단계에서 사용되는 Framework의 값들을 사용해야 할 때가 종종 있습니다. 이를 위해 Java 단계에서 native 함수 호출을 통해 Native에 값들을 인자값을 통해 전달하는 방법이 있습니다만 Native 단계에서 실행중인 프로세스가 Java로부터 데이터를 받아올 수 있는 방법은 없을까요?

 이러한 의문은 아래의 Native 소스코드를 통해 어떤 방식으로 진행되는지 대략적인 감을 잡으실 수 있을겁니다.


/frameworks/base/core/jni/android_view_Surface.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
int register_android_view_Surface(JNIEnv* env)
{
    int err = AndroidRuntime::registerNativeMethods(env, "android/view/Surface",
            gSurfaceMethods, NELEM(gSurfaceMethods));
 
    jclass clazz = env->FindClass("android/view/Surface");
    gSurfaceClassInfo.clazz = jclass(env->NewGlobalRef(clazz));
    gSurfaceClassInfo.mNativeObject =
            env->GetFieldID(gSurfaceClassInfo.clazz, "mNativeObject""I");
    gSurfaceClassInfo.mLock =
            env->GetFieldID(gSurfaceClassInfo.clazz, "mLock""Ljava/lang/Object;");
    gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>""(I)V");
 
    clazz = env->FindClass("android/graphics/Canvas");
    gCanvasClassInfo.mFinalizer = env->GetFieldID(clazz, "mFinalizer""Landroid/graphics/Canvas$CanvasFinalizer;");
    gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas""I");
    gCanvasClassInfo.mSurfaceFormat = env->GetFieldID(clazz, "mSurfaceFormat""I");
 
    clazz = env->FindClass("android/graphics/Canvas$CanvasFinalizer");
    gCanvasFinalizerClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas""I");
 
    clazz = env->FindClass("android/graphics/Rect");
    gRectClassInfo.left = env->GetFieldID(clazz, "left""I");
    gRectClassInfo.top = env->GetFieldID(clazz, "top""I");
    gRectClassInfo.right = env->GetFieldID(clazz, "right""I");
    gRectClassInfo.bottom = env->GetFieldID(clazz, "bottom""I");
 
    return err;
}
cs


 위의 소스코드는 우리들이 앞으로 예제를 통해 사용하게 될 소스코드의 일부입니다. 이 코드를 처음 보시는 분이시라면 상당히 복잡한 구조의 코드를 단번에 이해하시기 힘드실 것이라 생각합니다. 진행하기에 앞서 Android 환경에서 JNI의 활용에 대해 자세히 알고 싶으신 분은 이전에 작성하였던 포스팅을 참조해 주시기 바랍니다.



안드로이드 프레임워크 프로그래밍(7) [NDK 활용을 위한 JNI로 JAVA와 C 상호 호출]

안드로이드 프레임워크 프로그래밍(8) [JNI에서 작성된 C++ 코드에서 C 코드 함수 호출하기]

안드로이드 프레임워크 프로그래밍(10) [Native C/C++ 코드에서 Java 호출]


※본 포스팅의 예제는 이전에 작성하였던 예제를 토대로 진행할 예정입니다. 예제가 필요하신 분께서는 아래의 포스팅을 통해 예제를 다운로드 받으시길 바랍니다.

http://elecs.tistory.com/71



 이번 예제에서 사용되는 소스코드들중 일부를 미리 설명을 하고 넘어가도록 하겠습니다. 예제는 C언어를 기본으로 작성되었으며 C++에서는 약간의 수정이 필요함을 밝힙니다.

jclass (*env)->FindClass(env, "Java/Class/Path");    //C

jclass env->FindClass("Java/Class/Path");                //C++


jclass (*env)->FindClass(env, "Java/Class/Path")

 Native 소스코드에서 Java의 클래스의 경로를 저장하는 함수입니다.


jfieldID (*env)->GetFieldID(env, jclass, "변수명", "Signature값")

 FindClass를 통해 jclass에서 설정한 Class 내에 존재하는 변수의 이름과 해당 변수의 Signature를 설정하는 함수입니다.

 Signature에 대해 자세한 내용은 아래 포스팅을 참조해 주시길 바랍니다.

http://elecs.tistory.com/71


int (*env)->GetIntField(env, thiz, jfieldID);

char (*env)->GetCharField(env, thiz, jfieldID);

float (*env)->GetFloatField(env, thiz, jfieldID);

 이전 위에서 설정한 jclass와 jfieldID를 이용하여 Class 내에 있는 변수값을 가져옵니다. 이를 실행히면 Class에 저장된 현재값이 JNI를 통해 Native로 넘어오게 됩니다.


int (*env)->SetIntField(env, thiz, jfieldID, Class에 설정하고자 하는 값);

char (*env)->SetCharField(env, thiz, jfieldID, Class에 설정하고자 하는 값);

float (*env)->SetFloatField(env, thiz, jfieldID, Class에 설정하고자 하는 값);

 이를 통해 Java Class 내에 있는 변수의 값은 Native 단계에서 변경할 수 있습니다.


jmethodID(*env)->GetMethodID(env, jclass, "Method명", "Signature값");

jmethodID(*env)->GetStaticMethodID(env, jclass, "Method명", "Signature값");

 jclass에 설정된 Class 내에 있는 Method(함수)를 설정해 줍니다. 이를 통해 Native 단계에서 Java 내의 Method를 실행할 수 있는 준비가 완료됩니다.


(*env)->CallVoidMethod(env, jclass, jmethodID, ... );

(*env)->CallIntMethod(env, jclass, jmethodID, ... );

(*env)->CallCharMethod(env, jclass, jmethodID, ... );

(*env)->CallFloatMethod(env, jclass, jmethodID, ... );

(*env)->CallStaticVoidMethod(env, jCallJava, jmethodID, ...);

(*env)->CallStaticIntMethod(env, jCallJava, jmethodID, ...);

(*env)->CallStaticCharMethod(env, jCallJava, jmethodID, ...);

(*env)->CallStaticFloatMethod(env, jCallJava, jmethodID, ...);

 위에서 설정한 jclass와 jmethodID 값을 통해 Java Class 내의 Method를 실행합니다. 위의 ... 표시로 되어 있는 부분에 해당 호출하고자 하는 함수의 인자값을 넣어 실행하며 인자값이 없을 경우 빈칸으로 두시면 되겠습니다.


jmethodID (*env)->GetMethodID(env, jclass, "<init>", "Signature값");
jobject (*env)->NewObject(env, jclass, jmethodID);


 Native 단계에서 특정 Class를 생성하고자 할 때 사용하는 함수입니다. Native 단계 내에서 jmethodID를 통해 해당 클래스의 Constructor(생성자)를 함수명으로 "<init>"를 생성하며 NewObject() 함수를 통해 객체를 생성해 냅니다.


 아래는 이전 소스코드를 응용한 예제입니다.


MainActivity.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
 
 
package com.example.calljava;
 
import android.app.Activity;
import android.content.Context;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends Activity {
    private TextView tv;
    private Button bt;
    private static Context mContext;
    public int a = 11;
    public int b = 23;
    
    static {
        System.loadLibrary("hello");
    }
    
    public native String stringFromJNI();
    public native void callJava();
    public native int nativeSum();
    public native TestClass nativeConst();
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        tv = (TextView) findViewById(R.id.textview);
        tv.setText(this.stringFromJNI());
        mContext = this;
    }
 
    @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;
    }
 
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();
        if (id == R.id.action_settings) {
            return true;
        }
        return super.onOptionsItemSelected(item);
    }
    
    public void OnClick(View v){
        switch(v.getId()){
            case R.id.button1:
                this.callJava();
                break;
            case R.id.button2:
                Toast.makeText(thisthis.nativeSum()+"", Toast.LENGTH_SHORT).show();
                break;
            case R.id.button3:
                Toast.makeText(thisthis.nativeConst().toString(), Toast.LENGTH_SHORT).show();
        }
    }
    
    public static void testToast(){
        Toast.makeText(mContext, "Hello, Toast!", Toast.LENGTH_SHORT).show();
    }
    
}
 
class TestClass{
    public TestClass(){
        
    }
    
    public String toString(){
        return "JNI를 통해 생성된 클래스입니다!";
        
    }
}
cs


Activity_Main.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
<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.calljava.MainActivity" >
 
    <TextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />
 
    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@+id/textview"
        android:layout_below="@+id/textview"
        android:layout_marginTop="14dp"
        android:onClick="OnClick"
        android:text="Button1" />
 
    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/button1"
        android:layout_alignBottom="@+id/button1"
        android:layout_marginLeft="14dp"
        android:layout_toRightOf="@+id/button1"
        android:onClick="OnClick"
        android:text="Button2" />
 
    <Button
        android:id="@+id/button3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignBaseline="@+id/button2"
        android:layout_alignBottom="@+id/button2"
        android:layout_marginLeft="20dp"
        android:layout_toRightOf="@+id/button2"
        android:onClick="OnClick"
        android:text="Button3" />
 
</RelativeLayout>
cs


hello.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <string.h>
#include <jni.h>
#include <stdio.h>
 
void Java_com_example_calljava_MainActivity_callJava(JNIEnv* env, jobject thiz){
    jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
    //jclass jCallJava = (*env)->GetObjectClass(env, thiz);
 
    jmethodID testToast = (*env)->GetStaticMethodID(env, jCallJava, "testToast""()V");
    (*env)->CallStaticVoidMethod(env, jCallJava, testToast);
}
 
jstring Java_com_example_calljava_MainActivity_stringFromJNI(JNIEnv* env, jobject thiz){
    return (*env)->NewStringUTF(env, "Hello, JNI World!");
}
 
jint Java_com_example_calljava_MainActivity_nativeSum(JNIEnv* env, jobject thiz){
    jclass jCallJava = (*env)->FindClass(env, "com/example/calljava/MainActivity");
 
    jfieldID ja = (*env)->GetFieldID(env, jCallJava, "a""I");
    int a = (*env)->GetIntField(env, thiz, ja);
    jfieldID jb = (*env)->GetFieldID(env, jCallJava, "b""I");
    int b = (*env)->GetIntField(env, thiz, jb);
 
    int c = a+b;
 
    return c;
}
 
jobject Java_com_example_calljava_MainActivity_nativeConst(JNIEnv* env, jobject thiz){
 
    jclass jTestClass = (*env)->FindClass(env, "com/example/calljava/TestClass");
 
    jmethodID jmid = (*env)->GetMethodID(env, jTestClass, "<init>""()V");
    jobject obj = (*env)->NewObject(env, jTestClass, jmid);
    
    
    return obj;
    
}
cs







300x250

안드로이드 Framework에서 Camera 동작 원리 분석(3)

안드로이드/카메라 2015. 4. 27. 00:04

 안드로이드 카메라 기능을 꾸준히 분석해 보고 있습니다만 생각보다 복잡한 구조에 놀라우면서도 한 편으로는 안드로이드의 심오함을 동시에 느끼고 있습니다. 이번 포스팅은 지난시간에 이어 계속 이어가도록 하겠습니다.


 지난 포스팅까지 Camera의 Connect() 함수가 동작하는 과정에 대해 살펴보았었습니다. 이번 포스팅에서는 바로 그 다음부터 이어가도록 하겠습니다.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// connect to camera service
static void android_hardware_Camera_native_setup(JNIEnv *env, jobject thiz,
    jobject weak_this, jint cameraId, jstring clientPackageName)
{
    // Convert jstring to String16
    const char16_t *rawClientName = env->GetStringChars(clientPackageName, NULL);
    jsize rawClientNameLen = env->GetStringLength(clientPackageName);
    String16 clientName(rawClientName, rawClientNameLen);
    env->ReleaseStringChars(clientPackageName, rawClientName);
 
    sp<Camera> camera = Camera::connect(cameraId, clientName,
            Camera::USE_CALLING_UID);
 
    if (camera == NULL) {
        jniThrowRuntimeException(env, "Fail to connect to camera service");
        return;
    }
 
    // make sure camera hardware is alive
    if (camera->getStatus() != NO_ERROR) {
        jniThrowRuntimeException(env, "Camera initialization failed");
        return;
    }
 
    jclass clazz = env->GetObjectClass(thiz);
    if (clazz == NULL) {
        jniThrowRuntimeException(env, "Can't find android/hardware/Camera");
        return;
    }
 
    // We use a weak reference so the Camera object can be garbage collected.
    // The reference is only used as a proxy for callbacks.
    sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);
    context->incStrong((void*)android_hardware_Camera_native_setup);
    camera->setListener(context);
 
    // save context in opaque field
    env->SetIntField(thiz, fields.context, (int)context.get());
}
cs


이제 한 줄씩 살펴보도록 하겠습니다.


sp<JNICameraContext> context = new JNICameraContext(env, weak_this, clazz, camera);


JNICameraContext 클래스를 선언하는 모습입니다. JNICameraContext 클래스의 내용을 자세히 살펴보도록 합시다.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// provides persistent context for calls from native code to Java
class JNICameraContext: public CameraListener
{
public:
    JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera);
    ~JNICameraContext() { release(); }
    virtual void notify(int32_t msgType, int32_t ext1, int32_t ext2);
    virtual void postData(int32_t msgType, const sp<IMemory>& dataPtr,
                          camera_frame_metadata_t *metadata);
    virtual void postDataTimestamp(nsecs_t timestamp, int32_t msgType, const sp<IMemory>& dataPtr);
    void postMetadata(JNIEnv *env, int32_t msgType, camera_frame_metadata_t *metadata);
    void addCallbackBuffer(JNIEnv *env, jbyteArray cbb, int msgType);
    void setCallbackMode(JNIEnv *env, bool installed, bool manualMode);
    sp<Camera> getCamera() { Mutex::Autolock _l(mLock); return mCamera; }
    bool isRawImageCallbackBufferAvailable() const;
    void release();
 
private:
    void copyAndPost(JNIEnv* env, const sp<IMemory>& dataPtr, int msgType);
    void clearCallbackBuffers_l(JNIEnv *env, Vector<jbyteArray> *buffers);
    void clearCallbackBuffers_l(JNIEnv *env);
    jbyteArray getCallbackBuffer(JNIEnv *env, Vector<jbyteArray> *buffers, size_t bufferSize);
 
    jobject     mCameraJObjectWeak;     // weak reference to java object
    jclass      mCameraJClass;          // strong reference to java class
    sp<Camera>  mCamera;                // strong reference to native object
    jclass      mFaceClass;  // strong reference to Face class
    jclass      mRectClass;  // strong reference to Rect class
    Mutex       mLock;
 
    /*
     * Global reference application-managed raw image buffer queue.
     *
     * Manual-only mode is supported for raw image callbacks, which is
     * set whenever method addCallbackBuffer() with msgType =
     * CAMERA_MSG_RAW_IMAGE is called; otherwise, null is returned
     * with raw image callbacks.
     */
    Vector<jbyteArray> mRawImageCallbackBuffers;
 
    /*
     * Application-managed preview buffer queue and the flags
     * associated with the usage of the preview buffer callback.
     */
    Vector<jbyteArray> mCallbackBuffers; // Global reference application managed byte[]
    bool mManualBufferMode;              // Whether to use application managed buffers.
    bool mManualCameraCallbackSet;       // Whether the callback has been set, used to
                                         // reduce unnecessary calls to set the callback.
};
cs


 이 클래스는 Camera 관련 호출이 Native에서 Java로 전송해야 될 때 주로 쓰이는 클래스로 추측할 수 있습니다. 일단 자세한 사항은 다음에 기회가 될 때 살명하고 넘어가도록 하겠습니다.


/frameworks/base/core/jni/android_hardware_Camera.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
JNICameraContext::JNICameraContext(JNIEnv* env, jobject weak_this, jclass clazz, const sp<Camera>& camera)
{
    mCameraJObjectWeak = env->NewGlobalRef(weak_this);
    mCameraJClass = (jclass)env->NewGlobalRef(clazz);
    mCamera = camera;
 
    jclass faceClazz = env->FindClass("android/hardware/Camera$Face");
    mFaceClass = (jclass) env->NewGlobalRef(faceClazz);
 
    jclass rectClazz = env->FindClass("android/graphics/Rect");
    mRectClass = (jclass) env->NewGlobalRef(rectClazz);
 
    mManualBufferMode = false;
    mManualCameraCallbackSet = false;
}
cs


 위에서 보시는 바와 같이 JNICameraContext 클래스 내에 Camera와 관련된 Java 클래스값들을 선언하고 있는 것을 확인하실 수 있습니다.



context->incStrong((void*)android_hardware_Camera_native_setup);


함수 android_hardware_Camera_native_setup() 함수의 참조계수를 1 증가시킵니다.


camera->setListener(context);


위에서 선언하였던 JNICameraContext의 변수값을 camera 클래스 변수 내에 Listerner로 설정해줍니다.


/frameworks/av/camera/Camera.cpp

1
2
3
4
5
void Camera::setListener(const sp<CameraListener>& listener)
{
    Mutex::Autolock _l(mLock);
    mListener = listener;
}
cs


 이로서 대망의 Camera.open() 함수의 동작과정을 (1)~(3) 포스팅을 통해 모두 살펴보았습니다. Camera의 구현과정을 처음 보시는 분들이시라면 상당히 큰 어려움이 있으실 것이라 생각합니다. 막히더라도 일단 망설이지 마시고 대략 이러한 기능을 한다는 것을 기억하신 후 다음 단계로 넘어가신다면 이후 넘어갔던 부분이 이해가 되실 날이 오리라 저는 생각합니다!

300x250

안드로이드 Framework에서 Camera 동작 원리 분석(2)

안드로이드/카메라 2015. 4. 26. 09:00

 지난 시간에 이어 Framework 단계에서 Camera가 CameraService와 어떻게 연결되는지 이어서 진행해 보도록 하겠습니다.

 Application 단계에서 Camera 클래스의 open() 매소드를 실행한 후 이를 지속적으로 추적하여 CameraBase 까지 접근하였고 이 곳에서 CameraService와의 접점을 찾는 과정까지 진행하였습니다.


/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs

 이제 여기서부터 한 줄씩 분석을 하며 내려가 보도록 하겠습니다.


sp<TCam> c = new TCam(cameraId);


먼저 저 Templete인 TCam의 정체를 확인해 보도록 합시다. TCam은 위 함수가 호출되기 전 미리 정의가 되어 있을 것입니다.


/frameworks/av/camera/Camera.cpp

1
2
3
4
5
6
7
#include <camera/Camera.h>
 
sp<Camera> Camera::connect(int cameraId, const String16& clientPackageName,
        int clientUid)
{
    return CameraBaseT::connect(cameraId, clientPackageName, clientUid);
}
cs


TCam을 알아보려 함수를 거슬러 올라왔더니 이번에는 CameraBaseT가 떡하니 있군요. 이 녀석은 무엇을 하는 녀석일까요?


/frameworks/av/include/camera/Camera.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <camera/CameraBase.h>
 
class Camera :
    public CameraBase<Camera>,
    public BnCameraClient
{
public:
    enum {
        USE_CALLING_UID = ICameraService::USE_CALLING_UID
    };
 
            // construct a camera client from an existing remote
    static  sp<Camera>  create(const sp<ICamera>& camera);
    static  sp<Camera>  connect(int cameraId,
                                const String16& clientPackageName,
                                int clientUid);
}
cs

/frameworks/av/include/camera/CameraBase.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
 
    static sp<TCam>      connect(int cameraId,
                                 const String16& clientPackageName,
                                 int clientUid);
 
    typedef CameraBase<TCam>         CameraBaseT;
}
cs

 함수 하나를 설명하려다 보니 생각보다 많은 코드들이 추가되었군요. 일단 위 함수들을 근거로 하나씩 단서를 찾아보도록 합시다. 우선 CameraBaseT의 정체를 알아봅시다.


return CameraBaseT::connect(cameraId, clientPackageName, clientUid);


 바로 위의 CameraBase.h 파일 내에서 CameraBaseT가 typedef로 정의되어 있는 것을 확인하실 수 있습니다. 이를 변환해 보면 다음과 같군요.


return CameraBase<TCam>::connect(cameraId, clientPackageName, clientUid);

 그래도 아직 TCam의 정체는 풀리지 않았군요 그러나 이는 Camera.h 에 정의된 Camera의 상속 클래스를 확인하면 바로 풀리는 것을 보실 수 있습니다.


class Camera : public CameraBase<Camera>, public BnCameraClient


이로서 TCam의 정체는 Camera라는 것을 알게 되었습니다. 이를 다시 정리하면 다음과 같이 변환될 수 있음을 알 수 있습니다.


sp<Camera> c = new Camera(cameraId);


/frameworks/av/camera/Camera.cpp

1
2
3
4
Camera::Camera(int cameraId)
    : CameraBase(cameraId)
{
}
cs


/frameworks/av/include/camera/CameraBase.h

1
2
3
4
5
6
7
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
protected:
    CameraBase(int cameraId);
    const int                        mCameraId;
}
cs


/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
template <typename TCam, typename TCamTraits>
CameraBase<TCam, TCamTraits>::CameraBase(int cameraId) :
    mStatus(UNKNOWN_ERROR),
    mCameraId(cameraId)
{
}
cs

이러한 과정을 통해 Camera 객체가 생성되었음을 확인하였습니다. 이제 다음줄을 보도록 합니다.


sp<TCamCallbacks> cl = c;


자... 이번에는 TCamCallbacks가 뿅 하고 나타나 우리들을 괴롭히고 있군요! 이 녀석의 정체는 과연 누구일까요?

/frameworks/av/include/camera/Camera.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <camera/CameraBase.h>
 
template <>
struct CameraTraits<Camera>
{
    typedef CameraListener        TCamListener;
    typedef ICamera               TCamUser;
    typedef ICameraClient         TCamCallbacks;
    typedef status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,
                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);
    static TCamConnectService     fnConnectService;
};
cs


/frameworks/av/include/camera/CameraBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
template <typename TCam>
struct CameraTraits {
};
 
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
}
cs

위 코드를 처음 볼 땐 그냥 정신이 멍해질 겁니다. 하지만 다시 정신 바짝 차리시고 코드를 상세하게 살펴보도록 합니다!


/frameworks/av/include/camera/CameraBase.h
template <typename TCam>
struct CameraTraits {
};

template <typename TCam, typename TCamTraits = CameraTraits<TCam> >

우선 CameraBase.h 헤더에서 다음과 같이 CameraTraits<Tcam> 이 정의되어 있고 이는 struct 내의 CameraTraits에 적용이 됩니다.


/frameworks/av/include/camera/Camera.h

#include <camera/CameraBase.h>
 
template <>
struct CameraTraits<Camera>
{
....
    typedef ICameraClient         TCamCallbacks;
....
};

 위의 CameraTraits<Camera> 로 선언된 struct로 인해 TCam이 Camera임을 알 수 있습니다. 또한 ICameraClient 클래스가 TCamCallbacks로 선언되어 있는 모습을 보실 수 있습니다.


이제 문제는 CameraBase.h에 정의된 아래의 코드가 뜻을 의미하기 난해다다는 점이지요.

typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;

 저 위에 쓰인 typename은 template 내에서 쓰이는건 많이 보았는데 여기서는 클래스 내에까지 정의되어 있는 것을 보실 수 있습니다. 이는 위에 보이시는 TCamTraits라는 것이 class임을 컴파일에게 알려주기 위해 부득이하게 typename을 적어준 것이라고 이해해 주시면 되겠습니다. 이와 관련해서 좀 더 자세한 정보를 알고 싶으신 분께서는 아래 링크를 참조해 주시기 바랍니다.

http://ikpil.com/540

위의 코드를 우리들이 읽기 쉽게 변환하면 다음과 같다고 보실 수 있겠습니다.


typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;

typedef CameraTraits<Camera>::TCamCallbacks      TCamCallbacks;

typedef CameraTraits<Camera>::ICameraClient      TCamCallbacks;
CameraTraits<Camera>::ICameraClient


결국 TCamCallbacks는 ICameraClient와 같음을 확인하실 수 있습니다.

sp<ICameraClient> cl = c;


이제 다음 코드를 확인해 보도록 하겠습니다.


const sp<ICameraService>& cs = getCameraService();


ICameraService 클래스를 불러오라는 듯한 의미의 코드로 해석됩니다. 여기서 getCameraService() 함수를 살펴보도록 하겠습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// establish binder interface to camera service
template <typename TCam, typename TCamTraits>
const sp<ICameraService>& CameraBase<TCam, TCamTraits>::getCameraService()
{
    Mutex::Autolock _l(gLock);
    if (gCameraService.get() == 0) {
        sp<IServiceManager> sm = defaultServiceManager();
        sp<IBinder> binder;
        do {
            binder = sm->getService(String16(kCameraServiceName));
            if (binder != 0) {
                break;
            }
            ALOGW("CameraService not published, waiting...");
            usleep(kCameraServicePollDelay);
        } while(true);
        if (gDeathNotifier == NULL) {
            gDeathNotifier = new DeathNotifier();
        }
        binder->linkToDeath(gDeathNotifier);
        gCameraService = interface_cast<ICameraService>(binder);
    }
    ALOGE_IF(gCameraService == 0"no CameraService!?");
    return gCameraService;
}

cs

 위 코드는 BpCameraService와 BpBinder를 불러들이는 코드입니다. 위 코드에 대해 자세한 내용은 아래 포스팅을 참고해주시기 바랍니다.

http://elecs.tistory.com/93


 위 과정까지 진행하셨다면 CameraBase와 CameraService가 Binder를 통해 연결되었음을 확인하실 수 있습니다. 이제 다음으로 이후의 코드들을 살펴보도록 하겠습니다.

/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs

이제 이후의 코드들을 한 줄씩 분석해 보도록 하겠습니다.

TCamConnectService fnConnectService = TCamTraits::fnConnectService;


 이 코드는 typedef로 정의된 함수 포인터 변수를 선언하고 이것에 실행하고자 하는 함수를 저장하고 있습니다. fnConnectService 내에는 다음과 같은 내용의 함수가 저장됩니다.


/frameworks/av/camera/Camera.cpp

1
2
CameraTraits<Camera>::TCamConnectService CameraTraits<Camera>::fnConnectService =
        &ICameraService::connect;
cs

 fnConnectService 함수 포인터는 ICamaerService의 connect 함수를 저장함을 확인할 수 있습니다. typedef 함수 포인터에 대해 좀 더 자세히 알고 싶으신 분은 아래 포스팅을 참조하시기 바랍니다.


http://elecs.tistory.com/97

 

status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,

                                             /*out*/ c->mCamera);


 위에서 설정하였던 함수 포인터를 실행하는 코드입니다. sp<T> 클래스에서 get() 함수를 호출하면 자신이 가지고 있는 객체값의 포인터 값을 불러옵니다. 이 때 리턴값의 자료형은 T입니다. 위의 코드에서 변수 cs는 ICameraService를 리턴합니다. 이 때 cs에는 BpCameraService를 저장하고 있으므로 BpCameraService::connect 함수를 실행하게 된다.


/frameworks/av/camera/ICameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class BpCameraService: public BpInterface<ICameraService>
{
public:
    BpCameraService(const sp<IBinder>& impl)
        : BpInterface<ICameraService>(impl)
    {
    }
....
    // connect to camera service (android.hardware.Camera)
    virtual status_t connect(const sp<ICameraClient>& cameraClient, int cameraId,
                             const String16 &clientPackageName, int clientUid,
                             /*out*/
                             sp<ICamera>& device)
    {
        Parcel data, reply;
        data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
        data.writeStrongBinder(cameraClient->asBinder());
        data.writeInt32(cameraId);
        data.writeString16(clientPackageName);
        data.writeInt32(clientUid);
        remote()->transact(BnCameraService::CONNECT, data, &reply);
 
        if (readExceptionCode(reply)) return -EPROTO;
        status_t status = reply.readInt32();
        if (reply.readInt32() != 0) {
            device = interface_cast<ICamera>(reply.readStrongBinder());
        }
        return status;
    }
....
};
cs


 Proxy 측인 BpCameraService 클래스에서 실행된 Parcel에 대한 transact() 함수는 이후 Native 측인 BnCameraService 클래스로 Parcel을 통해 값이 넘어오면서 실행되기 시작합니다.


/frameworks/av/camera/ICameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
status_t BnCameraService::onTransact(
    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
    switch(code) {
    ....
        case CONNECT: {
            CHECK_INTERFACE(ICameraService, data, reply);
            sp<ICameraClient> cameraClient =
                    interface_cast<ICameraClient>(data.readStrongBinder());
            int32_t cameraId = data.readInt32();
            const String16 clientName = data.readString16();
            int32_t clientUid = data.readInt32();
            sp<ICamera> camera;
            status_t status = connect(cameraClient, cameraId,
                    clientName, clientUid, /*out*/ camera);
            reply->writeNoException();
            reply->writeInt32(status);
            if (camera != NULL) {
                reply->writeInt32(1);
                reply->writeStrongBinder(camera->asBinder());
            } else {
                reply->writeInt32(0);
            }
            return NO_ERROR;
        } break;
    ....
    }
 
}
cs


 위의 코드를 보았을 때 마치 connect() 함수가 이전에 실행했던 BpCameraService 클래스의 connect() 함수를 실행하는 듯한 모습을 보이고 있습니다. 하지만 사실은 Native 프로세스의 CameraService 내의 connect() 함수가 실행되고 있다는 것을 기억하시면 되겠습니다. 해당 코드를 분석해보도록 합시다.


/frameworks/av/services/camera/libcameraservice/CameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
class CameraService :
    public BinderService<CameraService>,
    public BnCameraService,
    public IBinder::DeathRecipient,
    public camera_module_callbacks_t
{
....
    virtual status_t connect(const sp<ICameraClient>& cameraClient, int cameraId,
            const String16& clientPackageName, int clientUid,
            /*out*/
            sp<ICamera>& device);
....
}
cs


 먼저 CameraService 클래스의 구조를 보겠습니다. 위에서 보시면 아시듯이 BnCameraService 클래스를 상속받고 있는 것을 보실 수 있으며 CameraService 클래스 내에 connect() 함수가 virtual로 선언되어 있는 모습을 확인하실 수 있습니다.


/frameworks/av/include/camera/ICameraService.h
1
2
3
4
5
6
7
8
9
10
class BnCameraService: public BnInterface<ICameraService>
{
public:
    virtual status_t    onTransact( uint32_t code,
                                    const Parcel& data,
                                    Parcel* reply,
                                    uint32_t flags = 0);
};
 
}; // namespace android
cs


 BnCameraService 클래스는 보시듯이 BnInterface를 상속받고 있으며 BnInterface는 ICameraService 상속하는 상황임을 보실 수 있습니다.


/frameworks/native/include/binder/IInterface.h

1
2
3
4
5
6
7
8
9
10
template<typename INTERFACE>
class BnInterface : public INTERFACE, public BBinder
{
public:
    virtual sp<IInterface>      queryLocalInterface(const String16& _descriptor);
    virtual const String16&     getInterfaceDescriptor() const;
 
protected:
    virtual IBinder*            onAsBinder();
};
cs


여기까지 확인하셨다면 일단 CameraService 클래스의 상속상황이 어떻게 되어있는지 어느정도 감을 잡으셨으리라 생각합니다.


/frameworks/av/include/camera/ICameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ICameraService : public IInterface
{
....
 
public:
    /**
     * clientPackageName and clientUid are used for permissions checking.  if
     * clientUid == USE_CALLING_UID, then the calling UID is used instead. Only
     * trusted callers can set a clientUid other than USE_CALLING_UID.
     */
    virtual status_t connect(const sp<ICameraClient>& cameraClient,
            int cameraId,
            const String16& clientPackageName,
            int clientUid,
            /*out*/
            sp<ICamera>& device) = 0;
....
}
cs


 자, 이제 저 connect() 함수는 어느 부분에서 선언되어 있는 것인지 확인해 보도록 합시다. C++에서 virtual 함수는 Java에서 Dynamic 함수로 정의하는 것으로 감을 잡으시면 이해가 쉬울 것입니다.

 이에 대한 힌트는 이전에 작성하였던 포스팅 중에 있습니다. 바로 CameraService가 등록되는 과정입니다.


http://elecs.tistory.com/83


/frameworks/native/include/binder/BinderService.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#ifndef ANDROID_BINDER_SERVICE_H
#define ANDROID_BINDER_SERVICE_H
 
#include <stdint.h>
 
#include <utils/Errors.h>
#include <utils/String16.h>
 
#include <binder/IServiceManager.h>
#include <binder/IPCThreadState.h>
#include <binder/ProcessState.h>
#include <binder/IServiceManager.h>
 
// ---------------------------------------------------------------------------
namespace android {
 
template<typename SERVICE>
class BinderService
{
public:
    static status_t publish(bool allowIsolated = false) {
        sp<IServiceManager> sm(defaultServiceManager());
        return sm->addService(
                String16(SERVICE::getServiceName()),
                new SERVICE(), allowIsolated);
    }
 
    static void publishAndJoinThreadPool(bool allowIsolated = false) {
        publish(allowIsolated);
        joinThreadPool();
    }
 
    static void instantiate() { publish(); }
 
    static status_t shutdown() { return NO_ERROR; }
 
private:
    static void joinThreadPool() {
        sp<ProcessState> ps(ProcessState::self());
        ps->startThreadPool();
        ps->giveThreadPoolName();
        IPCThreadState::self()->joinThreadPool();
    }
};
 
 
}; // namespace android
// ---------------------------------------------------------------------------
#endif // ANDROID_BINDER_SERVICE_H
 
 
cs

 

 내용이 다소 생략되어 있습니다만 위의 addService() 과정에서 CameraService 클래스가 new를 통해 생성되고 있음을 보실 수 있습니다. 즉, CameraService가 SystemService에 등록될 때 순수 CameraService 클래스가 선언되어 있으므로 virtual 함수의 dynamic 성질을 생각한다면 CameraService 내의 함수가 호출되어야 함이 맞음을 알 수 있을 것입니다.


/frameworks/av/services/camera/libcameraservice/CameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
status_t CameraService::connect(
        const sp<ICameraClient>& cameraClient,
        int cameraId,
        const String16& clientPackageName,
        int clientUid,
        /*out*/
        sp<ICamera>& device) {
 
    String8 clientName8(clientPackageName);
    int callingPid = getCallingPid();
 
    LOG1("CameraService::connect E (pid %d \"%s\", id %d)", callingPid,
            clientName8.string(), cameraId);
 
    status_t status = validateConnect(cameraId, /*inout*/clientUid);
    if (status != OK) {
        return status;
    }
 
 
    sp<Client> client;
    {
        Mutex::Autolock lock(mServiceLock);
        sp<BasicClient> clientTmp;
        if (!canConnectUnsafe(cameraId, clientPackageName,
                              cameraClient->asBinder(),
                              /*out*/clientTmp)) {
            return -EBUSY;
        } else if (client.get() != NULL) {
            device = static_cast<Client*>(clientTmp.get());
            return OK;
        }
 
        int facing = -1;
        int deviceVersion = getDeviceVersion(cameraId, &facing);
 
        // If there are other non-exclusive users of the camera,
        //  this will tear them down before we can reuse the camera
        if (isValidCameraId(cameraId)) {
            // transition from PRESENT -> NOT_AVAILABLE
            updateStatus(ICameraServiceListener::STATUS_NOT_AVAILABLE,
                         cameraId);
        }
 
        switch(deviceVersion) {
          case CAMERA_DEVICE_API_VERSION_1_0:
            client = new CameraClient(this, cameraClient,
                    clientPackageName, cameraId,
                    facing, callingPid, clientUid, getpid());
            break;
          case CAMERA_DEVICE_API_VERSION_2_0:
          case CAMERA_DEVICE_API_VERSION_2_1:
          case CAMERA_DEVICE_API_VERSION_3_0:
            client = new Camera2Client(this, cameraClient,
                    clientPackageName, cameraId,
                    facing, callingPid, clientUid, getpid(),
                    deviceVersion);
            break;
          case -1:
            ALOGE("Invalid camera id %d", cameraId);
            return BAD_VALUE;
          default:
            ALOGE("Unknown camera device HAL version: %d", deviceVersion);
            return INVALID_OPERATION;
        }
 
        status_t status = connectFinishUnsafe(client, client->getRemote());
        if (status != OK) {
            // this is probably not recoverable.. maybe the client can try again
            // OK: we can only get here if we were originally in PRESENT state
            updateStatus(ICameraServiceListener::STATUS_PRESENT, cameraId);
            return status;
        }
 
        mClient[cameraId] = client;
        LOG1("CameraService::connect X (id %d, this pid is %d)", cameraId,
             getpid());
    }
    // important: release the mutex here so the client can call back
    //    into the service from its destructor (can be at the end of the call)
 
    device = client;
    return OK;
}
 
cs

 현재 과정에서 보시는 바와 같이 client 에는 CameraClient() 혹은 Camera2Client() 함수가 저장되었고 이는 Camera 클래스 내의 mCamera에 저장됩니다.


 위 과정까지 마치게 된다면 카메라가 CameraService와 연결이 완료가 되면서 Camera 클래스 객체를 android_hardware_Camera.cpp 에서 실행하고 있던 초기화 함수로 return하게 됩니다.


 다음 포스팅에서는 android_hardware_Camera.cpp 에서 진행하고 있던 초기화 함수 부분에서 이어서 진행하도록 하겠습니다.



300x250

Canon MG2900 시리즈 무선 프린터 설치방법

흔치않은일상 2015. 4. 18. 00:56

https://elecs.tistory.com/342 간만에 프린터를 구매하면서 이왕이면 무선 인터넷을 통한 프린팅 기능이 있는 프린터를 사고자 하여 Canon PIXMA MG2990을 구매하게 되었습니다.





오늘날이 스마트 시대인 만큼 프린터도 무선 기능을 지원하는 추세이기도 한데요

컴퓨터에 직접 연결해서 쓸 때는 설명서 내용에 충실하면 문제없이 사용이 가능합니다.

그런데 무선 프린터의 가장 큰 불편한 점은

공유기 설정 관련 지식이 해박하지 않으신 분들은

설치과정에서 어려운 점이 한두가지가 아니라는 겁니다.

특히 저의 경우 MG2990에서 지원하는 WPS 설치를 진행하다 피를 봤으니 말이지요.


그런고로 여기서는 Canon사의 MG2900 시리즈를 구매하신 분들을 대상으로 무선으로 프린터를 설치하는 과정에 대해 알아보고자 합니다.


2019년 10월 11일 추가 - 만약 이 글에서 소개된 무선 설치 방법이 정상적으로 진행되지 않으시는 분들께서는 아래의 링크와 같이 USB를 연결하여 프린터를 설치해보시길 바랍니다.

https://elecs.tistory.com/342


1. 먼저 동봉된 DVD를 삽입하신 후 설치 파일인 MSetup4.exe 파일을 실행합니다.


만약 자신의 컴퓨터에 광매체 드라이브(ODD)가 없어 동봉된 DVD를 사용하지 못하시는 분은 아래 사이트를 통해 설치 파일을 다운로드 받아 진행하시기 바랍니다.


http://canon.com/ij-setup



다음과 같은 화면이 나오면 우선 설치 프로그램이 실행된 것입니다. 다음 버튼을 눌러 설치를 진행합니다.



곧바로 설치 프로그램이 무선공유기를 통해 프린터를 찾는 모드에 진입합니다.

만약 자신의 공유기가 프린터와 연결되어 있다면 곧바로 드라이버 설치 모드에 진입될 것입니다.



'무선 LAN 연결'을 선택하신 후 '다음'버튼을 클릭합니다.



여기서는 무선 공유기를 통해 연결할 예정이므로 '액세스 포인트 연결(권장)'을 선택하신 후 '다음' 버튼을 클릭합니다.




당연히 처음에는 공유기가 프린터와 연결되어 있지 않으므로 다음과 같은 화면이 나오는 것을 보실 수 있습니다. '다음' 버튼을 클릭합니다.



다음과 같은 화면이 진행 된 후 설치가이드 모드에 들어가게 됩니다.



'다음' 버튼을 클릭합니다.



설치 환경이 총 3가지가 주어져 있습니다.

이 중 가장 쉬운 방법은 WPS를 사용하는 방법입니다만

이게 몇몇 공유기는 자칫하면 프린터가 공유기의 설정을 제멋대로 바꾸어 버리기 때문에

잘못하면 다른 기기들이 Wi-Fi를 사용하지 못하는 경우가 발생합니다.


여기서는 오른쪽 빨간색으로 표시한 부분 PIN 코드 (방법)을 통해 진행하겠습니다.




위에서 설명하고 있는 바와 같이

자신의 공유기 설정을 확인하셔서 자신의 공유기 환경이 어떻게 구성되어 있는지 살펴봅니다.

만약 자신의 공유기가 'IEEE802.11n 전용'으로 설정되어 있다면 이를 다른 것으로 설정해 주시면 되겠습니다.


이를 위해 자신의 공유기에 접속하여 설정을 변경해야 하는데

거의 대부분의 공유기는 다음 주소를 통해 설정모드에 진입하실 수 있습니다.


http://192.168.1.1/


종종 다른 공유기의 경우 설정 모드에 진입하는 방식이 다른 경우가 있습니다. 자세한 사항은 자신의 공유기 제작사의 홈페이지를 참조하시기 바랍니다.



자신의 프린터에 전원을 넣은 후 위의 그림과 같이 중지(Stop) 버튼을 꾸욱 누르고 기다립니다.

그러면 알람(Alarm) 림프가 깜빡거리기 시작하는데 이것이 15회 깜빡이는 것을 확인하시면 바로 버튼에서 손을 땝니다. 그러면 아래와 같이 프린트 물이 출력되는 것을 확인하실 수 있습니다.



이렇게 자신의 프린터에 설정된 정보들이 프린터에 출력됨을 확인하실 수 있습니다.



위에서 진행했던 것과 같이 다시 프린터의 중지(Stop) 버튼울 꾸욱 누르고 기다립니다.

이번에는 알람(Alarm) 버튼이 16번 깜빡거리기를 기다리다가 떼어주시면 전원과 무선 부분 두 램프가 함께 깜빡이는 것을 보실 수 있습니다.



위와 같은 화면을 보셨다면 곧바로 자신의 공유기 설정 모드에 진입하셔서 위의 과정에서 출력했던 프린트물 내에 적혀진 PIN코드를 자신의 공유기에 입력해줍니다.


아래는 netis사 공유기의 펌웨어 화면으로 'WPS 설정'을 누르신 후 '장비 추가' 버튼을 클리갛면 아래부분에 '새로운 장비 추가' 메뉴가 나타납니다. 여기서 '연결할 무선 장비의 PIN코드 입력' 을 선택한 후 출력된 인쇄지에 적힌 PIN코드를 입력하신 후 '연결' 버튼을 클릭하시면 됩니다.



이 과정이 성공적으로 끝난다면 프린터의 전원과 Wi-Fi 버튼의 깜빡임이 멈추게 될 것입니다.



위의 화면과 같이 네트워크 프린터 목록에서 자신의 프린터 기기가 공유기와 연결되었음을 확인하실 수 있습니다. 자신의 프린터를 마우스로 클릭한 후 '다음'버튼을 클릭하시면 드라이버가 본격적으로 설치되기 시작합니다.


만약 위의 화면과 같은 결과가 나오지 않으신 분들은 '다음'버튼을 누르신 후 위의 과정을 한번 더 반복합니다.



위와 같은 화면을 보셨다면 드디어 자신의 컴퓨터에 드라이버가 실치될 것입니다!


300x250

Wi-Fi 프린팅 지원 Canon PIXMA MG2990 개봉기

흔치않은일상 2015. 4. 17. 00:01

지금까지 컴퓨터는 몇 번이고 바꾸어 오면서도 프린터기는 약 10년전 즈음부터 사용했던 HP 복합기를 사용해 왔었습니다.

지금도 인쇄가 되고 있기는 합니다만

워낙 연세를 많이 잡수시다 보니 나중에는 인쇄 속도가 많이 떨어지는 모습을 보이고 있었습니다.


그래서 이참에 새로운 프린터기를 한 대 장만해볼까 해서 알아보았는데

요즘은 스마트 시대에 도래하면서 NFC 기능을 사용한 프린터기도 선보이는 추세인데요.

그 중 Wi-Fi를 활용하여 무선으로 프린팅이 가능한 Canon사의 PIXMA MG2900을 구매해 보았습니다.



인터넷으로 주문한 PIXMA MG2990 기기가 도착하였습니다.



자세히 살펴보니 제가 원하는 기능이기도 한 Wi-Fi 무선 프린팅을 지원하고 있는 것을 확인하실 수 있습니다.



드디어 오픈~1



열자마자 내용물로 거대한 그림을 활용한 설시 설명서를 보실 수 있습니다.

이 순서대로 사용하신다면 간편하게 바로 설치하여 사용하실 수 있습니다.



설치설명서를 꺼낸 후 내부의 모습입니다.



드디어 내용물을 꺼내봅니다!



바로 위에는 취급설명서와 드라이브 설치CD를 확인하실 수 있습니다.



프린터 박스 아랫쪽에 길다란 노란 포장지가 보이는데요 이것의 정체는 무엇일까요?



내용을 확인해 보니



다름아닌 전원코드와 USB 연결선이 들어있었습니다.

이와중에 왼쪽 오른쪽에는 정체불명의 플라스틱 포장물이 보이는데요.



아하!

컬러잉크와 흑색잉크가 각각 동봉이 되어 있었던 것이었네요!!



드디어 본론이로군요! MG2990를 살펴보도록 합시다!!



오호



하얀 빛의 디자인이 상당히 매력적입니다!



용지 삽입 부분을 개방해 보았습니다.



스캐너 부분도 살펴봅시다.

구조는 다른 복합기와는 딱히 차이는 없어보이는군요.



프린터기의 내부입니다.

잉크를 넣을 수 있는 슬롯 2개가 보이는데요

왼쪽 슬롯이 컬러잉크, 오른쪽 슬롯이 검은잉크를 삽입하는 부분입니다.



MG2990의 전원은 어댑터가 보시는 바와 같이 본체에 붙어있는 방식입니다.

그덕에 덩치큰 어댑터를 따로 공간 낭비없이 간편하게 해결한 발상이 참 대단합니다.



드디어 설치 완료!



전원을 키니 보시는 바와 같이 무선모드도 동시에 켜지는 것으로 보입니다.



그 와중에 경고 램프가 켜져있는데요

생각해보니 아직 잉크를 설치하지 않았더군요.



역시나 내부에는 잉크가 삽입되어 있지 않습니다.



앞에서 보았던 이 두 잉크를 꺼내봅니다.



꺼내자마자 샷 한장 더!



앞에서 설명드린 거와 같이

슬롯의 왼쪽에는 컬러, 오른쪽에는 검은 잉크를 넣어주세요~!



두 잉크가 정상적으로 설치되었습니다!



다시 경고 램프는 문제 없다는 듯 조용히 꺼지는군요.



인쇄를 위해 용지를 넣어주고



종이 밭침대도 늘려주면



드디어 설치 완료!



이렇게 용지가 인쇄되기를 학수고대 해준다면



보시는 바와 같이 프린터가 정상적으로 용지를 인쇄하는 거을 확인하실 수 있습니다!


 혹시 Canon PIXMA MG2900 시리즈의 무선 상태에서 드라이버를 설치하는 방법에 대해 알고 싶으신 분은 아래 링크를 참조해 주시길 바랍니다.


http://elecs.tistory.com/101

300x250

세월호 참사 1주기 합동분향소 주변풍경

흔치않은일상 2015. 4. 16. 19:13

작년 이맘때 즈음,

저는 학교 PC실 한구석에서 작업을 하고 있었습니다.

잠시 인터넷 포털사이트를 접속하였을 때 보게 된 보게된 사진 한 장.

바로 세월호가 침몰하여 배의 일부분만 바다 위에 떠있던 장면이었습니다.


그 안에 수많은 학생들이 있었다는 사실을 접하게 되었을 때 경악을 금치 못하였습니다.

부디 배 안에 갇힌 학생들이 단 한명이라도 좋으니 무사히 구출되길 간절히 바랐지만

그러한 바람을 마음에 품고 지낸지 벌써 1년이라는 시간이 지나버렸습니다.


여전히 세월호 유족분들은 진상규명을 외치며 거리에 나와계시지만

1주기가 된 지금까지도 해결되지 못한 점이 참으로 마음이 아픕니다.




오늘 구글 메인 화면에는 세월호 유족들을 애도하는 검은 리본이 우리를 반기고 있군요.

이를 보고 오늘이 1주기임을 느낄 수 있었습니다.

아이들이 배와 함께 가라앉았던 사건이 불과 어제의 일인것만 같았던데 말이지요.


그렇게 바쁘게 시간이 흘러 1주기가 된 오늘 세월호 합동분향소에 다녀왔습니다.






안산 세월호 합동분향소는 회랑유원지에 위치해 있습니다.

지도를 보니 4호선 초지역에서 걸어서 갈 만한 거리에 있길래 직접 발걸음을 옮겨보기로 하였습니다.



역에서 내리자마자 제 눈앞에 등장한 현수막입니다.

여전히 부모는 세월호에 오른 아이들을 한없이 기다리고 있습니다.



열심히 걷다보니 어느덧 초지역에서 이만큼 걸어왔습니다.



정신없는 공사현장을 지나 앞으로 쭈욱 나아가다 보면 사거리가 눈에 보일 겁니다.



안산시민이 건 현수막입니다.

현수막 뒤로는 합동분향소 추모행사에 참가하러 가는 아이들이 눈에 보입니다.



아이들의 가슴에는 자신들이 직접 만든 리본을 달고 있더군요.



한 아이의 가방에 보이는 세월호 문구가 제 마음을 짠하게 만들고 있습니다.



가는 길마다 보이는 현수막들은

여전히 작년 이맘때 즈음의 사건을 잊지 않게 해줍니다.



작년 11월 세월호 실종자 수식이 종료된 이후 실종자 9명은 아직까지도 가족들 품에 돌아오지 못하고 있습니다.

실종자 9분들이 모두 가족의 품으로 돌아가기를 간절히 바랍니다.



드디어 합동분양소 입구에 도착하였습니다.



세월호 사고 희생자 합동분향소 전면입니다.



그날을 잊지 않기 위해 사람들이 그린 그림들이 전시되고 있습니다.



그 많은 그림들 중 갖아 인상에 남는 그림중 하나였습니다.

광화문 앞에서 단식농성을 벌이던 유민아빠의 모습을 보고

순간 눈시울이 붉어집니다.



1주기 추모식 행사가 한창 준비중인 모습입니다.

날씨가 좋지 않던데 무사히 진행되었으면 좋겠습니다.




분향소 앞에서 피켓을 들고 서있는 모습입니다.

세월호 참사 1주기가 되는 지금까지도 유가족들은 여전히 밝혀지지 않은 진상규명을 정부에게 요청하고 있습니다.



분향소 한쪽에서는 관련 추모 행사의 포스터가 걸려있는 것을 볼 수 있었습니다.



합동분향소 인근에 위치한 호수입니다.,

아이들이 살아있다면 이 곳에서 정다운 이야기를 하며 하루를 보냈을텐데...



가족들은 여전히 세월호 침몰의 정확한 원인을 알지 못합니다.



그렇기에 이렇게 정부에게 진상규명을 요구하지만

세월호의 진상은 여전히 밝혀지지 않은채 제자리 걸음을 하고 있습니다.



인근 주민센터에는 세월호 노란리본 깃발이 태극기와 함께 조기로 걸려있었습니다.





호수에서 단원고등학교로 가던 길에 갑자기 소나기가 내리기 시작합니다.

지금 아이들이 살아있었다면 이 길을 자기 친구들과 함께 해맑은 표정으로 학교에 가고 있을 모습이 문듯 스쳐갑니다.



2015년 4월 16일 단원고등학교 정문의 풍경입니다.

학교 분위기는 작년과 크게 달라보이지 않습니다.



정문 건너편에서의 풍경입니다. 빗줄기가 제법 굵어졌습니다.



학교 건너편에서는 세월호 관련 시민단체 분들이 모여 희생자 분의 이야기를 듣고 있습니다.

단원고 마지막 생존자가 물속에서 나오는 순간에 대한 이야기를 들으니

배 안에서 짫은 인생을 마감한 아이들의 슬픈 얼굴이 떠올랐습니다.



그렇게 합동분향소를 떠나 버스정류장에 도착한 오늘

2015년 4월 16일 목요일입니다......



▶◀ 세월호에 탑승하였다가 희생된 분들의 명복을 빕니다

300x250

안드로이드 프레임워크 프로그래밍(19) [Native 단계의 ServiceManager 동작원리]

안드로이드/프레임워크 2015. 4. 10. 00:33

 지금까지 우리는 안드로이드 내의 System service가 ServiceManager에 의해 관리되고 있음을 알 수 있었습니다. IServiceManager를 통해 BpServiceManager를 생성하여 Binder를 통해 서비스를 등록하거나 찾는 과정 또한 확인했습니다.


/frameworks/native/libs/binder/IServiceManager.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
android::sp<IServiceManager> IServiceManager::asInterface(                           
    const android::sp<android::IBinder>& obj)                       
    {
        android::sp<IServiceManager> intr;
        if (obj != NULL) {
            intr = static_cast<IServiceManager*>(
                obj->queryLocalInterface(
                    IServiceManager::descriptor).get());
        if (intr == NULL) {
            intr = new BpServiceManager(obj);
        }
    }
    return intr;
cs


이러한 과정을 거치는 한 편으로는 이런 생각을 가지신 분들도 계시리라 생각합니다.


"BpServiceManager와 BnServiceManager가 존재한다면 ServiceManager 클래스도 존재하지 않을까?"


 대부분의 분들이라면 분명 있으리라 생각하실 겁니다. 그러나 놀랍게도 ServiceManager는 Java 단계에서는 클래스가 존재합니다만 Native 단계에서는 ServiceManager 클래스를 확인하실 수 없습니다. 그렇다면 Native 단계에서는 ServiceManager가 사용되지 않는걸까요?

 사실 Native 단계에서 ServiceManager는 daemon 프로세스와 같이 백그라운드에서 지속적으로 동작하는 프로세스로 존재합니다. 비록 ServiceManager라는 이름은 아니지만 Binder를 등록하거나 검색을 할 수 있는 기능을 갖추어 놓고 있습니다. ServiceManager는 다음과 같은 파일들로 구성되어 있습니다.


/frameworks/native/cmds/servicemanager/binder.h

/frameworks/native/cmds/servicemanager/binder.c

/frameworks/native/cmds/servicemanager/service_manager.c


 시작하기에 앞서 Android.mk에 설정된 모습을 보도록 하겠습니다.

/frameworks/native/cmds/servicemanager/Android.mk

1
2
3
4
5
6
7
8
9
10
11
12
LOCAL_PATH:= $(call my-dir)
 
#include $(CLEAR_VARS)
#LOCAL_SRC_FILES := bctest.c binder.c
#LOCAL_MODULE := bctest
#include $(BUILD_EXECUTABLE)
 
include $(CLEAR_VARS)
LOCAL_SHARED_LIBRARIES := liblog
LOCAL_SRC_FILES := service_manager.c binder.c
LOCAL_MODULE := servicemanager
include $(BUILD_EXECUTABLE)
cs


 이제 ServiceManager가 실행되는 모습을 보도록 합시다.


/frameworks/native/cmds/servicemanager/service_manager.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void *svcmgr_handle;
 
int main(int argc, char **argv)
{
    struct binder_state *bs;
    void *svcmgr = BINDER_SERVICE_MANAGER;
 
    bs = binder_open(128*1024);
 
    if (binder_become_context_manager(bs)) {
        ALOGE("cannot become context manager (%s)\n", strerror(errno));
        return -1;
    }
 
    svcmgr_handle = svcmgr;
    binder_loop(bs, svcmgr_handler);
    return 0;
}
cs


/frameworks/native/cmds/servicemanager/binder.h

1
2
/* the one magic object */
#define BINDER_SERVICE_MANAGER ((void*0)
cs


 처음엔 binder_state 구조체 변수와 void 변수 svcmgr이 선언됩니다. binder_state에는 이름 그대로 바인더의 상태를 저장하기 위해 사용되는 구조체임을 알 수 있습니다. 여기서 main() 함수의 내용을 하나씩 살펴보겠습니다.


    bs = binder_open(128*1024);


 binder_open() 함수가 선언되어 있고 이를 통해 binder_state 구조체를 return 하는 모습을 보이고 있습니다. binder_open() 함수를 통해 binder가 설정되며 인자로 메모리에 할당할 용량을 설정합니다.


/frameworks/native/cmds/servicemanager/binder.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct binder_state
{
    int fd;
    void *mapped;
    unsigned mapsize;
};
 
struct binder_state *binder_open(unsigned mapsize)
{
    struct binder_state *bs;
 
    bs = malloc(sizeof(*bs));
    if (!bs) {
        errno = ENOMEM;
        return 0;
    }
 
    bs->fd = open("/dev/binder", O_RDWR);
    if (bs->fd < 0) {
        fprintf(stderr,"binder: cannot open device (%s)\n",
                strerror(errno));
        goto fail_open;
    }
 
    bs->mapsize = mapsize;
    bs->mapped = mmap(NULL, mapsize, PROT_READ, MAP_PRIVATE, bs->fd, 0);
    if (bs->mapped == MAP_FAILED) {
        fprintf(stderr,"binder: cannot map device (%s)\n",
                strerror(errno));
        goto fail_map;
    }
 
        /* TODO: check version */
 
    return bs;
 
fail_map:
    close(bs->fd);
fail_open:
    free(bs);
    return 0;
}
cs

 binder_open() 함수의 내부를 살펴보도록 합시다. open() 함수가 호출됨으로서 바딘더의 File descriptor를 얻어낸 후 mmap() 함수를 호출하여 실제 메모리에 바인더를 할당하는 작업을 진행합니다. 모든 것이 완료되면 binder_state를 저장한 구조체 변수의 포인터를 return 합니다.


binder_become_context_manager(bs)


binder_state의 값을 통하여 컨텍스트 매니저(ServiceManager)을 설정해줍니다.

/frameworks/native/cmds/servicemanager/binder.c

1
2
3
4
int binder_become_context_manager(struct binder_state *bs)
{
    return ioctl(bs->fd, BINDER_SET_CONTEXT_MGR, 0);
}
cs

binder.h

1
#define BINDER_SET_CONTEXT_MGR _IOW('b'7int)
cs

 해당 함수는 ioctl() 함수를 사용하여 바인더의 입출력을 제어합니다.


binder_loop(bs, svcmgr_handler);


 이 함수가 실행됨으로서 ServiceManager의 실행은 loop에 들어갑니다. 즉, 시스템에 특별한 이상이 발생하지 않는 한 작동이 계속 되는 것으로 이해하시면 좋을 듯 합니다.


/frameworks/native/cmds/servicemanager/binder.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
void binder_loop(struct binder_state *bs, binder_handler func)
{
    int res;
    struct binder_write_read bwr;
    unsigned readbuf[32];
 
    bwr.write_size = 0;
    bwr.write_consumed = 0;
    bwr.write_buffer = 0;
    
    readbuf[0= BC_ENTER_LOOPER;
    binder_write(bs, readbuf, sizeof(unsigned));
 
    for (;;) {
        bwr.read_size = sizeof(readbuf);
        bwr.read_consumed = 0;
        bwr.read_buffer = (unsigned) readbuf;
 
        res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
 
        if (res < 0) {
            ALOGE("binder_loop: ioctl failed (%s)\n", strerror(errno));
            break;
        }
 
        res = binder_parse(bs, 0, readbuf, bwr.read_consumed, func);
        if (res == 0) {
            ALOGE("binder_loop: unexpected reply?!\n");
            break;
        }
        if (res < 0) {
            ALOGE("binder_loop: io error %d %s\n", res, strerror(errno));
            break;
        }
    }
}
 
int binder_write(struct binder_state *bs, void *data, unsigned len)
{
    struct binder_write_read bwr;
    int res;
    bwr.write_size = len;
    bwr.write_consumed = 0;
    bwr.write_buffer = (unsigned) data;
    bwr.read_size = 0;
    bwr.read_consumed = 0;
    bwr.read_buffer = 0;
    res = ioctl(bs->fd, BINDER_WRITE_READ, &bwr);
    if (res < 0) {
        fprintf(stderr,"binder_write: ioctl failed (%s)\n",
                strerror(errno));
    }
    return res;
}
 
 
int binder_parse(struct binder_state *bs, struct binder_io *bio,
                 uint32_t *ptr, uint32_t size, binder_handler func)
{
    int r = 1;
    uint32_t *end = ptr + (size / 4);
 
    while (ptr < end) {
        uint32_t cmd = *ptr++;
#if TRACE
        fprintf(stderr,"%s:\n", cmd_name(cmd));
#endif
        switch(cmd) {
        case BR_NOOP:
            break;
        case BR_TRANSACTION_COMPLETE:
            break;
        case BR_INCREFS:
        case BR_ACQUIRE:
        case BR_RELEASE:
        case BR_DECREFS:
#if TRACE
            fprintf(stderr,"  %08x %08x\n", ptr[0], ptr[1]);
#endif
            ptr += 2;
            break;
        case BR_TRANSACTION: {
            struct binder_txn *txn = (void *) ptr;
            if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) {
                ALOGE("parse: txn too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            if (func) {
                unsigned rdata[256/4];
                struct binder_io msg;
                struct binder_io reply;
                int res;
 
                bio_init(&reply, rdata, sizeof(rdata), 4);
                bio_init_from_txn(&msg, txn);
                res = func(bs, txn, &msg, &reply);
                binder_send_reply(bs, &reply, txn->data, res);
            }
            ptr += sizeof(*txn) / sizeof(uint32_t);
            break;
        }
        case BR_REPLY: {
            struct binder_txn *txn = (void*) ptr;
            if ((end - ptr) * sizeof(uint32_t) < sizeof(struct binder_txn)) {
                ALOGE("parse: reply too small!\n");
                return -1;
            }
            binder_dump_txn(txn);
            if (bio) {
                bio_init_from_txn(bio, txn);
                bio = 0;
            } else {
                    /* todo FREE BUFFER */
            }
            ptr += (sizeof(*txn) / sizeof(uint32_t));
            r = 0;
            break;
        }
        case BR_DEAD_BINDER: {
            struct binder_death *death = (void**ptr++;
            death->func(bs, death->ptr);
            break;
        }
        case BR_FAILED_REPLY:
            r = -1;
            break;
        case BR_DEAD_REPLY:
            r = -1;
            break;
        default:
            ALOGE("parse: OOPS %d\n", cmd);
            return -1;
        }
    }
 
    return r;
}
cs


binder.h

1
2
3
4
5
6
7
8
9
10
struct binder_write_read {
 signed long write_size;
 signed long write_consumed;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
 unsigned long write_buffer;
 signed long read_size;
 signed long read_consumed;
 unsigned long read_buffer;
/* WARNING: DO NOT EDIT, AUTO-GENERATED CODE - SEE TOP FOR INSTRUCTIONS */
};
cs



 제 이전 포스팅에서 Native 단계에서의 Parcel의 전송과정에 대해 다루었던 과정을 보셨던 분이라면 위의 소스코드를 읽었을 때 '앗!'이라는 생각을 하시는 분들이 계시리라 생각합니다. 그렇습니다. Parcel에서 파일이 전송되려 할 때 transact() 함수를 통해 전송되어 오는 Parcel의 값들을 처리하고 있는 것임을 본 포스팅을 통해 확실히 알게 되셨으리라 생각합니다.


 사실 위의 과정에서 좀 더 많은 설명을 해드리고 싶습니다만 글을 더 진행하기엔 포스팅의 분량도 많아질 뿐더러 내용 또한 어려워지기 때문에 이후 시간이 된다면 Parcel이 Binder와 어떻게 동작하는 지에 대해 자세히 다루어 보는 시간을 가져보도록 하겠습니다.

300x250

[C/C++] typedef 함수 포인터 구현원리

프로그래밍 팁 2015. 4. 6. 13:33

 최근 안드로이드 프레임워크를 공부하다보니 JAVA는 물론 JNI를 통해 연결되는 C/C++ 코드들에 대해 빠삭하게 공부를 하고 있습니다. 정말이지 흔히 쓰는 저 언어들에 슬슬 도가 트고 있지 않은가 싶을정도로 자신의 실력에 대해 자만심이 들기도 할 정도입니다.

 소스코드들을 공부하는 과정에서 어려운 부분이 있다면 바로 흔히 사용하지 않는 방식으로 구현된 소스코드를 해석하는 때라고 생각합니다. 특히 수업시간에는 이론만 알고 넘어가는 함수 포인터라는 생소한 개념이 쓰였을 때는 처음엔 이것의 정체 조차 모르는 경우도 허다하지요.


 본론으로 들어가기에 앞서 간단한 소스코드를 통하여 함수 포인터에 대한 기념을 알아보도록 하겠습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<stdio.h>
 
void (*ptrfunc)(int);
 
void testprint(int n){
    printf("Number : %d\n", n);
}
 
int main(){
    testprint(100);
    ptrfunc = testprint;
    ptrfunc(77);
 
    return 0;
}
cs



위의 결과 출력을 보시면 대략적인 함수 포인터의 동작 원리를 이해하실 수 있을 것이라 생각합니다.

여기서 잠시 코드를 좀 더 자세히 설명 드리도록 하겠습니다.


void (*ptrfunc)(int);

함수 포인터는 위에서 보시는 바와 같은 구조로 이루어져 있습니다. 각 부분의 기능은 다음과 같습니다.

return값의 자료형 (*포인터 함수의 이름) (인자값)


 함수 포인터를 사용하실 때 주의하실 점은 함수 포인터가 이용하고자 하는 함수의 return값의 자료형과 인자값의 자료형 및 갯수가 일치해야 사용할 수 있다는 점입니다. 다음 코드를 확인해 봅시다.


ptrfunc = testprint;


 함수 포인터에 사용하고자 하는 함수의 이름을 입력합니다. 위 과정을 통해 기존 포인터와 같이 함수의 주소값이 포인터에 저장됨으로서 해당 함수 포인터는 자신이 가지고 있는 주소값의 함수와 같은 기능을 구현하게 됩니다.


 다음으로 typedef가 사용된 함수 포인터에 대해 살펴보도록 합시다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<stdio.h>
 
typedef void (*ptrfunc)(int);
 
void testprint(int n){
    printf("Number : %d\n", n);
}
 
int main(){
    testprint(100);
    ptrfunc elecs;
    elecs = testprint;
    elecs(77);
 
    return 0;
}
cs


 위에서 설명하였던 소스코드에 typedef를 적용하여 보았습니다. typedef문이 이곳에서는 어떻게 적용되고 있는지 살펴보도록 합시다.


typedef void (*ptrfunc)(int);


 보시는 대로 기존에 있던 함수 포인터가 선언된 부분 앞쪽에 typedef가 선언되어 있는 모습을 보고 계십니다. typedef문은 빈번하게 사용되는 소스코드가 복잡하거나 길 경우 이를 간결하게 사용하기 위한 목적으로 사용되는데요 함수 포인터에서의 typedef문은 지금껏 보았던 typedef문과는 약간 사용되는 방법이 다르지만 결국은 사용되는 목적은 같습니다.


 다음으로 typedef 함수 포인터가 응용되는 부분을 보여드리도록 하겠습니다.


    ptrfunc elecs;
    elecs = testprint;


 응용이라고 말씀드려서 뭔가 거창한 걸 하려나 하겠습니다만 사실 typedef로 선언된 함수 포인터는 위에서 보시는 바와 같이 매우 간결하게 쓰이고 있음을 아실 수 있습니다. ptrfunc로 정의된 typedef문의 함수 포인터를 elecs라는 이름의 함수 포인터 하나를 만들었다고 보시면 됩니다. 쉽게 설명해서 함수포인터인 변수 하나가 생겼다고 생각하시면 됩니다. 아직도 이해가 안되신다면 아래의 간단한 소스코드를 보시면 아하! 하고 이해하실 겁니다.


int elecs;

elecs = 199;


 이제 감이 오시는지요? 그렇습니다! typedef문으로 선언된 함수 포인터는 마치 자료형을 선언하는 것과 같이 간단하게 함수 포인터 변수를 선언한다고 생각하시면 되는 것입니다! 혹시나 해서 아직도 이해하지 못하신 분들을 위해 저 위에 typedef 함수 포인터가 실제로는 어떻게 구현되어 있는지 보여드리겠습니다.


    void (*elecs)(int);
    elecs = testprint;


 위에서 보시는 바와 같이 ptrfunc 부분이 elecs로 치환된 것이라고 생각하시면 제 설명을 정확히 이해하시는 것입니다!


 혹시 typedef 함수 포인터의 원리에 대해 알고자 하셔서 오신 분들이라면 포스팅을 여기까지만 읽어주셔도 자신의 실력으로 함수 포인터를 활용하실 수 있으리라 생각합니다. 아래에서 부터는 다소 어려우니 기죽지 마시고 이렇게 활용되고 있구나 하는 생각으로 읽어주셨으면 합니다.


 그렇다면 이제 실전에서 사용되고 있는 코드를 보도록 하겠습니다. 아래의 소스코드는 안드로이드 내에서 구현된 함수 포인터 입니다. 언어는 C++로 구성되어 있습니다만 함수포인터를 설멍하는데 큰 어려움은 없을 것입니다. 소스는 다음과 같습니다.


/frameworks/av/camera/CameraBase.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
template <typename TCam, typename TCamTraits>
sp<TCam> CameraBase<TCam, TCamTraits>::connect(int cameraId,
                                               const String16& clientPackageName,
                                               int clientUid)
{
    ALOGV("%s: connect", __FUNCTION__);
    sp<TCam> c = new TCam(cameraId);
    sp<TCamCallbacks> cl = c;
    status_t status = NO_ERROR;
    const sp<ICameraService>& cs = getCameraService();
 
    if (cs != 0) {
        TCamConnectService fnConnectService = TCamTraits::fnConnectService;
        status = (cs.get()->*fnConnectService)(cl, cameraId, clientPackageName, clientUid,
                                             /*out*/ c->mCamera);
    }
    if (status == OK && c->mCamera != 0) {
        c->mCamera->asBinder()->linkToDeath(c);
        c->mStatus = NO_ERROR;
    } else {
        ALOGW("An error occurred while connecting to camera: %d", cameraId);
        c.clear();
    }
    return c;
}
cs


 여기서 참으로 특이한 구조의 소스코드를 만나게 되었습니다.

TCamConnectService fnConnectService = TCamTraits::fnConnectService;


이제 이 부분이 어떻게 구현되었는지 자세히 보도록 합시다.

/frameworks/av/include/camera/CameraBase.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
template <typename TCam>
struct CameraTraits {
};
 
template <typename TCam, typename TCamTraits = CameraTraits<TCam> >
class CameraBase : public IBinder::DeathRecipient
{
public:
    typedef typename TCamTraits::TCamListener       TCamListener;
    typedef typename TCamTraits::TCamUser           TCamUser;
    typedef typename TCamTraits::TCamCallbacks      TCamCallbacks;
    typedef typename TCamTraits::TCamConnectService TCamConnectService;
....
}
cs

위 코드를 통하여 다음과 같은 사실을 알아내었습니다.

    typedef typename TCamTraits::TCamConnectService TCamConnectService;


 TCamConnectService로 정의된 부분이 CameraTraits<TCam>::TCamConnectService와 동일하다는 것을 알고 다음으로 CameraTraits에 대해 확인해 보도록 하겠습니다.


/frameworks/av/include/camera/Camera.h

1
2
3
4
5
6
7
8
9
10
11
12
template <>
struct CameraTraits<Camera>
{
    typedef CameraListener        TCamListener;
    typedef ICamera               TCamUser;
    typedef ICameraClient         TCamCallbacks;
    typedef status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,
                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);
    static TCamConnectService     fnConnectService;
};
cs


위 코드에서 정말 요상하게 친구가 하나 보이는군요.

typedef status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,

                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);


 위에서 배운 바와 같이 해당 코드는 typedef 함수 포인터입니다. 다만 포인터 함수의 이름이 참으로 독특한데 이는 멤버 포인터라는 C++에서 사용되고 있는 기능입니다. 멤버 포인터에 대해 좀 더 자세히 알고 싶으신 분은 아래 포스팅을 참조해 주시기 바랍니다.

http://showmiso.tistory.com/210


바로 그 아래에는 typedef로 선언된 함수 포인터에 대한 변수를 static으로 선언되었음을 확인하실 수 있습니다.


 static TCamConnectService     fnConnectService;


 이제 여기서 다시 앞에서 확인하였던 선언문을 다시 한 번 보도록 합니다.


TCamConnectService fnConnectService = TCamTraits::fnConnectService;


 위 소스코드는 TCamConnectService로 선언된 typedef 함수 포인터를 가진 변수명 fnConnectService 안에 TCamTratis::fnConnectService 함수의 주소값을 넣겠다는 의미로 이해해 주시면 되겠습니다. 그렇다면 여기서 TCamTratis::fnConnectService 함수는 어떻게 구현되었는지 찾아보도록 하겠습니다.


/frameworks/av/camera/Camera.cpp

1
2
CameraTraits<Camera>::TCamConnectService CameraTraits<Camera>::fnConnectService =
        &ICameraService::connect;
cs


 Camera.cpp 소스 코드 내에서 'CameraTraits<Camera>::fnConnectService' 라는 이름의 포인터 함수 변수가 선언되었고 해당 포인터 함수에 ICameraService::connect 함수의 주소를 넣어준다고 이해하시면 되겠습니다.


/frameworks/av/include/camera/ICameraService.h

1
2
3
4
5
6
7
8
9
10
11
12
class ICameraService : public IInterface
{
public:
....
    virtual status_t connect(const sp<ICameraClient>& cameraClient,
            int cameraId,
            const String16& clientPackageName,
            int clientUid,
            /*out*/
            sp<ICamera>& device) = 0;
....
}
cs


/frameworks/av/camera/ICameraService.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class BpCameraService: public BpInterface<ICameraService>
{
public:
    BpCameraService(const sp<IBinder>& impl)
        : BpInterface<ICameraService>(impl)
    {
    }
....
    // connect to camera service (android.hardware.Camera)
    virtual status_t connect(const sp<ICameraClient>& cameraClient, int cameraId,
                             const String16 &clientPackageName, int clientUid,
                             /*out*/
                             sp<ICamera>& device)
    {
        Parcel data, reply;
        data.writeInterfaceToken(ICameraService::getInterfaceDescriptor());
        data.writeStrongBinder(cameraClient->asBinder());
        data.writeInt32(cameraId);
        data.writeString16(clientPackageName);
        data.writeInt32(clientUid);
        remote()->transact(BnCameraService::CONNECT, data, &reply);
 
        if (readExceptionCode(reply)) return -EPROTO;
        status_t status = reply.readInt32();
        if (reply.readInt32() != 0) {
            device = interface_cast<ICamera>(reply.readStrongBinder());
        }
        return status;
    }
....
}
cs

 드디어 우리는 connect 함수를 찾아내는 데 성공하였습니다. 이로서 함수 포인터가 정의 되는 과정을 모두 살펴보았습니다. 마지막으로 이 기나긴 여정을 코드로 간결하게 요약하자면 다음과 같습니다.


TCamConnectService fnConnectService = TCamTraits::fnConnectService;


위 코드는 아래와 같이 변동이 됨을 확인하실 수 있습니다.


status_t (ICameraService::*TCamConnectService)(const sp<ICameraClient>&,

                                                           intconst String16&, int,
                                                           /*out*/
                                                           sp<ICamera>&);

TCamConnectService =
&ICameraService::connect;



 위의 코드를 처음 보시는 분들은 이 시점에서도 모두 이해가 안 되실 수 있습니다. 하지만 위 코드에서 일정 부분 만이라도 이해하셨다면 여러분들은 성공하신 겁니다!


300x250

컴퓨터가 대기모드 되자마자 바로 풀릴 때 해결방법

공대생의 팁 2015. 4. 5. 16:41

 최근에 노트북에 문제가 생긴 듯 하여 프로그램을 업데이트 하는 김에 BIOS를 최신버전으로 교체한 후 컴퓨터를 껏다 켜보았습니다. 이전과는 그다지 달라진 점은 보이지 않았습니다만 평상시 때와 같이 노트북을 닫았는데 이상하게도 노트북이 대기모드에 들어가자마자 바로 풀리는 것이 아닙니까! 그것도 노트북이 접혀있는 상태 그대로 말이지요!



 참으로 골치가 아파오더군요. 기껏 새로운 BIOS를 적용시켜 줬건만 평소에 쓰던대로 못하게 된게 참으로 골치가 아파오더군요. 원인이 대체 무엇인지 알 수 없어 일단은 절전모드 설정과 관련된 자료들을 살펴보았습니다.


 우선 자신의 컴퓨터에 절전모드에서 깨어나게 하는 기능들에 대해 알아보도록 합니다. 먼저 컴퓨터의 CMD창을 열어서 다음과 같이 검색해 주시면 되겠습니다. CMD는 Windows7 기준으로 시작메뉴를 누른 후 'cmd'라고 입력하시면 되겠습니다.


powercfg -devicequery wake_armed



 일단 위와 같이 자신의 컴퓨터를 깨우게 되는 기능들이 쭉 나오고 있습니다. HID의 경우 거의 대부분이 자신의 컴퓨터에 연결된 마우스로 추정하시면 될 듯 합니다.


 일단 제 컴퓨터의 상황상 마우스가 범인일 확률이 높아졌습니다. 안그래도 마우스가 고장나서 최근에 새로 구매했었는데 왠지 새로 구매한 마우스에 해당 기능을 가지고 있는 듯 해 보이는 것이었지요. 그래서 노트북에서 마우스의 연결을 해제한 후 절전모드에 들어가 보았습니다.


 이럴수가! 역시나 마우스가 범인이었습니다! 마우스를 빼고 절전모드에 돌입하더니 노트북 덮개를 열기 전까지 컴퓨터는 절전모드를 계속 유지하고 있음을 확인하였습니다.


 그렇다면 이제부터는 마우스가 연결된 상태에서도 노트북이 절전모드에서 깨어나지 않도록 한다면 문제를 해결할 수 있을 듯 합니다! 그래서 이번에는 마우스가 절전모드에 개입하지 않도록 설정하는 방법에 대해 알아보도록 합니다!


 먼저 시작메뉴를 여신 후 자신의 컴퓨터 속성 메뉴로 들어갑니다. 시작메뉴를 클릭하신 후 '컴퓨터' 메뉴에서 오른쪽 마우스 클릭 후 '속성(R)'을 클릭하시거나 'Windows키 + pause' 버튼을 동시에 누르면 바로 보실 수 있습니다.


그 다음 장치괸리자로 들어갑니다.



그 다음 장치관리자에서 자신의 기기에 해당하는 부분을 마우스 오른쪽을 클릭하신 후 '속성(R)'을 선택합니다.



'전원관리' 탭으로 이동하신 후 '이 장치를 사용하기 위해 컴퓨터의 대기 모드를 종료할 수 있음(O)'의 체크를 해제하신 후 확인 버튼을 눌러줍니다.



이제 자신의 컴퓨터에서 설정된 대기모드들을 확인해 봅니다.

powercfg -devicequery wake_armed



 보시는 바와 같이 HID가 해제되어있는 것을 확인하실 수 있습니다. 이제 자신의 컴퓨터의 절전모드가 문제없이 동작되는 것을 확인하실 수 있으실 겁니다!



300x250

우분투에서 인터넷창을 통해 윈도 미디어 플레이어 관련 영상 보는 방법

공대생의 팁 2015. 4. 4. 02:07

 불과 10년 전만 해도 인터넷 환경으로 동영상을 인터넷 창을 통해 실시간으로 감상하는 일은 쉽지 않았습니다. 지금에 비해 인터넷 속도가 고화질의 동영상을 실시간으로 받아보기에는 매우 느렸으며, 동영상 스트리밍을 운영하는 사이트들은 상당한 트래픽 압박 때문에 동영상을 전문적으로 하려던 사이트는 거의 전무하다 싶었지요.

 하지만 2005년 2월 유튜브가 인터넷 세계에 등장하면서 부터 인터넷 창을 통한 실시간 동영상 서비스라는 매력적인 사업이 발전하기 시작하였습니다. 유튜브가 등장한 이래로 한국 내에서도 mncast 등 동영상 스트리밍을 전문으로 하는 사이트들이 우후죽순 생겨났으나 위에서 어마어마한 트래픽을 감당하기엔 비용을 감당하기 어려워 순식간에 역사속에서 사라지고 맙니다. 그 덕분에 국내에서 동영상을 전문으로 다루는 사이트는 사실상 유튜브 뿐이라 해도 할 말이 없는 상황이 되어 버렸습니다.



 유튜브가 이렇게 성공할 수 있었던 이유는 사용자의 컴퓨터 환경에 전혀 구애받지 않고 어디에서든 재생할 수 있는 flv라는 플래시 비디오 기법을 통해 저용량으로 간편하게 동영상을 공유하는 방법을 사용하였습니다. 그 덕분에 이전에 AVI와 같은 동영상을 통째로 제공하던 사이트들은 이러한 간편함에 매료되어 유튜브를 사용하게 되었고 그 덕에 현재 유튜브는 전 세계 사람들이 동영상을 공유하기 위해 찾아오는 사이트로 우뚝 서게 되었습니다.


 .. 아 서론이 참으로 길었습니다. 두말하면 잔소리가 되겠습니다만 유튜브는 참으로 훌륭한 동영상 공유 사이트가 아닌가 싶습니다. 이렇게 현대에는 유튜브와 같이 동영상을 편하게 감상할 수 있는 환경이 구축되어 있는 경우가 많습니다만, 과거에 만들어진 국내의 몇몇 사이트들은 현재까지도 자체 동영상을 웹페이지에 직접 제공하는 사례가 종종 있습니다. Windows 운영체제를 사용하시는 분들이라면 약간의 대기시간만 있다면 동영상을 그나마 보실 수 있습니다만 우분투와 같은 리눅스의 경우 Windows 기반으로 만들어진 동영상을 보는게 쉽지 않습니다. 동영상을 시청하려 하게 되면 다음과 같은 화면이 우리를 맞이해 주기 때문이지요.



 그렇습니다! 과거의 사이트들은 거의 대부분이 Windows 환경의 컴퓨터를 기준으로 하여 사이트를 만들다 보니 위와 같이 Windows Media Player 기반의 영상들은 바로 볼 수 없는 불편함을 가지고 있습니다. 그렇다면 이제 우분투를 통해 이러한 영상을 볼 수 있는 방법에 대해 알려드리도록 하겠습니다.


 먼저 자신의 우분투 환경에 맞는 우분투 소프트웨어 센터를 실행합니다.

위 화면에서 마우스 커서가 있는 검색창 부분을 다음과 같이 입력해주세요.

'GStreamer'



 검색시 다음과 같은 화면을 보실 수 있습니다. GStreamer는 Windows Media Player 환경에서 실행할 수 있는 동영상을 linux에서도 실행할 수 있도록 해줍니다. 이제 위에 보이는 저 3개를 모두 설치해 줍니다.



먼저 첫 번째인 'GStreamer ffmpeg' 비디오 플러그인을 설치해주신 다음



나머지 2개의 코덱도 설치해주시면 되겠습니다.


설치후 인터넷창을 종료하신후 다시 실행하시면 동영상이 원활하게 돌아가고 있는 것을 보실 수 있을 것입니다.




300x250

구글 애드센스에 로그인 했더니...

흔치않은일상 2015. 4. 3. 01:06

 블로그를 오픈한지 어느덧 9달이 되어가고 있습니다. 처음에는 개인의 공부 자료실로 만들어 보고자 시작하였던 블로그였습니다. 처음엔 일주일에 두세 분 정도 오실까 말까 할 정도로 평범했던 블로그였습니다만 제가 안드로이드에 입문하면서 제가 배우게 된 내용들을 정리해서 글을 올리기 시작하니 하루하루 방문객을 늘어나기 시작하더니 현재는 평일에 하루 100분이 다녀가는 블로그가 되어버렸습니다.. 사실 처음엔 별 생각 없었습니다만 하루하루 손님이 늘어나니 기분이 좋아지더군요 ㅎㅎ



 블로그를 통해 약간의 부수익을 나름 창출해 보고자 구글의 애드센스를 시작한지 약 반년이 되어가는 시점이었습니다. 마침 방문하시는 분들도 늘어가니 애드센스의 수익류를 확인해 보고자 애드센스에 로그인을 해보았습니다. 그러자 이전에는 본 적도 없던 붉은 색의 바가 나타나며 마치 경고문을 보는 듯한 메시지가 눈에 띄더군요.


 '주소를 확인하지 않아 지급이 보류 중입니다 [확인]'



 비록 파워블로거 분들 처럼 하루 1만명이 오는 규모의 블로그는 아니기에 큰 수익을 기대하고 지내지는 않았습니다. 나중에 블로그 문 닫을 때 남는 돈으로 안주나 해볼까 했었는데 어느덧 10달러를 돌파해 있는 광경을 보게 되었습니다. 그렇습니다. 구글 애드센스는 수익이 10달러를 넘기는 시점 즈음부터 수익금을 받을 수 있게 되는 것이지요. 이를 위해 주소 인증을 해야 하는데 제가 드디어 그 과정에 돌입하게 된 것이지요!


 정황을 보니 구글에서는 2015년 3월 30일 저에게 편지를 보낸 것으로 보입니다. 이제 구글에서 오게 될 쪽지를 하루하루 기다리는 낙도 참 재미있을 것만 같습니다. ^^


 무엇보다도 제게 이러한 기쁨을 선사해주신 제 블로그 방문자 분들께 진심으로 감사 말씀 드립니다! 비록 필력이 모자라 설명하는데에 가끔 한계를 경험하기도 합니다만 꾸준히 글을 쓰면서 연습해 나가겠습니다. 아무쪼록 제 부족한 정보들에 도움이 되섰다면 저야말로 여러분들께 감사드리고 싶습니다!

 

300x250