How is a DLL, or other files, added to an add in with Visual Studio?

6437
7
Jump to solution
09-19-2019 12:21 PM
AlanStewart
Occasional Contributor

I have a Visual Studio solution for an ArcGIS Pro add in that currently has three core projects:


1. The primary C# project, which references
2. A C++/CLI project, which references
3. A C++ project.

All projects are configured to place their output in a single shared bin folder. The properties on the references are set to copy local. The generated .esriAddinX zip file also appears there, but that file only contains the first two DLLs in its Install folder. How do I get Visual Studio to recognize the final dependency in the chain and insert that DLL into the add in?

Also in the future I will need to add data files to my add in. Ideally those also should be in the .esriAddinX file.

0 Kudos
1 Solution

Accepted Solutions
AlanStewart
Occasional Contributor

I managed to use the Kerner32 function AddDllDirectory() in the modules Initialize() method to load my C++ Dlls.

Sorry, copy and paste did not preserve indentions.

internal class Kernel32

{

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]

public static extern IntPtr AddDllDirectory(string lpPathName);

[System.Flags]

public enum LoadLibraryFlags : uint

{

None = 0,

DONT_RESOLVE_DLL_REFERENCES = 0x00000001,

LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,

LOAD_LIBRARY_AS_DATAFILE = 0x00000002,

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,

LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,

LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200,

LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000,

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100,

LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800,

LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400,

LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008

}

[DllImport("kernel32.dll", SetLastError = true)]

public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull,

LoadLibraryFlags dwFlags);

[DllImport("kernel32.dll")]

public static extern uint GetLastError();

}; // Kernel32

internal class Module1 : ArcGIS.Desktop.Framework.Contracts.Module

