C++ CLR error after adding function to .h file - c#

I'm not a C++ programmer but rather a "code editor", so I hereby apologize sincerely in advance for the question in general.
For some broadcasting software I am editing a plug-in to give it interoperability between native C/++ and CLR (C#).
The host software: OBS (Open Broadcaster Software) - obsproject.com
The plug-in: CLR Host Plugin - github.com/kc5nra/CLRHostPlugin
What I want to do is exposing functions from OBS to some CLR libraries, the total list of exposible functions can be found here: https://github.com/jp9000/OBS/blob/master/OBSApi/APIInterface.h#L198
Now the plug-in is a proxy between the languages and exposes some of the functionalities named above through API.h/cpp, https://github.com/kc5nra/CLRHostPlugin/blob/master/CLRHostInterop/API.h. What I want to do is adding more functions to API.h to expose them to CLR libraries.
When I add a function in API.h, line 51, it'll give me linker errors.
The API.h change
namespace CLROBS
{
public ref class API
{
public:
void AddSettingsPane(SettingsPane^ settingsPane);
void AddImageSourceFactory(ImageSourceFactory^ imageSourceFactory);
IntPtr API::GetMainWindowHandle();
void Log(System::String^ format, ...array<System::Object^> ^arguments);
System::String^ GetPluginDataPath();
void SetChangedSettings(bool isChanged);
int GetMaxFPS();
void StartStopStream(); // Addition to the original.
};
};
The API.cpp change
#include "OBSApi.h"
#include "API.h"
#include "CLRHostApi.h"
#include "OBSUtils.h"
using namespace System::Runtime::InteropServices;
// Default code......
int API::GetMaxFPS()
{
return ::API->GetMaxFPS();
}
void API::StartStopStream()
{
OBSStartStopStream();
}
The Error
error LNK2022: metadata operation failed (80131187) : Inconsistent method declarations in duplicated types (types: CLROBS.API; methods: StartStopStream): (0x0600006c). <DIR>\CLRHostPlugin\CLRHostInterop\API.obj CLRHost.Interop
error LNK2022: metadata operation failed (80131187) : Inconsistent method declarations in duplicated types (types: CLROBS.API; methods: StartStopStream): (0x0600006c). <DIR>\CLRHostPlugin\CLRHostInterop\AbstractPlugin.obj CLRHost.Interop
error LNK2022: metadata operation failed (801311D6) : Differing number of methods in duplicated types (CLROBS.API): (0x02000008). <DIR>\CLRHostPlugin\CLRHostInterop\API.obj CLRHost.Interop
error LNK2022: metadata operation failed (801311D6) : Differing number of methods in duplicated types (CLROBS.API): (0x02000008). <DIR>\CLRHostPlugin\CLRHostInterop\AbstractPlugin.obj CLRHost.Interop
The in my eye's odd behaviour doesn't occur when using the plain code
namespace CLROBS
{
public ref class API
{
public:
void AddSettingsPane(SettingsPane^ settingsPane);
void AddImageSourceFactory(ImageSourceFactory^ imageSourceFactory);
IntPtr API::GetMainWindowHandle();
void Log(System::String^ format, ...array<System::Object^> ^arguments);
System::String^ GetPluginDataPath();
void SetChangedSettings(bool isChanged);
int GetMaxFPS();
// - commented out - void StartStopStream(); // Addition to the original.
};
};
Edit
Added API.cpp snippet
Even when I comment out OBSStartStopStream(); on API.cpp the error still exist.
/Edit
Please assist.
Regards,
MusicDemon

Related

Unable to load DLL the specified procedure could not be found. (Exception from HRESULT: 0x8007007F)

For a long time I have a problem with a project that I have to implement in mine, I'll explain it to you.
My goal is to call a C ++ class from a C # application (Project 1), the problem is that the C ++ project (Project 3) is not compatible with CLR.
What I have done so far has been created an intermediate project also in unmanaged C ++ (Project 2) to be compatible with the project 3.
Project 2 consists of a very simple method that initializes a class from project 3 and uses this object for different operations.
I'm working in Visual Studio and it does not give me an error when compiling, but at run time I get the following error:
Unable to load the DLL file 'PROJECT-ROUTE \ Project2.dll': The specified procedure could not be found. (Exception from HRESULT: 0x8007007F)
in project1.process ()
The thing is that the previous error comes out only when within the project2 method I initialize the class from project 3, if I comment the initialization line then it goes well, I can not understand why between 2 C ++ projects of the same type gives me this type of problems.
Can somebody help me?
thank you
C# Code (Project 1)
private const string DllFilePath = #"PATH_TO_DLL\Proyect2.dll";
[DllImport(DllFilePath, CallingConvention = CallingConvention.Cdecl, EntryPoint = "process")]
public extern static void process();
[HandleProcessCorruptedStateExceptions]
public static string Prox(string a, string b)
{
string str = "OK";
try
{
process();
}
catch (System.AccessViolationException exception)
{
return exception.Message + " " + exception.StackTrace;
}
catch (Exception exception)
{
return exception.Message + " " + exception.StackTrace + " ";
}
return str;
}
}
Middle proyect Unamanged C++ Code (Project 2)
Project2.h
#include <stdexcept>
#include "Project3.h"
using namespace std;
namespace FinalProcess
{
extern "C" { __declspec(dllexport) void __cdecl process(); }
}
Project2.cpp
#include "Project2.h"
#include <iostream>
#include <chrono> // To measure execution time
namespace FinalProcess
{
void process()
{
OCTA::Analyzer& ld = OCTA::Analyzer::getInstance(); // <-- Singleton
// if I comment this line then it goes well
}
}
Assuming that the information that you present here is correct (and I have my doubts because you've not copied it verbatim as can be seen from the DLL name of Proyect2.dll) then the error cannot be that the function process is not found. In which case the error has to be in the linking to Project3.
Your Project2.dll is presumably attempting to link to Project3.dll and to a function that is not exported from Project3.dll. That would explain the behaviour that you report. This would happen typically if the Project3.lib file that you linked when building Project2.dll did not match the Project3.dll file found by the executable at runtime.
Make sure that the version of Project3.dll that is being loaded is up to date and matches the .h and .lib files you used to build Project2.dll.
Rather than give an absolute path of the DLL in your C# code, you just the file name of the DLL. Place both DLLs in the same directory as your C# executable.

Creating an object inside a dll on heap to use in other dll calls

Background
I have a c# program that calls a c++ dll containing the code to create a model(Net type) and use the model to evaluate an image. As the dll function currently contains the code to load the model, the time taken to run this function is too long the for the task of evaluating a live feed.
I would like to only have to create the model once from a dll call and then pass a pointer to the model to an evaluate function, thus removing the overhead of loading the model from file.
Attempt
I have attempted to create a function that is passed a void **, creates a model on heap and points the passed void ** to it. This would allow me to pass the pointer to other functions within the dll and create a model object from the memory on heap.
I cannot get this to work and, although it seems to me like it should be possible, i am not sure if it is.
Any suggestions on how to tackle this or on how to fix my code would be appreciated, thanks.
Error
Attempted to read or write protected memory at "loadNet()"
c++/dll code
void loadNet(void **np)
{
String modelFile = "graph.pb";
Net *net;
net = (Net *)malloc(sizeof(Net));
*net = readNetFromTensorflow(modelFile)
*np = &net;
}
extern "C"{
DLLEXPORT void loadNet(void **);
}
c# code to call dll
[DllImport("Project5.dll")]
public static extern unsafe void loadNet(void **np);
static void Main(string[] args)
{
unsafe
{
void *np;
loadNet(&np);
Bitmap img = (Bitmap)Image.FromFile("2.png");
BitmapData bmpData = img.LockBits(new Rectangle(0,0,img.Width,
img.Height),ImageLockMode.ReadWrite,
img.PixelFormat);
Results results = evalImgGest2(bmpData.Scan0,&np);
img.UnlockBits(bmpData);
Console.WriteLine(results.five);
Console.WriteLine(results.fist);
Console.WriteLine(results.index);
}
}
Updates
net = (Net *)malloc(sizeof(Net)); // is causing error
Net *net = new Net(); // replacing with this removes error

How would I create a "type" of a Callable?

Is there a Pythonic way of creating a Callable type like below (TypeScript and C# examples)?
TypeScript:
export type MyHandler = (paramOne: ClassOne, num: int) => null;
C#:
public delegate void MyHandler(ClassOne paramOne, int num);
Python:
MyHandler = Callable[[ClassOne, int], None] # No lint errors...
While I'm not getting any lint errors in PyCharm; I haven't bothered running my snippet yet because of how not-Pythonic this feels. If this was going to be my own code for private consumption I don't think I would bother with creating this type, but it's going to be part of a library so I feel more obligated to create the type.
In usage, this type would show up in something like:
class SomeClass(ProbablyAnABC):
# __init__ where self._handlers is created
def register_handler(self, handler: MyHandler) -> None:
self._handlers.append(handler)

Marshalling C++ pointer interface back though C# function call in a non default AppDomain

I have a working CLI interface between C++ and C# code. The code has a C++ abstract interface like:
-------------C++ Interface---------------
namespace cppns
{
class cppInterface
{
public:
virtual bool Start(const char *pcDir) = 0;
};
}
------Implementation of abstract C++ interface in same dll---------
namespace cppns
{
class cppimp : public cppInterface
private:
gcroot<MyInternalCSharpClass^> mInternalClassAccess;
public:
cppimp::cppimp()
{
mInternalClassAccess = gcnew MyInternalCSharpClass();
}
virtual bool cppimp::Start(const char *pcDir)
{
System::AppDomain ^appDom = AppDomain::CurrentDomain::get();
System::String ^strDomainName = appDom->FriendlyName;
mInternalClassAccess->Initalize(pcDir);
}
}
---------Method to create an instance of the class in a factory--------------
cppns::cppInterface *GetImplObject()
{
return new cppns::cppimp();
}
----------Factory class .h to allow C++ to get an instance of the cppimp class------
------The C++ code knows about the abstract interface by including the header file--
------FactoryExport is __declspec(dllexport) when compiled in dll and---------------
----- __declspec(dllimport) when used as a header file in exe that uses header------
class FactoryExport ClassFactory
{
public:
static cppns::cppInterface *CreateImpl();
};
----------Factory class .cpp to allow C++ to get an instance of the cppimp class------
cppns::cppInterface *ClassFactory::CreateImpl()
{
return GetImplObject();
}
This code correctly allows me to call CreateImpl to get an implementation of the interface that contains the Start method. My issue is that I'm trying to force the whole CLR/.NET loading and executing into an AppDomain that is not the default AppDomain. I can create a secondary AppDomain using the following code:
CComPtr<ICorRuntimeHost> pRuntimeHost;
//Retrieve a pointer to the ICorRuntimeHost interface
HRESULT hr = CorBindToRuntimeEx(
L"v2.0.50727", //Retrieve last version before 4.0.
// NULL, //Retrieve latest version by default
L"wks",
STARTUP_LOADER_OPTIMIZATION_SINGLE_DOMAIN | STARTUP_CONCURRENT_GC,
CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(void**)&pRuntimeHost.p
);
hr = pRuntimeHost->Start();
DWORD dwAppDomainId = 22;
WCHAR domainName[80 + 1];
swprintf(domainName, 80, L"%s-%ld",L"NoDefaultDomain", dwAppDomainId);
CComPtr<IUnknown> pUnknownAppDomain;
hr = pRuntimeHost->CreateDomainEx(domainName, NULL, NULL, &pUnknownAppDomain);
CComPtr<_AppDomain> pAppDomain;
hr = pUnknownAppDomain->QueryInterface(__uuidof(_AppDomain), (VOID**)&pAppDomain.p);
BSTR bstrFriendlyName;
hr = pAppDomain->get_FriendlyName(&bstrFriendlyName);
if (SUCCEEDED(hr))
{
_bstr_t bstrFriendlyNameWrap(bstrFriendlyName, false);
}
_bstr_t bstrAssemblyName("InteropCode");
CComPtr<_Assembly> pAssembly;
hr = pAppDomain->Load_2(bstrAssemblyName, &pAssembly);
BSTR bstrFullName;
hr = pAssembly->get_FullName(&bstrFullName);
if (SUCCEEDED(hr))
{
_bstr_t bstrFullNameWrap(bstrFullName, false);
std::cout << "Assembly name is: " << bstrFullNameWrap << "\n";
}
Every attempt of getting the factory to return to me an interface to cppns::cppInterface within this secondary application domain has failed. I have even attempted to create a secondary factory that is a C# class that returns the pointer to the implemented interface so that an Invoke call on the Assembly would hopefully cause the rest of the code to execute in the AppDomain that I loaded the Assembly into but the Invoke returns an IDispatch pointer that I can't seem to map back into any type of C++ pointer on my interface.
namespace cppns
{
public ref class NetFactory
{
public:
NetFactory()
{
}
cppInterface *CreateInterop()
{
return GetImplObject();;
}
};
}
Is there another way to get everything to run in a secondary AppDomain or is the IDispatch pointer usable in calling the Start method?
I have managed to get most of the .NET stuff running in another domain. It seems like there is no way to get the CLI layer to run in anything other than the default AppDomain.
To make this work I needed to make the class that sits within both appdomains derive from MarshalByRefObject. In my example above that meant I had to change MyInternalCSharpClass so that it derived from MarshalByRefObject. It was also nessary to made the objects sent and returned from MyInternalCSharpClass also derive from MarshalByRefObject. Finally I these same objects that were passed and returned had to have the [Serializable] property and to also mark all their private variables public. Note if the classes being transferred though the AppDomains are already using the Serializable attribute you can use [XmlIgnore] on each formally private variable to avoid changing the serialization that is being done.
Now that everything can be moved between the AppDomains I created a second AppDomain by doing the following:
bool CreateInstanceInAppDomain(const char *pcAppDomainName)
{
bool bRtn = false;
gcroot<String^> csStrAppDomainName (gcnew String(pcAppDomainName));
mAppDomain = AppDomain::CreateDomain(csStrAppDomainName);
delete csStrAppDomainName;
Object^ MyInternalObject = mAppDomain->CreateInstanceAndUnwrap("AssemblyName", "ClassNameSpace.MyInternalCSharpClass");
mInternalClassAccess = dynamic_cast<MyInternalCSharpClass^>(MyInternalObject);
if (mInternalClassAccess)
{
bRtn = true;
}
return bRtn;
}

Ice Chat Application

I'm ICE starter. At http://zeroc.com there is good tutorial on how to create chat. I decided to use the tutorial as base. And first thing I tried to do was writing ChatRoom class in c# instead of given c++ implementation. I tried to do the same in my c# code. ChatRoom implementation in c++:
// C++
class ChatRoomCallbackAdapter { /* ... */ };
typedef IceUtil::Handle<ChatRoomCallbackAdapter> ChatRoomCallbackAdapterPtr;
class ChatRoom : public IceUtil::Shared
{
public:
ChatRoom(bool trace, const Ice::LoggerPtr& logger);
void reserve(const std::string&);
void unreserve(const std::string&);
void join(const std::string&, const ChatRoomCallbackAdapterPtr&);
void leave(const std::string&);
Ice::Long send(const std::string&, const std::string&);
private:
typedef std::map<std::string, ChatRoomCallbackAdapterPtr> ChatRoomCallbackMap;
ChatRoomCallbackMap _members;
std::set<std::string> _reserved;
IceUtil::Mutex _mutex;
const bool _trace;
const Ice::LoggerPtr _logger;
};
Some piece of class-members implementation:
// ...
void ChatRoom::reserve(const string& name)
{
IceUtil::Mutex::Lock sync(_mutex);
if(_reserved.find(name) != _reserved.end() ||
_members.find(name) != _members.end())
{
throw string("The name " + name + " is already in use.");
}
_reserved.insert(name);
}
// ...
I was writng next:
public class ChatRoom : IceUtil
when I encountered an error. I found that IceUtil dll in distribution package isn't COM-visible therefore I can't use it in my c# project.
What can I use instead of c++
IceUtil::Handle<T>
as far as I understand it is a smart pointer.
How can I implement server like the one's given in c#?
Would it be the same in c# (talking about mutexes) comparing to above c++ class:
public class ChatRoom
{
// ...
void Reserve(System.String Name)
{
lock(this)
{
// operations
}
}
}
?
Thanks!
I don't know anything about ICE, but their website lists a .NET implementation - why don't you use that instead of COM if you want to use C#? There's even a section of documentation with an example of a C# server.
C++ does not support reference counted pointers out of the box, that is why C++ API has IceUtil::Handle<> template. C# obviously does not need it. I'd recommend you start learning Ice for C# using C# examples rather than C++. You can find a lot of C# client/server examples in democs folder of demos packages. And, of course, Ice has absolutely nothing to do with COM technology, except that it is kind of a replacement.

Categories

Resources