I have been tasked with creating a Wrapper for a C-library to be used in C#. I have no control over the C-library's source code and I only have access to its header file and to the static library file (.lib).
I have followed several tutorials and got this working when creating an unmanaged C++ class which is wrapped using CLI/C++ and used in C#. No problem. The problem I am facing know though, is as C does not use namespaces, I have trouble figuring out how to tell the compiler when I want to call the function from the .lib file itself, rather than my identically named wrapper function. If it helps understanding, my header file only consists of function definitions, typedefs (structs, enums), but no classes (not even sure if C headers usually do?).
What I have done:
This is where I get a bit confused, mostly due to the tutorials all covering how to link a C++ library rather than a C library. The difference there being the lack of namespace in C, and (in my case) the lack of class.
Uncertainties:
Analytics::void SanSetApplicationContext(){...}
, but as the original C library header file does not contain classes or namespaces, there is no way for me to call that function and distinguishing between them. The C++ compiler always prefers the local definition and I am stuck with a function calling itself for all eternity. I hope you understand my point.I am probably doing something wrong and/or misunderstanding something, but how would you guys suggest I approach this issue? I will append my files contents below. In the files I have so far only tried implementing a single function. Code should be fixed and hopefully working
Wrapper.h
#pragma once
using namespace System;
namespace Wrapper {
public ref class Analytics
{
public:
static void SanSetApplicationContext(String^ ctx);
};
}
Wrapper.cpp
#include "pch.h"
#include "Wrapper.h"
#include "../SWA_lib/Analytics.h"
namespace Wrapper {
void Analytics::SanSetApplicationContext(String^ ctx) {
//Convert String^ to const char*
msclr::interop::marshal_context mCtx;
const char* convertedStr = mCtx.marshal_as<const char*>(ctx);
::SanSetApplicationContext(convertedStr);
}
}
Analytics.h
...
extern void SanSetApplicationContext(const char *ctx);
...
Update Updated the code to reflect my changes made based on your comments. Thanks!
Update 2
@Bodo asked me to explain another issue of mine, which is how I would handle wrapping functions that return opaque handles. The example used is the following: typedef struct SanEvent_s *SanEvent;
which is found in my C header file. So basically, this library provides an API. The API execution always starts with a call to an Initialize();
function, and ends with a call to the Terminate();
function. I have no previous experience of using this API, but from the documentation, I assume that all objects, references and what not are freed/destroyed when Terminate()
is called, as none of their examples show destroying objects.
Now, creating a SanEvent
is done like this (according to documentation):
SanEvent event;
event = SanNewEvent("The Main Event");
This opaque handle is considered protected on the C# side, so there does not seem to be a way for me of returning it all the way there. My idea is to keep the events in some kind of Collection, in the C++/CLI wrapper where the type can be used, and only return the index of the Event to C# (The index the even would have in a List or something). I am not sure this is a good idea, but this is as far as I have come with my plan. Something that represents a SanEvent
needs to be returned to C# as I need to be able to reference the event in the future, in order to add additional information to the event via other functions. This idea would of course need tailored "helper functions" on the C++ side which mediate between C# and C. I am sorry if the information is vague, but I don't really have a lot to go on myself as of now.
Adding "::" to the beginning of a function call tells the compiler to use the function found in global namespace rather than local namespace/class. So:
namespace Wrapper {
void Analytics::SanSetApplicationContext(const char *ctx) {
::SanSetApplicationContext(ctx);
}
}
Should call the c version correctly