개발 환경 준비
이 튜토리얼을 마치면 다음을 할 수 있습니다.
- SDK 묶음에서 헤더, 네이티브 라이브러리, .NET 어셈블리, 시뮬레이터의 위치를 찾습니다.
- 터미널마다 같은 환경 변수를 정합니다.
- C++와 C# 튜토리얼 예제를 SDK만으로 빌드합니다.
- 두 언어의 예제를 서로 연결하고 headless 시뮬레이터를 실행해 설치를 확인합니다.
준비물
섹션 제목: “준비물”- PDComm SECS/GEM SDK 묶음. Windows x64는 ZIP, Linux x64는 tar.gz로 제공됩니다. macOS(Apple silicon)용 빌드를 받은 경우에도 구성은 같습니다.
- C++ 컴파일러
- Windows: Visual Studio 2022의 C++ x64 도구. x64 Native Tools 또는 Developer PowerShell에서
cl을 씁니다. - Linux:
g++(C++14 이상) - macOS: Xcode Command Line Tools의
c++
- Windows: Visual Studio 2022의 C++ x64 도구. x64 Native Tools 또는 Developer PowerShell에서
- C# 예제를 빌드하려면 .NET 10 SDK. 실행만 하려면 .NET 10 런타임으로 충분합니다. 설치 방법은 Microsoft의 Windows 안내, Linux 안내, macOS 안내를 따릅니다. SDK 묶음에는 .NET SDK가 들어 있지 않습니다.
1. 묶음을 풀고 구성을 확인합니다
섹션 제목: “1. 묶음을 풀고 구성을 확인합니다”자신이 소유한 폴더에 묶음을 풉니다. 이 튜토리얼은 Windows에서 C:\pdcomm-tutorial, Linux와 macOS에서 $HOME/pdcomm-tutorial을 씁니다.
Expand-Archive .\pdcomm-secsgem-windows-x64-<version>.zip -DestinationPath C:\pdcomm-tutorialmkdir -p "$HOME/pdcomm-tutorial"tar -xzf pdcomm-secsgem-linux-x64-<version>.tar.gz -C "$HOME/pdcomm-tutorial"mkdir -p "$HOME/pdcomm-tutorial"# Extract the macOS build you received into this folder.압축을 푼 폴더는 다음과 같이 구성됩니다. 폴더 구조와 라이선스 고지(notice) 파일을 함께 유지합니다.
디렉터리pdcomm-tutorial/
디렉터리bin/ 미리 빌드된 튜토리얼 예제
- pdcomm-tutorial(.exe) C++ 예제
- PDComm.Tutorial.dll C# 예제
- PDComm.dll .NET 바인딩
디렉터리sdk/
디렉터리include/ C 헤더(
pdcomm*.h)와 C++ 래퍼(pdcomm*.hpp)- …
디렉터리lib/ 네이티브 라이브러리(
pdcomm_ffi.dll과pdcomm_ffi.dll.lib,libpdcomm_ffi.so,libpdcomm_ffi.dylib)- …
디렉터리managed/
- PDComm.dll .NET 바인딩
디렉터리examples/
디렉터리tutorial/ C++와 C# 튜토리얼 예제 소스
- …
디렉터리simulator/ GUI·headless·MCP 시뮬레이터와 의존 라이브러리
- …
sdk/lib의 네이티브 라이브러리가 SECS/GEM 구현입니다. C, C++, C# 모두 이 라이브러리를 씁니다.PDComm.dll은 .NET 바인딩 어셈블리입니다. 네이티브 라이브러리를 대신하지 않습니다.- 기능별 .NET 예제, Qt 예제, 진단 로그 예제는 별도의 예제 묶음으로 제공됩니다. 이 튜토리얼은 예제 묶음을 푼 폴더를
PDCOMM_EXAMPLES라고 부릅니다.
2. 환경 변수를 정합니다
섹션 제목: “2. 환경 변수를 정합니다”새 터미널을 열 때마다 아래 변수를 다시 정합니다. 이후 모든 튜토리얼의 명령이 이 변수를 씁니다.
$Root = 'C:\pdcomm-tutorial'$Sdk = Join-Path $Root 'sdk'$Native = Join-Path $Sdk 'lib\pdcomm_ffi.dll'$Simulator = Join-Path $Root 'simulator'$Examples = 'C:\pdcomm-examples'$env:PATH = "$Sdk\lib;$env:PATH"PDCOMM_ROOT="$HOME/pdcomm-tutorial"PDCOMM_SDK="$PDCOMM_ROOT/sdk"PDCOMM_NATIVE="$PDCOMM_SDK/lib/libpdcomm_ffi.so"PDCOMM_SIMULATOR="$PDCOMM_ROOT/simulator"PDCOMM_EXAMPLES="$HOME/pdcomm-examples"export LD_LIBRARY_PATH="$PDCOMM_SDK/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"PDCOMM_ROOT="$HOME/pdcomm-tutorial"PDCOMM_SDK="$PDCOMM_ROOT/sdk"PDCOMM_NATIVE="$PDCOMM_SDK/lib/libpdcomm_ffi.dylib"PDCOMM_SIMULATOR="$PDCOMM_ROOT/simulator"PDCOMM_EXAMPLES="$HOME/pdcomm-examples"3. 도구를 확인합니다
섹션 제목: “3. 도구를 확인합니다”dotnet --info출력의 .NET SDK 아래 Version이 10으로 시작하면 됩니다. 예를 들어 Version: 10.0.401입니다. 이 명령이 없다는 오류가 나오면 .NET SDK를 설치하고 새 터미널을 엽니다.
C++ 컴파일러는 Windows에서 cl, Linux에서 g++ --version, macOS에서 c++ --version으로 확인합니다.
4. C++ 튜토리얼 예제를 빌드합니다
섹션 제목: “4. C++ 튜토리얼 예제를 빌드합니다”New-Item -ItemType Directory -Force "$Root\build" | Out-Nullcl /nologo /W4 /WX /EHsc /std:c++14 "/I$Sdk\include" "$Sdk\examples\tutorial\pdcomm_tutorial.cpp" "$Sdk\lib\pdcomm_ffi.dll.lib" "/Fe:$Root\build\pdcomm-tutorial.exe" "/Fo:$Root\build\tutorial.obj"mkdir -p "$PDCOMM_ROOT/build"g++ -std=c++14 -Wall -Wextra -Werror -pedantic -I "$PDCOMM_SDK/include" \ "$PDCOMM_SDK/examples/tutorial/pdcomm_tutorial.cpp" \ -L "$PDCOMM_SDK/lib" -lpdcomm_ffi -o "$PDCOMM_ROOT/build/pdcomm-tutorial"mkdir -p "$PDCOMM_ROOT/build"c++ -std=c++14 -Wall -Wextra -Werror -pedantic -I "$PDCOMM_SDK/include" \ "$PDCOMM_SDK/examples/tutorial/pdcomm_tutorial.cpp" \ -L "$PDCOMM_SDK/lib" -lpdcomm_ffi -Wl,-rpath,"$PDCOMM_SDK/lib" \ -o "$PDCOMM_ROOT/build/pdcomm-tutorial"경고 없이 끝나면 성공입니다. -Werror(Windows는 /WX)는 경고를 오류로 처리하므로 헤더와 컴파일러 조합에 문제가 있으면 여기서 바로 드러납니다.
5. C# 튜토리얼 예제를 빌드합니다
섹션 제목: “5. C# 튜토리얼 예제를 빌드합니다”C# 프로젝트는 PDCommSdk 속성으로 SDK 폴더를 받아 managed/PDComm.dll을 참조합니다.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup><OutputType>Exe</OutputType><TargetFramework>net10.0</TargetFramework><Nullable>enable</Nullable><ImplicitUsings>enable</ImplicitUsings><TreatWarningsAsErrors>true</TreatWarningsAsErrors></PropertyGroup> <ItemGroup Condition="'$(PDCommSdk)' == ''"><ProjectReference Include="../../../bindings/dotnet/PDComm/PDComm.csproj" /></ItemGroup> <ItemGroup Condition="'$(PDCommSdk)' != ''"><Reference Include="PDComm"><HintPath>$(PDCommSdk)/managed/PDComm.dll</HintPath></Reference></ItemGroup></Project>dotnet build "$Sdk\examples\tutorial\PDComm.Tutorial\PDComm.Tutorial.csproj" -c Release "-p:PDCommSdk=$Sdk" --artifacts-path "$Root\build\managed"dotnet build "$PDCOMM_SDK/examples/tutorial/PDComm.Tutorial/PDComm.Tutorial.csproj" -c Release \ "-p:PDCommSdk=$PDCOMM_SDK" --artifacts-path "$PDCOMM_ROOT/build/managed"dotnet build "$PDCOMM_SDK/examples/tutorial/PDComm.Tutorial/PDComm.Tutorial.csproj" -c Release \ "-p:PDCommSdk=$PDCOMM_SDK" --artifacts-path "$PDCOMM_ROOT/build/managed"출력 끝에 Build succeeded.와 0 Warning(s), 0 Error(s)가 보이면 성공입니다. 결과는 build/managed/bin/PDComm.Tutorial/release/PDComm.Tutorial.dll입니다.
C# 예제의 HSMS·GEM 옵션은 한 파일에 모여 있습니다. 실제 장비와 연결할 때는 장비 계약서의 타이머와 한도로 바꿉니다.
using PDComm;internal static class TutorialOptions{ internal static readonly SecsLimits Limits = new(8192,4096,512,24); internal static GemFormattedProgramConfiguration Configuration() => new( new(4, 128, 512, 8, 8, 16, 32), new(4, 4, 8, 1024, 8, 8, 16, 32), new(4, 4, 8, 1024, 8, 2048, 8, 8, 16, 32), new(4, 4, 8, 1024, 8, 8, 16, 32), new(4, 32, 4, 8, 4, 32, 1024, 4096, 8, 8, 16, 32), new(4, 128, 512, 8, 8, 8, 16, 32)); internal static GemFormattedProgramCapabilityCommand Capability() => new("RUN", "Run", true, 0, [], [], [], 0, [], [], []); internal static HsmsOptions Transport(HsmsMode mode) => new() { Mode = mode, SessionId = 7, InitialSystemBytes = 1, T3Milliseconds = 2000, T5Milliseconds = 20, T6Milliseconds = 2000, T7Milliseconds = 5000, T8Milliseconds = 2000, ConnectTimeoutMilliseconds = 100, ShutdownTimeoutMilliseconds = 1000, WriteTimeoutMilliseconds = 2000, MaxFrameBytes = 8206, MaxEvents = 128, MaxQueuedBytes = 65536, MaxWriteFrames = 64, MaxWriteBytes = 65536, MaxIoChunk = 4096, MaxApplicationEvents = 128, MaxApplicationBytes = 65536, PollBudget = 32, ItemLimits = Limits }; internal static GemHostOptions HostOptions() => new() { MaxPendingIds = 64, ItemLimits = Limits }; internal static GemEquipmentOptions EquipmentOptions() => new() { Model = "PDComm", SoftwareRevision = "0.1.0", InitialControl = GemControlState.HostOffline, OnlineMode = GemOnlineMode.Remote, MaxVariables = 8, MaxMetadataBytes = 1024, MaxValueBytes = 8192, ItemLimits = Limits }; internal static HsmsWorkerOptions WorkerOptions() => new() { CommandCapacity = 32, MaxCommandBytes = 65536, EventCapacity = 32, MaxEventBytes = 131072, PollIntervalMilliseconds = 1, ShutdownTimeoutMilliseconds = 2000 };}6. 두 언어의 예제를 서로 연결합니다
섹션 제목: “6. 두 언어의 예제를 서로 연결합니다”C++ 장비와 C# 호스트를 자동 모드로 연결해 SDK, 네이티브 라이브러리, .NET 바인딩이 함께 동작하는지 확인합니다. 첫 번째 터미널에서 장비를 시작합니다.
"$PDCOMM_ROOT/build/pdcomm-tutorial" equipment passive 127.0.0.1:5000 --auto두 번째 터미널에서 C# 호스트를 시작합니다.
dotnet "$PDCOMM_ROOT/build/managed/bin/PDComm.Tutorial/release/PDComm.Tutorial.dll" "$PDCOMM_NATIVE" host active 127.0.0.1:5000 --autoWindows에서는 & "$Root\build\pdcomm-tutorial.exe" ...와 dotnet "$Root\build\managed\bin\PDComm.Tutorial\release\PDComm.Tutorial.dll" "$Native" ...로 바꿉니다.
C# 호스트는 결과마다 숫자와 함께 이름을 출력합니다.
RESULT kind=2 request=1 outcome=2 status=CommunicationEstablishedSECS-II 0102210100010241065044436F6D6D4105302E312E30RESULT kind=2 request=2 outcome=4 status=EquipmentIdentitySECS-II 010241065044436F6D6D4105302E312E30RESULT kind=2 request=3 outcome=5 status=ValuesReceivedSECS-II 0102910441AC000041074C4F542D303031PASS tutorial host active장비 쪽 마지막 줄은 PASS tutorial equipment passive입니다. 두 프로그램 모두 종료 코드 0으로 끝납니다.
7. 시뮬레이터를 확인합니다
섹션 제목: “7. 시뮬레이터를 확인합니다”headless 시뮬레이터는 GUI 없이 호스트와 장비 한 쌍을 내부에서 만들어 시나리오를 실행합니다. 설치 확인용 smoke 시나리오를 실행합니다.
& "$Simulator\pdcomm-simulator-headless.exe" --scenario smoke$LASTEXITCODE"$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario smoke; echo "exit=$?""$PDCOMM_SIMULATOR/pdcomm-simulator-headless" --scenario smoke; echo "exit=$?"한 줄에 JSON 객체 하나씩 출력하고, 마지막 줄이 요약(summary)입니다.
{"schema":1,"sequence":1,"scenario":"smoke","scope":"pair","kind":"start","detail":"scenario-started","truncated":false}...{"schema":1,"sequence":6,"scenario":"smoke","scope":"pair","kind":"summary","outcome":"passed","exit_code":0,"elapsed_ms":10,"records_emitted":6,"max_records":128,"truncated":false,"status_names":0,"status_values":0}"outcome":"passed", "exit_code":0이고 프로세스 종료 코드도 0이면 설치가 끝났습니다. elapsed_ms 값은 실행할 때마다 다릅니다.
자주 생기는 오류
섹션 제목: “자주 생기는 오류”| 증상 | 원인과 해결 방법 |
|---|---|
Windows에서 VCRUNTIME140.dll 또는 MSVCP140.dll이 없다는 오류 |
x64 Visual C++ 재배포 패키지가 없습니다. 설치하고 다시 실행합니다. |
Linux에서 error while loading shared libraries: libpdcomm_ffi.so |
현재 터미널에 LD_LIBRARY_PATH를 정하지 않았습니다. 2단계를 다시 실행합니다. |
macOS에서 Library not loaded: @rpath/libpdcomm_ffi.dylib |
-Wl,-rpath 없이 빌드했습니다. 4단계의 macOS 명령으로 다시 빌드합니다. |
C#에서 System.DllNotFoundException: Unable to load shared library ... |
첫 인자의 네이티브 라이브러리 경로가 틀렸습니다. PDCOMM_NATIVE가 실제 파일을 가리키는지 확인합니다. 운영체제와 CPU 아키텍처가 맞는 라이브러리인지도 확인합니다. |
C#에서 NotSupportedException |
PDComm.dll과 네이티브 라이브러리의 버전이 맞지 않습니다. 같은 묶음의 두 파일을 함께 씁니다. |
Usage: PDComm.Tutorial <absolute-library-path> ...와 종료 코드 2 |
인자 개수나 값이 틀렸습니다. 역할은 host 또는 equipment, 모드는 active 또는 passive입니다. |
| Qt 플랫폼 플러그인을 찾지 못한다는 오류 | simulator 폴더 전체를 함께 복사하지 않았거나 다른 Qt 설치의 QT_PLUGIN_PATH가 정해져 있습니다. 폴더를 그대로 두고 그 변수를 지운 뒤 실행합니다. |
자동 모드는 스스로 종료합니다. 빌드 결과가 필요 없으면 build 폴더를 지웁니다.
다음 단계
섹션 제목: “다음 단계”첫 통신에서 대화형 명령으로 통신 상태를 하나씩 바꿔 보고 시뮬레이터 GUI와 연결합니다.