함수 시그니처만 번역하는게 아니고, 관련된 상수, 구조체, 공용체도 같이 번역해서 준비해야 합니다.
번역이 당장 어려운 매개변수나 타입들은 치트키처럼 일단 void*나 IntPtr로 뭉개고 시작할 수는 있지만 언젠가는 번역해놔야 합니다.
C#의 경우, 공용체가 문법적으로는 지원되지 않는 관계로 StructLayoutAttribute를 무척 신중하게 써서 디자인해야 하고, 비트 필드의 경우 많이 접하는 케이스는 아니지만, 역시 대응 타입이 없어서 손수 비트 연산을 구현하거나 System.Collections.BitArray의 도움을 받아야 할 것 같습니다.
StructLayoutAttribute에 값을 넣을 때는 #pragma pack 전처리기에 어떻게 값을 썼는지 매크로도 잘 따라가면서 봐야 하는 점이 있습니다. 메모리 할당 크기 자체가 달라지게 되는 부분이더라고요.
데이터 타입에 대한 구분을 가능한 명확히 해두는게 좋습니다. C#으로 코드를 가져오다보면 typedef 선언의 의미가 대개 raw type으로 뭉개지기 때문에 타입 관리에 각별히 신경써야 하는 점이 있습니다.
대표적인 예시: C에서 size_t는 raw type으로 보면 그저 양의 정수 값에 불과하나 의미론적으로는 "길이 타입"으로 간주됩니다. C#에서는 using 키워드를 이용해서 typedef를 따라할 순 있긴 하지만, 같은 C# 소스 내에만 한정되는 별칭이라 C의 typedef랑은 성질이 많이 달랐죠.
마샬링이 얼마나 빈번하게 일어나는지, 힙 메모리에는 얼마나 의존하게 되는지도 생각해봐야 하는데, 이걸 줄이는데 도움이 되는게 unsafe 코드 블록을 쓰는 것입니다.
아, 그리고 요즈음은 Source Generator 덕분에 마샬링으로 뭉개던 동작을 코드로 만들어주어서 최적화할 수 있는 여지를 좀 더 많이 제공하는 것 같습니다.
varargs 계통의 가변 인자 함수 (예: printf, scanf)는 __arglist 특수 키워드나 ArgIterator 같은 친구들을 써서 인자를 넘기는 방법도 예전엔 쓴 것 같은데, 아쉽게도 닷넷 코어 계통으로 넘어와서는 이런 기능들은 Windows 전용 기능이 되어서, varargs 계통 함수를 P/Invoke 처리하는건 고민이 좀 더 필요하지 싶습니다. 일단 지금은 정해진 인자 값을 넘기도록 필요한 시그니처들을 추가해두는게 최선인 듯 합니다.