Protobuf 사용법 - string 필드 사용 시 발생한 LNK2001 오류 해결
1. Protobuf란?
Protobuf (Protocol Buffers)는 Google에서 개발한 언어 중립적이며, 플랫폼 독립적인 데이터 직렬화(Serialization) 포맷
쉽게 말해, 데이터를 작고 빠르고 안정적으로 저장하거나 네트워크로 주고받기 위해 사용하는 포맷이자 기술
2. Protobuf의 핵심 특징
1) 바이너리 포맷
- 사람이 읽을 수는 없지만, 기계에겐 훨씬 빠르고 효율적
2) 엄격한 스키마 기반
- .proto 파일에서 필드 타입, 순서, 번호를 지정합니다. 이로 인해 정확하고 예측 가능한 직렬화가 가능
3) 버전 호환성
- 새 필드를 추가하거나 삭제해도 기존 버전과 충돌 없이 호환이 됩니다. 필드 번호를 기반으로 구분하기 때문
4) 다양한 언어 지원
- C++, C#, Java, Python, Go, Rust, Dart, JavaScript 등 다수 언어 지원
3. 사용법
프로젝트에 적용하기 위해선 다음의 과정을 거쳐야한다.
1. https://github.com/protocolbuffers/protobuf/releases 에서 원하는 버전을 다운. 난 v21.12를 받았다.
Win64, Source Code 2개를 다운 받는다.
2. Win64에서 protoc.exe파일을 가지고 실행에 필요한 bat 파일을 만든다. 나중에 c++과 c#으로 둘다 사용할 예정이라 다음과 같이 작성했다.
@echo off
set PROTOC=protoc.exe
set PROTO_DIR=%~dp0protos
set OUT_CPP=generated\cpp
set OUT_CS=generated\csharp
if not exist %OUT_CPP% mkdir %OUT_CPP%
if not exist %OUT_CS% mkdir %OUT_CS%
echo Generating C++ code...
%PROTOC% --proto_path=%PROTO_DIR% --cpp_out=%OUT_CPP% %PROTO_DIR%\*.proto
echo Generating C# code...
%PROTOC% --proto_path=%PROTO_DIR% --csharp_out=%OUT_CS% %PROTO_DIR%\*.proto
echo Done.
pause
같은 경로에 있는 .proto 파일을 실행하여 c++/c#으로 파일을 만들어주는 bat 파일
3. 만들어진 파일을 프로젝트에 적용하여 사용한다. 이때 lib, dll, include 파일을 따로 가져와야하는데, include 파일은 내 기준으로 'protobuf-21.12\src\google' 에 있는 google 폴더를 통째로 가져오면 된다. protobuf-21.12 파일은 다운받은 Source Code를 의미한다.
lib, dll 파일은 protobuf-21.12 를 Cmake로 빌드하여 가져오면 된다.
4. 문제 상황
게임 서버 개발 중 Google의 Protobuf (Protocol Buffers)를 사용해 통신 프로토콜을 정의하고 있었고,
.proto 파일에 string 필드를 추가한 뒤 C++ 코드로 컴파일한 뒤 빌드하자 다음과 같은 링크 오류가 발생했다.
오류 LNK2001: 확인할 수 없는 외부 기호
google::protobuf::internal::fixed_address_empty_string
int32, bool, enum 등 다른 타입은 아무 문제 없이 잘 동작했지만,
string 필드만 포함되면 이 오류가 발생했다.
5. 원인 분석
이 문제는 string 필드를 사용할 경우, Protobuf 내부에서 google::protobuf::internal::fixed_address_empty_string 이라는 정적 심볼(symbol) 을 사용한다. 이 심볼은 libprotobuf.lib에 정의되어 있어야 하며, 링크 시 런타임 설정이 정확히 일치해야만 인식된다.
다시 말해, 오류의 진짜 원인은 런타임 라이브러리 설정 불일치다.
6. 해결 방법
프로젝트 속성 > C/C++ > 코드 생성 > 런타임 라이브러리
/MD 릴리즈 DLL
/MDd 디버그 DLL
/MT 릴리즈 정적
/MTd 디버그 정적
→ 나의 프로젝트는 /MD, libprotobuf.lib는 /MT로 빌드돼서 충돌 발생
CMake로 런타임 설정에 맞춰 Protobuf 재빌드
Release용 /MD 설정:
cmake .. -G "Visual Studio 17 2022" ^
-DCMAKE_BUILD_TYPE=Release ^
-Dprotobuf_BUILD_SHARED_LIBS=OFF ^
-DCMAKE_CXX_FLAGS_RELEASE="/MD" ^
-Dprotobuf_BUILD_TESTS=OFF
cmake --build . --config Release
Debug용 /MDd 설정:
cmake .. -G "Visual Studio 17 2022" ^
-DCMAKE_BUILD_TYPE=Debug ^
-Dprotobuf_BUILD_SHARED_LIBS=OFF ^
-DCMAKE_CXX_FLAGS_DEBUG="/MDd" ^
-Dprotobuf_BUILD_TESTS=OFF
cmake --build . --config Debug
생성된 lib 파일을 적용후, 프로젝트 속성 > C/C++ > 코드 생성 > 런타임 라이브러리에서 debug, release를 각각 /MDd, /MD로 설정하니 문제 없이 해결되었다.
+ 추가 문제
LNK2005: std::basic_streambuf<char>::eback() const 이(가) msvcprtd.lib(MSVCP140D.dll)에 이미 정의되어 있습니다.
위와 같은 오류가 발생하였고, 이는 현재 프로젝트(3DFPSServer)는 /MDd (동적 디버그 런타임)으로 설정되어 있고,
libprotobufd.lib은 /MTd (정적 디버그 런타임)으로 빌드된 것이라, C++ 런타임 구현이 중복 정의된 것이다.
eback()은 MSVC의 std::streambuf 관련 런타임 구현 중 하나이며, 두 개의 런타임이 동시에 링크되면서 충돌하는 것
이는 라이브러리 전체가 /MDd로 빌드되었다 해도, 일부 소스 파일이 /MTd 옵션으로 빌드되었을 수 있기 때문이다.
CMake를 사용할 때 CMakeCache.txt 파일을 보면 protobuf_MSVC_STATIC_RUNTIME:BOOL=ON 옵션이 켜져있는걸 볼 수 있는데, 이는 Protobuf 내부적으로 정적 런타임(/MT, /MTd)을 강제로 쓰도록 만드는 설정으로 이 값이 ON이면, 위의 /MD, /MDd 설정이 무시될 수 있다.
고로 명시적으로 꺼야한다. -Dprotobuf_MSVC_STATIC_RUNTIME=OFF
그럼 최종 코드는 다음과 같다.
Relese 예제
mkdir -p build_release && cd build_release && cmake ../protobuf -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS_RELEASE="/MD" -DCMAKE_CXX_FLAGS_RELEASE="/MD" -Dprotobuf_BUILD_SHARED_LIBS=OFF -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF && cmake --build . --config Release
Debug 예제
mkdir -p build_debug && cd build_debug && cmake ../protobuf -G "Visual Studio 17 2022" -A x64 -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS_DEBUG="/MDd" -DCMAKE_CXX_FLAGS_DEBUG="/MDd" -Dprotobuf_BUILD_SHARED_LIBS=OFF -Dprotobuf_BUILD_TESTS=OFF -Dprotobuf_MSVC_STATIC_RUNTIME=OFF && cmake --build . --config Debug
이로서 완벽하게 해결되었다.
+ 추가 문제
protobuf는 윈도우 환경에 따라 만들어지는 .lib 파일이 다르다. 고로 윈도우 10, 윈도우 11에서 각각 빌드하여 만들어진 .lib들을 환경에 따라 다르게 설정해야한다.