Kostiantyn's Blog

Generating .NET interop bindings via ClangSharp

Cogs working together
.NET and C working together.
Photo by Laura Ockel on Unsplash

Introduction

Sometimes you want to write a .NET application that talks to an unmanaged library. Your first step? To google bindings for that library. But what if there are no bindings for such a library? Well, you can generate them yourself!

TLDR: Final bindings are in the NetObsBindings repository

Table of Contents

  1. Introduction
  2. ClangSharp
    1. Generating bindings
    2. Getting bindings to compile
  3. Final result
  4. Conclusion

ClangSharp

ClangSharp is a generator library/tool that takes a C or C++ header files as input and spits out C# interop code. It has various configuration options to tweak the output. It's easier to show on an example. Let's take a practical scenario.

Generating bindings

Imaging I am building an OBS plugin. OBS - Open Broadcaster Software that provides an open source video streaming app. How do I generate .NET bindings for OBS? OBS has a concept of a video/audio source that can add video/audio to your video stream. I would like to add my own video source. To enable that capability, OBS provides a function obs_register_source_s in obs-source.h header file.

c
EXPORT void obs_register_source_s(const struct obs_source_info *info,
				  size_t size);

We established what we want, so let's do it. Firstly, we would want to clone the OBS repository and create a dummy project to test if our bindings compile.

powershell
mkdir obs-interop
cd obs-interop
git clone https://github.com/obsproject/obs-studio # clone obs repository
dotnet new console -o TestObsBindings -n TestObsBindings # create the dummy project

Next, we need to use the bindings generator. Let's use it as a global dotnet tool. For it to succeed, we also would need the clang compiler of the same version.

powershell
dotnet tool install --global ClangSharpPInvokeGenerator --version 13.0.0-beta1 # install the global dotnet tool
winget search clang # find the clang compiler
winget install LLVM.LLVM --version 13.0.0 # we found it, the package is called LLVM.LLVM

Let's try to use it.

powershell
ClangSharpPInvokeGenerator `
    --file .\obs-studio\libobs\obs-source.h <# file we want to generate bindings for #>  `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop.cs <# output #>

Oh-oh, we got a bunch of errors.

Diagnostics for '.\obs-studio\libobs\obs-source.h':
    .\obs-studio\libobs/obs-source.h:33:6: error: redefinition of 'obs_source_type'
    .\obs-studio\libobs/obs-source.h:40:6: error: redefinition of 'obs_balance_type'
    .\obs-studio\libobs/obs-source.h:46:6: error: redefinition of 'obs_icon_type'
    .\obs-studio\libobs/obs-source.h:63:6: error: redefinition of 'obs_media_state'
    .\obs-studio\libobs/obs-source.h:210:8: error: redefinition of 'obs_source_audio_mix'
    .\obs-studio\libobs/obs-source.h:217:8: error: redefinition of 'obs_source_info'
Skipping '.\obs-studio\libobs\obs-source.h' due to one or more errors listed above.

Hmm, it seems that the same file obs-source is imported twice. The header file has #pragma once but it is not respected when it is the root file. Well, let's try another way - by using the --traverse(-t) parameter to say which module we are interested in. But what to pick as the -f parameter? All OBS modules include obs-module.h and it transitively references all the needed headers, let's try that.