{

// other class methods, etc.

protected override bool Initialize()

{

string searchPath1 = @"C:\Windows\System32\";

string searchPath2 = @"C:\myProject\bin\";

IntPtr ptr1 = Kernel32.AddDllDirectory(searchPath1);

if (ptr1 == IntPtr.Zero)

return ptr1 == IntPtr.Zero;

IntPtr ptr2 = Kernel32.AddDllDirectory(searchPath2);

if (ptr2 == IntPtr.Zero)

return ptr2 == IntPtr.Zero;

string dllName1 = @"ui.dll";

string dllName2 = @"wxWidgets.dll";

string dllName3 = @"libi.dll";

string dllName4 = @"libiconv.dll";

IntPtr p = Kernel32.LoadLibraryEx(dllName4, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

p = Kernel32.LoadLibraryEx(dllName3, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

p = Kernel32.LoadLibraryEx(dllName2, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

p = Kernel32.LoadLibraryEx(dllName1, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

return base.Initialize();

} // Initialize

} // class Module1

View solution in original post

0 Kudos
7 Replies
ThanHtetAung_EsriAu
Esri Regular Contributor

Hi Alan,

In your visualstudio, in solution explorer windows, in the reference list, select the reference which you want to reference or right click properties,

In the properties windows make sure you  select copy to local is true.

Below is the screenshots.

0 Kudos
AlanStewart
Occasional Contributor

Thanks Than,

But I've tried that. Visual Studio does not allow the CLR C# project to reference the pure C++ project. VS doesn't explain why, it just pops a dialog saying it can't.

The main CLR C# project references the C++/CLI project. The  C++CLI project references the C++ project. All references are set to Copy Local.

I've also tried setting the C++ project to be build dependency of the C# project.

0 Kudos
AlanStewart
Occasional Contributor

ArcGIS Pro DAML supports a <dependencies> element, which seems to do nothing.

0 Kudos
AlanStewart
Occasional Contributor

By installing pythonnet into the ArcGIS Pro Python installation I've been able to verify that my C++/CLI DLL and C++ DLL work. I can insert the path to those DLLs, AddReference the C++/CLI DLL, import the class implemented in the C++/CLI DLL, and display the wxWidgets dialog implemented in the C++ DLL.

0 Kudos
DanH1
by
New Contributor II

Alan, 

We have done what you are trying to do.  That is accessing native C++ code from an ArcGIS Pro Add-In.

There are number of things to understand to get it to work correctly.  I will enumerate them here as I know them.

1.  Understand that your C# Add-In project and it's .Net dependencies will be compressed into a <NameOfYourAddIn>.esriAddInX file that you will find in your <My Documents Folder>\\ArcGIS\AddIns\ArcGISPro\{GUID_OF_YOUR_ADDIN} folder. 

2.  At runtime ArcGIS Pro checks this folder structure to see if there are AddIn's to be loaded.  It then takes the contents within the Install folder of that archive and extracts them to your <UsersDirectory>\AppData\Local\ESRI\ArcGISPro\AssemblyCache\{GUID_OF_YOUR_ADDIN}

3. Unfortunately, as stated earlier, this does not include your Native C++ dependencies

4. ArcGIS Pro 2.2 would only create the AssemblyCache entry if it did not exist.  We took advantage of this and created an installer that placed all of the required code in the appropriate AssemblyCache subfolder.  But, again, the trigger or ArcGIS Pro to even consider that an add in availabe is the *.esriAddInX file discussed above.  So we created that entry as well.  With those in place, our code worked great all the time.

5. Unfortunately we were taking advantage of an undocumentated "feature".  This is always risky practice as ArcGIS Pro 2.4 wipes the AssemblyCache clean evertime it runs and extracts the contents of the *.esriAddInX archive's install subfolder everytime.  So for ArcGIS Pro 2.4 we needed a runtime solution.  Fortunately every AddIn has Module class that is instantiated when the AddIn is loaded.  So our workaround was to install our Native C++ dependencies to a subdirectory in the user's "My Documents" folder and then at runtime our Module class copies all of those dependencies to the appropriate AssemblyCache subfolder.  The important thing here is to be sure there are no calls that would need to load your Native C++ dependencies before all the dependencies have been copied.

Good luck!

AlanStewart
Occasional Contributor

Thanks, Dan, it's good to have confirmation. I'm actually experimenting right now to see if there's a suitable technique for using AddDllDirectory() instead of having to copy the files, though that won't help in the future when I need to add data files also. If I make AddDllDirectory() work I'll post here.

0 Kudos
AlanStewart
Occasional Contributor

I managed to use the Kerner32 function AddDllDirectory() in the modules Initialize() method to load my C++ Dlls.

Sorry, copy and paste did not preserve indentions.

internal class Kernel32

{

[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]

public static extern IntPtr AddDllDirectory(string lpPathName);

[System.Flags]

public enum LoadLibraryFlags : uint

{

None = 0,

DONT_RESOLVE_DLL_REFERENCES = 0x00000001,

LOAD_IGNORE_CODE_AUTHZ_LEVEL = 0x00000010,

LOAD_LIBRARY_AS_DATAFILE = 0x00000002,

LOAD_LIBRARY_AS_DATAFILE_EXCLUSIVE = 0x00000040,

LOAD_LIBRARY_AS_IMAGE_RESOURCE = 0x00000020,

LOAD_LIBRARY_SEARCH_APPLICATION_DIR = 0x00000200,

LOAD_LIBRARY_SEARCH_DEFAULT_DIRS = 0x00001000,

LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR = 0x00000100,

LOAD_LIBRARY_SEARCH_SYSTEM32 = 0x00000800,

LOAD_LIBRARY_SEARCH_USER_DIRS = 0x00000400,

LOAD_WITH_ALTERED_SEARCH_PATH = 0x00000008

}

[DllImport("kernel32.dll", SetLastError = true)]

public static extern IntPtr LoadLibraryEx(string lpFileName, IntPtr hReservedNull,

LoadLibraryFlags dwFlags);

[DllImport("kernel32.dll")]

public static extern uint GetLastError();

}; // Kernel32

internal class Module1 : ArcGIS.Desktop.Framework.Contracts.Module

{

// other class methods, etc.

protected override bool Initialize()

{

string searchPath1 = @"C:\Windows\System32\";

string searchPath2 = @"C:\myProject\bin\";

IntPtr ptr1 = Kernel32.AddDllDirectory(searchPath1);

if (ptr1 == IntPtr.Zero)

return ptr1 == IntPtr.Zero;

IntPtr ptr2 = Kernel32.AddDllDirectory(searchPath2);

if (ptr2 == IntPtr.Zero)

return ptr2 == IntPtr.Zero;

string dllName1 = @"ui.dll";

string dllName2 = @"wxWidgets.dll";

string dllName3 = @"libi.dll";

string dllName4 = @"libiconv.dll";

IntPtr p = Kernel32.LoadLibraryEx(dllName4, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

p = Kernel32.LoadLibraryEx(dllName3, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

p = Kernel32.LoadLibraryEx(dllName2, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

p = Kernel32.LoadLibraryEx(dllName1, IntPtr.Zero,

Kernel32.LoadLibraryFlags.LOAD_LIBRARY_SEARCH_USER_DIRS);

if (p == IntPtr.Zero)

return false;

return base.Initialize();

} // Initialize

} // class Module1

0 Kudos