정의란?

C++에서 정의는 객체를 "실체"로 만들겠다는 뜻이다.

실체로 만들겠다 라는 것은 메모리에 할당하는 것을 의미한다. 메모리 상에 유효한 비트로 만들겠다는 것이다.

#include <iostream>

class CTest;

int g_Val;
int g_Arr[3];
CTest g_T;

int main()
{
	
    int v;
    int arr[3];
}

int Add(int a, int b)
{
	
    return a + b;
}

main 위에 정의한 전역변수는 데이터 영역에 , main 안에 있는 지역변수 스택 영역에 , Add함수 자체도 코드영역에 들어가기 때문에 모두 실체화 한거라고 볼 수 있고, 정의했다고 말할 수 있다.

이런 변수나 함수는 프로세스가 사용하는 가상 메모리이다.

class CTest
{
public:
	int m_Value;
    int MFunc();
}

이런 클래스 정의 자체는 프로세스와 상관 없이 컴파일러가 사용하는 메모리이다.

(함수도 같은거 아닌가? 둘 다 코드영역 아닌가? 함수는 포인터 형태로 프로세스가 사용하기 때문에 성격이 다른건가? 클래스 코드 자체는 컴파일 할 때만 참고하기 때문에 성격이 다르다고 하는건가?)

 

선언이란?

위에서 봤던 클래스, 전역변수, 지역변수, 함수 모두 선언한 것이다.

정의라고 해놓고 또 선언이란다. 무슨말이냐?

이유는 정의는 선언을 포함하기 때문이다. 정의를 한 순간 해당 개체는 알려지므로(선언되므로) 따로 선언해 줄 필요가 없다.

그럼 정의만 있어도 되는 거 아니냐?

선언이 꼭 필요한 경우가 있음.

// A.cpp
int g_Val;
int Func()
{
	return 1;
}
// Main.cpp
void main()
{
	g_Val = Func();
}

이렇게 사용하면 컴파일 에러가 발생한다.

g_Val과 Func()는 정의와 동시에 선언 되었지만, A.cpp 에 한정된다.

main도 g_Val과 Func()에 대해 알 수 있게 해주어야 한다.

// Main.cpp

extern int g_Val;
int Func();

void main()
{
	g_Val = Func();
}

따로 헤더파일을 만들어 include를 해주지 않아도, 각 cpp 파일이 obj파일로 만들어지고 심볼릭 테이블에서 정의를 찾아 매핑해주기 때문에 이 코드로도 컴파일/실행이 가능하다.

#include "A.cpp"

void main()
{
    g_Val = Func();
}

이렇게 하면 되는거 아니야?

아니다. 링킹 오류가 난다.

include는 전처리과정에서 복붙된다.

그래서 최종 obj 파일을 링킹할 때 정의가 2개씩 존재하기 때문에 링커가 선택할 수 없다.

extern & static

extern 외부에서 사용할 수 있음을 알려주고, static은 사용 범위가 한정된다는 의미를 알려준다.

extern은 선언할 때만 사용하는 것이 아니라, 정의할 때도 사용된다.

extern int a; // 선언만 됨.
extern int b = 3; // 선언과 정의가 동시에 됨.

int c; // 선언과 정의가 동시에 됨.
int d = 3; // 선언과 정의가 동시에 됨.

일반적으로 `extern int b = 3;`은 `int b = 3;`과 의미가 똑같기 때문에 생략한다.

// A.cpp
extern int g_Val;

// Main.cpp
#include <iostream>
extern int g_Val;

void main()
{
	printf("%d", g_Val);
}

그래서 이렇게 선언만 있는 경우, 링킹 오류가 발생한다. 심볼을 찾을 수 없다고 나온다.

클래스 선언과 정의

// A.cpp
class CTest
{
public:
	int m_Val;
};

// Main.cpp
class CTest; // 선언
void main()
{
	CTest t; // Compile Error
}

12 라인에서 컴파일 에러가 난다.

왜일까?

값형은 스택에 할당되고, 스택의 크기는 컴파일 시점에 결정된다.

CTest는 Main.cpp 입장에서는 선언만 돼있다. 어떤 멤버가 있는지, 크기가 몇인지는 알 수 없다.

그렇기 때문에 힙에 할당되는 포인터 형은 가능하다.

class CTest;
void main()
{
	CTest* pT;
}

사실 힙이라기보단 포인터 타입이 OS에 따라 4바이트 혹은 8바이트로 정의되어있기 때문이라고 할 수 있겠다.

해결 방법은?

Main.cpp에도 CTest를 정의해주면 된다. 클래스는 중복 정의를 허용한다.

그래서 보통 헤더 파일에 클래스 정의를 만들고, cpp 파일에서 인클루드 해서 사용한다.

중복 정의를 할 수 있기 때문에 중복된 정의가 다른 경우 문제가 발생할 수 도 있다.

클래스 크기는 같지만, 멤버 함수 순서가 달라 메모리 할당 순서가 바뀌는 경우

 

#define 이 왜?

1. 상수를 정의할 때,

디버깅이 안된다.

왜 디버깅이 안될까?

=> 컴파일 시점에 상수로 바꾼다.

=> symbol table에 들어가지 않는다.

요새는 ide가 해주겠지만, 에러 로그 등에서 확인할 때, 상수명이 아니라 153.2121 같이 숫자 자체가 찍히기 때문에 디버깅하기 어려워 질 수 있다.

#include <iostream>
#define ASPECT_RATIO 1.653

void func(float a);

int main()
{
    if (ASPECT_RATIO > 2) {
        // do someting;
    }

    // 1000줄 코드...

    func(ASPECT_RATIO);

    // 1000줄 코드...

    std::cout << ASPECT_RATIO;
}

이 코드에서 translation unit을 만들면 어떻게 될까?

clang -E fileName.cpp > fileName.pre

이런 식으로 상수로 바뀐다.

symbol table은 무엇일까?

assembler의 symbol table, compiler의 symbol table이 따로 존재한다. 지금은 compiler level의 symbol table을 알아보자.

- 컴파일러에 의해 생성되고 관리되는 "변수명", "함수명", objects, classes, interfaces 등에 대한 자료구조이다.

- 심볼 테이블은 symbol name, type, scope로 구성되고, 드물게 주소가 포함될 때도 있다.

- 컴파일러의 Analysis, Synthesis 단계 모두에서 해당 기호가 정의되었는지, 정확한지 검증하기 위해 사용되고, 이를 통해 중간 코드나 타겟 코드를 만들게 된다.

- 링킹과정에서 링커가 심볼 테이블을 보

- 더 자세한 내용은 아래 링크를 보자.(허수는 안보고 넘어감)

https://www.tutorialspoint.com/compiler_design/compiler_design_symbol_table.htm

https://iq.opengenus.org/symbol-table-in-compiler/

대신에 const 또는 enum을 사용해보자.

const double AspectRatio = 1.653;
class MyClass { private: enum { NumTurns = 5 } };

2. 매크로 함수를 만들때,

매크로 함수 인자로 표현식을 넣었다면, 평가를 여러번하는 오류가 발생할 수 있다.

이런 문제를 해결하기 위해 인라인 함수로 대체해보자.

매크로 처럼 효율적이고, 타입 안정성까지 취할 수 있다.

#define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
이런 매크로 함수가 있을 때,

int a = 5;
CALL_WITH_MAX(++a, 0);
CALL_WITH_MAX(++a, 10);

평가가 여러번 될 수 있다.

 

FCM Push 알림

 

FCM은 앱의 키값을 식별하여 특정 스마트폰에 데이터를 전달할 수 있다. 앱의 고유한 키 값은 Firebase에 앱을 등록하면 생성된다. 사용자가 이 앱을 설치하고 알람설정에 동의하면 기기의 FCM token을 얻을 수 있다.

 

 

