0

I need some serious help here... I'm trying to either get my member functions exported so I can call them in C#

WMIWrapper.h

#ifndef _WMIWRAPPER_H_
#define _WMIWRAPPER_H_

#include <Windows.h>  
#include <sstream>  
#include <iostream>
#include <WbemCli.h>  

using std::endl;
using std::wstring;
using std::wstringstream;

#pragma comment(lib, "wbemuuid.lib")  

static class WMIWrapper 
{  
public:
    __declspec(dllexport) WMIWrapper();
    __declspec(dllexport) ~WMIWrapper();

    __declspec(dllexport) wstring CreateCOM();
    __declspec(dllexport) wstring CreateService();
__declspec(dllexport) wstring GetMonitors();

private:
    IWbemLocator* _locator;
    IWbemServices* _service;
    IEnumWbemClassObject* _monitors;
};

#endif

WMIWrapper.cpp

#include "WMIWrapper.h"


extern "C" {

    WMIWrapper::WMIWrapper()
    {
        _locator = NULL;
        _service = NULL;
    }

    WMIWrapper::~WMIWrapper()
    {
        if(_service != NULL)
            _service->Release();
        if(_locator != NULL)
            _locator->Release();
    }

    wstring WMIWrapper::CreateCOM()
    {
        wstringstream ERRStream (wstringstream::in | wstringstream::out);
        HRESULT hRes = CoInitializeEx(NULL, COINIT_MULTITHREADED);  
        if(FAILED(hRes))  
        {  
            ERRStream << "Unable to launch COM: 0x" << std::hex << hRes << endl;
            return L"";  
        }  

        hRes = CoInitializeSecurity(NULL, -1, NULL, NULL, RPC_C_AUTHN_LEVEL_CONNECT, RPC_C_IMP_LEVEL_IMPERSONATE, NULL, EOAC_NONE, 0);
        if(FAILED(hRes))
        {
            ERRStream << "Unable to set security level for COM: " << std::hex << hRes << endl;
            return L"";
        }

        if(FAILED(hRes = CoCreateInstance(CLSID_WbemLocator, NULL, CLSCTX_ALL, IID_PPV_ARGS(&_locator))))  
        {  
            ERRStream << "Unable to create a WbemLocator: " << std::hex << hRes << endl;  
            return L"";  
        }

        const std::wstring& myWString = ERRStream.str();
        const LPCWSTR p = myWString.c_str();
        return p;

    }

    wstring WMIWrapper::CreateService()
    {
        wstringstream ERRStream (wstringstream::in | wstringstream::out);
        HRESULT hRes;
        if(_locator == NULL || FAILED(hRes = _locator->ConnectServer(L"root\\CIMV2", NULL, NULL, NULL, WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &_service)))  
        {  
            ERRStream << "Unable to connect to \"CIMV2\": " << std::hex << hRes << endl;  
            return L"";  
        }  

        const std::wstring& myWString = ERRStream.str();
        const LPCWSTR p = myWString.c_str();
        return p;
    }

    wstring WMIWrapper::GetMonitors()
    {
        HRESULT hRes;
        wstringstream ssMonitorDescription;
        if(_locator == NULL 
            || _service == NULL
            || FAILED(hRes = _service->ExecQuery(L"WQL", L"SELECT * FROM Win32_DesktopMonitor", WBEM_FLAG_FORWARD_ONLY, NULL, &_monitors)))
        {
            //ERRStream << "Unable to retrieve desktop monitors: " << std::hex << hRes << endl;
            return L"";
        }

        IWbemClassObject* clsObj = NULL;
        int numElems;
        while((hRes = _monitors->Next(WBEM_INFINITE, 1, &clsObj, (ULONG*)&numElems)) != WBEM_S_FALSE)
        {
            if(FAILED(hRes))
                break;

            VARIANT vRet;
            VariantInit(&vRet);
            if(SUCCEEDED(clsObj->Get(L"Description", 0, &vRet, NULL, NULL)) && vRet.vt == VT_BSTR)
            {
                //std::wcout <<  L"Description: " << vRet.bstrVal << endl;
                ssMonitorDescription << "Description: " << vRet.bstrVal << endl;
                VariantClear(&vRet);
            }
        }

        clsObj->Release();

        return ssMonitorDescription.str();
    }
}

Interface.cpp

#include "WMIWrapper.h"

extern "C" 
{
    __declspec( dllexport ) wstring GetMonitor()
    {
        WMIWrapper* wmiWrapper = new WMIWrapper();
        wmiWrapper->CreateCOM();
        wmiWrapper->CreateServiceW();
        return wmiWrapper->GetMonitors();
    }
}

Unity Script

using UnityEngine;
using System.Runtime.InteropServices;
using System;


public class HardwareDiagnostics : MonoBehaviour {

    //[DllImport("WMIWrapper", EntryPoint="CreateCOM", CharSet = CharSet.Unicode)]
    //static extern String CreateCOM();
    //
    //[DllImport("WMIWrapper", EntryPoint="CreateService", CharSet = CharSet.Unicode)]
    //static extern String CreateService();
    //
    //[DllImport("WMIWrapper", EntryPoint="GetMonitors", CharSet = CharSet.Unicode)]
    //static extern String GetMonitors();
    [DllImport("WMIWrapper", EntryPoint = "GetMonitor", CharSet = CharSet.Unicode)]
    static extern string GetMonitor();

    // Use this for initialization
    void Start () {
        Debug.Log(GetMonitor());
        Debug.Log ("Cock");

    }

    // Update is called once per frame
    void Update () {

    }


}

So I'm trying to call those member functions from the Unity script and I'm getting the EntryPointNotFoundExeption error. I thought maybe it was because you couldn't export member functions, so I tried writing that "Interface.cpp" to execute those functions and return the result but that returns the same error.

UPDATE

Per suggestion I have changed my C++ functions to this format

void WMIWrapper::CreateCOM(wchar_t* err, int errLength)
    {
        .../Determine wstringstream ERRStream


        wcscpy_s(err, errLength, ERRStream.str().c_str());

    }

And my C# like so:

public class HardwareDiagnostics : MonoBehaviour {

