├── .config └── dotnet-tools.json ├── .github └── workflows │ └── CI.yml ├── .gitignore ├── .gitmodules ├── .vscode └── tasks.json ├── Host.sln ├── Host ├── CMakeLists.txt ├── Common.h ├── Host.cpp ├── Host.vcxproj ├── Host.vcxproj.filters ├── Test_DllImport.h ├── Test_ManagedEntryPoint.h ├── Test_ManagedFunctionPointer.h ├── Test_ManagedString.h ├── Test_ManagedUnsafe.h ├── Test_NativeArray.h ├── Test_NativeExport.h ├── Test_NativeFunctionPointer.h ├── Test_NativeString.h └── Test_NativeVTable.h ├── LICENSE ├── Lib ├── ArrPointer.cs ├── CString.cs ├── Lib.csproj ├── ModuleLoader.cs ├── Test_DllImport.cs ├── Test_ManagedEntryPoint.cs ├── Test_ManagedFunctionPointer.cs ├── Test_ManagedString.cs ├── Test_ManagedUnsafe.cs ├── Test_NativeArray.cs ├── Test_NativeExport.cs ├── Test_NativeFunctionPointer.cs ├── Test_NativeString.cs ├── Test_NativeVTable.cs └── UnmanagedMemory.cs ├── README.md ├── README.source.md ├── build_run.bat └── build_run.sh /.config/dotnet-tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "isRoot": true, 4 | "tools": { 5 | "MarkdownSnippets.Tool": { 6 | "version": "23.1.4", 7 | "commands": [ 8 | "mdsnippets" 9 | ] 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /.github/workflows/CI.yml: -------------------------------------------------------------------------------- 1 | # This workflow sets up and runs MSBuild 2 | # to build and test a Visual Studio solution. 3 | 4 | name: Build and Test 5 | 6 | on: 7 | push: 8 | branches: 9 | - master 10 | paths-ignore: 11 | - 'README.md' 12 | pull_request: 13 | paths-ignore: 14 | - 'README.md' 15 | 16 | jobs: 17 | 18 | run-msbuild: 19 | runs-on: windows-latest 20 | name: Run Windows 21 | 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v2.1.0 25 | id: checkout_code 26 | with: 27 | submodules: recursive 28 | 29 | - name: Setup MSBuild path 30 | uses: microsoft/setup-msbuild@v1 31 | id: setup_msbuild 32 | 33 | - name: Setup dotnet path 34 | uses: actions/setup-dotnet@v1 35 | with: 36 | dotnet-version: '6.0.x' 37 | 38 | - name: Run build_run 39 | id: run_msbuild 40 | shell: cmd 41 | run: .\build_run.bat 42 | 43 | run-linux: 44 | runs-on: ubuntu-latest 45 | name: Run Linux 46 | 47 | steps: 48 | - name: Checkout code 49 | uses: actions/checkout@v2.1.0 50 | id: checkout_code 51 | with: 52 | submodules: recursive 53 | 54 | - name: Setup cmake 55 | uses: jwlawson/actions-setup-cmake@v1.9 56 | with: 57 | cmake-version: '3.16.x' 58 | 59 | - name: Setup dotnet path 60 | uses: actions/setup-dotnet@v1 61 | with: 62 | dotnet-version: '6.0.x' 63 | 64 | - name: Run build_run 65 | run: sh build_run.sh 66 | 67 | run-macos: 68 | runs-on: macos-latest 69 | name: Run MacOS 70 | 71 | steps: 72 | - name: Checkout code 73 | uses: actions/checkout@v2.1.0 74 | id: checkout_code 75 | with: 76 | submodules: recursive 77 | 78 | - name: Setup cmake 79 | uses: jwlawson/actions-setup-cmake@v1.9 80 | with: 81 | cmake-version: '3.16.x' 82 | 83 | - name: Setup dotnet path 84 | uses: actions/setup-dotnet@v1 85 | with: 86 | dotnet-version: '6.0.x' 87 | 88 | - name: Run build_run 89 | run: sh build_run.sh 90 | 91 | readme: 92 | runs-on: ubuntu-latest 93 | name: Generate README.md 94 | 95 | steps: 96 | - name: Checkout code 97 | uses: actions/checkout@v2.1.0 98 | id: checkout_code 99 | 100 | - name: Setup dotnet path 101 | uses: actions/setup-dotnet@v1 102 | with: 103 | dotnet-version: '5.0.x' 104 | 105 | - name: update snippets 106 | run: | 107 | dotnet tool restore 108 | dotnet mdsnippets ./ -e ./dotnet_runtime 109 | 110 | - name: update toc 111 | run: | 112 | curl https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc -o gh-md-toc 113 | chmod a+x gh-md-toc 114 | ./gh-md-toc --insert --no-backup --hide-footer README.md 115 | 116 | - name: push to remote 117 | continue-on-error: true 118 | run: | 119 | git config --global user.name "github-actions[bot]" 120 | git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com" 121 | 122 | git add -A 123 | git commit -m "Update README.md" 124 | git push -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | gh-md-toc 2 | 3 | .vs 4 | bin 5 | Debug 6 | 7 | .idea 8 | 9 | cmake-build-* 10 | 11 | build 12 | 13 | # Prerequisites 14 | *.d 15 | 16 | # Compiled Object files 17 | *.slo 18 | *.lo 19 | *.o 20 | *.obj 21 | 22 | # Precompiled Headers 23 | *.gch 24 | *.pch 25 | 26 | # Compiled Dynamic libraries 27 | *.so 28 | *.dylib 29 | *.dll 30 | 31 | # Fortran module files 32 | *.mod 33 | *.smod 34 | 35 | # Compiled Static libraries 36 | *.lai 37 | *.la 38 | *.a 39 | *.lib 40 | 41 | # Executables 42 | *.exe 43 | *.out 44 | *.app 45 | 46 | ## Ignore Visual Studio temporary files, build results, and 47 | ## files generated by popular Visual Studio add-ons. 48 | ## 49 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 50 | 51 | # User-specific files 52 | *.rsuser 53 | *.suo 54 | *.user 55 | *.userosscache 56 | *.sln.docstates 57 | 58 | # User-specific files (MonoDevelop/Xamarin Studio) 59 | *.userprefs 60 | 61 | # Mono auto generated files 62 | mono_crash.* 63 | 64 | # Build results 65 | [Dd]ebug/ 66 | [Dd]ebugPublic/ 67 | [Rr]elease/ 68 | [Rr]eleases/ 69 | x64/ 70 | x86/ 71 | [Ww][Ii][Nn]32/ 72 | [Aa][Rr][Mm]/ 73 | [Aa][Rr][Mm]64/ 74 | bld/ 75 | [Bb]in/ 76 | [Oo]bj/ 77 | [Ll]og/ 78 | [Ll]ogs/ 79 | 80 | # Visual Studio 2015/2017 cache/options directory 81 | .vs/ 82 | # Uncomment if you have tasks that create the project's static files in wwwroot 83 | #wwwroot/ 84 | 85 | # Visual Studio 2017 auto generated files 86 | Generated\ Files/ 87 | 88 | # MSTest test Results 89 | [Tt]est[Rr]esult*/ 90 | [Bb]uild[Ll]og.* 91 | 92 | # NUnit 93 | *.VisualState.xml 94 | TestResult.xml 95 | nunit-*.xml 96 | 97 | # Build Results of an ATL Project 98 | [Dd]ebugPS/ 99 | [Rr]eleasePS/ 100 | dlldata.c 101 | 102 | # Benchmark Results 103 | BenchmarkDotNet.Artifacts/ 104 | 105 | # .NET Core 106 | project.lock.json 107 | project.fragment.lock.json 108 | artifacts/ 109 | 110 | # ASP.NET Scaffolding 111 | ScaffoldingReadMe.txt 112 | 113 | # StyleCop 114 | StyleCopReport.xml 115 | 116 | # Files built by Visual Studio 117 | *_i.c 118 | *_p.c 119 | *_h.h 120 | *.ilk 121 | *.meta 122 | *.obj 123 | *.iobj 124 | *.pch 125 | *.pdb 126 | *.ipdb 127 | *.pgc 128 | *.pgd 129 | *.rsp 130 | *.sbr 131 | *.tlb 132 | *.tli 133 | *.tlh 134 | *.tmp 135 | *.tmp_proj 136 | *_wpftmp.csproj 137 | *.log 138 | *.vspscc 139 | *.vssscc 140 | .builds 141 | *.pidb 142 | *.svclog 143 | *.scc 144 | 145 | # Chutzpah Test files 146 | _Chutzpah* 147 | 148 | # Visual C++ cache files 149 | ipch/ 150 | *.aps 151 | *.ncb 152 | *.opendb 153 | *.opensdf 154 | *.sdf 155 | *.cachefile 156 | *.VC.db 157 | *.VC.VC.opendb 158 | 159 | # Visual Studio profiler 160 | *.psess 161 | *.vsp 162 | *.vspx 163 | *.sap 164 | 165 | # Visual Studio Trace Files 166 | *.e2e 167 | 168 | # TFS 2012 Local Workspace 169 | $tf/ 170 | 171 | # Guidance Automation Toolkit 172 | *.gpState 173 | 174 | # ReSharper is a .NET coding add-in 175 | _ReSharper*/ 176 | *.[Rr]e[Ss]harper 177 | *.DotSettings.user 178 | 179 | # TeamCity is a build add-in 180 | _TeamCity* 181 | 182 | # DotCover is a Code Coverage Tool 183 | *.dotCover 184 | 185 | # AxoCover is a Code Coverage Tool 186 | .axoCover/* 187 | !.axoCover/settings.json 188 | 189 | # Coverlet is a free, cross platform Code Coverage Tool 190 | coverage*.json 191 | coverage*.xml 192 | coverage*.info 193 | 194 | # Visual Studio code coverage results 195 | *.coverage 196 | *.coveragexml 197 | 198 | # NCrunch 199 | _NCrunch_* 200 | .*crunch*.local.xml 201 | nCrunchTemp_* 202 | 203 | # MightyMoose 204 | *.mm.* 205 | AutoTest.Net/ 206 | 207 | # Web workbench (sass) 208 | .sass-cache/ 209 | 210 | # Installshield output folder 211 | [Ee]xpress/ 212 | 213 | # DocProject is a documentation generator add-in 214 | DocProject/buildhelp/ 215 | DocProject/Help/*.HxT 216 | DocProject/Help/*.HxC 217 | DocProject/Help/*.hhc 218 | DocProject/Help/*.hhk 219 | DocProject/Help/*.hhp 220 | DocProject/Help/Html2 221 | DocProject/Help/html 222 | 223 | # Click-Once directory 224 | publish/ 225 | 226 | # Publish Web Output 227 | *.[Pp]ublish.xml 228 | *.azurePubxml 229 | # Note: Comment the next line if you want to checkin your web deploy settings, 230 | # but database connection strings (with potential passwords) will be unencrypted 231 | *.pubxml 232 | *.publishproj 233 | 234 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 235 | # checkin your Azure Web App publish settings, but sensitive information contained 236 | # in these scripts will be unencrypted 237 | PublishScripts/ 238 | 239 | # NuGet Packages 240 | *.nupkg 241 | # NuGet Symbol Packages 242 | *.snupkg 243 | # The packages folder can be ignored because of Package Restore 244 | **/[Pp]ackages/* 245 | # except build/, which is used as an MSBuild target. 246 | !**/[Pp]ackages/build/ 247 | # Uncomment if necessary however generally it will be regenerated when needed 248 | #!**/[Pp]ackages/repositories.config 249 | # NuGet v3's project.json files produces more ignorable files 250 | *.nuget.props 251 | *.nuget.targets 252 | 253 | # Microsoft Azure Build Output 254 | csx/ 255 | *.build.csdef 256 | 257 | # Microsoft Azure Emulator 258 | ecf/ 259 | rcf/ 260 | 261 | # Windows Store app package directories and files 262 | AppPackages/ 263 | BundleArtifacts/ 264 | Package.StoreAssociation.xml 265 | _pkginfo.txt 266 | *.appx 267 | *.appxbundle 268 | *.appxupload 269 | 270 | # Visual Studio cache files 271 | # files ending in .cache can be ignored 272 | *.[Cc]ache 273 | # but keep track of directories ending in .cache 274 | !?*.[Cc]ache/ 275 | 276 | # Others 277 | ClientBin/ 278 | ~$* 279 | *~ 280 | *.dbmdl 281 | *.dbproj.schemaview 282 | *.jfm 283 | *.pfx 284 | *.publishsettings 285 | orleans.codegen.cs 286 | 287 | # Including strong name files can present a security risk 288 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 289 | #*.snk 290 | 291 | # Since there are multiple workflows, uncomment next line to ignore bower_components 292 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 293 | #bower_components/ 294 | 295 | # RIA/Silverlight projects 296 | Generated_Code/ 297 | 298 | # Backup & report files from converting an old project file 299 | # to a newer Visual Studio version. Backup files are not needed, 300 | # because we have git ;-) 301 | _UpgradeReport_Files/ 302 | Backup*/ 303 | UpgradeLog*.XML 304 | UpgradeLog*.htm 305 | ServiceFabricBackup/ 306 | *.rptproj.bak 307 | 308 | # SQL Server files 309 | *.mdf 310 | *.ldf 311 | *.ndf 312 | 313 | # Business Intelligence projects 314 | *.rdl.data 315 | *.bim.layout 316 | *.bim_*.settings 317 | *.rptproj.rsuser 318 | *- [Bb]ackup.rdl 319 | *- [Bb]ackup ([0-9]).rdl 320 | *- [Bb]ackup ([0-9][0-9]).rdl 321 | 322 | # Microsoft Fakes 323 | FakesAssemblies/ 324 | 325 | # GhostDoc plugin setting file 326 | *.GhostDoc.xml 327 | 328 | # Node.js Tools for Visual Studio 329 | .ntvs_analysis.dat 330 | node_modules/ 331 | 332 | # Visual Studio 6 build log 333 | *.plg 334 | 335 | # Visual Studio 6 workspace options file 336 | *.opt 337 | 338 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 339 | *.vbw 340 | 341 | # Visual Studio LightSwitch build output 342 | **/*.HTMLClient/GeneratedArtifacts 343 | **/*.DesktopClient/GeneratedArtifacts 344 | **/*.DesktopClient/ModelManifest.xml 345 | **/*.Server/GeneratedArtifacts 346 | **/*.Server/ModelManifest.xml 347 | _Pvt_Extensions 348 | 349 | # Paket dependency manager 350 | .paket/paket.exe 351 | paket-files/ 352 | 353 | # FAKE - F# Make 354 | .fake/ 355 | 356 | # CodeRush personal settings 357 | .cr/personal 358 | 359 | # Python Tools for Visual Studio (PTVS) 360 | __pycache__/ 361 | *.pyc 362 | 363 | # Cake - Uncomment if you are using it 364 | # tools/** 365 | # !tools/packages.config 366 | 367 | # Tabs Studio 368 | *.tss 369 | 370 | # Telerik's JustMock configuration file 371 | *.jmconfig 372 | 373 | # BizTalk build output 374 | *.btp.cs 375 | *.btm.cs 376 | *.odx.cs 377 | *.xsd.cs 378 | 379 | # OpenCover UI analysis results 380 | OpenCover/ 381 | 382 | # Azure Stream Analytics local run output 383 | ASALocalRun/ 384 | 385 | # MSBuild Binary and Structured Log 386 | *.binlog 387 | 388 | # NVidia Nsight GPU debugger configuration file 389 | *.nvuser 390 | 391 | # MFractors (Xamarin productivity tool) working folder 392 | .mfractor/ 393 | 394 | # Local History for Visual Studio 395 | .localhistory/ 396 | 397 | # BeatPulse healthcheck temp database 398 | healthchecksdb 399 | 400 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 401 | MigrationBackup/ 402 | 403 | # Ionide (cross platform F# VS Code tools) working folder 404 | .ionide/ 405 | 406 | # Fody - auto-generated XML schema 407 | FodyWeavers.xsd -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "dotnet_runtime"] 2 | path = dotnet_runtime 3 | url = git@github.com:KevinGliewe/dotnet_runtime.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "mdsnippets", 8 | "type": "shell", 9 | "command": "dotnet mdsnippets ./ -e ./dotnet_runtime" 10 | }, 11 | { 12 | "label": "toc", 13 | "type": "shell", 14 | "command": "docker run -v ${workspaceFolder}:/data killergoldfisch/github-markdown-toc --insert --no-backup --hide-footer /data/README.md" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /Host.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31005.135 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Host", "Host\Host.vcxproj", "{EBD6EECD-7892-4886-9FD8-14EECB891826}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Lib", "Lib\Lib.csproj", "{70E8D25D-F32A-46F2-9885-39490E905747}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Debug|x64 = Debug|x64 14 | Debug|x86 = Debug|x86 15 | Release|Any CPU = Release|Any CPU 16 | Release|x64 = Release|x64 17 | Release|x86 = Release|x86 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Debug|Any CPU.ActiveCfg = Debug|Win32 21 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Debug|x64.ActiveCfg = Debug|x64 22 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Debug|x64.Build.0 = Debug|x64 23 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Debug|x86.ActiveCfg = Debug|Win32 24 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Debug|x86.Build.0 = Debug|Win32 25 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Release|Any CPU.ActiveCfg = Release|Win32 26 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Release|x64.ActiveCfg = Release|x64 27 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Release|x64.Build.0 = Release|x64 28 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Release|x86.ActiveCfg = Release|Win32 29 | {EBD6EECD-7892-4886-9FD8-14EECB891826}.Release|x86.Build.0 = Release|Win32 30 | {70E8D25D-F32A-46F2-9885-39490E905747}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {70E8D25D-F32A-46F2-9885-39490E905747}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {70E8D25D-F32A-46F2-9885-39490E905747}.Debug|x64.ActiveCfg = Debug|Any CPU 33 | {70E8D25D-F32A-46F2-9885-39490E905747}.Debug|x64.Build.0 = Debug|Any CPU 34 | {70E8D25D-F32A-46F2-9885-39490E905747}.Debug|x86.ActiveCfg = Debug|Any CPU 35 | {70E8D25D-F32A-46F2-9885-39490E905747}.Debug|x86.Build.0 = Debug|Any CPU 36 | {70E8D25D-F32A-46F2-9885-39490E905747}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {70E8D25D-F32A-46F2-9885-39490E905747}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {70E8D25D-F32A-46F2-9885-39490E905747}.Release|x64.ActiveCfg = Release|Any CPU 39 | {70E8D25D-F32A-46F2-9885-39490E905747}.Release|x64.Build.0 = Release|Any CPU 40 | {70E8D25D-F32A-46F2-9885-39490E905747}.Release|x86.ActiveCfg = Release|Any CPU 41 | {70E8D25D-F32A-46F2-9885-39490E905747}.Release|x86.Build.0 = Release|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {E4C03C14-A5AB-4C20-B701-23C080642B76} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /Host/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.00) 2 | project(Host) 3 | 4 | set(CMAKE_CXX_STANDARD 14) 5 | 6 | set(THREADS_PREFER_PTHREAD_FLAG ON) 7 | find_package(Threads REQUIRED) 8 | 9 | include_directories("../dotnet_runtime/includes") 10 | 11 | if(UNIX AND NOT APPLE) 12 | set(LINUX TRUE) 13 | endif() 14 | 15 | if (LINUX) 16 | set(CMAKE_EXE_LINKER_FLAGS "-Wl,-export-dynamic") 17 | endif (LINUX) 18 | 19 | add_executable(Host 20 | Host.cpp) 21 | 22 | TARGET_LINK_LIBRARIES(Host 23 | ${CMAKE_DL_LIBS} 24 | Threads::Threads) 25 | -------------------------------------------------------------------------------- /Host/Common.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "dotnet_runtime.h" 5 | 6 | #ifdef WIN32 7 | # define FEXPORT __declspec(dllexport) 8 | #else 9 | # define FEXPORT // empty 10 | #endif 11 | 12 | #define STR DOTNET_RUNTIME_STR 13 | #define DIR_SEPARATOR DOTNET_RUNTIME_DIR_SEPARATOR 14 | 15 | using wstring_t = std::basic_string; 16 | 17 | static dotnet_runtime::Runtime* g_pRuntime; 18 | static dotnet_runtime::Library* g_pLib; 19 | 20 | inline void LogTest(bool success, std::wstring name) 21 | { 22 | std::wcout << (success ? "OK" : "ERR"); 23 | std::wcout << L"\t" << name << std::endl; 24 | } 25 | 26 | template 27 | bool cmp(T* s1, T* s2) 28 | { 29 | int i = 0; 30 | while (true) 31 | { 32 | T sc1 = s1[i]; 33 | T sc2 = s2[i]; 34 | 35 | if (sc1 != sc2) 36 | return false; 37 | 38 | if (sc1 == 0) 39 | return true; 40 | 41 | i++; 42 | } 43 | } -------------------------------------------------------------------------------- /Host/Host.cpp: -------------------------------------------------------------------------------- 1 | // Host.cpp : Diese Datei enthält die Funktion "main". Hier beginnt und endet die Ausführung des Programms. 2 | // 3 | 4 | #include 5 | #include 6 | 7 | # define DOTNET_RUNTIME_VERSION "6.0.0" 8 | 9 | #include "Common.h" 10 | 11 | #include "Test_ManagedEntryPoint.h" 12 | #include "Test_NativeFunctionPointer.h" 13 | #include "Test_ManagedFunctionPointer.h" 14 | #include "Test_DllImport.h" 15 | #include "Test_NativeExport.h" 16 | #include "Test_NativeArray.h" 17 | #include "Test_NativeString.h" 18 | #include "Test_ManagedString.h" 19 | #include "Test_ManagedUnsafe.h" 20 | #include "Test_NativeVTable.h" 21 | 22 | 23 | #define RUN_TEST(TESTNAME) \ 24 | try { \ 25 | bool result = TESTNAME::Run(lib); \ 26 | std::wcout << (result ? "OK" : "ERR"); \ 27 | std::wcout << L"\t" << #TESTNAME << std::endl; \ 28 | success &= result;\ 29 | } catch (const std::exception& e) { \ 30 | std::wcout << "Exception during " << #TESTNAME << " '" << e.what() << "'\n"; \ 31 | } 32 | 33 | 34 | inline bool exists (string_t name) { 35 | std::ifstream f(name.c_str()); 36 | return f.good(); 37 | } 38 | 39 | 40 | 41 | #if defined(_WIN32) 42 | int __cdecl wmain(int argc, wchar_t *argv[]) 43 | #else 44 | int main(int argc, char *argv[]) 45 | #endif 46 | { 47 | 48 | for(int i = 0; i < argc; i++) 49 | std::wcout << "argv[" << i << "] = " << argv[i] << std::endl; 50 | 51 | string_t root_repo = 52 | argc > 1 ? 53 | argv[1] : 54 | STR("/Users/kevingliewe/Documents/prog/dotnet/dotnet_runtime_test/"); 55 | 56 | string_t root_path = root_repo + STR("dotnet_runtime") DIR_SEPARATOR; 57 | //string_t hostfxr_path = L"C:\\Program Files\\dotnet\\host\\fxr\\6.0.0\\hostfxr.dll";//root_path + DOTNET_RUNTIME_PATH_HOSTFXR; 58 | string_t hostfxr_path = root_path + DOTNET_RUNTIME_PATH_HOSTFXR; 59 | 60 | string_t lib_path = root_repo + 61 | DIR_SEPARATOR 62 | STR("bin") 63 | DIR_SEPARATOR 64 | STR("Lib") 65 | DIR_SEPARATOR 66 | STR("net6.0") 67 | DIR_SEPARATOR; 68 | 69 | string_t libDll_path = lib_path + STR("Lib.dll"); 70 | string_t libRuntimeconfig_path = lib_path + STR("Lib.runtimeconfig.json"); 71 | 72 | 73 | std::wcout << "root_path = " << root_path.c_str() << std::endl; 74 | std::wcout << "hostfxr_path = " << hostfxr_path.c_str() << std::endl; 75 | std::wcout << "lib_path = " << lib_path.c_str() << std::endl; 76 | std::wcout << "libDll_path = " << libDll_path.c_str() << std::endl; 77 | std::wcout << "libRuntimeconfig_path = " << libRuntimeconfig_path.c_str() << std::endl; 78 | 79 | assert(exists(hostfxr_path)); 80 | assert(exists(libDll_path)); 81 | assert(exists(libRuntimeconfig_path)); 82 | 83 | void* host_handle = dotnet_runtime::get_host_handle(); 84 | 85 | std::wcout << "host_handle = " << host_handle << std::endl; 86 | 87 | // begin-snippet: InitRuntimeAndLib 88 | // Init runtime and lib 89 | 90 | auto runtime = dotnet_runtime::Runtime(hostfxr_path, libRuntimeconfig_path); 91 | 92 | auto lib = dotnet_runtime::Library(&runtime, libDll_path, STR("Lib")); 93 | // end-snippet 94 | 95 | g_pRuntime = &runtime; 96 | g_pLib = &lib; 97 | 98 | // Running tests 99 | 100 | bool success = true; 101 | RUN_TEST(Test_ManagedEntryPoint); 102 | RUN_TEST(Test_NativeFunctionPointer); 103 | RUN_TEST(Test_ManagedFunctionPointer); 104 | RUN_TEST(Test_NativeArray); 105 | RUN_TEST(Test_NativeString); 106 | RUN_TEST(Test_ManagedString); 107 | RUN_TEST(Test_ManagedUnsafe); 108 | RUN_TEST(Test_DllImport); 109 | RUN_TEST(Test_NativeExport); 110 | RUN_TEST(Test_NativeVTable); 111 | 112 | 113 | std::wcout << "Success: " << (success ? "true" : "false") << std::endl; 114 | 115 | return success ? EXIT_SUCCESS : EXIT_FAILURE; 116 | } -------------------------------------------------------------------------------- /Host/Host.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | 16.0 23 | Win32Proj 24 | {ebd6eecd-7892-4886-9fd8-14eecb891826} 25 | Host 26 | 10.0 27 | 28 | 29 | 30 | Application 31 | true 32 | v142 33 | Unicode 34 | 35 | 36 | Application 37 | false 38 | v142 39 | true 40 | Unicode 41 | 42 | 43 | Application 44 | true 45 | v142 46 | Unicode 47 | 48 | 49 | Application 50 | false 51 | v142 52 | true 53 | Unicode 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | true 75 | 76 | 77 | false 78 | 79 | 80 | true 81 | 82 | 83 | false 84 | 85 | 86 | 87 | Level3 88 | true 89 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) 90 | true 91 | $(SolutionDir)/dotnet_runtime/includes;%(AdditionalIncludeDirectories) 92 | 93 | 94 | Console 95 | true 96 | 97 | 98 | 99 | 100 | Level3 101 | true 102 | true 103 | true 104 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 105 | true 106 | $(SolutionDir)/dotnet_runtime/includes;%(AdditionalIncludeDirectories) 107 | 108 | 109 | Console 110 | true 111 | true 112 | true 113 | 114 | 115 | 116 | 117 | Level3 118 | true 119 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions) 120 | true 121 | $(SolutionDir)/dotnet_runtime/includes;%(AdditionalIncludeDirectories) 122 | 123 | 124 | Console 125 | true 126 | 127 | 128 | 129 | 130 | Level3 131 | true 132 | true 133 | true 134 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions) 135 | true 136 | $(SolutionDir)/dotnet_runtime/includes;%(AdditionalIncludeDirectories) 137 | 138 | 139 | Console 140 | true 141 | true 142 | true 143 | 144 | 145 | 146 | 147 | false 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | -------------------------------------------------------------------------------- /Host/Host.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Quelldateien 20 | 21 | 22 | 23 | 24 | Headerdateien 25 | 26 | 27 | Headerdateien 28 | 29 | 30 | Headerdateien 31 | 32 | 33 | Headerdateien 34 | 35 | 36 | Headerdateien 37 | 38 | 39 | Headerdateien 40 | 41 | 42 | Headerdateien 43 | 44 | 45 | Headerdateien 46 | 47 | 48 | Headerdateien 49 | 50 | 51 | Headerdateien 52 | 53 | 54 | Headerdateien 55 | 56 | 57 | -------------------------------------------------------------------------------- /Host/Test_DllImport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | // begin-snippet: Test_DllImport_Export_CPP 6 | extern "C" 7 | { 8 | int FEXPORT Test_DllImport_ExternC(int number) 9 | { 10 | return number * 2; 11 | } 12 | } 13 | // end-snippet 14 | 15 | namespace Test_DllImport 16 | { 17 | 18 | bool Run(dotnet_runtime::Library& a_lib) 19 | { 20 | 21 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*, int); 22 | 23 | auto fpTest_DllImport_Call = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 24 | STR("LibNamespace.Test_DllImport"), 25 | STR("Test_DllImport_Call") 26 | ); 27 | 28 | // begin-snippet: Test_DllImport_Call_CPP 29 | void* host_handle = dotnet_runtime::get_host_handle(); 30 | return fpTest_DllImport_Call(host_handle, 4) == 8; 31 | // end-snippet 32 | } 33 | } -------------------------------------------------------------------------------- /Host/Test_ManagedEntryPoint.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | namespace Test_ManagedEntryPoint 6 | { 7 | // begin-snippet: Test_ManagedEntryPoint_Args_CPP 8 | struct args 9 | { 10 | int number1; 11 | int number2; 12 | }; 13 | // end-snippet 14 | 15 | bool Run(dotnet_runtime::Library& a_lib) 16 | { 17 | bool ret = true; 18 | 19 | std::wcout << "Test_ManagedEntryPoint: " << std::endl; 20 | 21 | args _args {1, 2}; 22 | 23 | { // COMPONENT entry point 24 | // begin-snippet: Test_ManagedEntryPoint_ComponentEntryPoint_CPP 25 | 26 | auto fpTest_ComponentEntryPoint = a_lib.GetComponentEntrypoint( 27 | STR("LibNamespace.Test_ManagedEntryPoint"), 28 | STR("Test_ComponentEntryPoint") 29 | ); 30 | 31 | bool success = fpTest_ComponentEntryPoint(&_args, sizeof(_args)) == 3; 32 | LogTest(success, L"Test_ComponentEntryPoint"); 33 | 34 | // end-snippet 35 | ret &= success; 36 | } 37 | 38 | 39 | { // CUSTOM entry point 40 | // begin-snippet: Test_ManagedEntryPoint_CustomEntryPoint_CPP 41 | 42 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(args); 43 | 44 | auto fpTest_CustomEntryPoint = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 45 | STR("LibNamespace.Test_ManagedEntryPoint"), 46 | STR("Test_CustomEntryPoint") 47 | ); 48 | 49 | bool success = fpTest_CustomEntryPoint(_args) == 3; 50 | LogTest(success, L"Test_CustomEntryPoint"); 51 | 52 | // end-snippet 53 | ret &= success; 54 | } 55 | 56 | 57 | return ret; 58 | } 59 | } -------------------------------------------------------------------------------- /Host/Test_ManagedFunctionPointer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | namespace Test_ManagedFunctionPointer 6 | { 7 | bool Run(dotnet_runtime::Library& a_lib) 8 | { 9 | bool ret = true; 10 | 11 | // begin-snippet: Test_ManagedFunctionPointer_Typedef_managed_callback_fn_CPP 12 | typedef int (*managed_callback_fn)(int); 13 | // end-snippet 14 | 15 | { // instance 16 | // begin-snippet: Test_ManagedFunctionPointer_Instance_CPP 17 | 18 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(int); 19 | 20 | // Get the managed entry point 21 | auto fpTest_ManagedFunctionPointer_Instance = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 22 | STR("LibNamespace.Test_ManagedFunctionPointer"), 23 | STR("Test_ManagedFunctionPointer_Instance") 24 | ); 25 | 26 | // Get the function pointer to the managed method 27 | managed_callback_fn managedCallback = (managed_callback_fn)fpTest_ManagedFunctionPointer_Instance(2); 28 | 29 | bool success = managedCallback(6) == 8; 30 | 31 | LogTest(success, L"Test_ManagedFunctionPointer_Instance"); 32 | 33 | // end-snippet 34 | ret &= success; 35 | } 36 | 37 | { // static 38 | // begin-snippet: Test_ManagedFunctionPointer_Static_CPP 39 | 40 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(); 41 | 42 | // Get the managed entry point 43 | auto fpTest_ManagedFunctionPointer_Static = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 44 | STR("LibNamespace.Test_ManagedFunctionPointer"), 45 | STR("Test_ManagedFunctionPointer_Static") 46 | ); 47 | 48 | // Get the function pointer to the managed function 49 | managed_callback_fn managedCallback = (managed_callback_fn)fpTest_ManagedFunctionPointer_Static(); 50 | 51 | bool success = managedCallback(5) == 8; 52 | 53 | LogTest(success, L"Test_ManagedFunctionPointer_Static"); 54 | 55 | // end-snippet 56 | ret &= success; 57 | } 58 | 59 | return ret; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Host/Test_ManagedString.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma once 4 | 5 | #include "Common.h" 6 | 7 | namespace Test_ManagedString 8 | { 9 | 10 | bool Run(dotnet_runtime::Library& a_lib) 11 | { 12 | bool ret = true; 13 | 14 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(); 15 | 16 | { // Ansi 17 | // begin-snippet: Test_ManagedString_Ansi_CPP 18 | 19 | auto fpTest_ManagedString_Ansi = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 20 | STR("LibNamespace.Test_ManagedString"), 21 | STR("Test_ManagedString_Ansi") 22 | ); 23 | 24 | char* str = (char*)fpTest_ManagedString_Ansi(); 25 | 26 | // Compare the ASCII strings 27 | bool success = cmp(str, (char*)"Hello Ansi"); 28 | 29 | LogTest(success, L"Test_ManagedString_Ansi"); 30 | 31 | // end-snippet 32 | ret &= success; 33 | } 34 | 35 | { // Wide 36 | // begin-snippet: Test_ManagedString_Wide_CPP 37 | 38 | auto fpTest_ManagedString_Wide = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 39 | STR("LibNamespace.Test_ManagedString"), 40 | STR("Test_ManagedString_Wide") 41 | ); 42 | 43 | wchar_t* str = (wchar_t*)fpTest_ManagedString_Wide(); 44 | 45 | // Compare the wide strings 46 | bool success = cmp(str, (wchar_t*)L"Hello ❤"); 47 | 48 | LogTest(success, L"Test_ManagedString_Wide"); 49 | 50 | // end-snippet 51 | ret &= success; 52 | } 53 | 54 | return ret; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Host/Test_ManagedUnsafe.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | namespace Test_ManagedUnsafe 6 | { 7 | // begin-snippet: Test_ManagedUnsafe_CPP 8 | struct Args 9 | { 10 | int number1; 11 | int number2; 12 | int sum; 13 | char returnMsg[128]; 14 | }; 15 | 16 | 17 | bool Run(dotnet_runtime::Library& a_lib) 18 | { 19 | bool ret = true; 20 | 21 | typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*); 22 | 23 | auto fpTest_ManagedUnsafe_Struct = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 24 | STR("LibNamespace.Test_ManagedUnsafe"), 25 | STR("Test_ManagedUnsafe_Struct") 26 | ); 27 | 28 | Args args; 29 | args.number1 = 1; 30 | args.number2 = 2; 31 | 32 | fpTest_ManagedUnsafe_Struct(&args); 33 | 34 | { //sum 35 | bool success = args.sum == 3; 36 | LogTest(success, L"Test_ManagedUnsafe_Struct.Sum"); 37 | ret &= success; 38 | } 39 | 40 | { // ReturnMsg 41 | bool success = cmp(args.returnMsg, (char*)"Hello Ansi"); 42 | LogTest(success, L"Test_ManagedUnsafe_Struct.ReturnMsg"); 43 | ret &= success; 44 | } 45 | return ret; 46 | } 47 | // end-snippet 48 | } 49 | -------------------------------------------------------------------------------- /Host/Test_NativeArray.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | namespace Test_NativeArray 6 | { 7 | // begin-snippet: Test_NativeArray_Args_CPP 8 | struct args 9 | { 10 | int Arr[8]; 11 | int Multiplier; 12 | }; 13 | // end-snippet 14 | 15 | bool Run(dotnet_runtime::Library& a_lib) 16 | { 17 | bool ret = true; 18 | 19 | // begin-snippet: Test_NativeArray_Args_Data_CPP 20 | args _args{ 21 | 1, 2, 3, 4, 5, 6, 7, 8, // Arr[8] 22 | 2 // Multiplier 23 | }; 24 | // end-snippet 25 | 26 | { // StructFixed 27 | // begin-snippet: Test_NativeArray_StructFixed_CPP 28 | 29 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(args); 30 | 31 | auto fpTest_NativeArray_StructFixed = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 32 | STR("LibNamespace.Test_NativeArray"), 33 | STR("Test_NativeArray_StructFixed") 34 | ); 35 | 36 | bool success = fpTest_NativeArray_StructFixed(_args) == 72; 37 | LogTest(success, L"Test_NativeArray_StructFixed"); 38 | 39 | // end-snippet 40 | ret &= success; 41 | } 42 | 43 | 44 | { // ArgumentFixed 45 | // begin-snippet: Test_NativeArray_ArgumentFixed_CPP 46 | 47 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(int[], int); 48 | 49 | auto fpTest_NativeArray_ArgumentFixed = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 50 | STR("LibNamespace.Test_NativeArray"), 51 | STR("Test_NativeArray_ArgumentFixed") 52 | ); 53 | 54 | bool success = fpTest_NativeArray_ArgumentFixed(_args.Arr, _args.Multiplier) == 72; 55 | LogTest(success, L"Test_NativeArray_ArgumentFixed"); 56 | 57 | // end-snippet 58 | ret &= success; 59 | } 60 | 61 | { // ArgumentFixed FunctionPointer 62 | // begin-snippet: Test_NativeArray_ArgumentFixed_FunctionPointer_CPP 63 | 64 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(); 65 | typedef int (*managed_callback_fn)(int[], int); 66 | 67 | auto fpTest_NativeArray_ArgumentFixed_FunctionPointer = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 68 | STR("LibNamespace.Test_NativeArray"), 69 | STR("Test_NativeArray_ArgumentFixed_FunctionPointer") 70 | ); 71 | 72 | managed_callback_fn callback = (managed_callback_fn)fpTest_NativeArray_ArgumentFixed_FunctionPointer(); 73 | 74 | bool success = callback(_args.Arr, _args.Multiplier) == 72; 75 | LogTest(success, L"Test_NativeArray_ArgumentFixed_FunctionPointer"); 76 | 77 | // end-snippet 78 | ret &= success; 79 | } 80 | 81 | 82 | return ret; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Host/Test_NativeExport.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | // begin-snippet: Test_NativeExport_Export_CPP 6 | extern "C" 7 | { 8 | int FEXPORT Test_NativeExport_ExternC(int number) 9 | { 10 | return number * 4; 11 | } 12 | } 13 | // end-snippet 14 | 15 | namespace Test_NativeExport 16 | { 17 | 18 | bool Run(dotnet_runtime::Library& a_lib) 19 | { 20 | 21 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*, int); 22 | 23 | auto fpTest_NativeExport_Call = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 24 | STR("LibNamespace.Test_NativeExport"), 25 | STR("Test_NativeExport_Call") 26 | ); 27 | 28 | // begin-snippet: Test_NativeExport_Call_CPP 29 | void* host_handle = dotnet_runtime::get_host_handle(); 30 | return fpTest_NativeExport_Call(host_handle, 4) == 16; 31 | // end-snippet 32 | } 33 | } -------------------------------------------------------------------------------- /Host/Test_NativeFunctionPointer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | 6 | namespace Test_NativeFunctionPointer 7 | { 8 | // begin-snippet: Test_NativeFunctionPointer_CallbackFunc_CPP 9 | int FEXPORT CallbackFunc(int i) 10 | { 11 | return i * 2; 12 | } 13 | // end-snippet 14 | 15 | bool Run(dotnet_runtime::Library& a_lib) 16 | { 17 | bool ret = true; 18 | 19 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*, int); 20 | 21 | // begin-snippet: Test_NativeFunctionPointer_CallbackPointer_CPP 22 | void* fpNativeCallback = (void*)&CallbackFunc; 23 | // end-snippet 24 | 25 | { // checked function pointer to delegate 26 | // begin-snippet: Test_NativeFunctionPointer_CallbackFunc_Checked_CPP 27 | 28 | auto fpTest_NativeFunctionPointer_Checked = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 29 | STR("LibNamespace.Test_NativeFunctionPointer"), 30 | STR("Test_NativeFunctionPointer_Checked") 31 | ); 32 | 33 | bool success = fpTest_NativeFunctionPointer_Checked(fpNativeCallback, 4) == 8; 34 | LogTest(success, L"Test_NativeFunctionPointer_Checked"); 35 | 36 | // end-snippet 37 | ret &= success; 38 | } 39 | 40 | { // unchecked function pointer call 41 | // begin-snippet: Test_NativeFunctionPointer_CallbackFunc_Unchecked_CPP 42 | 43 | auto fpTest_NativeFunctionPointer_Unchecked = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 44 | STR("LibNamespace.Test_NativeFunctionPointer"), 45 | STR("Test_NativeFunctionPointer_Unchecked") 46 | ); 47 | 48 | bool success = fpTest_NativeFunctionPointer_Unchecked(fpNativeCallback, 4) == 8; 49 | LogTest(success, L"Test_NativeFunctionPointer_Unchecked"); 50 | 51 | // end-snippet 52 | ret &= success; 53 | } 54 | 55 | return ret; 56 | } 57 | } -------------------------------------------------------------------------------- /Host/Test_NativeString.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | namespace Test_NativeString 6 | { 7 | // begin-snippet: Test_NativeString_RetArgs_CPP 8 | struct RetArgs 9 | { 10 | bool (*CallbackAnsi)(const char*); 11 | bool (*CallbackWide)(const wchar_t*); 12 | }; 13 | // end-snippet 14 | 15 | bool Run(dotnet_runtime::Library& a_lib) 16 | { 17 | bool ret = true; 18 | 19 | typedef bool (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*); 20 | 21 | { // Ansi 22 | // begin-snippet: Test_NativeString_Ansi_CPP 23 | 24 | auto fpTest_NativeString_Ansi = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 25 | STR("LibNamespace.Test_NativeString"), 26 | STR("Test_NativeString_Ansi") 27 | ); 28 | 29 | bool success = fpTest_NativeString_Ansi((void*)"Hello Ansi"); 30 | LogTest(success, L"Test_NativeString_Ansi"); 31 | 32 | // end-snippet 33 | ret &= success; 34 | } 35 | 36 | { // Wide 37 | // begin-snippet: Test_NativeString_Wide_CPP 38 | 39 | auto fpTest_NativeString_Wide = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 40 | STR("LibNamespace.Test_NativeString"), 41 | STR("Test_NativeString_Wide") 42 | ); 43 | 44 | bool success = fpTest_NativeString_Wide((void*)L"Hello ❤"); 45 | LogTest(success, L"Test_NativeString_Wide"); 46 | 47 | // end-snippet 48 | ret &= success; 49 | } 50 | 51 | { // function pointer 52 | // begin-snippet: Test_NativeString_FunctionPointer_CPP 53 | 54 | typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn2)(void*); 55 | 56 | auto fpTest_NativeString_FunctionPointer = (custom_entry_point_fn2)a_lib.GetCustomEntrypoint( 57 | STR("LibNamespace.Test_NativeString"), 58 | STR("Test_NativeString_FunctionPointer") 59 | ); 60 | 61 | RetArgs retArgs; 62 | fpTest_NativeString_FunctionPointer(&retArgs); 63 | 64 | { // Ansi 65 | bool success = retArgs.CallbackAnsi("Hello Ansi"); 66 | LogTest(success, L"Test_NativeString_FunctionPointer.CallbackAnsi"); 67 | 68 | ret &= success; 69 | } 70 | 71 | { // Wide 72 | bool success = retArgs.CallbackWide(L"Hello ❤"); 73 | LogTest(success, L"Test_NativeString_FunctionPointer.CallbackWide"); 74 | 75 | ret &= success; 76 | } 77 | 78 | // end-snippet 79 | } 80 | 81 | return ret; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /Host/Test_NativeVTable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Common.h" 4 | 5 | 6 | namespace Test_NativeVTable 7 | { 8 | // begin-snippet: Test_NativeVTable_Class_CPP 9 | class ClassLayout 10 | { 11 | private: 12 | int m_iTest = 0; 13 | public: 14 | virtual void AddOne() { this->m_iTest += 1; } 15 | virtual void AddTwo() { this->m_iTest += 2; } 16 | 17 | int GetTest() { return m_iTest; } 18 | }; 19 | // end-snippet 20 | 21 | 22 | bool Run(dotnet_runtime::Library& a_lib) 23 | { 24 | bool ret = true; 25 | bool success = false; 26 | 27 | typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*); 28 | 29 | auto fpTest_NativeVTable_Call = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 30 | STR("LibNamespace.Test_NativeVTable"), 31 | STR("Test_NativeVTable_Call") 32 | ); 33 | 34 | // begin-snippet: Test_NativeVTable_Call_CPP 35 | ClassLayout* instance = new ClassLayout(); 36 | 37 | // Calls AddOne and AddTwo, then overwrites VTable 38 | fpTest_NativeVTable_Call((void*)instance); 39 | // end-snippet 40 | 41 | success = instance->GetTest() == 3; 42 | LogTest(success, L"Test_NativeVTable_Call.ManagedCall"); 43 | 44 | ret &= success; 45 | 46 | 47 | instance->AddOne(); // Calls delegate_SubOne 48 | instance->AddTwo(); // Calls delegate_SubOne 49 | 50 | success = instance->GetTest() == 1; 51 | LogTest(success, L"Test_NativeVTable_Call.ManagedOverwrite"); 52 | 53 | ret &= success; 54 | 55 | return ret; 56 | } 57 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Kevin Gliewe (kevingliewe@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /Lib/CString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | 6 | namespace GCore.NativeInterop 7 | { 8 | public enum CEncoding 9 | { 10 | Ascii, 11 | Wide, 12 | UTF8, 13 | UTF16, 14 | UTF32 15 | } 16 | 17 | public static class CEncodingExtension 18 | { 19 | public static int GetStride(this Encoding self) 20 | { 21 | if (self == Encoding.ASCII) 22 | return 1; 23 | if (self == Encoding.UTF8) 24 | return 1; 25 | if (self == Encoding.Unicode) // UTF16 26 | return 2; 27 | if (self == Encoding.UTF32) 28 | return 4; 29 | if (self == Encoding.Default) // UTF8 30 | return 1; 31 | throw new NotImplementedException(); 32 | } 33 | 34 | public static int GetStride(this CEncoding self) => self.ToEncoding().GetStride(); 35 | 36 | public static Encoding ToEncoding(this CEncoding self) 37 | { 38 | return self switch 39 | { 40 | CEncoding.Ascii => Encoding.ASCII, 41 | CEncoding.Wide => Environment.OSVersion.Platform == PlatformID.Unix 42 | || Environment.OSVersion.Platform == PlatformID.MacOSX 43 | ? System.Text.Encoding.UTF32 44 | : System.Text.Encoding.Unicode, // UTF-16 45 | CEncoding.UTF8 => Encoding.UTF8, 46 | CEncoding.UTF16 => Encoding.Unicode, 47 | CEncoding.UTF32 => Encoding.UTF32, // UTF-16 48 | _ => Encoding.ASCII 49 | }; 50 | } 51 | 52 | public static byte[] GetBytes(this CEncoding self, string str) => self.ToEncoding().GetBytes(str + '\0'); 53 | 54 | public static string GetString(this CEncoding self, byte[] str) => self.ToEncoding().GetString(str); 55 | 56 | public static string GetString(this CEncoding self, IntPtr str) => self.ToEncoding().GetString(str); 57 | 58 | public static string GetString(this Encoding self, IntPtr str) 59 | { 60 | if (str == IntPtr.Zero) 61 | return null; 62 | 63 | int stride = self.GetStride(); 64 | int pos = 0; 65 | 66 | if (stride == 1) 67 | while (Marshal.ReadByte(str, pos) != 0) 68 | pos += stride; 69 | else if (stride == 2) 70 | while (Marshal.ReadInt16(str, pos) != 0) 71 | pos += stride; 72 | else if (stride == 4) 73 | while (Marshal.ReadInt32(str, pos) != 0) 74 | pos += stride; 75 | 76 | byte[] strbuf = new byte[pos]; 77 | Marshal.Copy(str, strbuf, 0, pos); 78 | return self.GetString(strbuf); 79 | } 80 | } 81 | 82 | public class CString : UnmanagedMemory 83 | { 84 | public Encoding Enc { get; protected set; } 85 | public String Str { get; protected set; } 86 | 87 | public CString(string str, CEncoding env = CEncoding.Ascii) 88 | { 89 | Enc = env.ToEncoding(); 90 | Str = str; 91 | 92 | var data = Enc.GetBytes(str + '\0'); 93 | Size = data.Length; 94 | Alloc(Size); 95 | Marshal.Copy(data, 0, Ptr, Size); 96 | } 97 | } 98 | 99 | public struct NativeString 100 | { 101 | public IntPtr Ptr; 102 | 103 | public override string ToString() 104 | { 105 | return Encoding.ASCII.GetString(Ptr); 106 | } 107 | } 108 | 109 | public struct NativeWString 110 | { 111 | public IntPtr Ptr; 112 | 113 | public override string ToString() 114 | { 115 | return CEncoding.Wide.GetString(Ptr); 116 | } 117 | } 118 | } -------------------------------------------------------------------------------- /Lib/Lib.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | true 6 | latest 7 | true 8 | 9 | 10 | 11 | $(SolutionDir)\bin\Lib 12 | 13 | 14 | 15 | $(SolutionDir)\bin\Lib 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Lib/ModuleLoader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq.Expressions; 3 | using System.Reflection; 4 | using System.Runtime.InteropServices; 5 | using System.Text; 6 | 7 | namespace GCore.NativeInterop 8 | { 9 | public class ModuleLoader 10 | { 11 | public static class PosixDefinitions 12 | { 13 | [Flags] 14 | public enum PosixFlags 15 | { 16 | RTLD_LAZY = (0 << 0), // Lazy function call binding. 17 | RTLD_NOW = (1 << 0), // Immediate function call binding. 18 | RTLD_GLOBAL = (1 << 1), 19 | RTLD_LOCAL = (1 << 2) 20 | } 21 | 22 | [StructLayout(LayoutKind.Sequential)] 23 | public struct dl_info 24 | { 25 | public IntPtr dli_fbase; 26 | public IntPtr dli_fname; 27 | public IntPtr dli_saddr; 28 | public IntPtr dli_sname; 29 | } 30 | 31 | [DllImport("libdl", CharSet = CharSet.Ansi)] 32 | public static extern IntPtr dlopen(string filename, int flags); 33 | 34 | [DllImport("libdl")] 35 | public static extern int dlclose(IntPtr handle); 36 | 37 | [DllImport("libdl", CharSet = CharSet.Ansi)] 38 | public static extern IntPtr dlsym(IntPtr handle, string symbol); 39 | 40 | [DllImport("libdl")] 41 | public static extern int dladdr(IntPtr handle, ref dl_info dl_info); 42 | } 43 | 44 | public class WindowsDefinitions 45 | { 46 | [Flags] 47 | public enum WindowsFlags 48 | { 49 | GET_MODULE_HANDLE_EX_FLAG_PIN = 0x1, 50 | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT = 0x2, 51 | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS = 0x4 52 | } 53 | 54 | [DllImport("kernel32")] 55 | public static extern IntPtr LoadLibraryW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); 56 | 57 | [DllImport("kernel32")] 58 | [return: MarshalAs(UnmanagedType.Bool)] 59 | public static extern bool FreeLibrary(IntPtr hModule); 60 | 61 | [DllImport("kernel32", CharSet = CharSet.Ansi, ExactSpelling = true)] 62 | public static extern IntPtr GetProcAddress(IntPtr hModule, string procName); 63 | 64 | [DllImport("kernel32")] 65 | public static extern IntPtr GetModuleHandleW([MarshalAs(UnmanagedType.LPWStr)] string lpFileName); 66 | 67 | [DllImport("kernel32")] 68 | [return: MarshalAs(UnmanagedType.Bool)] 69 | public static extern bool GetModuleHandleExW(UInt32 dwFlags, [MarshalAs(UnmanagedType.LPWStr)] string lpModuleName, ref IntPtr hModule); 70 | 71 | [DllImport("kernel32")] 72 | [return: MarshalAs(UnmanagedType.Bool)] 73 | public static extern bool GetModuleHandleEx(UInt32 dwFlags, IntPtr lpModuleName, ref IntPtr hModule); 74 | 75 | [DllImport("kernel32")] 76 | public static extern UInt32 GetModuleFileNameW(IntPtr hModule, [MarshalAs(UnmanagedType.LPWStr)] StringBuilder lpFilename, UInt32 nSize); 77 | } 78 | 79 | public static bool IsPosix => Environment.OSVersion.Platform switch 80 | { 81 | PlatformID.MacOSX => true, 82 | PlatformID.Unix => true, 83 | PlatformID.Other => true, 84 | _ => false 85 | }; 86 | 87 | public static IntPtr LoadLibrary(string name) 88 | { 89 | if (IsPosix) 90 | { 91 | return PosixDefinitions.dlopen(name, 92 | (int)(PosixDefinitions.PosixFlags.RTLD_LAZY | PosixDefinitions.PosixFlags.RTLD_LOCAL)); 93 | } 94 | else 95 | { 96 | return WindowsDefinitions.LoadLibraryW(name); 97 | } 98 | } 99 | 100 | public static bool UnloadLibrary(IntPtr handle) 101 | { 102 | if (IsPosix) 103 | { 104 | return PosixDefinitions.dlclose(handle) == 0; 105 | } 106 | else 107 | { 108 | return WindowsDefinitions.FreeLibrary(handle); 109 | } 110 | } 111 | 112 | public static IntPtr GetExport(IntPtr handle, string name) 113 | { 114 | if (IsPosix) 115 | { 116 | return PosixDefinitions.dlsym(handle, name); 117 | } 118 | else 119 | { 120 | return WindowsDefinitions.GetProcAddress(handle, name); 121 | } 122 | } 123 | 124 | public static T GetExport(IntPtr handle, string name) where T : Delegate 125 | { 126 | var fPtr = GetExport(handle, name); 127 | if (fPtr == IntPtr.Zero) 128 | throw new Exception($"Export '{name}' returned IntPtr.Zero for the handle {handle}!"); 129 | return (T)Marshal.GetDelegateForFunctionPointer(fPtr, typeof(T)); 130 | } 131 | 132 | public static string? GetModulePath(IntPtr handle) 133 | { 134 | if (IsPosix) 135 | { 136 | PosixDefinitions.dl_info dlInfo = new PosixDefinitions.dl_info(); 137 | if (PosixDefinitions.dladdr(handle, ref dlInfo) == 0) 138 | return null; 139 | if (dlInfo.dli_fname == IntPtr.Zero) 140 | return null; 141 | /*Console.WriteLine($"dli_fname={dlInfo.dli_fname}"); 142 | Console.WriteLine($"dli_fbase={dlInfo.dli_fbase}"); 143 | Console.WriteLine($"dli_saddr={dlInfo.dli_saddr}"); 144 | Console.WriteLine($"dli_sname={dlInfo.dli_sname}");*/ 145 | return Encoding.ASCII.GetString(dlInfo.dli_fname) + " : " + Encoding.ASCII.GetString(dlInfo.dli_sname); 146 | } 147 | else 148 | { 149 | var sb = new StringBuilder(1024); 150 | WindowsDefinitions.GetModuleFileNameW(handle, sb, (uint)sb.Capacity); 151 | return sb.ToString(); 152 | } 153 | } 154 | 155 | public static IntPtr GetHandle(IntPtr symbolPtr) 156 | { 157 | if (IsPosix) 158 | { 159 | PosixDefinitions.dl_info dlInfo = new PosixDefinitions.dl_info(); 160 | if (PosixDefinitions.dladdr(symbolPtr, ref dlInfo) == 0) 161 | return IntPtr.Zero; 162 | return dlInfo.dli_fbase; 163 | } 164 | else 165 | { 166 | IntPtr handle = IntPtr.Zero; 167 | WindowsDefinitions.GetModuleHandleEx( 168 | (UInt32)(WindowsDefinitions.WindowsFlags.GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | 169 | WindowsDefinitions.WindowsFlags.GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT), 170 | (IntPtr)symbolPtr, 171 | ref handle 172 | ); 173 | return handle; 174 | } 175 | } 176 | 177 | public static IntPtr GetHostHandle() 178 | { 179 | if (IsPosix) 180 | { 181 | return PosixDefinitions.dlopen(null, 182 | (int) (PosixDefinitions.PosixFlags.RTLD_LAZY | PosixDefinitions.PosixFlags.RTLD_LOCAL)); 183 | } 184 | else 185 | { 186 | return WindowsDefinitions.GetModuleHandleW(null); 187 | } 188 | } 189 | } 190 | } -------------------------------------------------------------------------------- /Lib/Test_DllImport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Runtime.InteropServices; 4 | 5 | 6 | namespace LibNamespace 7 | { 8 | public static class Test_DllImport 9 | { 10 | // begin-snippet: Test_DllImport_CS 11 | private static IntPtr s_moduleHandle = IntPtr.Zero; 12 | 13 | private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) 14 | { 15 | IntPtr ret = libraryName == "Test_DllImport_LibName" ? s_moduleHandle : NativeLibrary.Load(libraryName, assembly, searchPath); 16 | //Console.WriteLine($"ImportResolver s_moduleHandle={s_moduleHandle} ret={ret}"); 17 | return ret; 18 | } 19 | 20 | [DllImport("Test_DllImport_LibName")] 21 | public static extern int Test_DllImport_ExternC(int number); 22 | 23 | [UnmanagedCallersOnly] 24 | public static int Test_DllImport_Call(IntPtr moduleHandle, int number) 25 | { 26 | s_moduleHandle = moduleHandle; 27 | NativeLibrary.SetDllImportResolver(typeof(Test_DllImport).Assembly, ImportResolver); 28 | 29 | return Test_DllImport_ExternC(number); 30 | } 31 | // end-snippet 32 | } 33 | } -------------------------------------------------------------------------------- /Lib/Test_ManagedEntryPoint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection.Metadata.Ecma335; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace LibNamespace 6 | { 7 | public static class Test_ManagedEntryPoint 8 | { 9 | // begin-snippet: Test_ManagedEntryPoint_Args_CS 10 | [StructLayout(LayoutKind.Sequential)] 11 | public unsafe struct Args 12 | { 13 | public int Number1; 14 | public int Number2; 15 | } 16 | // end-snippet 17 | 18 | // begin-snippet: Test_ManagedEntryPoint_ComponentEntryPoint_CS 19 | public static int Test_ComponentEntryPoint(IntPtr arg, int argLength) 20 | { 21 | if (argLength < Marshal.SizeOf(typeof(Args))) 22 | { 23 | return 1; 24 | } 25 | 26 | Args args = Marshal.PtrToStructure(arg); 27 | 28 | return args.Number1 + args.Number2; 29 | } 30 | // end-snippet 31 | 32 | // begin-snippet: Test_ManagedEntryPoint_CustomEntryPoint_CS 33 | [UnmanagedCallersOnly] 34 | public static int Test_CustomEntryPoint(Args args) 35 | { 36 | return args.Number1 + args.Number2; 37 | } 38 | // end-snippet 39 | } 40 | } -------------------------------------------------------------------------------- /Lib/Test_ManagedFunctionPointer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace LibNamespace 5 | { 6 | public static class Test_ManagedFunctionPointer 7 | { 8 | // begin-snippet: Test_ManagedFunctionPointer_FunctionPointerCallbackDelegate_CS 9 | public delegate int FunctionPointerCallbackDelegate(int a); 10 | // end-snippet 11 | 12 | 13 | #region Test_ManagedFunctionPointer_Instance_CS 14 | 15 | public class CallableObject 16 | { 17 | public FunctionPointerCallbackDelegate CallbackDelegate; 18 | public IntPtr CallbackFunctionPointer; 19 | 20 | private int Member; 21 | 22 | public int Callback(int i) => i + Member; 23 | 24 | public CallableObject(int member) 25 | { 26 | Member = member; 27 | CallbackDelegate = new FunctionPointerCallbackDelegate(Callback); 28 | CallbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(CallbackDelegate); 29 | } 30 | 31 | } 32 | 33 | public static CallableObject CallableObjectInstance; 34 | 35 | // Enty point for unmanaged code 36 | [UnmanagedCallersOnly] 37 | public static IntPtr Test_ManagedFunctionPointer_Instance(int member) 38 | { 39 | CallableObjectInstance = new CallableObject(member); 40 | return CallableObjectInstance.CallbackFunctionPointer; 41 | } 42 | 43 | 44 | #endregion // Test_ManagedFunctionPointer_Instance_CS ---------------------------------------------------------- 45 | 46 | #region Test_ManagedFunctionPointer_Static_CS 47 | 48 | public static FunctionPointerCallbackDelegate FunctionPointerCallbackDelegateStatic = new FunctionPointerCallbackDelegate(Callback); 49 | 50 | public static int Callback(int i) => i + 3; 51 | 52 | // Enty point for unmanaged code 53 | [UnmanagedCallersOnly] 54 | public static IntPtr Test_ManagedFunctionPointer_Static() 55 | { 56 | return Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackDelegateStatic); 57 | } 58 | 59 | 60 | #endregion // Test_ManagedFunctionPointer_Static_CS ------------------------------------------------------------- 61 | 62 | } 63 | } -------------------------------------------------------------------------------- /Lib/Test_ManagedString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using GCore.NativeInterop; 4 | 5 | namespace LibNamespace 6 | { 7 | public class Test_ManagedString 8 | { 9 | #region Test_ManagedString_Ansi_CS 10 | public static readonly CString HelloAnsi = new CString("Hello Ansi"); 11 | 12 | [UnmanagedCallersOnly] 13 | public static IntPtr Test_ManagedString_Ansi() 14 | { 15 | return HelloAnsi.Ptr; 16 | } 17 | #endregion 18 | 19 | #region Test_ManagedString_Wide_CS 20 | public static readonly CString HelloWide = new CString("Hello ❤", CEncoding.Wide); 21 | 22 | [UnmanagedCallersOnly] 23 | public static IntPtr Test_ManagedString_Wide() 24 | { 25 | return HelloWide.Ptr; 26 | } 27 | #endregion 28 | } 29 | } -------------------------------------------------------------------------------- /Lib/Test_ManagedUnsafe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using GCore.NativeInterop; 4 | 5 | namespace LibNamespace 6 | { 7 | public static class Test_ManagedUnsafe 8 | { 9 | // begin-snippet: Test_ManagedUnsafe_CS 10 | [StructLayout(LayoutKind.Sequential)] 11 | public unsafe struct Args 12 | { 13 | public int Number1; 14 | public int number2; 15 | public int Sum; 16 | public fixed byte ReturnMsg[128]; 17 | } 18 | 19 | [UnmanagedCallersOnly] 20 | public static void Test_ManagedUnsafe_Struct(IntPtr ptr) 21 | { 22 | unsafe 23 | { 24 | Args* args = (Args*) ptr; 25 | 26 | args->Sum = args->Number1 + args->number2; 27 | 28 | // Get the memory offset of Args.ReturnMsg 29 | var destReturnMsg = IntPtr.Add(ptr, (int)Marshal.OffsetOf(typeof(Args), nameof(Args.ReturnMsg))); 30 | 31 | var data = CEncoding.Ascii.GetBytes("Hello Ansi"); 32 | 33 | // Copy the data to the unmanaged memory 34 | Marshal.Copy(data, 0, (IntPtr)destReturnMsg, data.Length); 35 | } 36 | } 37 | // end-snippet 38 | } 39 | } -------------------------------------------------------------------------------- /Lib/Test_NativeArray.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Runtime.InteropServices; 4 | using GCore.NativeInterop; 5 | 6 | namespace LibNamespace 7 | { 8 | public static class Test_NativeArray 9 | { 10 | #region Test_NativeArray_StructFixed_CS 11 | [StructLayout(LayoutKind.Sequential)] 12 | public unsafe struct Args 13 | { 14 | public fixed int Arr[8]; 15 | public int Multiplier; 16 | } 17 | 18 | [UnmanagedCallersOnly] 19 | public static unsafe int Test_NativeArray_StructFixed(Args args) 20 | { 21 | int ret = 0; 22 | 23 | for(int i = 0; i < 8; i++) 24 | { 25 | ret += args.Arr[i] * args.Multiplier; 26 | } 27 | 28 | return ret; 29 | } 30 | 31 | #endregion // ---------------------------------------------------------------------------------------------------- 32 | 33 | #region Test_NativeArray_ArgumentFixed_CS 34 | [UnmanagedCallersOnly] 35 | public static int Test_NativeArray_ArgumentFixed(IntPtr arrPtr, int multiplier) 36 | { 37 | // ArrPointer as argument does not work here! 38 | 39 | ArrPointer8 arr = new ArrPointer8(arrPtr); 40 | 41 | return arr.Sum(el => el * multiplier); ; 42 | } 43 | 44 | #endregion // ---------------------------------------------------------------------------------------------------- 45 | 46 | #region Test_NativeArray_ArgumentFixed_FunctionPointer_CS 47 | public delegate int FunctionPointerCallbackDelegate(ArrPointer8 arr, int multiplier); 48 | 49 | public static FunctionPointerCallbackDelegate FunctionPointerCallbackDelegateInstance = 50 | new FunctionPointerCallbackDelegate(Callback); 51 | 52 | public static int Callback(ArrPointer8 arr, int multiplier) => arr.Sum(el => el * multiplier); 53 | 54 | [UnmanagedCallersOnly] 55 | public static IntPtr Test_NativeArray_ArgumentFixed_FunctionPointer() 56 | { 57 | return Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackDelegateInstance); 58 | } 59 | #endregion 60 | } 61 | } -------------------------------------------------------------------------------- /Lib/Test_NativeExport.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using GCore.NativeInterop; 4 | using LibNamespace; 5 | 6 | namespace LibNamespace 7 | { 8 | public static class Test_NativeExport 9 | { 10 | // begin-snippet: Test_NativeExport_CS 11 | public delegate int FunctionPointerCallbackDelegate(int a); 12 | 13 | [UnmanagedCallersOnly] 14 | public static int Test_NativeExport_Call(IntPtr moduleHandle, int number) 15 | { 16 | var exportedDelegate = ModuleLoader.GetExport(moduleHandle, "Test_NativeExport_ExternC"); 17 | int result = exportedDelegate(number); 18 | 19 | unsafe 20 | { 21 | unchecked 22 | { 23 | var callbackFuncPtr = (delegate* unmanaged[Cdecl])ModuleLoader.GetExport(moduleHandle, "Test_NativeExport_ExternC"); 24 | result += callbackFuncPtr(number); 25 | } 26 | } 27 | 28 | return result / 2; 29 | } 30 | // end-snippet 31 | } 32 | } -------------------------------------------------------------------------------- /Lib/Test_NativeFunctionPointer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | 4 | namespace LibNamespace 5 | { 6 | public static class Test_NativeFunctionPointer 7 | { 8 | #region Test_NativeFunctionPointer_Checked_CS 9 | public delegate int FunctionPointerCallbackDelegate(int a); 10 | 11 | [UnmanagedCallersOnly] 12 | public static int Test_NativeFunctionPointer_Checked(IntPtr nativeFunctionPtr, int number) 13 | { 14 | var callbackFuncDelegate = 15 | (FunctionPointerCallbackDelegate)Marshal.GetDelegateForFunctionPointer(nativeFunctionPtr, 16 | typeof(FunctionPointerCallbackDelegate)); 17 | return callbackFuncDelegate(number); 18 | } 19 | #endregion 20 | 21 | #region Test_NativeFunctionPointer_Unchecked_CS 22 | [UnmanagedCallersOnly] 23 | public static int Test_NativeFunctionPointer_Unchecked(IntPtr nativeFunctionPtr, int number) 24 | { 25 | unsafe 26 | { 27 | unchecked 28 | { 29 | var callbackFuncPtr = (delegate* unmanaged[Cdecl])nativeFunctionPtr; 30 | return callbackFuncPtr(number); 31 | } 32 | } 33 | } 34 | #endregion 35 | } 36 | } -------------------------------------------------------------------------------- /Lib/Test_NativeString.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | using System.Text; 5 | using GCore.NativeInterop; 6 | 7 | namespace LibNamespace 8 | { 9 | public static class Test_NativeString 10 | { 11 | 12 | #region Test_NativeString_Ansi_CS 13 | [UnmanagedCallersOnly] 14 | public static int Test_NativeString_Ansi(IntPtr stringPtr) 15 | { 16 | return CEncoding.Ascii.GetString(stringPtr) == "Hello Ansi" ? 1 : 0; 17 | } 18 | #endregion 19 | 20 | #region Test_NativeString_Wide_CS 21 | [UnmanagedCallersOnly] 22 | public static int Test_NativeString_Wide(IntPtr stringPtr) 23 | { 24 | return CEncoding.Wide.GetString(stringPtr) == "Hello ❤" ? 1 : 0; 25 | } 26 | #endregion 27 | 28 | // --------------------------------------------------------------------------------------------- 29 | #region Test_NativeString_FunctionPointer_CS 30 | 31 | public delegate bool FunctionPointerCallbackAnsiDelegate(NativeString nstr); 32 | public delegate bool FunctionPointerCallbackWideDelegate(NativeWString nstr); 33 | 34 | public static FunctionPointerCallbackAnsiDelegate FunctionPointerCallbackAnsiDelegateInstance = 35 | new FunctionPointerCallbackAnsiDelegate(CallbackAnsi); 36 | 37 | public static FunctionPointerCallbackWideDelegate FunctionPointerCallbackWideDelegateInstance = 38 | new FunctionPointerCallbackWideDelegate(CallbackWide); 39 | 40 | public static bool CallbackAnsi(NativeString nstr) => nstr.ToString() == "Hello Ansi"; 41 | public static bool CallbackWide(NativeWString nstr) => nstr.ToString() == "Hello ❤"; 42 | 43 | [StructLayout(LayoutKind.Sequential)] 44 | public struct RetArgs 45 | { 46 | public IntPtr CallbackAnsi; 47 | public IntPtr CallbackWide; 48 | } 49 | 50 | [UnmanagedCallersOnly] 51 | public static void Test_NativeString_FunctionPointer(IntPtr retArgsPtr) 52 | { 53 | unsafe 54 | { 55 | RetArgs* retArgs = (RetArgs*)retArgsPtr; 56 | retArgs->CallbackAnsi = 57 | Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackAnsiDelegateInstance); 58 | retArgs->CallbackWide = 59 | Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackWideDelegateInstance); 60 | } 61 | } 62 | #endregion 63 | } 64 | } -------------------------------------------------------------------------------- /Lib/Test_NativeVTable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using GCore.NativeInterop; 4 | 5 | namespace LibNamespace 6 | { 7 | public static class Test_NativeVTable 8 | { 9 | #region Test_NativeVTable_Class_CS 10 | [StructLayout(LayoutKind.Sequential)] 11 | private struct ClassLayout 12 | { 13 | public IntPtr VTable; 14 | public int test; 15 | } 16 | 17 | [StructLayout(LayoutKind.Sequential)] 18 | private struct ClassVTable 19 | { 20 | public IntPtr Method_AddOne; 21 | public IntPtr Method_AddTwo; 22 | } 23 | 24 | delegate void MethodDelegate(IntPtr thisPtr); 25 | #endregion 26 | 27 | #region Test_NativeVTable_Overwrite_CS 28 | // This delegate will be the new virtual method 29 | private static readonly unsafe MethodDelegate delegate_SubOne = new MethodDelegate(thisPtr => 30 | { 31 | var instance = (ClassLayout*)thisPtr; 32 | instance->test -= 1; 33 | }); 34 | 35 | // Create new unmanaged VTable instance 36 | private static readonly UnmanagedMemory OverwrittenVTable = new(); 37 | #endregion 38 | 39 | [UnmanagedCallersOnly] 40 | public static void Test_NativeVTable_Call(IntPtr classInstance) 41 | { 42 | 43 | { 44 | #region Test_NativeVTable_ManagedCall_CS 45 | ClassLayout instance = Marshal.PtrToStructure(classInstance); 46 | 47 | ClassVTable vTable = Marshal.PtrToStructure(instance.VTable); 48 | 49 | MethodDelegate method_AddOne = 50 | Marshal.GetDelegateForFunctionPointer(vTable.Method_AddOne); 51 | MethodDelegate method_AddTwo = 52 | Marshal.GetDelegateForFunctionPointer(vTable.Method_AddTwo); 53 | 54 | method_AddOne(classInstance); 55 | method_AddTwo(classInstance); 56 | 57 | #endregion 58 | } 59 | 60 | #region Test_NativeVTable_ManagedOverwrite_CS 61 | unsafe 62 | { 63 | // Get the new VTable 64 | var vTable = OverwrittenVTable.PtrElem; 65 | 66 | // Set the virtual methods with the new managed function pointer (Both the same for simplicity) 67 | vTable->Method_AddTwo = vTable->Method_AddOne = 68 | Marshal.GetFunctionPointerForDelegate(delegate_SubOne); 69 | 70 | // Overwrite the VTable reference of the instance 71 | var instance = (ClassLayout*) classInstance; 72 | instance->VTable = (IntPtr) vTable; 73 | } 74 | #endregion 75 | } 76 | } 77 | } -------------------------------------------------------------------------------- /Lib/UnmanagedMemory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace GCore.NativeInterop 6 | { 7 | public class UnmanagedMemory : IDisposable 8 | { 9 | public IntPtr Ptr { get; protected set; } = IntPtr.Zero; 10 | public int Size { get; protected set; } = 0; 11 | 12 | protected void Alloc(int size) 13 | { 14 | if (size > 0) 15 | { 16 | System.Diagnostics.Debug.Assert(Ptr == IntPtr.Zero); 17 | Ptr = Marshal.AllocHGlobal(size); 18 | Size = size; 19 | } 20 | } 21 | 22 | protected UnmanagedMemory() { } 23 | 24 | public UnmanagedMemory(IntPtr ptr) 25 | { 26 | Ptr = ptr; 27 | } 28 | 29 | public UnmanagedMemory(int size) 30 | { 31 | Alloc(size); 32 | } 33 | 34 | public UnmanagedMemory(Type dataType, int count = 1) 35 | { 36 | Alloc(Marshal.SizeOf(dataType) * count); 37 | } 38 | 39 | ~UnmanagedMemory() 40 | { 41 | Dispose(); 42 | } 43 | 44 | public void Dispose() 45 | { 46 | if (Ptr != IntPtr.Zero) 47 | Marshal.FreeHGlobal(Ptr); 48 | Ptr = IntPtr.Zero; 49 | Size = 0; 50 | } 51 | 52 | public static UnmanagedMemory New(int count = 1) where T : struct 53 | { 54 | return new UnmanagedMemory(Marshal.SizeOf() * count); 55 | } 56 | } 57 | 58 | public class UnmanagedMemory : UnmanagedMemory where T : unmanaged 59 | { 60 | public unsafe T* PtrElem => (T*) Ptr; 61 | 62 | public int Stride => Marshal.SizeOf(); 63 | public int Count { get; protected set; } = 0; 64 | 65 | protected UnmanagedMemory() {} 66 | 67 | public UnmanagedMemory(IntPtr ptr, int count = 1) : base(ptr) 68 | { 69 | Count = count; 70 | } 71 | 72 | public UnmanagedMemory(int count = 1) 73 | { 74 | Alloc(Stride * count); 75 | Count = count; 76 | } 77 | 78 | public unsafe T* GetElement(int index) 79 | { 80 | return (T*)(Ptr + index * Stride); 81 | } 82 | 83 | public unsafe T*[] ToArray() 84 | { 85 | var ret = new T*[Count]; 86 | for (int i = 0; i < Count; i++) 87 | ret[i] = (T*) (Ptr + i * Stride); 88 | return ret; 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 |
10 |

Embedded .NET 6 Runtime

11 | Build and Test 12 |
13 | 14 | This repo contains a project with examples for using a embedded .NET 6 runtime in a C++ application using [dotnet_runtime](https://github.com/KevinGliewe/dotnet_runtime). 15 | 16 | 17 | * [Embedded .NET 6 Runtime](#embedded-net-6-runtime) 18 | * [Build and Run](#build-and-run) 19 | * [Windows](#windows) 20 | * [Requirements](#requirements) 21 | * [Steps](#steps) 22 | * [What happens during the build?](#what-happens-during-the-build) 23 | * [Linux & MacOS](#linux--macos) 24 | * [Requirements](#requirements-1) 25 | * [Steps](#steps-1) 26 | * [What happens during the build?](#what-happens-during-the-build-1) 27 | * [Examples](#examples) 28 | * [Load and initialize .NET runtime](#load-and-initialize-net-runtime) 29 | * [Managed Entrypoint](#managed-entrypoint) 30 | * [Managed component-entrypoint](#managed-component-entrypoint) 31 | * [Managed custom-entrypoint](#managed-custom-entrypoint) 32 | * [Managed Strings](#managed-strings) 33 | * [Return managed ASCII string](#return-managed-ascii-string) 34 | * [Return managed wide string](#return-managed-wide-string) 35 | * [Native Strings](#native-strings) 36 | * [Native ASCII string](#native-ascii-string) 37 | * [Native Wide string](#native-wide-string) 38 | * [Native string to managed function pointer](#native-string-to-managed-function-pointer) 39 | * [Managed function-pointer](#managed-function-pointer) 40 | * [Managed function-pointer to instance method](#managed-function-pointer-to-instance-method) 41 | * [Managed function-pointer to static function](#managed-function-pointer-to-static-function) 42 | * [Native function-pointer](#native-function-pointer) 43 | * [Calling checked native function pointer](#calling-checked-native-function-pointer) 44 | * [Calling unchecked native function pointer](#calling-unchecked-native-function-pointer) 45 | * [Usage of unsafe managed code to access native objects](#usage-of-unsafe-managed-code-to-access-native-objects) 46 | * [Native Arrays](#native-arrays) 47 | * [Native arrays using fixed struct member](#native-arrays-using-fixed-struct-member) 48 | * [Native arrays using pointer](#native-arrays-using-pointer) 49 | * [Native arrays using ArrPointerX on function-pointer](#native-arrays-using-arrpointerx-on-function-pointer) 50 | * [Native Symbols](#native-symbols) 51 | * [Calling native exported symbols using DllImport](#calling-native-exported-symbols-using-dllimport) 52 | * [Calling native exported symbols using GetProcAddress/dlsym](#calling-native-exported-symbols-using-getprocaddressdlsym) 53 | * [Native VTable](#native-vtable) 54 | * [Calling native VTable from managed code](#calling-native-vtable-from-managed-code) 55 | * [Overwriting native VTable with managed code](#overwriting-native-vtable-with-managed-code) 56 | * [LICENSE](#license) 57 | 58 | 59 | # Build and Run 60 | 61 | ## Windows 62 | 63 | ### Requirements 64 | 65 | * Visual Studio 2019 66 | * .NET 6 SDK 67 | 68 | ### Steps 69 | 70 | 1. Clone this repository recursively using 71 | `git clone --recursive git@github.com:KevinGliewe/embedded_dotnet_runtime_examples.git` 72 | 2. Run `build_run.bat` 73 | 74 | ### What happens during the build? 75 | 76 | * Imports `VsDevCmd.bat` 77 | * Installs dotnet tool [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 78 | * Downloads the propper .NET 6 runtime for your system using [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 79 | * Builds `Lib` and `Host` using MSBuild 80 | * Runs `Host` 81 | 82 | ## Linux & MacOS 83 | 84 | ### Requirements 85 | 86 | * CMake 3.16 or newer 87 | * .NET 6 SDK 88 | * C++ compiler 89 | 90 | ### Steps 91 | 92 | 1. Clone this repository recursively using 93 | `git clone --recursive git@github.com:KevinGliewe/embedded_dotnet_runtime_examples.git` 94 | 2. Run `sh build_run.sh` 95 | 96 | ### What happens during the build? 97 | 98 | * Installs dotnet tool [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 99 | * Downloads the propper .NET 6 runtime for your system using [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 100 | * Builds `Lib` using .NET 6 SDK 101 | * Creates makefile using CMake 102 | * Builds `Host` using make 103 | * Runs `Host` 104 | 105 | # Examples 106 | 107 | ## Load and initialize .NET runtime 108 | 109 | 110 | 111 | ```cpp 112 | // Init runtime and lib 113 | 114 | auto runtime = dotnet_runtime::Runtime(hostfxr_path, libRuntimeconfig_path); 115 | 116 | auto lib = dotnet_runtime::Library(&runtime, libDll_path, STR("Lib")); 117 | ``` 118 | snippet source | anchor 119 | 120 | 121 | --- 122 | 123 | ## Managed Entrypoint 124 | 125 | > Entrypoints can only use [blittable types](https://en.wikipedia.org/wiki/Blittable_types) 126 | 127 | 128 | ### Managed component-entrypoint 129 | 130 |
Native 131 |

132 | 133 | 134 | 135 | ```h 136 | struct args 137 | { 138 | int number1; 139 | int number2; 140 | }; 141 | ``` 142 | snippet source | anchor 143 | 144 | 145 | 146 | 147 | ```h 148 | auto fpTest_ComponentEntryPoint = a_lib.GetComponentEntrypoint( 149 | STR("LibNamespace.Test_ManagedEntryPoint"), 150 | STR("Test_ComponentEntryPoint") 151 | ); 152 | 153 | bool success = fpTest_ComponentEntryPoint(&_args, sizeof(_args)) == 3; 154 | LogTest(success, L"Test_ComponentEntryPoint"); 155 | ``` 156 | snippet source | anchor 157 | 158 |

159 |
160 | 161 |
Managed 162 |

163 | 164 | 165 | 166 | ```cs 167 | [StructLayout(LayoutKind.Sequential)] 168 | public unsafe struct Args 169 | { 170 | public int Number1; 171 | public int Number2; 172 | } 173 | ``` 174 | snippet source | anchor 175 | 176 | 177 | 178 | 179 | ```cs 180 | public static int Test_ComponentEntryPoint(IntPtr arg, int argLength) 181 | { 182 | if (argLength < Marshal.SizeOf(typeof(Args))) 183 | { 184 | return 1; 185 | } 186 | 187 | Args args = Marshal.PtrToStructure(arg); 188 | 189 | return args.Number1 + args.Number2; 190 | } 191 | ``` 192 | snippet source | anchor 193 | 194 |

195 |
196 | 197 | ### Managed custom-entrypoint 198 | 199 | > Entrypoints can only use [blittable types](https://en.wikipedia.org/wiki/Blittable_types) 200 | 201 |
Native 202 |

203 | 204 | 205 | 206 | ```h 207 | struct args 208 | { 209 | int number1; 210 | int number2; 211 | }; 212 | ``` 213 | snippet source | anchor 214 | 215 | 216 | 217 | 218 | ```h 219 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(args); 220 | 221 | auto fpTest_CustomEntryPoint = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 222 | STR("LibNamespace.Test_ManagedEntryPoint"), 223 | STR("Test_CustomEntryPoint") 224 | ); 225 | 226 | bool success = fpTest_CustomEntryPoint(_args) == 3; 227 | LogTest(success, L"Test_CustomEntryPoint"); 228 | ``` 229 | snippet source | anchor 230 | 231 |

232 |
233 | 234 |
Managed 235 |

236 | 237 | 238 | 239 | ```cs 240 | [StructLayout(LayoutKind.Sequential)] 241 | public unsafe struct Args 242 | { 243 | public int Number1; 244 | public int Number2; 245 | } 246 | ``` 247 | snippet source | anchor 248 | 249 | 250 | 251 | 252 | ```cs 253 | [UnmanagedCallersOnly] 254 | public static int Test_CustomEntryPoint(Args args) 255 | { 256 | return args.Number1 + args.Number2; 257 | } 258 | ``` 259 | snippet source | anchor 260 | 261 |

262 |
263 | 264 | --- 265 | 266 | ## Managed Strings 267 | 268 | ### Return managed ASCII string 269 | 270 |
Native 271 |

272 | 273 | 274 | 275 | ```h 276 | auto fpTest_ManagedString_Ansi = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 277 | STR("LibNamespace.Test_ManagedString"), 278 | STR("Test_ManagedString_Ansi") 279 | ); 280 | 281 | char* str = (char*)fpTest_ManagedString_Ansi(); 282 | 283 | // Compare the ASCII strings 284 | bool success = cmp(str, (char*)"Hello Ansi"); 285 | 286 | LogTest(success, L"Test_ManagedString_Ansi"); 287 | ``` 288 | snippet source | anchor 289 | 290 |

291 |
292 | 293 |
Managed 294 |

295 | 296 | 297 | 298 | ```cs 299 | public static readonly CString HelloAnsi = new CString("Hello Ansi"); 300 | 301 | [UnmanagedCallersOnly] 302 | public static IntPtr Test_ManagedString_Ansi() 303 | { 304 | return HelloAnsi.Ptr; 305 | } 306 | ``` 307 | snippet source | anchor 308 | 309 |

310 |
311 | 312 | What does CString do? 313 | 1. Apends a `\0` character on the end of the string. 314 | 2. Converts the string into ANSI encoding. 315 | 3. Allocate memory for native access using [`Marshal.AllocHGlobal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.allochglobal?view=net-6.0). 316 | 4. Copy the encoded string into the allocated memory. 317 | 318 | ### Return managed wide string 319 | 320 | > The encoding depends on the platform. For windows systems it is UTF16 and for posix systems it is UTF32. 321 | 322 |
Native 323 |

324 | 325 | 326 | 327 | ```h 328 | auto fpTest_ManagedString_Wide = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 329 | STR("LibNamespace.Test_ManagedString"), 330 | STR("Test_ManagedString_Wide") 331 | ); 332 | 333 | wchar_t* str = (wchar_t*)fpTest_ManagedString_Wide(); 334 | 335 | // Compare the wide strings 336 | bool success = cmp(str, (wchar_t*)L"Hello ❤"); 337 | 338 | LogTest(success, L"Test_ManagedString_Wide"); 339 | ``` 340 | snippet source | anchor 341 | 342 |

343 |
344 | 345 |
Managed 346 |

347 | 348 | What does CString do? 349 | 1. Determins the correct encoding for the current platform. (UTF16 or UTF32) 350 | 2. Apends a `\0` character on the end of the string. 351 | 3. Converts the string into the propper encoding. 352 | 4. Allocate memory for native access using [`Marshal.AllocHGlobal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.allochglobal?view=net-6.0). 353 | 5. Copy the encoded string into the allocated memory. 354 | 355 | 356 | 357 | ```cs 358 | public static readonly CString HelloWide = new CString("Hello ❤", CEncoding.Wide); 359 | 360 | [UnmanagedCallersOnly] 361 | public static IntPtr Test_ManagedString_Wide() 362 | { 363 | return HelloWide.Ptr; 364 | } 365 | ``` 366 | snippet source | anchor 367 | 368 |

369 |
370 | 371 | --- 372 | 373 | ## Native Strings 374 | 375 | ### Native ASCII string 376 | 377 |
Native 378 |

379 | 380 | 381 | 382 | ```h 383 | auto fpTest_NativeString_Ansi = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 384 | STR("LibNamespace.Test_NativeString"), 385 | STR("Test_NativeString_Ansi") 386 | ); 387 | 388 | bool success = fpTest_NativeString_Ansi((void*)"Hello Ansi"); 389 | LogTest(success, L"Test_NativeString_Ansi"); 390 | ``` 391 | snippet source | anchor 392 | 393 |

394 |
395 | 396 |
Managed 397 |

398 | 399 | 400 | 401 | ```cs 402 | [UnmanagedCallersOnly] 403 | public static int Test_NativeString_Ansi(IntPtr stringPtr) 404 | { 405 | return CEncoding.Ascii.GetString(stringPtr) == "Hello Ansi" ? 1 : 0; 406 | } 407 | ``` 408 | snippet source | anchor 409 | 410 |

411 |
412 | 413 | ### Native Wide string 414 | 415 |
Native 416 |

417 | 418 | 419 | 420 | ```h 421 | auto fpTest_NativeString_Wide = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 422 | STR("LibNamespace.Test_NativeString"), 423 | STR("Test_NativeString_Wide") 424 | ); 425 | 426 | bool success = fpTest_NativeString_Wide((void*)L"Hello ❤"); 427 | LogTest(success, L"Test_NativeString_Wide"); 428 | ``` 429 | snippet source | anchor 430 | 431 |

432 |
433 | 434 |
Managed 435 |

436 | 437 | 438 | 439 | ```cs 440 | [UnmanagedCallersOnly] 441 | public static int Test_NativeString_Wide(IntPtr stringPtr) 442 | { 443 | return CEncoding.Wide.GetString(stringPtr) == "Hello ❤" ? 1 : 0; 444 | } 445 | ``` 446 | snippet source | anchor 447 | 448 |

449 |
450 | 451 | ### Native string to managed function pointer 452 | 453 |
Native 454 |

455 | 456 | 457 | 458 | ```h 459 | struct RetArgs 460 | { 461 | bool (*CallbackAnsi)(const char*); 462 | bool (*CallbackWide)(const wchar_t*); 463 | }; 464 | ``` 465 | snippet source | anchor 466 | 467 | 468 | 469 | 470 | ```h 471 | typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn2)(void*); 472 | 473 | auto fpTest_NativeString_FunctionPointer = (custom_entry_point_fn2)a_lib.GetCustomEntrypoint( 474 | STR("LibNamespace.Test_NativeString"), 475 | STR("Test_NativeString_FunctionPointer") 476 | ); 477 | 478 | RetArgs retArgs; 479 | fpTest_NativeString_FunctionPointer(&retArgs); 480 | 481 | { // Ansi 482 | bool success = retArgs.CallbackAnsi("Hello Ansi"); 483 | LogTest(success, L"Test_NativeString_FunctionPointer.CallbackAnsi"); 484 | 485 | ret &= success; 486 | } 487 | 488 | { // Wide 489 | bool success = retArgs.CallbackWide(L"Hello ❤"); 490 | LogTest(success, L"Test_NativeString_FunctionPointer.CallbackWide"); 491 | 492 | ret &= success; 493 | } 494 | ``` 495 | snippet source | anchor 496 | 497 |

498 |
499 | 500 |
Managed 501 |

502 | 503 | 504 | 505 | ```cs 506 | public delegate bool FunctionPointerCallbackAnsiDelegate(NativeString nstr); 507 | public delegate bool FunctionPointerCallbackWideDelegate(NativeWString nstr); 508 | 509 | public static FunctionPointerCallbackAnsiDelegate FunctionPointerCallbackAnsiDelegateInstance = 510 | new FunctionPointerCallbackAnsiDelegate(CallbackAnsi); 511 | 512 | public static FunctionPointerCallbackWideDelegate FunctionPointerCallbackWideDelegateInstance = 513 | new FunctionPointerCallbackWideDelegate(CallbackWide); 514 | 515 | public static bool CallbackAnsi(NativeString nstr) => nstr.ToString() == "Hello Ansi"; 516 | public static bool CallbackWide(NativeWString nstr) => nstr.ToString() == "Hello ❤"; 517 | 518 | [StructLayout(LayoutKind.Sequential)] 519 | public struct RetArgs 520 | { 521 | public IntPtr CallbackAnsi; 522 | public IntPtr CallbackWide; 523 | } 524 | 525 | [UnmanagedCallersOnly] 526 | public static void Test_NativeString_FunctionPointer(IntPtr retArgsPtr) 527 | { 528 | unsafe 529 | { 530 | RetArgs* retArgs = (RetArgs*)retArgsPtr; 531 | retArgs->CallbackAnsi = 532 | Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackAnsiDelegateInstance); 533 | retArgs->CallbackWide = 534 | Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackWideDelegateInstance); 535 | } 536 | } 537 | ``` 538 | snippet source | anchor 539 | 540 |

541 |
542 | 543 | --- 544 | 545 | ## Managed function-pointer 546 | 547 | ### Managed function-pointer to instance method 548 | 549 |
Native 550 |

551 | 552 | 553 | 554 | ```h 555 | typedef int (*managed_callback_fn)(int); 556 | ``` 557 | snippet source | anchor 558 | 559 | 560 | 561 | 562 | ```h 563 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(int); 564 | 565 | // Get the managed entry point 566 | auto fpTest_ManagedFunctionPointer_Instance = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 567 | STR("LibNamespace.Test_ManagedFunctionPointer"), 568 | STR("Test_ManagedFunctionPointer_Instance") 569 | ); 570 | 571 | // Get the function pointer to the managed method 572 | managed_callback_fn managedCallback = (managed_callback_fn)fpTest_ManagedFunctionPointer_Instance(2); 573 | 574 | bool success = managedCallback(6) == 8; 575 | 576 | LogTest(success, L"Test_ManagedFunctionPointer_Instance"); 577 | ``` 578 | snippet source | anchor 579 | 580 |

581 |
582 | 583 |
Managed 584 |

585 | 586 | 587 | 588 | ```cs 589 | public delegate int FunctionPointerCallbackDelegate(int a); 590 | ``` 591 | snippet source | anchor 592 | 593 | 594 | 595 | 596 | ```cs 597 | public class CallableObject 598 | { 599 | public FunctionPointerCallbackDelegate CallbackDelegate; 600 | public IntPtr CallbackFunctionPointer; 601 | 602 | private int Member; 603 | 604 | public int Callback(int i) => i + Member; 605 | 606 | public CallableObject(int member) 607 | { 608 | Member = member; 609 | CallbackDelegate = new FunctionPointerCallbackDelegate(Callback); 610 | CallbackFunctionPointer = Marshal.GetFunctionPointerForDelegate(CallbackDelegate); 611 | } 612 | 613 | } 614 | 615 | public static CallableObject CallableObjectInstance; 616 | 617 | // Enty point for unmanaged code 618 | [UnmanagedCallersOnly] 619 | public static IntPtr Test_ManagedFunctionPointer_Instance(int member) 620 | { 621 | CallableObjectInstance = new CallableObject(member); 622 | return CallableObjectInstance.CallbackFunctionPointer; 623 | } 624 | ``` 625 | snippet source | anchor 626 | 627 |

628 |
629 | 630 | ### Managed function-pointer to static function 631 | 632 |
Native 633 |

634 | 635 | 636 | 637 | ```h 638 | typedef int (*managed_callback_fn)(int); 639 | ``` 640 | snippet source | anchor 641 | 642 | 643 | 644 | 645 | ```h 646 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(); 647 | 648 | // Get the managed entry point 649 | auto fpTest_ManagedFunctionPointer_Static = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 650 | STR("LibNamespace.Test_ManagedFunctionPointer"), 651 | STR("Test_ManagedFunctionPointer_Static") 652 | ); 653 | 654 | // Get the function pointer to the managed function 655 | managed_callback_fn managedCallback = (managed_callback_fn)fpTest_ManagedFunctionPointer_Static(); 656 | 657 | bool success = managedCallback(5) == 8; 658 | 659 | LogTest(success, L"Test_ManagedFunctionPointer_Static"); 660 | ``` 661 | snippet source | anchor 662 | 663 |

664 |
665 | 666 |
Managed 667 |

668 | 669 | 670 | 671 | ```cs 672 | public delegate int FunctionPointerCallbackDelegate(int a); 673 | ``` 674 | snippet source | anchor 675 | 676 | 677 | 678 | 679 | ```cs 680 | public static FunctionPointerCallbackDelegate FunctionPointerCallbackDelegateStatic = new FunctionPointerCallbackDelegate(Callback); 681 | 682 | public static int Callback(int i) => i + 3; 683 | 684 | // Enty point for unmanaged code 685 | [UnmanagedCallersOnly] 686 | public static IntPtr Test_ManagedFunctionPointer_Static() 687 | { 688 | return Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackDelegateStatic); 689 | } 690 | ``` 691 | snippet source | anchor 692 | 693 |

694 |
695 | 696 | --- 697 | 698 | ## Native function-pointer 699 | 700 | ### Calling checked native function pointer 701 | 702 |
Native 703 |

704 | 705 | 706 | 707 | ```h 708 | int FEXPORT CallbackFunc(int i) 709 | { 710 | return i * 2; 711 | } 712 | ``` 713 | snippet source | anchor 714 | 715 | 716 | 717 | 718 | ```h 719 | void* fpNativeCallback = (void*)&CallbackFunc; 720 | ``` 721 | snippet source | anchor 722 | 723 | 724 | 725 | 726 | ```h 727 | auto fpTest_NativeFunctionPointer_Checked = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 728 | STR("LibNamespace.Test_NativeFunctionPointer"), 729 | STR("Test_NativeFunctionPointer_Checked") 730 | ); 731 | 732 | bool success = fpTest_NativeFunctionPointer_Checked(fpNativeCallback, 4) == 8; 733 | LogTest(success, L"Test_NativeFunctionPointer_Checked"); 734 | ``` 735 | snippet source | anchor 736 | 737 |

738 |
739 | 740 |
Managed 741 |

742 | 743 | 744 | 745 | ```cs 746 | public delegate int FunctionPointerCallbackDelegate(int a); 747 | 748 | [UnmanagedCallersOnly] 749 | public static int Test_NativeFunctionPointer_Checked(IntPtr nativeFunctionPtr, int number) 750 | { 751 | var callbackFuncDelegate = 752 | (FunctionPointerCallbackDelegate)Marshal.GetDelegateForFunctionPointer(nativeFunctionPtr, 753 | typeof(FunctionPointerCallbackDelegate)); 754 | return callbackFuncDelegate(number); 755 | } 756 | ``` 757 | snippet source | anchor 758 | 759 |

760 |
761 | 762 | ### Calling unchecked native function pointer 763 | 764 |
Native 765 |

766 | 767 | 768 | 769 | ```h 770 | int FEXPORT CallbackFunc(int i) 771 | { 772 | return i * 2; 773 | } 774 | ``` 775 | snippet source | anchor 776 | 777 | 778 | 779 | 780 | ```h 781 | void* fpNativeCallback = (void*)&CallbackFunc; 782 | ``` 783 | snippet source | anchor 784 | 785 | 786 | 787 | 788 | ```h 789 | auto fpTest_NativeFunctionPointer_Unchecked = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 790 | STR("LibNamespace.Test_NativeFunctionPointer"), 791 | STR("Test_NativeFunctionPointer_Unchecked") 792 | ); 793 | 794 | bool success = fpTest_NativeFunctionPointer_Unchecked(fpNativeCallback, 4) == 8; 795 | LogTest(success, L"Test_NativeFunctionPointer_Unchecked"); 796 | ``` 797 | snippet source | anchor 798 | 799 |

800 |
801 | 802 |
Managed 803 |

804 | 805 | 806 | 807 | ```cs 808 | [UnmanagedCallersOnly] 809 | public static int Test_NativeFunctionPointer_Unchecked(IntPtr nativeFunctionPtr, int number) 810 | { 811 | unsafe 812 | { 813 | unchecked 814 | { 815 | var callbackFuncPtr = (delegate* unmanaged[Cdecl])nativeFunctionPtr; 816 | return callbackFuncPtr(number); 817 | } 818 | } 819 | } 820 | ``` 821 | snippet source | anchor 822 | 823 |

824 |
825 | 826 | --- 827 | 828 | ## Usage of unsafe managed code to access native objects 829 | 830 |
Native 831 |

832 | 833 | 834 | 835 | ```h 836 | struct Args 837 | { 838 | int number1; 839 | int number2; 840 | int sum; 841 | char returnMsg[128]; 842 | }; 843 | 844 | bool Run(dotnet_runtime::Library& a_lib) 845 | { 846 | bool ret = true; 847 | 848 | typedef void (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(void*); 849 | 850 | auto fpTest_ManagedUnsafe_Struct = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 851 | STR("LibNamespace.Test_ManagedUnsafe"), 852 | STR("Test_ManagedUnsafe_Struct") 853 | ); 854 | 855 | Args args; 856 | args.number1 = 1; 857 | args.number2 = 2; 858 | 859 | fpTest_ManagedUnsafe_Struct(&args); 860 | 861 | { //sum 862 | bool success = args.sum == 3; 863 | LogTest(success, L"Test_ManagedUnsafe_Struct.Sum"); 864 | ret &= success; 865 | } 866 | 867 | { // ReturnMsg 868 | bool success = cmp(args.returnMsg, (char*)"Hello Ansi"); 869 | LogTest(success, L"Test_ManagedUnsafe_Struct.ReturnMsg"); 870 | ret &= success; 871 | } 872 | return ret; 873 | } 874 | ``` 875 | snippet source | anchor 876 | 877 |

878 |
879 | 880 |
Managed 881 |

882 | 883 | 884 | 885 | ```cs 886 | [StructLayout(LayoutKind.Sequential)] 887 | public unsafe struct Args 888 | { 889 | public int Number1; 890 | public int number2; 891 | public int Sum; 892 | public fixed byte ReturnMsg[128]; 893 | } 894 | 895 | [UnmanagedCallersOnly] 896 | public static void Test_ManagedUnsafe_Struct(IntPtr ptr) 897 | { 898 | unsafe 899 | { 900 | Args* args = (Args*) ptr; 901 | 902 | args->Sum = args->Number1 + args->number2; 903 | 904 | // Get the memory offset of Args.ReturnMsg 905 | var destReturnMsg = IntPtr.Add(ptr, (int)Marshal.OffsetOf(typeof(Args), nameof(Args.ReturnMsg))); 906 | 907 | var data = CEncoding.Ascii.GetBytes("Hello Ansi"); 908 | 909 | // Copy the data to the unmanaged memory 910 | Marshal.Copy(data, 0, (IntPtr)destReturnMsg, data.Length); 911 | } 912 | } 913 | ``` 914 | snippet source | anchor 915 | 916 |

917 |
918 | 919 | --- 920 | 921 | ## Native Arrays 922 | 923 | ### Native arrays using fixed struct member 924 | 925 |
Native 926 |

927 | 928 | 929 | 930 | ```h 931 | struct args 932 | { 933 | int Arr[8]; 934 | int Multiplier; 935 | }; 936 | ``` 937 | snippet source | anchor 938 | 939 | 940 | 941 | 942 | ```h 943 | args _args{ 944 | 1, 2, 3, 4, 5, 6, 7, 8, // Arr[8] 945 | 2 // Multiplier 946 | }; 947 | ``` 948 | snippet source | anchor 949 | 950 | 951 | 952 | 953 | ```h 954 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(args); 955 | 956 | auto fpTest_NativeArray_StructFixed = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 957 | STR("LibNamespace.Test_NativeArray"), 958 | STR("Test_NativeArray_StructFixed") 959 | ); 960 | 961 | bool success = fpTest_NativeArray_StructFixed(_args) == 72; 962 | LogTest(success, L"Test_NativeArray_StructFixed"); 963 | ``` 964 | snippet source | anchor 965 | 966 |

967 |
968 | 969 |
Managed 970 |

971 | 972 | 973 | 974 | ```cs 975 | [StructLayout(LayoutKind.Sequential)] 976 | public unsafe struct Args 977 | { 978 | public fixed int Arr[8]; 979 | public int Multiplier; 980 | } 981 | 982 | [UnmanagedCallersOnly] 983 | public static unsafe int Test_NativeArray_StructFixed(Args args) 984 | { 985 | int ret = 0; 986 | 987 | for(int i = 0; i < 8; i++) 988 | { 989 | ret += args.Arr[i] * args.Multiplier; 990 | } 991 | 992 | return ret; 993 | } 994 | ``` 995 | snippet source | anchor 996 | 997 |

998 |
999 | 1000 | ### Native arrays using pointer 1001 | 1002 |
Native 1003 |

1004 | 1005 | 1006 | 1007 | ```h 1008 | typedef int (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(int[], int); 1009 | 1010 | auto fpTest_NativeArray_ArgumentFixed = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 1011 | STR("LibNamespace.Test_NativeArray"), 1012 | STR("Test_NativeArray_ArgumentFixed") 1013 | ); 1014 | 1015 | bool success = fpTest_NativeArray_ArgumentFixed(_args.Arr, _args.Multiplier) == 72; 1016 | LogTest(success, L"Test_NativeArray_ArgumentFixed"); 1017 | ``` 1018 | snippet source | anchor 1019 | 1020 |

1021 |
1022 | 1023 |
Managed 1024 |

1025 | 1026 | 1027 | 1028 | ```cs 1029 | [UnmanagedCallersOnly] 1030 | public static int Test_NativeArray_ArgumentFixed(IntPtr arrPtr, int multiplier) 1031 | { 1032 | // ArrPointer as argument does not work here! 1033 | 1034 | ArrPointer8 arr = new ArrPointer8(arrPtr); 1035 | 1036 | return arr.Sum(el => el * multiplier); ; 1037 | } 1038 | ``` 1039 | snippet source | anchor 1040 | 1041 |

1042 |
1043 | 1044 | ### Native arrays using ArrPointerX on function-pointer 1045 | 1046 |
Native 1047 |

1048 | 1049 | 1050 | 1051 | ```h 1052 | typedef void* (CORECLR_DELEGATE_CALLTYPE* custom_entry_point_fn)(); 1053 | typedef int (*managed_callback_fn)(int[], int); 1054 | 1055 | auto fpTest_NativeArray_ArgumentFixed_FunctionPointer = (custom_entry_point_fn)a_lib.GetCustomEntrypoint( 1056 | STR("LibNamespace.Test_NativeArray"), 1057 | STR("Test_NativeArray_ArgumentFixed_FunctionPointer") 1058 | ); 1059 | 1060 | managed_callback_fn callback = (managed_callback_fn)fpTest_NativeArray_ArgumentFixed_FunctionPointer(); 1061 | 1062 | bool success = callback(_args.Arr, _args.Multiplier) == 72; 1063 | LogTest(success, L"Test_NativeArray_ArgumentFixed_FunctionPointer"); 1064 | ``` 1065 | snippet source | anchor 1066 | 1067 |

1068 |
1069 | 1070 |
Managed 1071 |

1072 | 1073 | 1074 | 1075 | ```cs 1076 | public delegate int FunctionPointerCallbackDelegate(ArrPointer8 arr, int multiplier); 1077 | 1078 | public static FunctionPointerCallbackDelegate FunctionPointerCallbackDelegateInstance = 1079 | new FunctionPointerCallbackDelegate(Callback); 1080 | 1081 | public static int Callback(ArrPointer8 arr, int multiplier) => arr.Sum(el => el * multiplier); 1082 | 1083 | [UnmanagedCallersOnly] 1084 | public static IntPtr Test_NativeArray_ArgumentFixed_FunctionPointer() 1085 | { 1086 | return Marshal.GetFunctionPointerForDelegate(FunctionPointerCallbackDelegateInstance); 1087 | } 1088 | ``` 1089 | snippet source | anchor 1090 | 1091 |

1092 |
1093 | 1094 | --- 1095 | 1096 | ## Native Symbols 1097 | 1098 | ### Calling native exported symbols using `DllImport` 1099 | 1100 |
Native 1101 |

1102 | 1103 | 1104 | 1105 | ```h 1106 | extern "C" 1107 | { 1108 | int FEXPORT Test_DllImport_ExternC(int number) 1109 | { 1110 | return number * 2; 1111 | } 1112 | } 1113 | ``` 1114 | snippet source | anchor 1115 | 1116 | 1117 | 1118 | 1119 | ```h 1120 | void* host_handle = dotnet_runtime::get_host_handle(); 1121 | return fpTest_DllImport_Call(host_handle, 4) == 8; 1122 | ``` 1123 | snippet source | anchor 1124 | 1125 | 1126 | > For posix systems, use the `-export-dynamic` flag for the linker. 1127 |

1128 |
1129 | 1130 |
Managed 1131 |

1132 | 1133 | 1134 | 1135 | ```cs 1136 | private static IntPtr s_moduleHandle = IntPtr.Zero; 1137 | 1138 | private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath) 1139 | { 1140 | IntPtr ret = libraryName == "Test_DllImport_LibName" ? s_moduleHandle : NativeLibrary.Load(libraryName, assembly, searchPath); 1141 | //Console.WriteLine($"ImportResolver s_moduleHandle={s_moduleHandle} ret={ret}"); 1142 | return ret; 1143 | } 1144 | 1145 | [DllImport("Test_DllImport_LibName")] 1146 | public static extern int Test_DllImport_ExternC(int number); 1147 | 1148 | [UnmanagedCallersOnly] 1149 | public static int Test_DllImport_Call(IntPtr moduleHandle, int number) 1150 | { 1151 | s_moduleHandle = moduleHandle; 1152 | NativeLibrary.SetDllImportResolver(typeof(Test_DllImport).Assembly, ImportResolver); 1153 | 1154 | return Test_DllImport_ExternC(number); 1155 | } 1156 | ``` 1157 | snippet source | anchor 1158 | 1159 |

1160 |
1161 | 1162 | ### Calling native exported symbols using `GetProcAddress`/`dlsym` 1163 | 1164 |
Native 1165 |

1166 | 1167 | 1168 | 1169 | ```h 1170 | extern "C" 1171 | { 1172 | int FEXPORT Test_NativeExport_ExternC(int number) 1173 | { 1174 | return number * 4; 1175 | } 1176 | } 1177 | ``` 1178 | snippet source | anchor 1179 | 1180 | 1181 | 1182 | 1183 | ```h 1184 | void* host_handle = dotnet_runtime::get_host_handle(); 1185 | return fpTest_NativeExport_Call(host_handle, 4) == 16; 1186 | ``` 1187 | snippet source | anchor 1188 | 1189 | 1190 | > For posix systems, use the `-export-dynamic` flag for the linker. 1191 |

1192 |
1193 | 1194 |
Managed 1195 |

1196 | 1197 | 1198 | 1199 | ```cs 1200 | public delegate int FunctionPointerCallbackDelegate(int a); 1201 | 1202 | [UnmanagedCallersOnly] 1203 | public static int Test_NativeExport_Call(IntPtr moduleHandle, int number) 1204 | { 1205 | var exportedDelegate = ModuleLoader.GetExport(moduleHandle, "Test_NativeExport_ExternC"); 1206 | int result = exportedDelegate(number); 1207 | 1208 | unsafe 1209 | { 1210 | unchecked 1211 | { 1212 | var callbackFuncPtr = (delegate* unmanaged[Cdecl])ModuleLoader.GetExport(moduleHandle, "Test_NativeExport_ExternC"); 1213 | result += callbackFuncPtr(number); 1214 | } 1215 | } 1216 | 1217 | return result / 2; 1218 | } 1219 | ``` 1220 | snippet source | anchor 1221 | 1222 |

1223 |
1224 | 1225 | --- 1226 | 1227 | ## Native VTable 1228 | 1229 | [VTable](https://en.wikipedia.org/wiki/Virtual_method_table) 1230 | 1231 | ### Calling native VTable from managed code 1232 | 1233 |
Native 1234 |

1235 | 1236 | 1237 | 1238 | ```h 1239 | class ClassLayout 1240 | { 1241 | private: 1242 | int m_iTest = 0; 1243 | public: 1244 | virtual void AddOne() { this->m_iTest += 1; } 1245 | virtual void AddTwo() { this->m_iTest += 2; } 1246 | 1247 | int GetTest() { return m_iTest; } 1248 | }; 1249 | ``` 1250 | snippet source | anchor 1251 | 1252 | 1253 | 1254 | 1255 | ```h 1256 | ClassLayout* instance = new ClassLayout(); 1257 | 1258 | // Calls AddOne and AddTwo, then overwrites VTable 1259 | fpTest_NativeVTable_Call((void*)instance); 1260 | ``` 1261 | snippet source | anchor 1262 | 1263 | 1264 |

1265 |
1266 | 1267 |
Managed 1268 |

1269 | 1270 | 1271 | 1272 | ```cs 1273 | [StructLayout(LayoutKind.Sequential)] 1274 | private struct ClassLayout 1275 | { 1276 | public IntPtr VTable; 1277 | public int test; 1278 | } 1279 | 1280 | [StructLayout(LayoutKind.Sequential)] 1281 | private struct ClassVTable 1282 | { 1283 | public IntPtr Method_AddOne; 1284 | public IntPtr Method_AddTwo; 1285 | } 1286 | 1287 | delegate void MethodDelegate(IntPtr thisPtr); 1288 | ``` 1289 | snippet source | anchor 1290 | 1291 | 1292 | 1293 | 1294 | ```cs 1295 | ClassLayout instance = Marshal.PtrToStructure(classInstance); 1296 | 1297 | ClassVTable vTable = Marshal.PtrToStructure(instance.VTable); 1298 | 1299 | MethodDelegate method_AddOne = 1300 | Marshal.GetDelegateForFunctionPointer(vTable.Method_AddOne); 1301 | MethodDelegate method_AddTwo = 1302 | Marshal.GetDelegateForFunctionPointer(vTable.Method_AddTwo); 1303 | 1304 | method_AddOne(classInstance); 1305 | method_AddTwo(classInstance); 1306 | ``` 1307 | snippet source | anchor 1308 | 1309 | 1310 |

1311 |
1312 | 1313 | ### Overwriting native VTable with managed code 1314 | 1315 |
Native 1316 |

1317 | 1318 | 1319 | 1320 | ```h 1321 | class ClassLayout 1322 | { 1323 | private: 1324 | int m_iTest = 0; 1325 | public: 1326 | virtual void AddOne() { this->m_iTest += 1; } 1327 | virtual void AddTwo() { this->m_iTest += 2; } 1328 | 1329 | int GetTest() { return m_iTest; } 1330 | }; 1331 | ``` 1332 | snippet source | anchor 1333 | 1334 | 1335 | 1336 | 1337 | ```h 1338 | ClassLayout* instance = new ClassLayout(); 1339 | 1340 | // Calls AddOne and AddTwo, then overwrites VTable 1341 | fpTest_NativeVTable_Call((void*)instance); 1342 | ``` 1343 | snippet source | anchor 1344 | 1345 | 1346 |

1347 |
1348 | 1349 |
Managed 1350 |

1351 | 1352 | 1353 | 1354 | ```cs 1355 | [StructLayout(LayoutKind.Sequential)] 1356 | private struct ClassLayout 1357 | { 1358 | public IntPtr VTable; 1359 | public int test; 1360 | } 1361 | 1362 | [StructLayout(LayoutKind.Sequential)] 1363 | private struct ClassVTable 1364 | { 1365 | public IntPtr Method_AddOne; 1366 | public IntPtr Method_AddTwo; 1367 | } 1368 | 1369 | delegate void MethodDelegate(IntPtr thisPtr); 1370 | ``` 1371 | snippet source | anchor 1372 | 1373 | 1374 | 1375 | 1376 | ```cs 1377 | // This delegate will be the new virtual method 1378 | private static readonly unsafe MethodDelegate delegate_SubOne = new MethodDelegate(thisPtr => 1379 | { 1380 | var instance = (ClassLayout*)thisPtr; 1381 | instance->test -= 1; 1382 | }); 1383 | 1384 | // Create new unmanaged VTable instance 1385 | private static readonly UnmanagedMemory OverwrittenVTable = new(); 1386 | ``` 1387 | snippet source | anchor 1388 | 1389 | 1390 | 1391 | 1392 | ```cs 1393 | unsafe 1394 | { 1395 | // Get the new VTable 1396 | var vTable = OverwrittenVTable.PtrElem; 1397 | 1398 | // Set the virtual methods with the new managed function pointer (Both the same for simplicity) 1399 | vTable->Method_AddTwo = vTable->Method_AddOne = 1400 | Marshal.GetFunctionPointerForDelegate(delegate_SubOne); 1401 | 1402 | // Overwrite the VTable reference of the instance 1403 | var instance = (ClassLayout*) classInstance; 1404 | instance->VTable = (IntPtr) vTable; 1405 | } 1406 | ``` 1407 | snippet source | anchor 1408 | 1409 | 1410 |

1411 |
1412 |
1413 | 1414 | # LICENSE 1415 | 1416 | dotnet_runtime_test is licensed under MIT license. See [LICENSE](./LICENSE) for more details. 1417 | -------------------------------------------------------------------------------- /README.source.md: -------------------------------------------------------------------------------- 1 | 2 |
3 |

Embedded .NET 6 Runtime

4 | Build and Test 5 |
6 | 7 | This repo contains a project with examples for using a embedded .NET 6 runtime in a C++ application using [dotnet_runtime](https://github.com/KevinGliewe/dotnet_runtime). 8 | 9 | 10 | 11 | 12 | # Build and Run 13 | 14 | ## Windows 15 | 16 | ### Requirements 17 | 18 | * Visual Studio 2019 19 | * .NET 6 SDK 20 | 21 | ### Steps 22 | 23 | 1. Clone this repository recursively using 24 | `git clone --recursive git@github.com:KevinGliewe/embedded_dotnet_runtime_examples.git` 25 | 2. Run `build_run.bat` 26 | 27 | ### What happens during the build? 28 | 29 | * Imports `VsDevCmd.bat` 30 | * Installs dotnet tool [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 31 | * Downloads the propper .NET 6 runtime for your system using [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 32 | * Builds `Lib` and `Host` using MSBuild 33 | * Runs `Host` 34 | 35 | ## Linux & MacOS 36 | 37 | ### Requirements 38 | 39 | * CMake 3.16 or newer 40 | * .NET 6 SDK 41 | * C++ compiler 42 | 43 | ### Steps 44 | 45 | 1. Clone this repository recursively using 46 | `git clone --recursive git@github.com:KevinGliewe/embedded_dotnet_runtime_examples.git` 47 | 2. Run `sh build_run.sh` 48 | 49 | ### What happens during the build? 50 | 51 | * Installs dotnet tool [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 52 | * Downloads the propper .NET 6 runtime for your system using [`runtimedl`](https://github.com/KevinGliewe/dotnet_runtime/tree/runtimedl-tool) 53 | * Builds `Lib` using .NET 6 SDK 54 | * Creates makefile using CMake 55 | * Builds `Host` using make 56 | * Runs `Host` 57 | 58 | # Examples 59 | 60 | ## Load and initialize .NET runtime 61 | 62 | snippet: InitRuntimeAndLib 63 | 64 | --- 65 | 66 | ## Managed Entrypoint 67 | 68 | > Entrypoints can only use [blittable types](https://en.wikipedia.org/wiki/Blittable_types) 69 | 70 | 71 | ### Managed component-entrypoint 72 | 73 |
Native 74 |

75 | 76 | snippet: Test_ManagedEntryPoint_Args_CPP 77 | 78 | snippet: Test_ManagedEntryPoint_ComponentEntryPoint_CPP 79 |

80 |
81 | 82 |
Managed 83 |

84 | 85 | snippet: Test_ManagedEntryPoint_Args_CS 86 | 87 | snippet: Test_ManagedEntryPoint_ComponentEntryPoint_CS 88 |

89 |
90 | 91 | ### Managed custom-entrypoint 92 | 93 | > Entrypoints can only use [blittable types](https://en.wikipedia.org/wiki/Blittable_types) 94 | 95 |
Native 96 |

97 | 98 | snippet: Test_ManagedEntryPoint_Args_CPP 99 | 100 | snippet: Test_ManagedEntryPoint_CustomEntryPoint_CPP 101 |

102 |
103 | 104 |
Managed 105 |

106 | 107 | snippet: Test_ManagedEntryPoint_Args_CS 108 | 109 | snippet: Test_ManagedEntryPoint_CustomEntryPoint_CS 110 |

111 |
112 | 113 | --- 114 | 115 | ## Managed Strings 116 | 117 | ### Return managed ASCII string 118 | 119 |
Native 120 |

121 | 122 | snippet: Test_ManagedString_Ansi_CPP 123 |

124 |
125 | 126 |
Managed 127 |

128 | 129 | snippet: Test_ManagedString_Ansi_CS 130 |

131 |
132 | 133 | What does CString do? 134 | 1. Apends a `\0` character on the end of the string. 135 | 2. Converts the string into ANSI encoding. 136 | 3. Allocate memory for native access using [`Marshal.AllocHGlobal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.allochglobal?view=net-6.0). 137 | 4. Copy the encoded string into the allocated memory. 138 | 139 | ### Return managed wide string 140 | 141 | > The encoding depends on the platform. For windows systems it is UTF16 and for posix systems it is UTF32. 142 | 143 |
Native 144 |

145 | 146 | snippet: Test_ManagedString_Wide_CPP 147 |

148 |
149 | 150 |
Managed 151 |

152 | 153 | What does CString do? 154 | 1. Determins the correct encoding for the current platform. (UTF16 or UTF32) 155 | 2. Apends a `\0` character on the end of the string. 156 | 3. Converts the string into the propper encoding. 157 | 4. Allocate memory for native access using [`Marshal.AllocHGlobal`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.marshal.allochglobal?view=net-6.0). 158 | 5. Copy the encoded string into the allocated memory. 159 | 160 | snippet: Test_ManagedString_Wide_CS 161 |

162 |
163 | 164 | --- 165 | 166 | ## Native Strings 167 | 168 | ### Native ASCII string 169 | 170 |
Native 171 |

172 | 173 | snippet: Test_NativeString_Ansi_CPP 174 |

175 |
176 | 177 |
Managed 178 |

179 | 180 | snippet: Test_NativeString_Ansi_CS 181 |

182 |
183 | 184 | ### Native Wide string 185 | 186 |
Native 187 |

188 | 189 | snippet: Test_NativeString_Wide_CPP 190 |

191 |
192 | 193 |
Managed 194 |

195 | 196 | snippet: Test_NativeString_Wide_CS 197 |

198 |
199 | 200 | ### Native string to managed function pointer 201 | 202 |
Native 203 |

204 | 205 | snippet: Test_NativeString_RetArgs_CPP 206 | 207 | snippet: Test_NativeString_FunctionPointer_CPP 208 |

209 |
210 | 211 |
Managed 212 |

213 | 214 | snippet: Test_NativeString_FunctionPointer_CS 215 |

216 |
217 | 218 | --- 219 | 220 | ## Managed function-pointer 221 | 222 | ### Managed function-pointer to instance method 223 | 224 |
Native 225 |

226 | 227 | snippet: Test_ManagedFunctionPointer_Typedef_managed_callback_fn_CPP 228 | 229 | snippet: Test_ManagedFunctionPointer_Instance_CPP 230 |

231 |
232 | 233 |
Managed 234 |

235 | 236 | snippet: Test_ManagedFunctionPointer_FunctionPointerCallbackDelegate_CS 237 | 238 | snippet: Test_ManagedFunctionPointer_Instance_CS 239 |

240 |
241 | 242 | ### Managed function-pointer to static function 243 | 244 |
Native 245 |

246 | 247 | snippet: Test_ManagedFunctionPointer_Typedef_managed_callback_fn_CPP 248 | 249 | snippet: Test_ManagedFunctionPointer_Static_CPP 250 |

251 |
252 | 253 |
Managed 254 |

255 | 256 | snippet: Test_ManagedFunctionPointer_FunctionPointerCallbackDelegate_CS 257 | 258 | snippet: Test_ManagedFunctionPointer_Static_CS 259 |

260 |
261 | 262 | --- 263 | 264 | ## Native function-pointer 265 | 266 | ### Calling checked native function pointer 267 | 268 |
Native 269 |

270 | 271 | snippet: Test_NativeFunctionPointer_CallbackFunc_CPP 272 | 273 | snippet: Test_NativeFunctionPointer_CallbackPointer_CPP 274 | 275 | snippet: Test_NativeFunctionPointer_CallbackFunc_Checked_CPP 276 |

277 |
278 | 279 |
Managed 280 |

281 | 282 | snippet: Test_NativeFunctionPointer_Checked_CS 283 |

284 |
285 | 286 | ### Calling unchecked native function pointer 287 | 288 |
Native 289 |

290 | 291 | snippet: Test_NativeFunctionPointer_CallbackFunc_CPP 292 | 293 | snippet: Test_NativeFunctionPointer_CallbackPointer_CPP 294 | 295 | snippet: Test_NativeFunctionPointer_CallbackFunc_Unchecked_CPP 296 |

297 |
298 | 299 |
Managed 300 |

301 | 302 | snippet: Test_NativeFunctionPointer_Unchecked_CS 303 |

304 |
305 | 306 | --- 307 | 308 | ## Usage of unsafe managed code to access native objects 309 | 310 |
Native 311 |

312 | 313 | snippet: Test_ManagedUnsafe_CPP 314 |

315 |
316 | 317 |
Managed 318 |

319 | 320 | snippet: Test_ManagedUnsafe_CS 321 |

322 |
323 | 324 | --- 325 | 326 | ## Native Arrays 327 | 328 | ### Native arrays using fixed struct member 329 | 330 |
Native 331 |

332 | 333 | snippet: Test_NativeArray_Args_CPP 334 | 335 | snippet: Test_NativeArray_Args_Data_CPP 336 | 337 | snippet: Test_NativeArray_StructFixed_CPP 338 |

339 |
340 | 341 |
Managed 342 |

343 | 344 | snippet: Test_NativeArray_StructFixed_CS 345 |

346 |
347 | 348 | ### Native arrays using pointer 349 | 350 |
Native 351 |

352 | 353 | snippet: Test_NativeArray_ArgumentFixed_CPP 354 |

355 |
356 | 357 |
Managed 358 |

359 | 360 | snippet: Test_NativeArray_ArgumentFixed_CS 361 |

362 |
363 | 364 | ### Native arrays using ArrPointerX on function-pointer 365 | 366 |
Native 367 |

368 | 369 | snippet: Test_NativeArray_ArgumentFixed_FunctionPointer_CPP 370 |

371 |
372 | 373 |
Managed 374 |

375 | 376 | snippet: Test_NativeArray_ArgumentFixed_FunctionPointer_CS 377 |

378 |
379 | 380 | --- 381 | 382 | ## Native Symbols 383 | 384 | ### Calling native exported symbols using `DllImport` 385 | 386 |
Native 387 |

388 | 389 | snippet: Test_DllImport_Export_CPP 390 | 391 | snippet: Test_DllImport_Call_CPP 392 | 393 | > For posix systems, use the `-export-dynamic` flag for the linker. 394 |

395 |
396 | 397 |
Managed 398 |

399 | 400 | snippet: Test_DllImport_CS 401 |

402 |
403 | 404 | ### Calling native exported symbols using `GetProcAddress`/`dlsym` 405 | 406 |
Native 407 |

408 | 409 | snippet: Test_NativeExport_Export_CPP 410 | 411 | snippet: Test_NativeExport_Call_CPP 412 | 413 | > For posix systems, use the `-export-dynamic` flag for the linker. 414 |

415 |
416 | 417 |
Managed 418 |

419 | 420 | snippet: Test_NativeExport_CS 421 |

422 |
423 | 424 | --- 425 | 426 | ## Native VTable 427 | 428 | [VTable](https://en.wikipedia.org/wiki/Virtual_method_table) 429 | 430 | ### Calling native VTable from managed code 431 | 432 |
Native 433 |

434 | 435 | snippet: Test_NativeVTable_Class_CPP 436 | 437 | snippet: Test_NativeVTable_Call_CPP 438 | 439 |

440 |
441 | 442 |
Managed 443 |

444 | 445 | snippet: Test_NativeVTable_Class_CS 446 | 447 | snippet: Test_NativeVTable_ManagedCall_CS 448 | 449 |

450 |
451 | 452 | ### Overwriting native VTable with managed code 453 | 454 |
Native 455 |

456 | 457 | snippet: Test_NativeVTable_Class_CPP 458 | 459 | snippet: Test_NativeVTable_Call_CPP 460 | 461 |

462 |
463 | 464 |
Managed 465 |

466 | 467 | snippet: Test_NativeVTable_Class_CS 468 | 469 | snippet: Test_NativeVTable_Overwrite_CS 470 | 471 | snippet: Test_NativeVTable_ManagedOverwrite_CS 472 | 473 |

474 |
475 |
476 | 477 | # LICENSE 478 | 479 | dotnet_runtime_test is licensed under MIT license. See [LICENSE](./LICENSE) for more details. 480 | -------------------------------------------------------------------------------- /build_run.bat: -------------------------------------------------------------------------------- 1 | WHERE MSBuild 2 | IF %ERRORLEVEL% NEQ 0 call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\Tools\VsDevCmd.bat" 3 | 4 | if exist %~dp0\dotnet_runtime\bin\windows\x64\dotnet.exe ( 5 | echo Runtime already installed. 6 | ) else ( 7 | echo Runtime not installed. 8 | Powershell -executionpolicy remotesigned -File %~dp0\dotnet_runtime\scripts\download-runtime.ps1 9 | ) 10 | 11 | dotnet restore 12 | 13 | MSBuild %~dp0\Host.sln /p:Configuration=Release /p:Platform=x64 14 | 15 | %~dp0\x64\Release\Host.exe %~dp0 16 | echo %ERRORLEVEL% 17 | exit %ERRORLEVEL% -------------------------------------------------------------------------------- /build_run.sh: -------------------------------------------------------------------------------- 1 | unameOut="$(uname -s)" 2 | case "${unameOut}" in 3 | Linux*) machine=linux;; 4 | Darwin*) machine=macos;; 5 | CYGWIN*) machine=cygwin;; 6 | MINGW*) machine=mingw;; 7 | *) machine="UNKNOWN:${unameOut}" 8 | esac 9 | 10 | if [ -f "dotnet_runtime/bin/${machine}/x64/dotnet" ]; then 11 | echo "runtime alrady installed" 12 | else 13 | echo "runtime not installed" 14 | cd dotnet_runtime 15 | dotnet tool restore 16 | dotnet runtimedl --version-pattern "6.0.0" --output "bin" 17 | cd .. 18 | fi 19 | 20 | dotnet build Host.sln 21 | 22 | mkdir build 23 | cd build 24 | cmake ../Host 25 | make 26 | ./Host ../ --------------------------------------------------------------------------------