작업 프로세스

  1. Manifest 수정
    • 서비스 등록
    • 권한 설정
  1. Token 생성
    • 앱이 처음으로 실행되면 자동으로 발급된다.
      • 앱이 새 기기에서 복원 될 때, 사용자가 앱을 제거/재설치할 때, 사용자가 앱 데이터를 지울 때 토큰이 변경될 수 있다.
    • FirebaseMessagingService를 상속받아 onNewToken() 을 오버라이딩 해야한다.
      • 새 토큰이 생성될 때마다 onNewToken() 콜백이 실행된다.
    /**
     * Called if the FCM registration token is updated. This may occur if the security of
     * the previous token had been compromised. Note that this is called when the
     * FCM registration token is initially generated so this is where you would retrieve the token.
     */
    override fun onNewToken(token: String) {
        Log.d(TAG, "Refreshed token: $token")
    
        // If you want to send messages to this application instance or
        // manage this apps subscriptions on the server side, send the
        // FCM registration token to your app server.
        sendRegistrationToServer(token)
    }
  1. 현재 토큰 가져오기
    • 현재 토큰을 검색해야 하는 경우 FirebaseMessaging.getInstance().getToken()을 호출한다.
    FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
        if (!task.isSuccessful) {
            Log.w(TAG, "Fetching FCM registration token failed", task.exception)
            return@OnCompleteListener
        }
    
        // Get new FCM registration token
        val token = task.result
    
        // Log and toast
        val msg = getString(R.string.msg_token_fmt, token)
        Log.d(TAG, msg)
        Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show()
    })
  1. 메세지 수신
    • FirebaseMessagingService를 상속받은 서비스 에서 onMessageReceived() 콜백을 오버라이딩 해야한다.
    override fun onMessageReceived(remoteMessage: RemoteMessage) {
        // TODO(developer): Handle FCM messages here.
        // Not getting messages here? See why this may be: https://goo.gl/39bRNJ
        Log.d(TAG, "From: ${remoteMessage.from}")
    
        // Check if message contains a data payload.
        if (remoteMessage.data.isNotEmpty()) {
            Log.d(TAG, "Message data payload: ${remoteMessage.data}")
    
            if (/* Check if data needs to be processed by long running job */ true) {
                // For long-running tasks (10 seconds or more) use WorkManager.
                scheduleJob()
            } else {
                // Handle message within 10 seconds
                handleNow()
            }
        }
    
        // Check if message contains a notification payload.
        remoteMessage.notification?.let {
            Log.d(TAG, "Message Notification Body: ${it.body}")
        }
    
        // Also if you intend on generating your own notifications as a result of a received FCM
        // message, here is where that should be initiated. See sendNotification method below.
    }
    Notification를 받는 경우, Foreground/Background에 따라 다르게 동작한다.
  1. 안드로이드 알림 생성(Data를 받는 경우)
    private fun sendNotification(title: String, text: String, requestId: Int){
          
            val intent = Intent(this, CardRequestActivity::class.java).apply {
                flags = Intent.FLAG_ACTIVITY_CLEAR_TOP and Intent.FLAG_ACTIVITY_NEW_TASK
            }
            val pendingIntent = PendingIntent.getActivity(this, 0, intent, FLAG_MUTABLE)
    
            val channelId = getString(R.string.notification_channel_id)
            val channelName = getString(R.string.default_notification_channel_name)
            val defaultSound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION)
            val notificationBuilder = NotificationCompat.Builder(this, channelId)
                .setAutoCancel(true)
                .setSound(defaultSound)
                .setContentText(text)
                .setContentTitle(title)
                .setContentIntent(pendingIntent)
                .setSmallIcon(R.drawable.bling)
            // create notification channel
            val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_HIGH)
            notificationManager.createNotificationChannel(channel)
            notificationManager.notify(0, notificationBuilder.build())
        }
    }
    • PendingIntent를 생성해서 연결될 페이지를 지정하면 된다.
     

 