    [DllImport( "WMIWrapper", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private static extern void CreateCOM(StringBuilder str, int length);

    [DllImport( "WMIWrapper", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private static extern void CreateService(StringBuilder str, int length);

    [DllImport( "WMIWrapper", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
    private static extern void GetMonitors(StringBuilder str, int length);

    // Use this for initialization
    void Start () {
        StringBuilder buffer = new StringBuilder(255);

        CreateCOM(buffer, buffer.Capacity);
        Debug.Log(buffer.ToString());

        CreateService(buffer, buffer.Capacity);
        Debug.Log(buffer.ToString());

        GetMonitors(buffer, buffer.Capacity);
        Debug.Log(buffer.ToString());

    }

    // Update is called once per frame
    void Update () {

    }


}

HOWEVER, I'm still getting "EntryPointNotFoundExeption" when calling the first function, CreateCOM();

Josh Elias
  • 3,250
  • 7
  • 42
  • 73

3 Answers3

2

Is this windows? Because you are missing your

Bool WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID);

definition if it is windows.

See chapter 21 of "Programming Windows: Fifth Edition" by Charles Petzold.

If you don't have it. Get it. All windows programmers should read it.

The DllMain function is called when the library first begins and when it terminates. The first parameter to DllMain is the instance handle of the library. If your library uses resources that require an instance handle (such as DialogBox), you should save hInstance as a global variable. The last parameter to DllMain is reserved by the system...

A fdwReason value of DLL_PROCESS_ATTACH indicates that the dynamic-link library has been mapped into the address space of a process. This is a cue for the lib rary to do any initialization tasks it requires to service subsequent requests from the process....

If the initialization is successful, DllMain should return a nonzero value. Returning 0 will cause Windows to not run the program.

When fdwReason has a value of DLL_PROCESS_DETACH, it means that the DLL is no longer needed by the process.. clean up...

Later versions of Visual Studio, may do this for you--I am not sure about that part.

Also, you will get an EntryPointNotFound exception if your function names are being mangled. The reason your function names are being mangled even with the extern "C" declaration is because you have placed extern "C" around class methods. In order for that to work without the mangled names you have to do C style function declarations.

i.e.

#ifdef _cplusplus
extern "C" {
#endif

__declspec(dllexport) wstring CreateCOM();

...

outside the context of a class. By having those declared as class members, the names will still be mangled, even with the extern "C" declaration.

Jonathan Henson
  • 8,076
  • 3
  • 28
  • 52
  • Wow, is that used instead of int main() {} ? I made the .dll in VS2010 Pro, its meant to be run in windows through Unity. – Josh Elias Jul 24 '12 at 14:30
  • @JoshElias I am looking for a good link for you, because I got this out of Petzold's book. – Jonathan Henson Jul 24 '12 at 14:33
  • A specific main for DLLs...I don't know why Unity's documentation is so limited in this matter, it seems like a pretty in depth subject. Of course if you have the Pro Version of Unity maybe they assume a certain level of knowledge lol. I appreciate the help! – Josh Elias Jul 24 '12 at 14:36
  • I don't think I need a main though, the code is being executed by a Unity Script – Josh Elias Jul 24 '12 at 15:39
  • @JoshElias If you want windows to load that dll at all, you need a dllmain. – Jonathan Henson Jul 24 '12 at 16:14
  • I find it odd how 2 days of searching didn't bring anything like this up. Where do I put this DLLMain? I didn't expect an entry point, I just wanted to export some functions... – Josh Elias Jul 24 '12 at 17:10
  • I added a main.cpp with what you suggested. Same Error. – Josh Elias Jul 24 '12 at 17:39
  • @JoshElias Glad you found it. After some research, I think that visual studio generates that for you, along with the def files. – Jonathan Henson Jul 24 '12 at 18:32
  • You've earned your Answer sir. Thank you so much! – Josh Elias Jul 24 '12 at 19:21
1

Would this be of some help ?

Return contents of a std::wstring from C++ into C#

How to: Marshal Unicode Strings Using C++ Interop

Default Marshaling for Strings

Community
  • 1
  • 1
aybe
  • 15,516
  • 9
  • 57
  • 105
  • So are you using UnmanagedFunctionPointerAttribute instead of DLLImport? Do I have to use C++ callbacks? Exactly what part of my code is wrong? I'm sorry I just find your answer very cryptic and vague. I'm no expert on this subject, I'm just trying to write my first plugin for Unity. – Josh Elias Jul 23 '12 at 21:11
  • Sorry about that, I thought it was a delegate ... I was thinking about CDecl, I updated my answer, in fact it was the MarshalAsAttribute which also specifies calling convention, not UnmanagedFunctionPointerAttribute. – aybe Jul 23 '12 at 21:34
  • I posted an update, your suggestions are good but I don't think they are directly related to the error I'm getting. – Josh Elias Jul 24 '12 at 14:01
-1

The error was from the fact that the function name was getting mangled by the C++ compiler. I thought wrapping the function declarations in .cpp with extern "C" {} solved that but I guess not.

[DllImport( "WMIWrapper", EntryPoint = "?CreateCOM@WMIWrapper@1@SAXPA_WH@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void CreateCOM(StringBuilder str, int length);

[DllImport( "WMIWrapper", EntryPoint = "?CreateServiceW@WMIWrapper@1@SAXPA_WH@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void CreateService(StringBuilder str, int length);

[DllImport( "WMIWrapper", EntryPoint = "?GetMonitors@WMIWrapper@1@SAXPA_WH@Z", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
private static extern void GetMonitors(StringBuilder str, int length);

If anybody knows how to properly use extern "C" in this context it would be very helpful.

Josh Elias
  • 3,250
  • 7
  • 42
  • 73
  • hmmm, I actually have had this error a million times. Don't know why I didn't think of it. It probably said: EntryPointNotFoundExeption, no entry point named GetMonitors ... Anyhow, glad you found it. – Jonathan Henson Jul 24 '12 at 19:04
  • w.r.t the mangled names, see the update I am about to put up. – Jonathan Henson Jul 24 '12 at 19:08