powershell
ClangSharpPInvokeGenerator `
    --file .\obs-studio\libobs\obs-source.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-source.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop.cs <# output folder #>

Success!

VSCode screenshot with the generated DllImport

I would like to configure the generation a little bit. Let's make it prettier by splitting things into multiple files and using file scoped namespaces.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-source.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-source.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #>

VSCode screenshot with the generated DllImport w/ scoped namespaces and multiple files

Nice!

Getting bindings to compile

powershell
dotnet build .\TestObsBindings\TestObsBindings.csproj

When trying to compile, I get a lot of errors in the console. I will tackle them one by one.

  • The type or namespace name 'NativeTypeNameAttribute' could not be found

Hmm, there are attributes sprinkled everywhere in the form of

csharp
[NativeTypeName("const struct obs_source_info *")]

It seems that I didn't specify a configuration switch generate-helper-types for those helper types. Adding it fixes these types of errors.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-source.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #>
  • The type or namespace name 'audio_output_data' could not be found

Ok, this is interesting. The ClangSharp generator didn't include the type audio_output_data in the final bindings. Why?

VSCode screenshot with the location of audio_output_data

Seems like it is included in another file. Oh well, let's add it. But let's add the bindings in a separate ObsAudio method class name to not pollute our ObsSource one.

powershell
# ... omitting script for ObsSource ...

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\media-io\audio-io.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsAudio <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #>
  • Unsafe code may only appear if compiling with /unsafe

To fix that, we need to enable unsafe blocks in the project file (.csproj).

xml
<PropertyGroup>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
  • The type or namespace name 'obs_data' could not be found

VSCode screenshot with the location of obs data struct

Oh, the struct definition is in a .c file. Why is it included in other header files?

VSCode screenshot with the typedef of the obs data

Aha! It seems that the actual structure of the obs_data is an implementation detail, and the library works with a type alias obs_data_t. It is also sometimes called an 'opaque' type. For that, we can leverage the 'remapping' mechanism of ClangSharp (-r parameter) that allows us to redefine any type in the source headers. Let's remap all pointers to obs_data as void pointers (since we don't now and don't care about the internal structure).

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-source.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void*  <# remappings #>

# ... omitting script for ObsAudio ...
  • The type or namespace name 'obs_source' could not be found
  • The type or namespace name 'obs_properties' could not be found
  • The type or namespace name 'gs_effect' could not be found
  • The type or namespace name 'obs_missing_files' could not be found

These all seem to be cases of 'opaque' types, let's add them to the remappings

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-source.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void*
    
# ... omitting script for ObsAudio ...
  • The type or namespace name 'obs_source_frame' could not be found

VSCode screenshot with the location of obs_source_frame

This struct seems to be in the obs.h header. Let's generate bindings for that too.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName Obs <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void*
    
# ... omitting script for ObsAudio ...
# ... omitting script for ObsSource ...

Oh wow, there are a lot more errors to handle. But we won't get discouraged!

  • The type or namespace name 'video_format' could not be found

VSCode screenshot with the location of obs video format

This type is located in video-io.h. We can add it to our generation routine.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\media-io\video-io.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsVideo <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void*
    
# ... omitting script for ObsAudio ...
# ... omitting script for ObsSource ...
# ... omitting script for Obs ...
  • The type or namespace name 'vec2' could not be found
  • The type or namespace name 'vec3' could not be found
  • The type or namespace name 'vec4' could not be found

These are vector data types. We can reuse System.Numerics.Vector2/3/4 for these by adding them to the remappings.

-r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4
  • The type or namespace name 'profiler_name_store' could not be found
  • The type or namespace name 'text_lookup' could not be found
  • The type or namespace name 'signal_handler' could not be found
  • The type or namespace name 'proc_handler' could not be found
  • The type or namespace name 'obs_data_array' could not be found
  • The type or namespace name 'gs_texture' could not be found

These ones look like opaque types or we don't care about their types yet. Let's add them to our remappings.

-r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void*
  • The type or namespace name 'gs_color_format' could not be found

VSCode screenshot with the location of obs color format

This type is in the graphics.h header file, let's add it to our generation procedure.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\graphics\graphics.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsGraphics <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void*
  • The type or namespace name 'obs_mouse_event' could not be found

VSCode screenshot with the location of obs mouse event

This type is in the obs-interacton.h header file, let's add it to our generation procedure.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-interaction.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsInteraction <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void*
  • The type or namespace name 'input_subsystem' could not be found

This looks like an 'opaque type', let's add it to the remappings.

powershell
-r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*
  • The type or namespace name 'obs_encoder_type' could not be found

VSCode screenshot with the location of obs encoder

This type is defined in the obs-encoder.h header, let's add it to the generation routine.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-encoder.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsEncoder <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*
  • The type or namespace name 'obs_service_resolution' could not be found

This type is defined in the obs-service.h header, let's add it to the generation routine.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-service.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsService <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*
  • The name 'bzalloc' does not exist in the current context

What the hell? I would like to look closer.

VSCode screenshot with the bzalloc

OK, looks like the generator generated some redundant functions. Let's add exclude-funcs-with-body config switch in order to get rid of it.

powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\graphics\graphics.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsGraphics <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*
    
#... excluded other header files for brevity

We finally finished. The bindings are ready.

Build succeeded.
    0 Warning(s)
    0 Error(s)

Final result

We got 139 files generated. Even though the process was meticulous, the time savings are overwhelming. We could not possibly have created 139 files of valid interop code in such a short time.

powershell
> (ls .\TestObsBindings\ObsInterop\).Count

139 # 139 files

Final generation script
powershell
ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-source.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsSource <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\media-io\audio-io.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsAudio <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName Obs <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\media-io\video-io.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsVideo <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\graphics\graphics.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsGraphics <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-interaction.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsInteraction <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-encoder.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsEncoder <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

ClangSharpPInvokeGenerator `
    -c multi-file generate-file-scoped-namespaces generate-helper-types exclude-funcs-with-body <# configuration for the generator#> `
    --file .\obs-studio\libobs\obs-module.h <# file we want to generate bindings for #>  `
    --traverse .\obs-studio\libobs\obs-service.h `
    -n ObsInterop <# namespace of the bindings #> `
    --methodClassName ObsService <# class name where to put methods #> `
    --libraryPath obs <# name of the DLL #> `
    -o .\TestObsBindings\ObsInterop <# output folder #> `
    -r obs_data*=@void* gs_effect*=@void* obs_source*=@void* obs_properties*=@void* obs_missing_files*=@void* vec2=@System.Numerics.Vector2 vec3=@System.Numerics.Vector3 vec4=@System.Numerics.Vector4 profiler_name_store*=@void* text_lookup*=@void* signal_handler*=@void* proc_handler*=@void* obs_data_array*=@void* gs_texture*=@void* input_subsystem*=@void*

Conclusion

ClangSharp is a useful tool to generate .NET interop bindings. It saves a bunch of time and effort. However, there are lots of caveats where you need to understand interop and C code in order to work with them. The ones I would point out are:

  • 'opaque' types (e.g. typedef struct type type_t)
  • knowing which headers to generate
  • knowing what to use as a 'root' header (-f param) and what to use as the actual generation target (-t param).
  • knowing which .NET types we can reuse (that have the same structure and layout).
  • figuring out the configuration for the generation tool (e.g. invalid C# function bodies).