[Android] FCM PUSH Notification (FCM PUSH 알림
https://koharinn.tistory.com/587

Android에서 Firebase 클라우드 메시징 클라이언트 앱 설정
https://firebase.google.com/docs/cloud-messaging/android/client?hl=ko

 

 

WebView Communication

Device Token

FirebaseMessagingService 클래스의 onNewToken(String token) 메소드를 override 하여 DeviceToken값을 얻을 수 있다.

 

FirebaseMessagingService
 
 

Communication between WebView and Native

 

  • 안드로이드에서 자바스크립트 코드 실행
    • webView.evaluateJavascript(script, resultCallback) 사용
    • 예제1. 웹뷰의 배경색 변경 (1-way communication) 
    • webView.evaluateJavascript("document.body.style.background = 'blue';", null)
    • 예제2. 네이티브 쪽으로 데이터 리턴받기
      • 익명함수를 생성할 수 도 있다.
      • 반환값은 JSON value, JSON object, JSON array등으로 오기 때문에, unwrap하는 과정이 필요하다.
      webView.evaluateJavascript(
      		"(function() { return document.getElementById('toastMessage').value; })();"
      ) { returnValue ->
      		Toast.makeText(requireContext(), returnValue, Toast.LENGTH_SHORT).show()
      }
    • 그 외에, 자바스크립트 함수를 호출하거나, 파라미터로 값을 넘길 수 있다.
      webView.evaluateJavascript("getToastMessage();") { returnValue ->
          Toast.makeText(requireContext(), returnValue, Toast.LENGTH_SHORT).show()
      }
      webView.evaluateJavascript("concatenateStrings('${arg1}', '${arg2}');") { returnValue ->
          Toast.makeText(requireContext(), returnValue, Toast.LENGTH_SHORT).show()
      }

     

    Communication with WebView Using JavaScript in Android

  • 자바스크립트에서 안드로이드 메소드 호출

    In Android

    1. 자바스크립트 사용 설정
      val myWebView: WebView = findViewById(R.id.webview)
      myWebView.settings.javaScriptEnabled = true
    1. 자바스크립트 코드를 Android 코드에 결합
      • 자바스크립트 코드와 안드로이드 코드 간에 인터페이스를 만들 수 있다.
      • 자바 스크립트에서 Android 코드의 메소드를 호출 가능하게 해준다.
        • 아래 예제에서는 Toast를 띄운다.
      class WebAppInterface(private val mContext: Context) {
      		@JavascriptInterface
      		fun showToast(toast: String) {
      				Toast.makeText(mContext, toast, Toast.LENGTH_SHORT).show()
      		}
      }
      • 웹뷰에서 실행되는 자바 스크립트에 이 클래스를 결합하고, 인터페이스의 이름을 지정할 수 있다.
        • 아래 예제에서 인터페이스의 이름을 Android로 설정하였다.
      myWebView.addJavaScriptInterface(WebAppInterface(this), "Android")

    In Web

    1. 인터페이스 사용
      • 위와 같이 자바스크립트 인터페이스를 만들었다면, WebView에서 실행되는 자바스크립트를 위한 Android라는 인터페이스가 만들어진다.
      • 자바 스크립트에서 Android라는 인터페이스를 초기화할 필요 없이 WebView가 자동으로 설정해준다.
      <input type="button" value="Say hello" onClick="showAndroidToast('Hello Android!')" />
      
      <script type="text/javascript">
          function showAndroidToast(toast) {
              Android.showToast(toast);
          }
      </script>
      • 버튼을 클릭하면, WebAppInterface.showToast()를 호출한다.
      • 인자에 원하는 값을 담아 전달할 수 있다.

     

    WebView에서 웹 앱 빌드 | Android 개발자 | Android Developers

     

 

+ Recent posts