05/01/2012

Dynamic P/Invoke for custom native exported methods

When P/Invoking an exported ObjectARX or Win32 method, you usually do not need to specify the full path of the dll/executable that contains that method, as it will be already loaded either by AutoCAD or the operating system in case of a Win32 API.

Here is a couple of examples:

[DllImport("user32.dll",

CharSet = CharSet.Auto,

CallingConvention = CallingConvention.StdCall)]

publicstaticexternint SetWindowsHookEx(

int idHook, HookFunc lpfn, IntPtr hInstance, int threadId);

[DllImport("acad.exe",

CharSet = CharSet.Unicode,

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "acedCmd")]

publicstaticexternint acedCmd(System.IntPtr vlist);

However it might happen that you have exported a C++ method from one of your custom dll or arx and do not have the dll loaded inside AutoCAD process. In that case you will have to specify the full path of that dll inside the DllImport attribute, as follow:

[DllImport(@"C:\Temp\myDll.dll")]

publicstaticexternint MyExportedMethod();

We can see that the approach is not convenient, because of the hardcoded path inside the DllImport attribute.

I will expose two ways to workaround that limitation. In order to test them, let’s first create an exported method from a custom arx. It requires 3 steps:

1/ Declare the exported method in some header file:

extern"C" HWND _declspec(dllexport) getViewHandle();

2/ Implement the method:

/////////////////////////////////////////////////////////////

//Use: Exported View Handle

//

/////////////////////////////////////////////////////////////

HWND getViewHandle()

{

CView *pView = acedGetAcadDwgView();

if (pView != NULL)

{

return pView->m_hWnd;

}

return NULL;

}

3/ Declare the exported method in a .def file:

LIBRARY"myArx"

EXPORTS getViewHandle

That’s it concerning our custom exported method, we are now able to P/invoke “getViewHandle” from .Net as follow and the arx does not have to be loaded in AutoCAD for this to work:

[DllImport(@"C:\Temp\myDll\myArx.arx",

CharSet = CharSet.Unicode,

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "getViewHandle")]

privatestaticexternIntPtr getViewHandle();

So how to avoid the hardcode path in DllImport?

1/ First approach: Rely on the SetDllDirectory Win32 API and dynamically specify the path of the dll on your system.

/////////////////////////////////////////////////////////////

// Use: Dynamic P/Invoke using Win API SetDllDirectory

// Author: Philippe Leefsma

/////////////////////////////////////////////////////////////

[DllImport("Kernel32.dll")]

publicstaticexternbool SetDllDirectory([In]string lpPathName);

[DllImport("myArx.arx",

CharSet = CharSet.Unicode,

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "getViewHandle")]

privatestaticexternIntPtr getViewHandle();

[CommandMethod("DllDirPInvoke")]

publicvoid DllDirPInvoke()

{

Document doc = Application.DocumentManager.MdiActiveDocument;

Database db = doc.Database;

Editor ed = doc.Editor;

bool b = SetDllDirectory(@"C:\Temp\");

IntPtr res = getViewHandle();

ed.WriteMessage("\nViewHandle: " + res.ToString());

}

2/ Handle loading and unloading of the dll yourself using LoadLibraryEx / FreeLibrary, then access the method with GetProcAddress:

Comments

When P/Invoking an exported ObjectARX or Win32 method, you usually do not need to specify the full path of the dll/executable that contains that method, as it will be already loaded either by AutoCAD or the operating system in case of a Win32 API.

Here is a couple of examples:

[DllImport("user32.dll",

CharSet = CharSet.Auto,

CallingConvention = CallingConvention.StdCall)]

publicstaticexternint SetWindowsHookEx(

int idHook, HookFunc lpfn, IntPtr hInstance, int threadId);

[DllImport("acad.exe",

CharSet = CharSet.Unicode,

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "acedCmd")]

publicstaticexternint acedCmd(System.IntPtr vlist);

However it might happen that you have exported a C++ method from one of your custom dll or arx and do not have the dll loaded inside AutoCAD process. In that case you will have to specify the full path of that dll inside the DllImport attribute, as follow:

[DllImport(@"C:\Temp\myDll.dll")]

publicstaticexternint MyExportedMethod();

We can see that the approach is not convenient, because of the hardcoded path inside the DllImport attribute.

I will expose two ways to workaround that limitation. In order to test them, let’s first create an exported method from a custom arx. It requires 3 steps:

1/ Declare the exported method in some header file:

extern"C" HWND _declspec(dllexport) getViewHandle();

2/ Implement the method:

/////////////////////////////////////////////////////////////

//Use: Exported View Handle

//

/////////////////////////////////////////////////////////////

HWND getViewHandle()

{

CView *pView = acedGetAcadDwgView();

if (pView != NULL)

{

return pView->m_hWnd;

}

return NULL;

}

3/ Declare the exported method in a .def file:

LIBRARY"myArx"

EXPORTS getViewHandle

That’s it concerning our custom exported method, we are now able to P/invoke “getViewHandle” from .Net as follow and the arx does not have to be loaded in AutoCAD for this to work:

[DllImport(@"C:\Temp\myDll\myArx.arx",

CharSet = CharSet.Unicode,

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "getViewHandle")]

privatestaticexternIntPtr getViewHandle();

So how to avoid the hardcode path in DllImport?

1/ First approach: Rely on the SetDllDirectory Win32 API and dynamically specify the path of the dll on your system.

/////////////////////////////////////////////////////////////

// Use: Dynamic P/Invoke using Win API SetDllDirectory

// Author: Philippe Leefsma

/////////////////////////////////////////////////////////////

[DllImport("Kernel32.dll")]

publicstaticexternbool SetDllDirectory([In]string lpPathName);

[DllImport("myArx.arx",

CharSet = CharSet.Unicode,

CallingConvention = CallingConvention.Cdecl,

EntryPoint = "getViewHandle")]

privatestaticexternIntPtr getViewHandle();

[CommandMethod("DllDirPInvoke")]

publicvoid DllDirPInvoke()

{

Document doc = Application.DocumentManager.MdiActiveDocument;

Database db = doc.Database;

Editor ed = doc.Editor;

bool b = SetDllDirectory(@"C:\Temp\");

IntPtr res = getViewHandle();

ed.WriteMessage("\nViewHandle: " + res.ToString());

}

2/ Handle loading and unloading of the dll yourself using LoadLibraryEx / FreeLibrary, then access the method with GetProcAddress: