For our C/C++ app we are using Security Transforms API for doing some basic encryption/decryption.
And now we need to calculate hash (especially SHA256) of data, and though documentation claims that Security Transforms also provides a way of hashing, but seems there is no details of how to do it. And seems google doesn't bring any example or details on it as well.
So question is:
Is it really possible to calculate hash (SHA256 if possible) using Security Transforms?
And if no, then is there any other API (provided by Apple) to calculate it using C/C++?
I don't know about Security Transforms. You can use Apple's CommonCrypto library for this, though.
Oddly much of CommonCrypto does not seem to be well documented (at least that I can find), but in https://opensource.apple.com//source/CommonCrypto/CommonCrypto-7/CommonCrypto/CommonDigest.h.auto.html find the following declarations:
extern int CC_SHA256_Init(CC_SHA256_CTX *c);
extern int CC_SHA256_Update(CC_SHA256_CTX *c, const void *data, CC_LONG len);
extern int CC_SHA256_Final(unsigned char *md, CC_SHA256_CTX *c);
After digging a lot it turned out that it's possible with Security Transforms API though it was not documented. To make it work, crafted samples of AES encryption with SecDigestTransformCreate and used list of available hashing algorithms there.
Here is a C and C++ friendly solution:
#include <CoreFoundation/CoreFoundation.h>
#include <Security/Security.h>
#ifdef __cplusplus
#include <vector>
#else // C
#include <stdbool.h> // For adding boolean support
#endif // __cplusplus
// Convenience define for cleanup
#define _CLEANUP_IF(a) if ((a)) goto Cleanup;
#ifdef __cplusplus
// Wrap into class in case of C++
class Sha256Calculator {
public:
#endif // __cplusplus
// Calculates SHA256 hash from given array of data and returns array
// Note: Parameter "outHash" is manually allocated so consider calling free(outHash) after using it
static bool calculateSha256(uint8_t** outHash, size_t* outHashSize, const uint8_t *data, const size_t dataSize)
{
bool result = false;
CFErrorRef error = NULL;
SecTransformRef digestTransform = NULL;
CFDataRef sourceData = NULL;
CFDataRef outDataRef = NULL;
const UInt8 * outData = NULL;
CFIndex outDataSize = 0;
// Create a CFData object from the source
sourceData = CFDataCreate(kCFAllocatorDefault, (const UInt8*)data, dataSize);
_CLEANUP_IF(!sourceData);
digestTransform = SecDigestTransformCreate(kSecDigestSHA2, 256, &error);
_CLEANUP_IF(error);
SecTransformSetAttribute(digestTransform, kSecTransformInputAttributeName, (CFDataRef)sourceData, &error);
_CLEANUP_IF(error);
outDataRef = (CFDataRef)SecTransformExecute(digestTransform, &error);
_CLEANUP_IF(error);
_CLEANUP_IF(!outDataRef);
// Extract data from CFDataRef to array
outData = CFDataGetBytePtr(outDataRef); // Returns read-only (UInt8*) pointer to the data
outDataSize = CFDataGetLength(outDataRef);
if (outHash) {
*outHash = (uint8_t*)malloc(outDataSize);
if (*outHash) {
memcpy(*outHash, outData, outDataSize);
if (outHashSize) {
*outHashSize = (size_t)outDataSize;
}
result = true;
}
}
// Notes:
// * All the objects are released except "outData" since it's handled and cleaned by using outDataRef
// * CFRelease throws error if the passed object is NULL, so check objects before releasing
Cleanup:
// Use CFShow(error) for getting details about error
if (error) { CFRelease(error); }
if (digestTransform) { CFRelease(digestTransform); }
if (sourceData) { CFRelease(sourceData); }
if (outDataRef) { CFRelease(outDataRef); }
return result;
}
#ifdef __cplusplus
// Convenience method for cpp using vectors
static bool calculateSha256(std::vector<uint8_t>& outHash, const std::vector<uint8_t>& data)
{
// Call original method
uint8_t * outHashArray = nullptr;
size_t outHashSize;
bool result;
result = calculateSha256(&outHashArray, &outHashSize, data.data(), data.size());
if (!result)
return false;
// Put resulting array in vector
outHash.clear();
outHash.insert(outHash.end(), &outHashArray[0], &outHashArray[outHashSize]);
// Clean allocated array
if (outHashArray)
free(outHashArray);
return result;
}
};
#endif // __cplusplus
Note:
In order to use any other hashing algorithgm instead of SHA256 feel free to modify the line:
SecDigestTransformCreate(kSecDigestSHA2, 256, &error);
with desired available hashing algorithm name and appropriate length.
PS Hope, Apple will guys will update their documentation...
Related
I have a C++ function which I want to export via NIF. It accepts and operates with custom data structures, std::vectors and the like.
I'm confused about what sequence of steps should be to convert Elixir type into C++ and back.
I'm aware about enif_make_resource(), enif_release_resource() and enif_open_resource_type()
Do they have to be used when returning data? Or only when parsing incoming parameters?
Code, partly:
static int nif_load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) {
ErlNifResourceType* rt = enif_open_resource_type(env, nullptr, "vector_of_my_struct_s1",
vec1_dtor, ERL_NIF_RT_CREATE, nullptr);
if (rt == nullptr) {
return -1;
}
assert(vec1_res == nullptr);
vec1_res = rt;
return 0;
}
ERL_NIF_INIT(Elixir.MyApp, nif_funcs, nif_load, nullptr, nullptr, nullptr);
And function:
ERL_NIF_TERM do_cpp_calculations_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) {
// [........for now, parsing incoming arguments is skipped...]
std::vector<MyStructS1> native_res1 = do_cpp_calculations1(....);
ERL_NIF_TERM term;
// how to return 'native_res1' ?
// do I have to I use these functions at all?
// enif_alloc_resource(...) ?
// enif_make_resource(..) ?
// and how?
return term;
}
In the doc and this module from the official repo you have examples on how to do it.
Usually the steps you need are:
Create empty resource
Operate with the resource
Sometimes you do both in the same function.
Create empty resource:
Example from the doc:
ERL_NIF_TERM term;
MyStruct* obj = enif_alloc_resource(my_resource_type, sizeof(MyStruct));
/* initialize struct ... */
term = enif_make_resource(env, obj);
if (keep_a_reference_of_our_own) {
/* store 'obj' in static variable, private data or other resource object */
}
else {
enif_release_resource(obj);
/* resource now only owned by "Erlang" */
}
return term;
I'd recommend releasing the resource immediately and relying on the GC for the destructor, which should free the vector memory (you should make std::vector use enif_alloc to manage the memory), so in the end you may have something along the lines of:
static ERL_NIF_TERM create(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
void* pointer_to_resource_memory = enif_alloc_resource(vec1_res, sizeof(ResourceStruct));
// TODO Initialize the resource memory
ERL_NIF_TERM ret = enif_make_resource(env, pointer_to_resource_memory);
enif_release_resource(pointer_to_resource_memory);
return ret;
}
Operate with the resource
In order to work with it, you only need to extract the pointer from the resource:
static ERL_NIF_TERM do_stuff(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
{
void* pointer_to_resource_memory = NULL;
if (!enif_get_resource(env, argv[0], vec1_res, &pointer_to_resource_memory) {
return enif_make_badarg(env);
}
// TODO do stuff with the resource memory
// TODO make a term and return it, no need to create a new resource
return enif_make_int(env, 0);
}
Keep in mind that you don't need to create a new resource nor return it again if you don't want to, you're modifying the memory pointed by it.
Wrapping a std:vector in a resource
You may have noticed that in the snippets above I used only 'resource' and not 'vector', you have a choice there (my C++ is a bit rusty, though, so take the following with a grain of salt):
You can have the resource hold a pointer to the vector (safest):
typedef struct {
std::vector<MyStructS1>* vector;
} ResourceStruct;
void* pointer_to_resource_memory = enif_alloc_resource(vec1_res, sizeof(ResourceStruct));
pointer_to_resource_memory->vector = new std::vector(...) // std::vector constructor is called here
// TODO 'new' should use enif_alloc(), destroy, enif_free()
or you can have the resource be the vector (I'm not sure if this syntax is allowed, but you get the idea):
void* pointer_to_vector_object_memory = enif_alloc_resource(vec1_res, sizeof(std::vector<MyStructS1>));
// TODO: Somehow initialize a std::vector in the given memory
Short version:
how can I convert an std::string (object returned by a .cpp function called from a .Swift file using bridging) to a Swift String?
Long version:
I have a library written in C++ and I have to call some code using Swift. I created a bridge, adding two files in my Xcode projects:
a bridging header, which allows Swift to call C functions (afaik Swift can't call C++ functions directly, so it needs to pass through C functions)
//file bridgingHeader.h
const char * getLastOpenedFile();
and a .cpp files which can call (of course) C++ functions inside and can define C functions with extern "C"
//file wrapper.cpp
#include <string>
#include "my_library.hpp"
extern "C" const char * getStringFromLibrary()
{
const char * s = get_string_from_library().c_str();
return s;
}
I can access the return value from a .swift file using
let myString = String(cString: getStringFromLibrary())
Swift.print(myString)
Placing a breakpoint to check the value of s inside the function getStringFromLibrary() I can see the content of the string, so the function from the library is correctly called.
Anyway the .swift file prints some strange symbols and not the original string.
Changing getStringFromLibrary() to be the following
extern "C" const char * getStringFromLibrary()
{
return get_string_from_library().c_str();
}
I have as a consequence that the .swift code prints a prefix of the real string. This makes me think of a problem of memory: probably when getStringFromLibrary() exits, the std::string object returned by get_string_from_library() is destroyed, and so the memory pointed by the pointer returned with .c_str() is not reliable anymore, reason why I get wrong outputs from Swift.print().
What is the correct way to access an std::string from a .swift file and then release its memory?
You can write an Objective-C++ wrapper to work with C++ codes.
bridging header:
#include "Wrapper.hpp"
Wrapper.hpp:
#ifndef Wrapper_hpp
#define Wrapper_hpp
#import <Foundation/Foundation.h>
#if defined(__cplusplus)
extern "C" {
#endif
NSString * _Nonnull getStringFromLibrary();
#if defined(__cplusplus)
}
#endif
#endif /* Wrapper_hpp */
Wrapper.mm:
#include "Wrapper.hpp"
#include <string>
#include "my_library.hpp"
NSString * _Nonnull getStringFromLibrary() {
return [NSString stringWithUTF8String:get_string_from_library().c_str()];
}
Swift code:
print(getStringFromLibrary())
[NSString stringWithUTF8String:] copies the contents of the buffer into some internal storage and ARC manages freeing it. You have no need to define free_something().
The std::string object owns the buffer to which a pointer is returned via c_str. That means that getStringFromLibrary returns a pointer to nothing.
There are several ways you could avoid that:
1) Copy the contents of the buffer somewhere long-lived, and return a pointer to that. This usually means allocating some memory via new[] or malloc:
extern "C"
{
const char* getStringFromLibrary()
{
std::string str = get_string_from_library();
char* s = new char[str.size() + 1]{};
std::copy(str.begin(), str.end(), s);
return s;
}
void freeStringFromLibrary(char* s)
{
delete[] s;
}
}
This method is simple, but it does incur the cost of an extra copy. That may or may not be relevant depending on how big str is and how often getStringFromLibrary is called. It also requires the user to deal with resource management, so it's not particularly exception-safe.
2) Return a opaque "handle" to a std::string object and let swift access its underlying buffer and free it by different functions:
extern "C"
{
void* getStringFromLibrary()
{
std::string* s = new std::string(get_string_from_library());
return s;
}
const char* libraryGetCString(void* s)
{
return static_cast<std::string*>(s)->c_str();
}
void freeStringFromLibrary(void* s)
{
delete static_cast<std::string*>(s);
}
}
Now in swift you can do something like this:
let handle: UnsafeMutablePointer<COpaquePointer> = getStringFromLibrary()
let myString = String(cString: libraryGetCString(handle))
freeStringFromLibrary(handle)
This approach eliminates the extra copy, but it's still not exception-safe since the caller has to deal with resource management.
There's likely a way to mix Objective-C++ into the solution to avoid the issue with resource management, but sadly I'm not familiar enough with Swift or Objective-C++ to tell you what that solution looks like.
I define a function (it could be a static String extension, if you prefer):
func getStringAndFree(_ cString: UnsafePointer<Int8>) -> String {
let res = String(cString: cString)
cString.deallocate()
return res
}
and in C++,
extern "C" char *getStringFromLibrary()
{
return strdup(get_string_from_library().c_str());
}
Now in Swift, I can simply use this in a fire-and-forget way:
print("String from C++ is: " + getStringAndFree(getStringFromLibrary()))
I solved, I post the solution I ended up with.
In the future I will probably write a class which will allow me to manage the delete operation in an automatic an safer way. I will update the answer.
bridging header:
//file bridgingHeader.h
char * getStringFromLibrary();
void freeString(char * string);
wrapper:
char * std_string_to_c_string(std::string s0) {
size_t length = s0.length() + 1;
char * s1 = new char [length];
std::strcpy(s1, s0.c_str());
return s1;
}
extern "C" void freeString(char * string) {
delete [] string;
}
extern "C" char * getStringFromLibrary()
{
return std_string_to_c_string(get_string_from_library().c_str());
}
Swift code:
let c_string: UnsafeMutablePointer<Int8>! = getStringFromLibrary()
let myString = String(cString: c_string)
freeString(c_string)
Swift.print(myString)
we are having a discussion as what is a good way to return multiple strings from one dll function. Currently we have 8 strings, but there will be more. For simplicity I now consider all strings will have equal lengths.
extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);
where
struct TestResults
{
int stringLengths;
char* string1;
char* string2;
char* string3;
char* string4;
...
};
or second option: where
struct TestResults
{
int stringLengths;
char string1[64];
char string2[64];
char string3[64];
char string4[64];
...
};
third option:
extern "C" int DLLNAME_ _stdcall GetResult(int stringLengths, char* string1, char* string2, char* string3, ...);
The dll will communicate over a serial line and retrieve information that will be filled into the strings. Where the memory needs to be allocated is open for discussion and can be part of the answer.
The background is that we have a VB6 application team that prefers the second method and a C++/C# team that prefers the first method. Last method looks to suit both teams but looks a bit strange to me with so many parameters.
Maybe there are more options. What is common practice under Windows? Any examples from the Windows API or arguments to choose one over the other?
Edit: The strings have a meaning as in first name, last name, email. We currently have eight, but in the future we might add a couple for example for address. An array would not be the correct choice for this, but that was not clear from the original context.
The best way is probably using a safe array storing BSTR strings.
Both VB and C# understand safe arrays very well: in C#, a safe array of BSTR strings is automatically converted to a string[] array.
On the C++ side, you can use the ATL::CComSafeArray helper class to simplify safe array programming.
You will find interesting material in this MSDN Magazine article (in particular, take a look at the paragraph Producing a Safe Array of Strings).
From the aforementioned article: On the C++ side, you can implement a C-interface DLL, exporting a function like this:
extern "C" HRESULT MyDllGetStrings(/* [out] */ SAFEARRAY** ppsa)
{
try {
// Create a SAFEARRAY containing 'count' BSTR strings
CComSafeArray<BSTR> sa(count);
for (LONG i = 0; i < count; i++) {
// Use ATL::CComBSTR to safely wrap BSTR strings in C++
CComBSTR bstr = /* your string, may build from std::wstring or CString */ ;
// Move the the BSTR string into the safe array
HRESULT hr = sa.SetAt(i, bstr.Detach(), FALSE);
if (FAILED(hr)) {
// Error...
return hr;
}
}
// Return ("move") the safe array to the caller
// as an output parameter (SAFEARRAY **ppsa)
*ppsa = sa.Detach();
} catch (const CAtlException& e) {
// Convert ATL exceptions to HRESULTs
return e;
}
// All right
return S_OK;
}
On the C# side, you can use this PInvoke declaration:
[DllImport("MyDll.dll", PreserveSig = false)]
public static extern void MyDllGetStrings(
[Out, MarshalAs(UnmanagedType.SafeArray, SafeArraySubType = VarEnum.VT_BSTR)]
out string[] result);
As you declare your function as extern "C" I suppose that you cannot use std::vector<std::string> as return type.
Another possibility would be:
struct String
{
int size; /* size of string */
const char* str; /* actual string */
}
struct TestResults
{
int size; /* number of strings */
String* arr; /* pointer to an array of String */
};
and then the same as before:
extern "C" int DLLNAME_ _stdcall GetResult(TestResults* testResults);
With that you are flexible to return as much strings as you like. Also loop through your TestResults is easy.
Edit #1: As said in the comments: use BSTR. So your struct would look like:
struct TestResults
{
int size; /* number of strings */
BSTR* arr; /* pointer to an array of BSTR */
};
A BSTR will be allocated by: BSTR MyBstr = SysAllocString(L"I am a happy BSTR");. This allocation also sets the member which contain the length of the string. You have to free the allocated memory with: SysFreeString(MyBstr);. Also you need to allocate the whole array BSTR*.
So I'm making a graphical application (game) that I want to utilize an Asset Manager. For this class I decided to use a Singleton Design. So in my main.cpp I would load something like...
ASSET_MANAGER.LoadImage("res/graphics/background.png", "background");
Here is the implication of the macros/methods used in the line above. This is sort of a mashup of code that I have to make things simpler to look at instead of pasting a few hundred lines of code into here.
assetmanager.h
#define ASSET_MANAGER AssetManager::GetAssetManager()
#define DEBUG
class AssetManager {
public:
static AssetManager &GetAssetManager();
//-----------------------------------------------------------------------------
// Purpose: Load a new image for the game to use. This function will store an
// instance of the asset in memory (in a hash map corresponding with
// the data type provided.
//
// param file: The location on disk of the asset
// param key: The string you use to receive this asset (defaults to the path str)
//-----------------------------------------------------------------------------
bool LoadImage(const char *file, const char *key = "");
//-----------------------------------------------------------------------------
// Purpose: Returns the image
//
// param key: The string used to store the asset in memory
//-----------------------------------------------------------------------------
ALLEGRO_BITMAP *GetImage(const char *key);
//-----------------------------------------------------------------------------
// Purpose: Destroys an asset that is presumably no longer needed by the game.
// This function is good for performance so that you don't use more
// RAM than you need to.
//
// param key: The string you use to receive this asset (defaults to the path str)
//-----------------------------------------------------------------------------
void DiscardImage(const char *key);
private:
AssetManager();
~AssetManager();
std::map<const char *, std::shared_ptr<ALLEGRO_BITMAP>> _ImageMap;
}
assetmanager.cpp
AssetManager &AssetManager::GetAssetManager() {
static AssetManager instance;
return instance;
}
bool AssetManager::LoadImage(const char *file, const char *key) {
key = key == "" ? file : key;
std::shared_ptr<ALLEGRO_BITMAP> x(al_load_bitmap(file), al_destroy_bitmap);
if (!x) {
fprintf(stderr, "Failed to load %s\n", file);
return false;
}
#ifdef DEBUG
printf("DEBUG: Loaded %s\n", key); //debug
#endif // DEBUG
_ImageMap.insert(std::pair<const char *, std::shared_ptr<ALLEGRO_BITMAP>>(key, x));
return true;
}
ALLEGRO_BITMAP *AssetManager::GetImage(const char *key) {
return _ImageMap.find(key) != _ImageMap.end() ? _ImageMap.at(key).get() : nullptr;
}
void AssetManager::DiscardImage(const char *key) {
_ImageMap.erase(key);
#ifdef DEBUG
printf("DEBUG: Discarded %s\n", key); //debug
#endif // DEBUG
}
This class only works from the class I initialized the asset manager in while I expected it to work anywhere that I called ASSET_MANAGER. It compiles fine, it only crashes when I try to use the manager in a different class and pass it into an allegro function because it returns something that is null instead of the proper allegro data types. What don't I understand about this?
The Char* in the map stores the location and not the data which makes the program think it's null.
I have a DLL written in C++ that wraps FindFirstFile/FindNextFile/FindClose to provide a file-search function:
std::vector<std::wstring> ELFindFilesInFolder(std::wstring folder, std::wstring fileMask = TEXT(""), bool fullPath = false);
This function returns a std::vector containing a list of filenames within the given folder matching the given filemask. So far so good; the function works as expected.
I need to write a C wrapper around this library, though, because I can't pass a vector across DLL boundaries. This is leading to no end of headaches.
I initially thought I would just set up a function that would receive a two-dimensional wchar_t array, modify it to contain the filename list, and return it:
bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t* filesBuffer[], size_t* filesBufferSize);
This proved to be a bad idea, however, as at least the second dimension's size has to be known at compile-time. I suppose I could just force the caller to make the second dimension MAX_PATH (so the function would receive a variable-length list of filename buffers, each MAX_PATH long), but this seems messy to me.
I considered a wrapper in the style of the Windows APIs:
bool ELFindNextFileInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t* fileBuffer, size_t* fileBufferSize, HANDLE* searchToken);
This would perform the search, return the first filename found, and save the search handle provided by FindFirstFile. Future calls to ELFindNextFileInFolder would provide this search handle, making it easy to pick up where the last call left off: FindNextFile would just get the saved search handle. However, such handles are required to be closed via FindClose, and C doesn't seem to have the C++ concept of a smart pointer so I can't guarantee the searchToken will ever be closed. I can close some of the HANDLEs myself when FindNextFile indicates there are no more results, but if the caller abandons the search before that point there'll be a floating HANDLE left open. I'd very much like my library to be well-behaved and not leak HANDLEs everywhere, so this is out. I'd also prefer not to provide an ELCloseSearchHandle function, since I'm not sure I can trust callers to use it properly.
Is there a good, preferably single-function way to wrap these Windows APIs, or am I simply going to have to pick one from a list of imperfect solutions?
What about something like this?
In the DLL module:
#include <windows.h>
#include <vector>
#include <unordered_map>
unsigned int global_file_count; //just a counter..
std::unordered_map<unsigned int, std::vector<std::wstring>> global_file_holder; //holds vectors of strings for us.
/** Example file finder C++ code (not exported) **/
std::vector<std::wstring> Find_Files(std::wstring FileName)
{
std::vector<std::wstring> Result;
WIN32_FIND_DATAW hFound = {0};
HANDLE hFile = FindFirstFileW(FileName.c_str(), &hFound);
if (hFile != INVALID_HANDLE_VALUE)
{
do
{
Result.emplace_back(hFound.cFileName);
} while(FindNextFileW(hFile, &hFound));
}
FindClose(hFile);
return Result;
}
/** C Export **/
extern "C" __declspec(dllexport) unsigned int GetFindFiles(const wchar_t* FileName)
{
global_file_holder.insert(std::make_pair(++global_file_count, Find_Files(FileName)));
return global_file_count;
}
/** C Export **/
extern "C" __declspec(dllexport) int RemoveFindFiles(unsigned int handle)
{
auto it = global_file_holder.find(handle);
if (it != global_file_holder.end())
{
global_file_holder.erase(it);
return 1;
}
return 0;
}
/** C Export **/
extern "C" __declspec(dllexport) const wchar_t* File_Get(unsigned int handle, unsigned int index, unsigned int* len)
{
auto& ref = global_file_holder.find(handle)->second;
if (ref.size() > index)
{
*len = ref[index].size();
return ref[index].c_str();
}
*len = 0;
return nullptr;
}
/** C Export (really crappy lol.. maybe clear and reset is better) **/
extern "C" __declspec(dllexport) void File_ResetReferenceCount()
{
global_file_count = 0;
//global_file_holder.clear();
}
extern "C" __declspec(dllexport) bool __stdcall DllMain(HINSTANCE hinstDLL, DWORD fdwReason, void* lpvReserved)
{
switch (fdwReason)
{
case DLL_PROCESS_ATTACH:
DisableThreadLibraryCalls(hinstDLL);
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;
}
return true;
}
Then in the C code you can use it like:
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
int main()
{
HMODULE module = LoadLibrary("CModule.dll");
if (module)
{
unsigned int (__cdecl *GetFindFiles)(const wchar_t* FileName) = (void*)GetProcAddress(module, "GetFindFiles");
int (__cdecl *RemoveFindFiles)(unsigned int handle) = (void*)GetProcAddress(module, "RemoveFindFiles");
const wchar_t* (__cdecl *File_Get)(unsigned int handle, unsigned int index, unsigned int* len) = (void*)GetProcAddress(module, "File_Get");
void (__cdecl *File_ResetReferenceCount)() = (void*)GetProcAddress(module, "File_ResetReferenceCount");
unsigned int index = 0, len = 0;
const wchar_t* file_name = NULL;
unsigned int handle = GetFindFiles(L"C:/Modules/*.dll"); //not an actual handle!
while((file_name = File_Get(handle, index++, &len)) != NULL)
{
if (len)
{
wprintf(L"%s\n", file_name);
}
}
RemoveFindFiles(handle); //Optional..
File_ResetReferenceCount(); //Optional..
/** The above two functions marked optional only need to be called
if you used FindFiles a LOT! Why? Because you'd be having a ton
of vectors not in use. Not calling it has no "leaks" or "bad side-effects".
Over time it may. (example is having 500+ (large) vectors of large strings) **/
FreeLibrary(module);
}
return 0;
}
It seems a bit dirty to be honest but I really don't know any "amazing" ways of doing it. This is just the way I do it.. Most of the work is done on the C++ side and you don't really have to worry about leaks.. Even exporting a function to clear the map would be nice too..
It would be better if the C exports were added to a template class and then export each of those.. That would make it re-useable for most C++ containers.. I think..
Change wchar_t* filesBuffer[] to wchar_t** *filesBuffer, then the caller can pass in a pointer to a wchar_t** variable to receive the array and does not need to know anything about any bounds at compile time. As for the array itself, the DLL can allocate a one-dimensional array of wchar_t* pointers that point to null-terminated strings. That way, your size_t* filesBufferSize parameter is still relevant - it receives the number of strings in the array.
bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, wchar_t** *filesBuffer, size_t* filesBufferSize);
wchar_t **files;
size_t numFiles;
if (ELFindFilesInFolder(..., &files, &numFiles))
{
for(size_t i = 0; i < numFiles; ++i)
{
// use files[i] as needed ...
}
// pass files back to DLL to be freed ...
}
Another option is to do something similar to WM_DROPFILES does. Have ELFindFilesInFolder() return an opaque pointer to an internal list, and then expose a separate function that can retrieve a filename at a given index within that list.
bool ELFindFilesInFolder(const wchar_t* folderPath, const wchar_t* fileMask, const bool fullPath, void** filesBuffer, size_t* filesBufferSize);
bool ELGetFile(const wchar_t* fileName, size_t fileNameSize, void* filesBuffer, size_t fileIndex);
void *files;
size_t numFiles;
wchar_t fileName[MAX_PATH + 1];
if (ELFindFilesInFolder(..., &files, &numFiles))
{
for(size_t i = 0; i < numFiles; ++i)
{
ELGetFile(fileName, MAX_PATH, files, i);
// use fileName as needed ...
}
// pass files back to DLL to be freed ...
}
Any way you do it, the DLL has to manage the memory, so you have to pass some kind of state info to the caller and then have that passed back to the DLL for freeing. There is no many ways around that in C, unless the DLL keeps track of the state info internally (but then you have to worry about thread safety, reentrancy, etc) and frees it after the last file is retrieved. But that would require the caller to reach the last file, whereas the other approaches allow the caller to finish earlier if desired.